如何在 UWP 中获取 StorageFile 或 StorageFolder 的 Win32 HANDLE?
How can I get a Win32 HANDLE for a StorageFile or StorageFolder in UWP?
我知道我可以使用 Win32 API 访问我自己的本地数据文件夹中的文件(例如,请参阅 ),但我需要访问我的应用程序外部的文件(例如,从图片库)和我尝试使用的库都基于 Win32 文件 HANDLE
s 和/或它们依赖于使用相对文件名。
由于获取图片库中文件(或获取从选取器返回的文件/文件夹)的唯一方法是通过 StorageFile
对象,我如何才能重新使用现有代码?我是否必须将其全部重写为异步并依赖于 WinRT 存储 API?
从 "Anniversary Update"(又名 "RS1" 或内部版本 10.0.14393)开始,您可以从 StorageItem
(文件或文件夹)获取 Win32 HANDLE
并从 StorageFolder
中创建新的命名文件(返回 HANDLE
)。您可以使用新的 IStorageFolderHandleAccess
and IStorageItemHandleAccess
API 来执行此操作。
Note: These APIs have been accidentally placed inside the WINAPI_PARTITION_DESKTOP
partition (they're not desktop-specific; they're available to UWPs). This will be addressed in future SDK updates.
要使用这些新的 COM 接口之一,您只需 QI 接口的 StorageFile
或 StorageFolder
。如果该接口不受支持,则意味着您的应用程序 运行 处于下层 OS(或者存储项实际上并非由真实文件支持,而是伪-文件)。您可以从 C++(C++/CX 或 WRL)或 C# 使用这些接口。
这是一个简单的示例,使用 FolderPicker
让用户在他们的磁盘上选择一个位置(returns 代理 StorageFolder
对象)然后使用 Win32 APIs ReadFile
和 WriteFile
从该位置读取和写入文件。
如上所述,我们必须将接口声明复制到我们自己的代码中,因为真正的 SDK 版本位于错误的 API 分区中。 (我建议不要修改 SDK 文件来解决问题)。所以首先是我们自己的头文件,例如 StorageHandleAccess.h
,它从 SDK 文件 WindowsStorageCOM.h
:
复制声明
#pragma once
// These are copied from WindowsStorageCOM.h
// You can remove this header file once the real file has been updated
// to fix the WINAPI_PARTITION_DESKTOP block
typedef interface IOplockBreakingHandler IOplockBreakingHandler;
typedef interface IStorageItemHandleAccess IStorageItemHandleAccess;
typedef interface IStorageFolderHandleAccess IStorageFolderHandleAccess;
#ifdef __cplusplus
extern "C" {
#endif
typedef /* [v1_enum] */
enum HANDLE_OPTIONS
{
HO_NONE = 0,
HO_OPEN_REQUIRING_OPLOCK = 0x40000,
HO_DELETE_ON_CLOSE = 0x4000000,
HO_SEQUENTIAL_SCAN = 0x8000000,
HO_RANDOM_ACCESS = 0x10000000,
HO_NO_BUFFERING = 0x20000000,
HO_OVERLAPPED = 0x40000000,
HO_WRITE_THROUGH = 0x80000000
} HANDLE_OPTIONS;
DEFINE_ENUM_FLAG_OPERATORS(HANDLE_OPTIONS);
typedef /* [v1_enum] */
enum HANDLE_ACCESS_OPTIONS
{
HAO_NONE = 0,
HAO_READ_ATTRIBUTES = 0x80,
HAO_READ = 0x120089,
HAO_WRITE = 0x120116,
HAO_DELETE = 0x10000
} HANDLE_ACCESS_OPTIONS;
DEFINE_ENUM_FLAG_OPERATORS(HANDLE_ACCESS_OPTIONS);
typedef /* [v1_enum] */
enum HANDLE_SHARING_OPTIONS
{
HSO_SHARE_NONE = 0,
HSO_SHARE_READ = 0x1,
HSO_SHARE_WRITE = 0x2,
HSO_SHARE_DELETE = 0x4
} HANDLE_SHARING_OPTIONS;
DEFINE_ENUM_FLAG_OPERATORS(HANDLE_SHARING_OPTIONS);
typedef /* [v1_enum] */
enum HANDLE_CREATION_OPTIONS
{
HCO_CREATE_NEW = 0x1,
HCO_CREATE_ALWAYS = 0x2,
HCO_OPEN_EXISTING = 0x3,
HCO_OPEN_ALWAYS = 0x4,
HCO_TRUNCATE_EXISTING = 0x5
} HANDLE_CREATION_OPTIONS;
EXTERN_C const IID IID_IOplockBreakingHandler;
MIDL_INTERFACE("826ABE3D-3ACD-47D3-84F2-88AAEDCF6304")
IOplockBreakingHandler : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE OplockBreaking(void) = 0;
};
EXTERN_C const IID IID_IStorageItemHandleAccess;
MIDL_INTERFACE("5CA296B2-2C25-4D22-B785-B885C8201E6A")
IStorageItemHandleAccess : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Create(
/* [in] */ HANDLE_ACCESS_OPTIONS accessOptions,
/* [in] */ HANDLE_SHARING_OPTIONS sharingOptions,
/* [in] */ HANDLE_OPTIONS options,
/* [optional][in] */ __RPC__in_opt IOplockBreakingHandler *oplockBreakingHandler,
/* [system_handle][retval][out] */ __RPC__deref_out_opt HANDLE *interopHandle) = 0;
};
EXTERN_C const IID IID_IStorageFolderHandleAccess;
MIDL_INTERFACE("DF19938F-5462-48A0-BE65-D2A3271A08D6")
IStorageFolderHandleAccess : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Create(
/* [string][in] */ __RPC__in_string LPCWSTR fileName,
/* [in] */ HANDLE_CREATION_OPTIONS creationOptions,
/* [in] */ HANDLE_ACCESS_OPTIONS accessOptions,
/* [in] */ HANDLE_SHARING_OPTIONS sharingOptions,
/* [in] */ HANDLE_OPTIONS options,
/* [optional][in] */ __RPC__in_opt IOplockBreakingHandler *oplockBreakingHandler,
/* [system_handle][retval][out] */ __RPC__deref_out_opt HANDLE *interopHandle) = 0;
};
#ifdef __cplusplus
}
#endif
接下来是API的简单用法。此示例采用 StorageFolder
、文件名和创建标志(打开或创建)并尝试打开(或创建)命名文件,从文件中读取(或写入)一些文本,然后写入一些输出到调试控制台。
该代码在现实环境中不是特别有用,但说明了如何使用 API。这可以在空白的 C++ XAML 项目中用于替换 MainPage.xaml.cpp
文件(您应该只需要更新命名空间):
#include "pch.h"
#include "MainPage.xaml.h"
#include <ppltasks.h>
// TODO: Replace with your namespace
#error Replace this with your real namespace
using namespace FileHandleFromStorageFolder;
// Uncomment out this line and delete the next line once the SDK is fixed
//#include <WindowsStorageCOM.h>
#include "StorageHandleAccess.h"
// For ComPtr<>
#include <wrl\client.h>
// For HandleT<>
#include <wrl\wrappers\corewrappers.h>
__declspec(noreturn) inline void ThrowWithHRESULT(HRESULT hr, const wchar_t* message)
{
using namespace Platform;
throw ref new Exception(hr, ref new String(message));
}
__declspec(noreturn) inline void ThrowWithGetLastError(const wchar_t* message)
{
using namespace Platform;
throw ref new Exception(HRESULT_FROM_WIN32(GetLastError()), ref new String(message));
}
// Test is a simple test function. Pass in one of the HANDLE_CREATION_OPTIONS values
// (eg, HCO_CREATE_ALWAYS or HCO_OPEN_ALWAYS) and the function will try and either
// write to the file (if it's empty) or read from it (if it's not).
void Test(Windows::Storage::StorageFolder^ folder, const wchar_t* filename, HANDLE_CREATION_OPTIONS options)
{
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
// Get an IUnknown from the ref class, and then QI for IStorageFolderHandleAccess
ComPtr<IUnknown> abiPointer(reinterpret_cast<IUnknown*>(folder));
ComPtr<IStorageFolderHandleAccess> handleAccess;
HRESULT hr = abiPointer.As(&handleAccess);
if (FAILED(hr))
ThrowWithHRESULT(hr, L"Can't QI");
// Standard RAII wrapper for HANDLEs that represent files
HandleT<HandleTraits::FileHandleTraits>win32fileHandle;
// This is roughly equivalent of calling CreateFile2
hr = handleAccess->Create(filename, options,
HANDLE_ACCESS_OPTIONS::HAO_WRITE | HANDLE_ACCESS_OPTIONS::HAO_READ,
HANDLE_SHARING_OPTIONS::HSO_SHARE_NONE, HANDLE_OPTIONS::HO_NONE, nullptr,
win32fileHandle.GetAddressOf());
if (FAILED(hr))
ThrowWithHRESULT(hr, L"Can't access file");
// From here, it's standard Win32 code - nothing WinRT specific at all
LARGE_INTEGER size{ 0 };
if (FALSE == GetFileSizeEx(win32fileHandle.Get(), &size))
ThrowWithGetLastError(L"Can't get file size");
static const DWORD BUFFER_SIZE = 500;
char buffer[BUFFER_SIZE];
DWORD bytesUsed{ 0 };
if (size.QuadPart == 0)
{
const static auto str = "Hello, world\r\n";
if (FALSE == WriteFile(win32fileHandle.Get(), str, strlen(str), &bytesUsed, nullptr))
ThrowWithGetLastError(L"Can't write to file");
sprintf_s(buffer, ARRAYSIZE(buffer), "Wrote %d bytes to file\r\n", bytesUsed);
OutputDebugStringA(buffer);
}
else
{
if (FALSE == ReadFile(win32fileHandle.Get(), buffer, ARRAYSIZE(buffer) - 1, &bytesUsed, nullptr))
ThrowWithGetLastError(L"Can't read from file");
buffer[bytesUsed] = 0;
OutputDebugStringA(buffer);
}
}
// Trivial driver that gets a StorageFolder and then creates a file
// inside it, writes some text, then re-opens it to read text.
void TestWrapper()
{
using namespace Windows::Storage;
using namespace Windows::Storage::Pickers;
auto picker = ref new FolderPicker();
picker->FileTypeFilter->Append(L".txt");
picker->SuggestedStartLocation = PickerLocationId::Desktop;
concurrency::create_task(picker->PickSingleFolderAsync()).then([]
(StorageFolder^ folder)
{
if (folder != nullptr)
{
// Create and then read back a simple file
Test(folder, L"win32handletest.txt", HANDLE_CREATION_OPTIONS::HCO_CREATE_ALWAYS);
Test(folder, L"win32handletest.txt", HANDLE_CREATION_OPTIONS::HCO_OPEN_ALWAYS);
}
}
);
}
MainPage::MainPage()
{
InitializeComponent();
TestWrapper();
}
我知道我可以使用 Win32 API 访问我自己的本地数据文件夹中的文件(例如,请参阅 HANDLE
s 和/或它们依赖于使用相对文件名。
由于获取图片库中文件(或获取从选取器返回的文件/文件夹)的唯一方法是通过 StorageFile
对象,我如何才能重新使用现有代码?我是否必须将其全部重写为异步并依赖于 WinRT 存储 API?
从 "Anniversary Update"(又名 "RS1" 或内部版本 10.0.14393)开始,您可以从 StorageItem
(文件或文件夹)获取 Win32 HANDLE
并从 StorageFolder
中创建新的命名文件(返回 HANDLE
)。您可以使用新的 IStorageFolderHandleAccess
and IStorageItemHandleAccess
API 来执行此操作。
Note: These APIs have been accidentally placed inside the
WINAPI_PARTITION_DESKTOP
partition (they're not desktop-specific; they're available to UWPs). This will be addressed in future SDK updates.
要使用这些新的 COM 接口之一,您只需 QI 接口的 StorageFile
或 StorageFolder
。如果该接口不受支持,则意味着您的应用程序 运行 处于下层 OS(或者存储项实际上并非由真实文件支持,而是伪-文件)。您可以从 C++(C++/CX 或 WRL)或 C# 使用这些接口。
这是一个简单的示例,使用 FolderPicker
让用户在他们的磁盘上选择一个位置(returns 代理 StorageFolder
对象)然后使用 Win32 APIs ReadFile
和 WriteFile
从该位置读取和写入文件。
如上所述,我们必须将接口声明复制到我们自己的代码中,因为真正的 SDK 版本位于错误的 API 分区中。 (我建议不要修改 SDK 文件来解决问题)。所以首先是我们自己的头文件,例如 StorageHandleAccess.h
,它从 SDK 文件 WindowsStorageCOM.h
:
#pragma once
// These are copied from WindowsStorageCOM.h
// You can remove this header file once the real file has been updated
// to fix the WINAPI_PARTITION_DESKTOP block
typedef interface IOplockBreakingHandler IOplockBreakingHandler;
typedef interface IStorageItemHandleAccess IStorageItemHandleAccess;
typedef interface IStorageFolderHandleAccess IStorageFolderHandleAccess;
#ifdef __cplusplus
extern "C" {
#endif
typedef /* [v1_enum] */
enum HANDLE_OPTIONS
{
HO_NONE = 0,
HO_OPEN_REQUIRING_OPLOCK = 0x40000,
HO_DELETE_ON_CLOSE = 0x4000000,
HO_SEQUENTIAL_SCAN = 0x8000000,
HO_RANDOM_ACCESS = 0x10000000,
HO_NO_BUFFERING = 0x20000000,
HO_OVERLAPPED = 0x40000000,
HO_WRITE_THROUGH = 0x80000000
} HANDLE_OPTIONS;
DEFINE_ENUM_FLAG_OPERATORS(HANDLE_OPTIONS);
typedef /* [v1_enum] */
enum HANDLE_ACCESS_OPTIONS
{
HAO_NONE = 0,
HAO_READ_ATTRIBUTES = 0x80,
HAO_READ = 0x120089,
HAO_WRITE = 0x120116,
HAO_DELETE = 0x10000
} HANDLE_ACCESS_OPTIONS;
DEFINE_ENUM_FLAG_OPERATORS(HANDLE_ACCESS_OPTIONS);
typedef /* [v1_enum] */
enum HANDLE_SHARING_OPTIONS
{
HSO_SHARE_NONE = 0,
HSO_SHARE_READ = 0x1,
HSO_SHARE_WRITE = 0x2,
HSO_SHARE_DELETE = 0x4
} HANDLE_SHARING_OPTIONS;
DEFINE_ENUM_FLAG_OPERATORS(HANDLE_SHARING_OPTIONS);
typedef /* [v1_enum] */
enum HANDLE_CREATION_OPTIONS
{
HCO_CREATE_NEW = 0x1,
HCO_CREATE_ALWAYS = 0x2,
HCO_OPEN_EXISTING = 0x3,
HCO_OPEN_ALWAYS = 0x4,
HCO_TRUNCATE_EXISTING = 0x5
} HANDLE_CREATION_OPTIONS;
EXTERN_C const IID IID_IOplockBreakingHandler;
MIDL_INTERFACE("826ABE3D-3ACD-47D3-84F2-88AAEDCF6304")
IOplockBreakingHandler : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE OplockBreaking(void) = 0;
};
EXTERN_C const IID IID_IStorageItemHandleAccess;
MIDL_INTERFACE("5CA296B2-2C25-4D22-B785-B885C8201E6A")
IStorageItemHandleAccess : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Create(
/* [in] */ HANDLE_ACCESS_OPTIONS accessOptions,
/* [in] */ HANDLE_SHARING_OPTIONS sharingOptions,
/* [in] */ HANDLE_OPTIONS options,
/* [optional][in] */ __RPC__in_opt IOplockBreakingHandler *oplockBreakingHandler,
/* [system_handle][retval][out] */ __RPC__deref_out_opt HANDLE *interopHandle) = 0;
};
EXTERN_C const IID IID_IStorageFolderHandleAccess;
MIDL_INTERFACE("DF19938F-5462-48A0-BE65-D2A3271A08D6")
IStorageFolderHandleAccess : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Create(
/* [string][in] */ __RPC__in_string LPCWSTR fileName,
/* [in] */ HANDLE_CREATION_OPTIONS creationOptions,
/* [in] */ HANDLE_ACCESS_OPTIONS accessOptions,
/* [in] */ HANDLE_SHARING_OPTIONS sharingOptions,
/* [in] */ HANDLE_OPTIONS options,
/* [optional][in] */ __RPC__in_opt IOplockBreakingHandler *oplockBreakingHandler,
/* [system_handle][retval][out] */ __RPC__deref_out_opt HANDLE *interopHandle) = 0;
};
#ifdef __cplusplus
}
#endif
接下来是API的简单用法。此示例采用 StorageFolder
、文件名和创建标志(打开或创建)并尝试打开(或创建)命名文件,从文件中读取(或写入)一些文本,然后写入一些输出到调试控制台。
该代码在现实环境中不是特别有用,但说明了如何使用 API。这可以在空白的 C++ XAML 项目中用于替换 MainPage.xaml.cpp
文件(您应该只需要更新命名空间):
#include "pch.h"
#include "MainPage.xaml.h"
#include <ppltasks.h>
// TODO: Replace with your namespace
#error Replace this with your real namespace
using namespace FileHandleFromStorageFolder;
// Uncomment out this line and delete the next line once the SDK is fixed
//#include <WindowsStorageCOM.h>
#include "StorageHandleAccess.h"
// For ComPtr<>
#include <wrl\client.h>
// For HandleT<>
#include <wrl\wrappers\corewrappers.h>
__declspec(noreturn) inline void ThrowWithHRESULT(HRESULT hr, const wchar_t* message)
{
using namespace Platform;
throw ref new Exception(hr, ref new String(message));
}
__declspec(noreturn) inline void ThrowWithGetLastError(const wchar_t* message)
{
using namespace Platform;
throw ref new Exception(HRESULT_FROM_WIN32(GetLastError()), ref new String(message));
}
// Test is a simple test function. Pass in one of the HANDLE_CREATION_OPTIONS values
// (eg, HCO_CREATE_ALWAYS or HCO_OPEN_ALWAYS) and the function will try and either
// write to the file (if it's empty) or read from it (if it's not).
void Test(Windows::Storage::StorageFolder^ folder, const wchar_t* filename, HANDLE_CREATION_OPTIONS options)
{
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
// Get an IUnknown from the ref class, and then QI for IStorageFolderHandleAccess
ComPtr<IUnknown> abiPointer(reinterpret_cast<IUnknown*>(folder));
ComPtr<IStorageFolderHandleAccess> handleAccess;
HRESULT hr = abiPointer.As(&handleAccess);
if (FAILED(hr))
ThrowWithHRESULT(hr, L"Can't QI");
// Standard RAII wrapper for HANDLEs that represent files
HandleT<HandleTraits::FileHandleTraits>win32fileHandle;
// This is roughly equivalent of calling CreateFile2
hr = handleAccess->Create(filename, options,
HANDLE_ACCESS_OPTIONS::HAO_WRITE | HANDLE_ACCESS_OPTIONS::HAO_READ,
HANDLE_SHARING_OPTIONS::HSO_SHARE_NONE, HANDLE_OPTIONS::HO_NONE, nullptr,
win32fileHandle.GetAddressOf());
if (FAILED(hr))
ThrowWithHRESULT(hr, L"Can't access file");
// From here, it's standard Win32 code - nothing WinRT specific at all
LARGE_INTEGER size{ 0 };
if (FALSE == GetFileSizeEx(win32fileHandle.Get(), &size))
ThrowWithGetLastError(L"Can't get file size");
static const DWORD BUFFER_SIZE = 500;
char buffer[BUFFER_SIZE];
DWORD bytesUsed{ 0 };
if (size.QuadPart == 0)
{
const static auto str = "Hello, world\r\n";
if (FALSE == WriteFile(win32fileHandle.Get(), str, strlen(str), &bytesUsed, nullptr))
ThrowWithGetLastError(L"Can't write to file");
sprintf_s(buffer, ARRAYSIZE(buffer), "Wrote %d bytes to file\r\n", bytesUsed);
OutputDebugStringA(buffer);
}
else
{
if (FALSE == ReadFile(win32fileHandle.Get(), buffer, ARRAYSIZE(buffer) - 1, &bytesUsed, nullptr))
ThrowWithGetLastError(L"Can't read from file");
buffer[bytesUsed] = 0;
OutputDebugStringA(buffer);
}
}
// Trivial driver that gets a StorageFolder and then creates a file
// inside it, writes some text, then re-opens it to read text.
void TestWrapper()
{
using namespace Windows::Storage;
using namespace Windows::Storage::Pickers;
auto picker = ref new FolderPicker();
picker->FileTypeFilter->Append(L".txt");
picker->SuggestedStartLocation = PickerLocationId::Desktop;
concurrency::create_task(picker->PickSingleFolderAsync()).then([]
(StorageFolder^ folder)
{
if (folder != nullptr)
{
// Create and then read back a simple file
Test(folder, L"win32handletest.txt", HANDLE_CREATION_OPTIONS::HCO_CREATE_ALWAYS);
Test(folder, L"win32handletest.txt", HANDLE_CREATION_OPTIONS::HCO_OPEN_ALWAYS);
}
}
);
}
MainPage::MainPage()
{
InitializeComponent();
TestWrapper();
}