如何在 UWP 应用程序中访问我的应用程序文件夹之外的文件(如 SQLite 数据库)?

Pet*_*SFT 5 sqlite winapi uwp

我知道 UWP 应用程序可以在他们自己的 AppData 目录中使用 SQLite 数据库,但我想访问用户从另一个位置(例如他们的Downloads目录)选择的 SQLite 数据库。我可以将数据库复制到我的应用程序目录并在那里打开它,但如果它很大,那么复制将需要很长时间,或者如果用户修改了数据库,那么我必须将其复制回来等等,我不想管理这种复杂性。

我知道 UWP 应用程序可以访问其自己的私有目录之外的文件,如果用户使用 a 选择文件FileOpenPicker或应用程序具有此broadFileSystemAccess功能,但这仅适用于StorageFile对象,而不适用于SQLite 等现有库,这些库仅将文件名作为争论。我也知道我可以构建一个“完全信任”打包的 Win32 桌面应用程序,但我想构建一个在其他平台上运行的 UWP 应用程序。

UWP 中有什么新东西可以提供帮助吗?

Pet*_*SFT 10

UWP 中有几个新功能可以解决打开 SQLite 数据库的具体问题。此处使用的通用技术可以解决其他一些UWP 文件访问问题,但不是所有问题——请参阅最后的警告。

第一个特征,使这个可能是...FromApp在Windows的API推出10版1803年这些年纪大的Win32 API等的偏差CreateFileW,并DeleteFileW从AppContainer内工作(安全上下文,其中UWP应用程序运行),并允许访问文件以外的应用程序的私有目录。如果您从头开始编写新代码,调用这些 API 而不是旧的 API 可确保您的代码在 UWP 上下文中“正常工作”。尽管 MSDN 还没有关于这些的很好的文档,但您可以fileapifromapp.h在 Windows SDK的标题中找到它们。修改 SQLite 代码库以使用这些较新的 API 将使其“适合您”(请参阅​​下文以了解要更改的 API)。

但是,如果您不想重新编译 SQLite,或者您正在使用一个没有源代码的不同库,该怎么办?

这是使这成为可能的第二个功能派上用场的地方——API 重定向,在 Windows 10 版本 1809 中引入。此功能允许 UWP 应用程序从其自己的 DLL 中“重定向”API 导入,并改为调用不同的 API。因此,如果您的项目中有一个 DLL 尝试调用CreateFileW并且您希望它改为调用CreateFileFromAppW,那么现在就可以了。无需修改源代码或编译后的 DLL。

API 重定向依赖于包中的 DLL,该 DLL 导出名为__RedirectionInformation__. 此表列出了要替换的 API 集以及要调用的函数。要调用的函数是在 DLL 本身内部实现的。

它是如何工作的?

首先是重定向文件。创建一个 C++ UWP DLL 并将以下代码添加到主 CPP 文件中。让我们假设这个项目产生一个名为 的输出AppRedirections.dll

#include "pch.h"

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <fileapifromapp.h>

// Same signature are CreateFile2, forward it on to ...FromApp
HANDLE WINAPI CreateFile2Forwarder(LPCWSTR lpFileName, DWORD dwDesiredAccess,
  DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams)
{
  return CreateFile2FromAppW(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams);
}

// Same signature are DeleteFileW, forward it on to ...FromApp
BOOL WINAPI DeleteFileWForwarder(LPCWSTR lpFileName)
{
  return DeleteFileFromAppW(lpFileName);
}

// Same signature are GetFileAttributesExW, forward it on to ...FromApp
BOOL WINAPI GetFileAttributesExWForwarder(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId,
  LPVOID lpFileInformation)
{
  return GetFileAttributesExFromAppW(lpFileName, fInfoLevelId, lpFileInformation);
}

// List of {exporting DLL}, {exported function name}, {replacement function pointer}
const REDIRECTION_FUNCTION_DESCRIPTOR RedirectedFunctions[] =
{
    { "api-ms-win-core-file-l1-2-1.dll", "CreateFile2", &CreateFile2Forwarder },
    { "api-ms-win-core-file-l1-2-1.dll", "DeleteFileW", &DeleteFileWForwarder },
    { "api-ms-win-core-file-l1-2-1.dll", "GetFileAttributesExW", &GetFileAttributesExWForwarder },
};

