Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
510 views
in Technique[技术] by (71.8m points)

winapi - How can I get a Win32 HANDLE for a StorageFile or StorageFolder in UWP?

I know I can use Win32 APIs for accessing files within my own local data folder (eg, see this answered question) but I need to access files outside of my app (eg, from the Pictures Library) and the libraries I'm trying to use are all based on Win32 file HANDLEs and / or they rely on using relative filenames.

Since the only way to get at files in the Pictures Library (or to get files / folders returned from a picker) is via StorageFile objects, how can I re-use my existing code? Do I have to re-write it all to be asynchronous and rely on the WinRT storage APIs?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Starting in the "Anniversary Update" (aka "RS1" or build 10.0.14393) you are able to get a Win32 HANDLE from a StorageItem (file or folder) and to create new named files (returning a HANDLE) from within a StorageFolder. You do this using the new IStorageFolderHandleAccess and IStorageItemHandleAccess APIs.

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.

To use one of these new COM interfaces, you simply QI the StorageFile or StorageFolder for the interface. If the interface isn't supported, it means your app is running on a down-level OS (or perhaps the Storage Item isn't actually backed by a real file, but is rather a pseudo-file). You can use these interfaces from C++ (C++/CX or WRL) or from C#.

Here's a simple example of using a FolderPicker to have the user pick a location on their disk (which returns a brokered StorageFolder object) and then using Win32 APIs ReadFile and WriteFile to read and write a file from that location.

As noted above, we have to copy the declarations for the interface into our own code because the real SDK versions are in the wrong API partition. (I would advise against modifying the SDK files to solve the problem). So first up is our own header file, eg StorageHandleAccess.h, that copies the declarations from the SDK file 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

Next up is a simple usage of the API. This example takes a StorageFolder, a filename, and a creation flag (open or create) and tries to open (or create) the named file, reads (or writes) some text from (to) the file, and writes some output to the Debug console.

The code isn't particularly useful in a real-world setting, but illustrates how to use the API. This can be used in a blank C++ XAML project to replace the MainPage.xaml.cpp file (you should only need to update the namespace):

#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 <wrlclient.h>

// For HandleT<>
#include <wrlwrapperscorewrappers.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
";
    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
", 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();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...