MRが楽しい

MRやVRについて学習したことを書き残す

UWPアプリでファイルパスでのファイルアクセスを利用する(C++編)

本日は UWP アプリの調査枠です。
前回インストールを行った FBX SDK を UWP アプリで利用しようとしたところ、インポータのAPIがファイルパス指定しか受け付けませんでした。
bluebirdofoz.hatenablog.com

しかし、以下の情報によると UWP アプリはアプリデータディレクトリでしかトラディショナルなファイルアクセスは利用できないとのこと。
・[UWP][C++] WinRT file access
 https://social.msdn.microsoft.com/Forums/windowsapps/en-US/a31ef6b8-9373-4968-8960-fef6e5edb591/uwpc-winrt-file-access?forum=wpdevelop


ドキュメントフォルダやカメラロールは、権限を与えればファイルアクセスはできます。
しかし、StorageItem API 経由のアクセスとなり、この API を利用する場合、ファイルパスなどの利用はできません。
・Skip the path: stick to the StorageFile
 https://blogs.msdn.microsoft.com/wsdevsol/2012/12/04/skip-the-path-stick-to-the-storagefile/

よって、外部ファイルに対してファイルパスでのファイルアクセスを利用したい場合。
・権限を与えて外部ファイルに StorageItem API 経由でアクセスする。
 ↓
・StorageItem API を使ってアプリデータディレクトリに外部ファイルをコピーする。
 ↓
・インストールディレクトリのファイルにトラディショナルなファイルアクセスを利用する。
理論上、これらの段階を踏めば利用可能です。


早速試してみました。前回作成した DirectX11 プロジェクトで実施します。
bluebirdofoz.hatenablog.com

外部ファイルのアクセスですが、今回はファイルピッカーによるアクセスを利用します。
docs.microsoft.com

ファイルピッカーを利用すれば、アプリ側の権限設定は不要です。
以下のコードで、ユーザが指定したファイルに StorageItem API 経由でアクセスできます。

#include "ppltasks.h"

int FBXLoader::FBXLoadTestPickerSave()
{
  // ファイルピッカーを初期化する
  Windows::Storage::Pickers::FileOpenPicker^ openPicker = ref new Windows::Storage::Pickers::FileOpenPicker();
  // 表示モードをサムネイル表示に設定する
  openPicker->ViewMode = Windows::Storage::Pickers::PickerViewMode::Thumbnail;
  // 初期ディレクトリをドキュメントディレクトリに設定する
  openPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::PicturesLibrary;
  // ファイルタイプのフィルタを設定する
  openPicker->FileTypeFilter->Append("fbx");

  // ファイルピッカーを起動する
  concurrency::create_task(openPicker->PickSingleFileAsync()).then([this](Windows::Storage::StorageFile^ file)
  {
    // ファイル読み込みが成功したか
    if (file)
    {
      // ファイル読み込みが成功した場合
      Platform::String^ pickFilePath = file->Name;
    else
    {
      // ファイル読み込みが失敗した場合
    }
  });
  return 0;
}

docs.microsoft.com

アクセスが成功したら、次はファイルコピーを行います。
アプリデータディレクトリの LocalState ディレクトリにファイルをコピーします。
以下のコードで StorageItem API を利用してファイルコピーを行います。

  // アプリデータディレクトリの LocalState ディレクトリへのパスを取得する
  Windows::Storage::StorageFolder^ localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
  // 書き込む際のファイル名を指定する(読み込みファイルと同名)
  Platform::String^ fbxName = file->Name;

  // ファイルコピーを行う(上書き保存を許可)
  concurrency::create_task(file->CopyAsync(localFolder, fbxName, Windows::Storage::NameCollisionOption::ReplaceExisting)).then([this](Windows::Storage::StorageFile^ copiedFile)
  {
    // ファイルコピーが成功したか
    if (copiedFile)
    {
      // ファイルコピーが成功した場合
      Platform::String^ copyFilePath = copiedFile->Name;
    else
    {
      // ファイルコピーが失敗した場合
    }
  });

・StorageFile.CopyAsync(IStorageFolder, String, NameCollisionOption)
 https://msdn.microsoft.com/ja-jp/library/windows/apps/br227192.aspx

これでコピーしたファイルに対して、ファイルパスでのファイルアクセスが利用可能です。
ファイルパスは Platform::String 型で取得されるので、必要であれば型の変換を行います。
今回は Platform::String を const char* に変換しました。

  // ファイルパスを取得し、const char* 型に変換する
  Platform::String^ filePathPS = copiedFile->Path;
  std::wstring filePathWS(filePathPS->Begin());
  std::string filePathSS(filePathWS.begin(), filePathWS.end());
  const char* filePath = filePathSS.c_str();

  // ここでファイルアクセス処理を実行する

・How to convert Platform::String to char*?
 https://stackoverflow.com/questions/11746146/how-to-convert-platformstring-to-char

これでユーザ指定の外部ファイルにファイルパスでのファイルアクセスが利用できました。
因みに、PC上で本アプリを動作した場合、コピーが行われる LocalState ディレクトリは以下のパスになります。
・C:\Users\(ユーザ名)\AppData\Local\Packages\(アプリ名)\LocalState

最終的に、全てを組み合わせたコードは以下になりました。

#include "ppltasks.h"

int FBXLoader::FBXLoadTestPickerSave()
{
  // ファイルピッカーを初期化する
  Windows::Storage::Pickers::FileOpenPicker^ openPicker = ref new Windows::Storage::Pickers::FileOpenPicker();
  // 表示モードをサムネイル表示に設定する
  openPicker->ViewMode = Windows::Storage::Pickers::PickerViewMode::Thumbnail;
  // 初期ディレクトリをドキュメントディレクトリに設定する
  openPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary;
  // ファイルタイプのフィルタを設定する
  openPicker->FileTypeFilter->Append("*");

  // ファイルピッカーを起動する
  concurrency::create_task(openPicker->PickSingleFileAsync()).then([this](Windows::Storage::StorageFile^ file)
  {
    // ファイル読み込みが成功したか
    if (file)
    {
      // ファイル読み込みが成功した場合
      Platform::String^ pickFilePath = file->Name;
      // アプリデータディレクトリの LocalState ディレクトリへのパスを取得する
      Windows::Storage::StorageFolder^ localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
      // 書き込む際のファイル名を指定する(読み込みファイルと同名)
      Platform::String^ fbxName = file->Name;

      // ファイルコピーを行う(上書き保存を許可)
      concurrency::create_task(file->CopyAsync(localFolder, fbxName, Windows::Storage::NameCollisionOption::ReplaceExisting)).then([this](Windows::Storage::StorageFile^ copiedFile)
      {
        // ファイルコピーが成功したか
        if (copiedFile)
        {
          // FBXファイルの読み込みパスを指定
          Platform::String^ filePathPS = copiedFile->Path;
          std::wstring filePathWS(filePathPS->Begin());
          std::string filePathSS(filePathWS.begin(), filePathWS.end());
          const char* filePath = filePathSS.c_str();

          // ここでファイルアクセス処理を実行する
        }
        else
        {
          // ファイルコピーが失敗した場合
          // Operation cancelled.
        }
      });
    }
    else
    {
      // ファイル読み込みが失敗した場合
      // Operation cancelled.
    }
  });
  return 0;
}