在UWP应用中,我如何访问位于我的应用文件夹之外的文件(比如SQLite数据库)?

5
我知道UWP应用程序可以在其自己的AppData目录中使用SQLite数据库,但我想访问用户从另一个位置选择的SQLite数据库(例如他们的下载目录)。我可以将数据库复制到我的应用程序目录中并在那里打开它,但如果它很大,则复制需要很长时间,或者如果用户修改了数据库,则我必须将其复制回来等等,我不想管理这种复杂性。
我知道UWP应用程序可以访问其私有目录之外的文件,如果用户使用FileOpenPicker选择文件或应用程序具有broadFileSystemAccess功能,但这仅适用于StorageFile对象,而不适用于像SQLite这样只需要文件名作为参数的现有库。我也知道我可以构建一个“完全信任”的打包Win32桌面应用程序,但我想构建一个在其他平台上运行的UWP应用程序。
在UWP中是否有任何新的方法可以帮助?
1个回答

10

UWP中有几个新功能可以解决打开SQLite数据库的特定问题。这里使用的一般技术可以解决一些其他UWP文件访问问题,但不是所有问题--请参见结尾的提示。

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

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

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

API重定向依赖于软件包中导出名为__RedirectionInformation__的特殊表的DLL。该表列出要替换的一组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
};

这个文件重新定向了三个API: CreateFile2DeleteFileWGetFileAttributesExW,从API Set api-ms-win-core-file-l1-2-1.dll 到它们的实现,这三个API至少是使SQLite基本操作得以运行所需的。请注意,实现重定向的API无需导出,因为没有人直接链接到它们(尽管您可以导出它们)。

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

现在,请将以下条目添加/更新到您的Package.appxmanifest文件中(如果您不使用Visual Studio,则为AppXManifest.xml)。您需要右键单击并“打开方式...”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>

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

假设您的包中有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);
  });
}

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

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

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

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


请纠正我,但是看起来您可以通过使用 IAT hooking、修补 sqlite3.dll 的 IAT 来实现类似的效果。然后您也可以依赖于系统提供的方法。这是除非封装应用程序受到我不知道的额外限制。 - Paul
1
不要“添加引用” - 只需将文件添加到您的项目中,并确保它是“内容”。但是请注意,这种技术不太可能适用于托管代码(尽管它可能适用于项目中的其他本机DLL)。 - Peter Torr - MSFT
我遇到了一个与这个解决方案相关的新问题。在我的项目中,由于appredirections.dll,它打开了SQLite数据库,但是该应用程序无法打开位于OneDrive上的文件。有人知道如何修复它或者可以搜索一下吗? - fnc12
UWP应用程序通过Win32 API调用打开OneDrive文件存在已知限制。 - Peter Torr - MSFT
真是一团糟。我又来了 - 与系统斗争而不是解决问题... - Richard Hammond
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接