しばらく更新しない間にWindows 10もリリースされてしまいましたが、手元の環境ではまだVM上でしか動かしていなかったり。
「Windows Phone」改め「Windows 10 Mobile」も今日プレビュー版の新版がリリースされましたが、Insider Previewを入れているLumia 620では相変わらず重すぎて触る気が起きないレベルなのが何とも…
そんな中、チマチマとWindows Phone 8.1向けWindowsランタイム(WinRT)ベースのアプリを製作しております。
最新のUWPアプリはWindows 10シリーズでないと動かないため、現時点でいちばん普及しているWP8.1をターゲットとしている次第。
というわけで、この開発作業中に出くわした、とある問題とその対処法について。
WinRTアプリでフォルダやファイルを取り扱う場合、StorageFolderやStorageFileというクラスを利用します。
そしてそれらを通常はLocalFolderというアプリ固有の場所に格納しておくわけですが、まず第一の問題はStorageFolderクラスにファイルの存在チェックメソッドが存在しません。(Phoneのみ、PCはTryGetItemAsyncメソッドでチェックできます)
存在しないファイルにアクセスしようとすると例外を吐くので、情けないことにこれを拾って処理するなどしなければなりません。
次の問題として、あるフォルダに格納されている数千件レベルの大量ファイルの一覧(コレクション)を取得しようとするとやたら時間がかかります。
StorageFileのインスタンスが欲しい場合は仕方ないにしても、ファイル名の一覧だけを取りたい場合などこれでは困ります。
エミュレータは速かったりするので気づきにくいのですが、実機で動かすと数秒レベルで待たされてしまいます。
また、更新日付やファイルサイズなどのプロパティ情報を取得するには別途メソッドコールが必要となり更に遅くなるなど、とにかく大量のファイルに対して何か処理しようと思うと不都合なことが頻発します。
当初はインデックス情報のようなファイルを別途用意して回避しようと考えていたのですが、調べてみるとC++/CXを使うとフォルダアクセスが劇的に速くなることが判明。
C++は門外漢なのですが見よう見まねでC#から呼び出すためのコンポーネントを作ってみたところ、素晴らしく速くなったので参考までにご紹介。
ちなみにネタ元はこちらです。
とにかくFindFirstFileExがやたら速いのでStorageFileインスタンスそのものを必要とないフォルダスキャン処理はC++側に寄せてしまうのもよろしいかと。
using namespace Platform;
namespace LocalStorageComponent
{
public ref struct LocalStorageInfo sealed
{
public:
Windows::Foundation::IAsyncOperation<Boolean>^ LocalStorageInfo::IsFileExistsAsync(String^ Folder, String^ Filename);
Boolean LocalStorageInfo::IsFileExists(String^ Folder, String^ Filename);
Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVectorView<String^>^>^ GetFilenamesAsync(String^ Folder, String^ Filename);
Windows::Foundation::Collections::IVectorView<String^>^ GetFilenames(String^ Folder, String^ Filename);
private:
};
}
#include "LocalStorageInfo.h"
using namespace Platform;
using namespace Windows::Storage;
namespace LocalStorageComponent
{
Boolean LocalStorageInfo::IsFileExists(String^ Folder, String^ Filename)
{
std::wstring localFolderPath(ApplicationData::Current->LocalFolder->Path->Data());
std::wstring folder(Folder->Data());
std::wstring filename(Filename->Data());
localFolderPath += L"\\" + folder + L"\\" + filename;
bool found = false;
WIN32_FIND_DATA findData{ 0 };
HANDLE hFile = FindFirstFileEx(localFolderPath.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH);
if (hFile != INVALID_HANDLE_VALUE)
{
found = true;
FindClose(hFile);
}
return found;
}
Windows::Foundation::IAsyncOperation<Boolean>^ LocalStorageInfo::IsFileExistsAsync(String^ Folder, String^ Filename)
{
return concurrency::create_async([this, Folder, Filename]
{
return IsFileExists(Folder, Filename);
});
}
Windows::Foundation::Collections::IVectorView<String^>^ LocalStorageInfo::GetFilenames(String^ Folder, String^ Filename)
{
auto results = ref new Collections::Vector<String^>();
std::wstring localFolderPath(ApplicationData::Current->LocalFolder->Path->Data());
std::wstring folder(Folder->Data());
std::wstring filename(Filename->Data());
localFolderPath += L"\\" + folder + L"\\" + filename;
uint32 size = 0;
WIN32_FIND_DATA findData{ 0 };
HANDLE hFile = FindFirstFileEx(localFolderPath.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH);
if (hFile != INVALID_HANDLE_VALUE)
{
do
{
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
results->Append(ref new String(findData.cFileName));
}
} while (FindNextFile(hFile, &findData) != 0);
auto hr = GetLastError();
FindClose(hFile);
if (hr != ERROR_NO_MORE_FILES)
throw ref new Exception(hr, L"Error finding files");
}
return results->GetView();
}
Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVectorView<String^>^>^ LocalStorageInfo::GetFilenamesAsync(String^ Folder, String^ Filename)
{
return concurrency::create_async([this, Folder, Filename]
{
return GetFilenames(Folder, Filename);
});
}
}