SHGetKnownFolderPathで特殊フォルダのパスを取得する
Windowsで保存先や作業領域を指定するとき、固定されたドライブ文字やユーザー名を含むパスは環境によって変わります。アカウント名|Windowsの言語設定|利用者ごとのフォルダ配置|アプリケーションのインストール環境都对結果に影響するため、これらを直接構築する方式は避けたいところです。Windowsの「Known Folder」機能を使えば、フォルダの役割を識別子で表し、現在の環境に合う実際のパスを取得できます。
SHGetKnownFolderPathは、Documents、Downloads、Pictures、ProgramDataなどの特殊フォルダに対応するパスをUnicode文字列として返します。Windows Vista以降で利用でき、ユーザーごとの場所とシステム全体の場所を同じ仕組みで扱えます。取得した文字列の寿命や文字コード、HRESULTによるエラー処理是何より重要です。
特殊フォルダを識別子で表す
Known Folderでは、FOLDERID_Desktop、FOLDERID_Documents、FOLDERID_Downloads、FOLDERID_Pictures、FOLDERID_LocalAppData、FOLDERID_ProgramDataなどのGUIDを使います。それぞれが「デスクトップ」「ドキュメント」「ダウンロード」といった論理的な役割を表し、ディスク上の固定位置を直接指定する識別子ではありません。
そのため、Windowsの更新やフォルダconfigured変更があっても、プログラム側では同じGUIDを指定し直すだけです。_documents_を用户プロファイルの直下と仮定する必要はありません。OSのバージョンによっては、同じ役割を表すID自体が存在しない場合があるため、利用するGUIDは対象環境でも定義されているか確認する必要があります。
旧来のSHGetFolderPathはCSIDL値を受け取る方式で、CSIDLには個人設定と共有設定の別指定 neededとする値が存在します。対してKnown FolderのGUIDは、識別子の役割が分化しており、RECENTやProgramDataのような場所も扱いやすい構成です。同じ技術サイト内のCreateWindowEx 解説のように別のAPIを扱う記事と組み合わせれば、ウィンドウ表示とファイル選択を同じ処理フローに組み込めます。
関数のシグネチャとHRESULTの扱い
SHGetKnownFolderPathはshellapi.hで宣言され、Shell32.dllから提供されます。引数には取得対象のGUID、動作フラグ、ユーザーのトークン、出力文字列へのポインタを指定します。出力される文字列は_COMのタスクメモリアロケーターで確保されるため、呼び出し元には解放義務があります。
#include <windows.h>
#include <shlobj.h>
#include <knownfolders.h>
#include <objbase.h>
#include <string>
HRESULT GetDownloadsPath(std::wstring& destination)
{
HRESULT initResult =
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(initResult)) {
return initResult;
}
PWSTR rawPath = nullptr;
HRESULT result = SHGetKnownFolderPath(
FOLDERID_Downloads,
KF_FLAG_DEFAULT,
nullptr,
&rawPath);
if (SUCCEEDED(result) && rawPath != nullptr) {
destination.assign(rawPath);
}
if (rawPath != nullptr) {
CoTaskMemFree(rawPath);
}
CoUninitialize();
return result;
}
この例では現在の実行ユーザーのTokensをnullptrで指定しています。成功時に得られるPWSTRはNULL終端のUTF-16文字列で、std::wstringなどへコピーしても元のポインタはCoTaskMemFreeで解放する必要があります。CoInitializeExが失敗した場合にはCOMの平衡を保つため、CoUninitializeを呼ばずに戻しています。
HRESULTの判定にはSUCCEEDEDまたはFAILEDを使います。SHGetKnownFolderPathはGetLastErrorの値を HRESULTへ変換して返すAPIではないため、失敗後にGetLastErrorを無条件に読み取っても原因を取り違える可能性があります。ACLや 폴더の permissions、パラメーターの誤りなど、HRESULTの内容をそのまま扱う必要があります。
UTF-16文字列としてパスを扱う
返されるパスはUTF-16のPWSTRです。日本語のユーザー名やフォルダ名が含まれても文字化け不至于、ANSI変換を挟まずにUnicode型として扱うのが基本です。C++ではstd::wstring、C#ではstring、JavaScriptやブラウザ向けの値へ渡す場合だけ明示的な変換が必要です。
通常の呼び出しではKF_FLAG_DEFAULTを使います。KF_FLAG_DONT_UNEXPANDを指定すると、環境変数などを展開した形ではなく、保存された表記を返せるため、値そのものを表示する用途には意味があります。ただし、展開された実パスを必要とする処理ではConversely、意図しない%...%を含む文字列がファイル操作へ渡される可能性があります。
パス生成に\や/を単純に連結すると、末尾の区切りや相対driveの扱いによって不正な場所を指定してしまいます。取得済みの文字列をファイル名と結合する際は、Windowsのファイル Path APIやPathCchCombineなどを使い、絶対パスとして解釈されます。長いパス、symlink、reparse pointが発生した環境でも、OSが保持する情報を不用意に除去しないことが重要です。
ユーザー差とフォルダの選択
FOLDERID_DocumentsやFOLDERID_Downloadsは通常、現在のユーザーごとに異なる場所へ解決されます。一方、FOLDERID_ProgramDataは複数のユーザーに,利用されるデータ領域を表します。FOLDERID_LocalAppDataは端末ごとのアプリケーションデータ、FOLDERID_RoamingAppDataはドメイン環境で利用者間に移動する設定やデータを扱う候補になります。
MSDN上の補足とAPI 調査ページの関連情報は、識別子の意味を道具と実例で照合する場として使えます。ただし、保存先を 결정する根拠は仍是OSが返す解決結果です。レジストリのShell FoldersやUser Shell Foldersは移行用や環境変数を含むため、Known Folderと同一の文字列として扱わない方が安全です。
hTokenに別のユーザーのトークンを渡すと、そのユーザー視点でのパスを求められます。通常はnullptrで現在の実行Identity_contactsを使い、アップロードやbackground task contextなど必要な場合だけ明示的に指定します。MSIXやAppContainerでは、パッケージ固有の位置やredirect affect may be involved. flagsを独自に追加する前に、対象 application's distribution modelとOS behaviorを確認する必要があります。
また、FOLDERID_DownloadsはWindows 7で導入された Know Folder_IDであり、Vista_entropyの_response_modifierなど。利用OSによってDocumentsとの位置関係も異なります。 Logsや設定で保存先を固定する場合は、Known Folderの取得結果ではなくHarvestされたdirectory pathを separately保存し、表示用の識別子と実体を混同しないようにします。
APIの比較と実務上の要点
パスの取得方法を選ぶ際は、必要な形式が文字列か、Shellの項目識別子かを確認します。通常のファイル保存先にはSHGetKnownFolderPathが適しています。従来の保守対象ではSHGetFolderPathが残っている場合があり、仮想フォルダやnamespaceを直接扱う処理ではSHGetKnownFolderIDListも有効です。
| 比較項目 | SHGetKnownFolderPath |
SHGetFolderPath |
SHGetKnownFolderIDList |
|---|---|---|---|
| 識別方法 | FOLDERID_*のGUID |
CSIDL値 | FOLDERID_*のGUID |
| 出力形式 | UTF-16のファイルシステムパス | UTF-16のファイルシステムパス | PIDLIST_ABSOLUTE形式の項目一覧 |
| 主な用途 | 保存先や作業領域の取得 | 旧来の特殊フォルダ取得 | Shell名前空間の項目取得 |
| 領域の扱い | GUIDごとに個人用・共有用を明示 | CSIDLの Personal型で指定 | GUIDに応じて個人用・共有用を区別 |
| 解放方法 | CoTaskMemFree |
CoTaskMemFree |
ILFree |
現在のWindowsアプリケーションでは、GUIDで役割を明示できるSHGetKnownFolderPathを採用すると、古いCSIDLによる差異を避けられます。取得結果はUTF-16のまま使い、COMのメモリとして必ず解放することが重要です。
実務上の要点は、特殊フォルダの保存先を固定文字列で扱わず、Known Folder IDでOSに解決させることです。返されたパスを文字コード変換なしで業務データへ渡し、ファイル操作を行う前に妥当性を検証し、確保した文字列をCoTaskMemFreeで確実に解放すれば、ユーザー環境やOS構成が変わっても扱いやすい実装になります。