// The exported table, with version and size information.
extern "C" __declspec(dllexport) const REDIRECTION_DESCRIPTOR __RedirectionInformation__ =
{
    1, // version number of the structure
    ARRAYSIZE(RedirectedFunctions),
    RedirectedFunctions
};
Run Code Online (Sandbox Code Playgroud)

该文件从 API 集(这些是使 SQLite 工作所需的三个 API - 至少对于基本操作而言)重定向三个 API CreateFile2DeleteFileW、 和。请注意,不必导出实现重定向的 API,因为没有人直接链接到它们(尽管您可以根据需要导出它们)。GetFileAttributesExWapi-ms-win-core-file-l1-2-1.dll

接下来,确保包含AppRedirections.dll在使用 SQLite 的 UWP 应用项目中。通常,您可以从主项目“添加引用...”到重定向项目。

现在将以下条目添加/更新到您的Package.appxmanifest文件中(或者AppXManifest.xml如果您没有使用 Visual Studio)。您需要右键单击 XML 编辑器并“打开方式...”,因为设计器不支持添加此功能。

<Package
  [other stuff]
  xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" 
  IgnorableNamespaces="[other stuff] uap7">

  [more stuff...]

  [place after 'VisualElements']
  <uap7:Properties>
    <uap7:ImportRedirectionTable>AppRedirections.dll</uap7:ImportRedirectionTable>
  </uap7:Properties>
</Application>
Run Code Online (Sandbox Code Playgroud)

这告诉 Windows 当它加载你的应用程序时,它应该首先加载AppRedirections.dll文件,处理重定向表,然后修复它在包中的其余文件中看到的所有未来导入。请注意,如果您输入的文件名错误,或者 Windows 找不到该文件,或者它没有正确导出重定向表,您的应用程序将无法激活 (launch)

假设你有SQLite3.dll你的包(它是我测试过的相同版本),你现在可以使用如下代码打开 SQLite 数据库 - 请注意需要使用FutureAccessList来“证明”你有权访问该文件:

#include <sqlite3.h>
#include <ppltasks.h>

// ...

sqlite3* db;

void OpenDatabase()
{
  using namespace Windows::Storage;
  using namespace Windows::Storage::Pickers;
  using namespace Windows::Storage::AccessCache;

  auto picker = ref new FileOpenPicker();
  picker->FileTypeFilter->Append(L".db");
  picker->SuggestedStartLocation = PickerLocationId::Desktop;
  concurrency::create_task(picker->PickSingleFileAsync()).then([](StorageFile^ pickedFile)
  {
    // StorageFile *must* be added to the future access list to ensure the Win32 APIs can grant access.
    StorageApplicationPermissions::FutureAccessList->Add(pickedFile);

    // now SQLite "just works"... carry on from here
    int err = sqlite3_open16(pickedFile->Path->Data(), &db);
  });
}
Run Code Online (Sandbox Code Playgroud)

现在,您的 UWP 应用程序应该可以使用外部 SQLite 数据库文件。类似的技术可用于其他库,但有以下注意事项(截至 2019 年 12 月):

  1. 重定向仅适用于应用程序包图中的 DLL(即应用程序及其使用的任何框架包)。
  2. 重定向不适用于通过GetProcAddress;访问的函数。它们仅适用于导入表中直接列出的函数。

第一个限制意味着系统提供的 DLL 中的函数不会被重定向,因此您必须sqlite3.dll在应用程序中包含一个版本,而不是依赖系统提供的版本(无论如何这是默认行为)。这也意味着,虽然可以从内部重定向的APIVCLibs框架包,你不能从重定向的API ucrtbase.dll......这意味着,这种技术目前没有工作,如果应用程序使用fopenstd::fstream等可以静态链接的CRT到您的应用解决这个问题,但它可能不会通过商店认证(如果你关心微软商店)。

第二个限制主要影响 .NET 代码,因为 CLR 依赖LoadLibrary/GetProcAddress来解析 P/Invoke 调用(尽管一些版本自适应 C/C++ 库也使用GetProcAddress)。请注意,.NET Native 编译器会生成正确的 DLL 导入表,但正常的调试版本 (F5) 将不起作用。