且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-11-04 19:53:05

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

There are several new features in UWP that can solve the specific problem of opening SQLite databases. The general technique used here can solve some other UWP file access problems, but not all of them -- see caveat at the end.

使这成为可能的第一个功能是 Windows 10 版本 1803 中引入的 ...FromApp API.这些是旧 Win32 API 的变体,例如 CreateFileWDeleteFileW 在 AppContainer(运行 UWP 应用的安全上下文)内工作并允许访问应用私有目录之外的文件.如果您从头开始编写新代码,调用这些 API 而不是旧的 API 可确保您的代码在 UWP 上下文中正常工作".尽管 MSDN 还没有关于这些的很好的文档,但您可以在 Windows SDK 的 fileapifromapp.h 标头中找到它们.修改 SQLite 代码库以使用这些较新的 API 将使其正常工作"(有关 API 的更改请参见下文).

The first feature that makes this possible is the ...FromApp APIs introduced in Windows 10 version 1803. These are variations of older Win32 APIs like CreateFileW and DeleteFileW that work from within an AppContainer (the security context in which a UWP app runs) and allow access to files outside of the app's private directories. If you are writing new code from scratch, calling these APIs instead of the older ones ensures your code will "just work" from a UWP context. Although MSDN doesn't have great documentation on these yet, you can find them in the fileapifromapp.h header in the Windows SDK. Modifying the SQLite codebase to use these newer APIs will make it "just work" for you (see below for the APIs to change).

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

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

This is where the second feature that makes this possible comes in handy -- API Redirection, introduced in Windows 10 version 1809. This feature allows a UWP application to "redirect" the API imports from its own DLLs and to call different APIs instead. So if you have a DLL in your project that tries to call CreateFileW and you want it to call CreateFileFromAppW instead, that's now possible. No modifications to the source code or the compiled DLL are needed.

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

API Redirection relies on a DLL in your package that exports a special table named __RedirectionInformation__. This table lists the set of APIs to be replaced, and the functions to call instead. The functions to be called instead are implemented inside the DLL itself.

它是如何工作的?

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

First, the redirection file. Create a C++ UWP DLL and add the following code into the main CPP file. Let's assume this project produces an output named 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
};

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

This file re-directs the three APIs CreateFile2, DeleteFileW, and GetFileAttributesExW from the API Set api-ms-win-core-file-l1-2-1.dll (these are the three APIs needed to make SQLite work - at least for basic operations). Note that the APIs implementing the redirections don't have to be exported, since nobody is linking directly to them (although you can export them if you like).

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

Next, make sure to include AppRedirections.dll in the UWP app project that is using SQLite. Typically you can just "Add Reference..." to the redirection project from your main project.

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

Now add / update the following entries to your Package.appxmanifest file (or AppXManifest.xml if you're not using Visual Studio). You'll need to right-click and "Open with..." the XML editor since the designer doesn't support adding this functionality.

<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>

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

This tells Windows that when it loads your app, it should first load the AppRedirections.dll file, process the redirection table, and then fix-up all future imports it sees for the rest of the files in your package. Note that if you get the filename wrong, or Windows can't find the file, or it doesn't export the redirection table correctly, your app will fail to activate (launch).

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

Assuming you have SQLite3.dll in your package (and it's the same version I tested with) you will now be able to open SQLite databases with code such as the following - note the required use of the FutureAccessList to "prove" you have the right to access the file:

#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);
  });
}

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

Now your UWP application should work with external SQLite database files. A similar technique could be used with other libraries, with the following caveats (as of December 2019):

  1. 重定向仅适用于应用程序包图中的 DLL(即应用程序及其使用的任何框架包).
  2. 重定向不适用于通过 GetProcAddress 访问的函数;它们仅适用于导入表中直接列出的函数.
  1. The redirections only apply to DLLs in the app's package graph (that is, the app and any Framework Packages it uses).
  2. The redirections do not apply to functions accessed via GetProcAddress; they only work for functions directly listed in the import table.

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

The first limitation means that functions in system-provided DLLs will not get redirected, so you must include a version of sqlite3.dll in your app rather than rely on the system-provided one (this is the default behaviour anyway). It also means that whilst you can redirect APIs from within the VCLibs Framework Package, you cannot redirect APIs from ucrtbase.dll... this means that this technique currently doesn't work if the app uses fopen or std::fstream etc. You can statically-link the CRT into your application to solve this problem, but it might not pass Store Certification (if you care about the Microsoft Store).

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

The second limitation mostly affects .NET code, since the CLR relies on LoadLibrary / GetProcAddress for resolving P/Invoke calls (although some version-adaptive C/C++ libraries use GetProcAddress as well). Note that the .NET Native compiler generates proper DLL import tables, but the normal debug builds (F5) won't work.