C#、C++で特別なフォルダ(AppData以下)にファイルを保存したい!
Pythonで作ったWindowsアプリの性能をあげたいと思い、VisualStudio2022で、C++、C#を用いてWindowsアプリを再構築しようとしています。
C#やC++はどちらも初めてで、VisualStudioでのWindowsアプリの開発も初めてです。
詳しい方から言えば、"何を当たり前のことを"という内容かもしれませんが、これだけでざっくり8時間以上は潰したので、記念に残しておきます。
WindowsアプリはAppData以下にアプリの設定ファイルなどを保存するらしい
開発しているWindowsアプリでは、各クライアントごとに作成しなければならないファイル(TensorRTのengineファイル)があり、それを各クライアントで保存しなくてはならない。
WindowsではC:\Users\ユーザ名\AppData\
以下に自分のアプリフォルダを作成し、設定ファイルなどを保存するという。
AppData
は隠しフォルダで、フォルダを開いてみると確かに配下にはいろんなアプリのフォルダと思しきものが並んでいる!
"ここに私のアプリも並ぶのかー。キャー(嬉しい)"
GUIはC#で作成しており、Windows Copilotに相談して、次のようなコードでAppData
配下にフォルダを作ろうとしました。
WoLNamesBlackedOut
は私のアプリ名です。
大量に不要なデバッグ用のコードがたくさんついているのは、私が苦労した証拠です。
try
{
// AppDataのパスを取得
string localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
// アプリケーション専用のフォルダを作成
string appFolder = System.IO.Path.Combine(localAppDataPath, "WoLNamesBlackedOut");
System.IO.Directory.CreateDirectory(appFolder);
// フォルダが正しく作成されたかを確認
if (Directory.Exists(appFolder))
{
Console.WriteLine($"Folder created successfully: {appFolder}");
}
else
{
Console.WriteLine($"Failed to create folder: {appFolder}");
}
// さらに詳細なデバッグ情報を表示
DirectoryInfo dirInfo = new DirectoryInfo(appFolder);
Console.WriteLine($"Directory Full Name: {dirInfo.FullName}");
Console.WriteLine($"Directory Exists: {dirInfo.Exists}");
Console.WriteLine($"Directory Attributes: {dirInfo.Attributes}");
}
catch (Exception ex)
{
Console.WriteLine($"Exception occurred: {ex.Message}");
}
ブレイクポイントを利用して、1行ずつ確認します。
うん、作ってる!
C++で作ったDLLからも書き出し出来てるし、大丈夫ぽいな!
一応作ったファイルのツラを拝んどくか
自分が作っているものはUWPアプリだった!
何を言っているのかと思われるかもしれませんが、私が作っているアプリはUWPアプリだったようです。
Microsoftストアにも公開できたらいいなと思っていたので、できるだけWindowsの最新の作り方がいいだろうとは思って、WinUI3などを採用していましたが、それはUWPアプリという扱いのものだったようなのです。
UWPアプリはパッケージというものになっており、上記の特別なフォルダはC:\Users\ユーザ名\AppData\Local\Packages
以下になります。
アプリ単位(?)に、暗号のような長い名前のフォルダがあり、その配下に下図のような特別なフォルダが存在します。
なお、先ほどブレイクポイントで確認しながら作ったと思っていたWoLNamesBlackedOut
のフォルダは、ここのLocalCache
の下のLocal
にありました。
じゃあそこに作ったよってVS2022には言って欲しいところだけど、きっとWindowsが気を利かせてそのように振る舞ってくれたのでしょうし、あまり大きな声で文句も言えません。
LocalCache
がずっと使えるデータなら良かったのですが、一時的なデータを保存する場所のようなので、ずっと使えるLocalState
の方にフォルダを作って、そこにデータ保存する方針としました。
アプリ起動時にフォルダを作るとして、C++で作ったDLLは、C#からフルパスを渡すか直接アクセスするか悩んだのですが、C++から直接アクセスする方法としました。
C#でLocalState
にフォルダを作成する
これは簡単でした。
try
{
// LocalStateのパスを取得
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
string localAppDataPath = localFolder.Path;
// アプリケーション専用のフォルダを作成
string appFolder = System.IO.Path.Combine(localAppDataPath, "WoLNamesBlackedOut");
if (!Directory.Exists(appFolder))
{
Directory.CreateDirectory(appFolder);
Console.WriteLine($"Folder created successfully: {appFolder}");
}
else
{
Console.WriteLine($"Folder already exists: {appFolder}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception occurred: {ex.Message}");
}
C++でLocalState
に作成したフォルダにファイルを保存する。
こっちはよくわからなくて大変でした。
下のコードが最終形です。
my_yolov8m.engine
は保存するファイル名です。
#include <Windows.Storage.h>
#include <Windows.Foundation.h>
#include <Windows.h>
#include <winrt/Windows.Storage.h>
using namespace winrt::Windows::Storage;
// ローカルフォルダのパスを取得
auto localFolder = ApplicationData::Current().LocalFolder();
std::wstring localAppDataPath = localFolder.Path().c_str();
// アプリケーション専用のフォルダパスを組み立てる
std::wstring appFolderPath = std::wstring(localAppDataPath) + std::wstring { L"\\WoLNamesBlackedOut" };
std::wstring engineFilePath = appFolderPath + std::wstring{ L"\\my_yolov8m.engine" };
std::string engineFilePathStr(engineFilePath.length(), 0);
std::transform(engineFilePath.begin(), engineFilePath.end(), engineFilePathStr.begin(), [](wchar_t c) {
return (char)c;
});
const char* engineFilePathCStr = engineFilePathStr.c_str();
C++では、まずはNuGet Microsoft.Windows.CppWinRT
!
C++でLocalState
にアクセスするにはどうしたら?
と検索すると下記が見つかりました。
ふむふむ。
using namespace Windows::Storage;
を使えば
const wchar_t* wPath = localFolder->Path->Data();
と。
で、早速using namespace Windows::Storage;
って書いてみたんですが、Windows
にStorage
は無いよ?と言われます。
ええ?#include <Windows.Storage.h>
してるけど・・?
#include <winrt/Windows.Storage.h>
って書くのかな?と思って書くと、VS2022はwinrt
なんぞ知らんと出る。
あれこれ調べると、どうやら先にMicrosoft.Windows.CppWinRT
をnugetで入れないといけないらしい。
wstringからcharにしたい
下記の2番目の記事が参考になりました。
Windowsでは、WideCharToMultiByte
というものもあるらしいです。
終わりに
ファイル保存するだけで1日以上試行錯誤してしまいました。
WindowsアプリはWPF、UWP、WinRTと最近いろいろとフレームワークが移ろったためか、情報がまとまってなく、特にWinRT/C++はかなりハードルが高いと感じました。
WindowsアプリはC#で作ってね、ということなのかもしれませんが、凝ったことをしようとするとC++を使わざるを得ず、そのC#とC++の境界となるインターフェース部分にはいろいろノウハウがありそうだなーと感じました。