第 8 章: Windows 7 ライブラリとシェルの使用
ドキュメントや画像などのユーザー ファイルは、さまざまな場所 (ローカルで取り付けられたさまざまな種類のハードウェア、またはネットワーク上の別のコンピューター) に保存されている可能性があります。従来、画像は [マイ ピクチャ] フォルダー、ドキュメントは [マイ ドキュメント] フォルダーというように、多くの場合、ファイルの種類に応じて特定の物理的な場所に保存されていました。しかし、ファイルにアクセスする方法としては、ファイルの種類よりも場所の方が、はるかに強力で新しい方法といえます。Windows 7 ライブラリの目的は、そこにあります。多くの異なる場所にファイルが保存されていても、ファイルの種類に応じて 1 つの論理的な場所から、さまざまな場所にあるファイルにアクセスすることができます。ライブラリはユーザー定義のコンテンツ コレクションであり、インデックスによって迅速な検索とソートが可能です。Hilo では Windows 7 ライブラリ機能を使用してユーザーの画像にアクセスします。
シェル名前空間の使用
Windows シェル名前空間は、階層構造によって広い範囲のオブジェクトへのアクセスを提供します。Windows Vista では、特殊なフォルダーに関する旧来の Constant Special Item ID List (CSIDL) 名前付けに代わって、既知のフォルダー ID が導入されました。どちらのケースでも、特殊なフォルダーには、ビデオ、画像、音楽などの特定の種類のデータが含まれています。ただし、これらのフォルダーのアクセスには ID を使用し、その ID が実行時にディスク ストレージ パスへのアクセスを提供します。Windows Vista の既知のフォルダーは、既知のフォルダーに依存するアプリケーションを変更せずに保存先を変更する機能など、CSIDL よりも多くの機能が提供されます (CSIDL の場合、ユーザーが変更できるのは [マイ ドキュメント] の場所のみです)。
Windows 7 では、ライブラリによって既知のフォルダーの概念を拡張しています。Windows 7 ライブラリはユーザーが定義するフォルダーの集合であり、ライブラリで実行される操作は、ライブラリ内のすべてのフォルダーに適用されます。つまり、Windows 7 ライブラリを検索するとき、ライブラリに含まれる全フォルダーが検索の対象となります。また、ライブラリ内で項目をスタック化すると、フォルダーの実際の場所にかかわらず、ライブラリ内のすべてのフォルダーの項目がスタックに含まれます。Windows 7 ライブラリには、Windows 7 のインデックス化が適用されます。つまり、ライブラリの検索は、ライブラリ内の全フォルダーを対象に実行されます。
Windows エクスプローラーは、ナビゲーション ペインでライブラリを表示します (図 1)。実際に含まれるフォルダーは、ライブラリのプロパティによって示されます。このプロパティ ダイアログを使用して、フォルダーの追加や削除を行ったり、既定の保存先として使用するフォルダーを決定できます。ユーザーは自分のアカウントでアクセス可能なローカル ディスク上の任意のフォルダーを追加したり、USB ドライブやサーバー上のシェアなど、外部のドライブ上のフォルダーを追加することもできます。一方、リムーバブル ドライブ上のフォルダーや、オフラインでは使用不可能なリモート シェアのフォルダーを追加することはできません (MSDN にライブラリに追加できないフォルダーの一覧があります)。
図 1 Windows 7 ライブラリ
ライブラリを選択すると、Windows エクスプローラーはそのライブラリに含まれるファイルとフォルダーを集約したビューを表示します (図 2)。
図 2 ドキュメント ライブラリの内容を表示
ライブラリはユーザー コンテンツの論理表現です。つまり、ライブラリ自体にファイルを保存するのではなく、ライブラリに含まれるフォルダーにファイルを保存します。したがって、たとえば Documents ライブラリはドキュメントの既定の場所であり、[マイ ドキュメント] フォルダーや [パプリック ドキュメント] フォルダーに含まれるユーザーのドキュメントを含んでいます。Windows エクスプローラーは Documents ライブラリをフォルダーのように表示しますが、物理的なフォルダーが存在するわけではありません。したがって、ユーザーが Documents ライブラリにファイルを保存した場合、そのファイルは実際には既定の保存先に保存されます (図 1 の場合、保存先は [マイ ドキュメント] に設定されています)。
Windows 7 アプリケーション プログラミング インターフェイス (API) は、ライブラリのコンテンツにアクセスするための COM ベース オブジェクトを提供します。保存先の絶対パスを知らなくても、論理階層を通じてシェル項目オブジェクトをスキャンすることができます (ただし、システム ファイル パスを取得することは可能です)。オブジェクトでは、シェル内のすべての項目を IShellItem インターフェイスによって表しますが、シェル項目ごとに別のインターフェイスを実装します (たとえば、フォルダー オブジェクトは IShellFolder インターフェイスも実装します)。Windows 7 アプリケーションでは、ファイル システムの絶対ファイル パスではなく、シェル API を使用してシェル項目にアクセスすることが非常に重要です。それと同じように重要なのは、アプリケーションで Windows 7 の共通ファイル ダイアログを使用することです。これらのダイアログではシステムのライブラリが表示され、ダイアログを通じて選択される適切な IShellItem オブジェクトが提供されるからです。
シェル項目の使用
シェル名前空間の中心には、IShellItem インターフェイスを実装する、シェル項目と呼ばれるオブジェクトがあります。新しい共通ファイル ダイアログ (CLSID_FileOpenDialog または CLSID_FileSaveDialog) は、シェル項目オブジェクトによって項目を参照し、IShellItemArray インターフェイスを通じて IShellItem または項目の配列を返します。呼び出し側は個別の IShellItem オブジェクトを使用して、ファイル システム パスを取得したり、項目のストリーム オブジェクトを開いて情報の読み書きを実行したり、特定のシェルの種類用に実装されている 1 つ以上のシェル インターフェイスをクエリしたりすることができます。これらのファイル ダイアログでは、ファイル システム フォルダーの項目も、ライブラリなどのシェルに存在するその他の仮想フォルダーの項目もアクセス可能なので、IShellItem オブジェクトを使用することが重要です。また、Windows 7 で提供される新しいオブジェクト CLSID_ShellLibrary は、ライブラリのアクセス専用です。
C++ でシェル API を使用するには、shobjidl.h ヘッダー ファイルをインクルードします。このファイルは、シェル インターフェイスと、作成可能なシェル オブジェクトの CLSID に対応するシンボルを宣言します。また、このファイルにはヘルパー メソッドも含まれます。リスト 1 は、シェル項目を作成して使用する単純なコードです。SHCreateItemFromParsingName メソッドはシステム ファイル パスを取り、シェル項目オブジェクトを返します。この例では、シェル項目オブジェクトを使用して、その項目に対応するユーザー読み取り可能文字列を取得しています。
リスト 1 シェル項目の作成
LPWSTR szFilePath = GetFileNameFromSomewhere(); // どこかからファイル名を取得する。
IShellItem* pItem = nullptr;
HRESULT hr = ::SHCreateItemFromParsingName(
szFilePath, nullptr, IID_PPV_ARGS(&pItem));
if (SUCCEEDED(hr))
{
LPWSTR szName = nullptr;
hr = pItem->GetDisplayName(SIGDN_NORMALDISPLAY, &szName);
if (SUCCEEDED(hr))
{
wprintf(L"Shell item name: %s\n", szName);
::CoTaskMemFree(szName);
}
pItem->Release();
}
シェル項目は、ファイル、フォルダー、ショートカット、さらには [ごみ箱] のような仮想フォルダー項目、ライブラリなど、シェル内で表示可能な任意のオブジェクトを表すことができます。項目の種類に関する情報を取得するには、IShellItem::GetAttributes メソッドを呼び出します。リスト 2 は、シェル オブジェクトの属性にアクセスするコードです。この場合、SHCreateItemInKnownFolder メソッドを呼び出して、コンピューター上のライブラリに対応するシェル オブジェクトを取得しています。このメソッドは、シェル項目オブジェクトが参照するファイルまたはフォルダーを作成するのではなく、シェル項目オブジェクトを作成します。このメソッドの最初のパラメーターは、knownfolders.h で定義される値の 1 つである既知のフォルダーの GUID です。3 番目のパラメーターは、アクセスする既知のフォルダー内の項目の名前です。このパラメーターが (この場合のように) Null であれば、シェル項目オブジェクトは既知のフォルダーに対応しています。リスト 2 は Libraries フォルダーにアクセスします。前述のように、このフォルダーはコンピューター上に物理的に存在するわけではなく、シェル項目がシェル名前空間内の他のライブラリへのアクセスを提供します。
リスト 2 既知のフォルダーのアクセス
IShellItem* pItem = nullptr;
HRESULT hr = ::SHCreateItemInKnownFolder(
FOLDERID_Libraries, 0, nullptr, IID_PPV_ARGS(&pItem));
if (SUCCEEDED(hr))
{
DWORD dwAttr = 0;
hr = pItem->GetAttributes(SFGAO_FILESYSANCESTOR, &dwAttr);
if (SUCCEEDED(hr))
{
if (SFGAO_FILESYSANCESTOR == dwAttr)
{
wprintf(L"Item is a file system folder\n");
}
}
pItem->Release();
}
SHGetKnownFolderItem 関数を使用して、既知のフォルダーのシェル項目を取得することもできます。この関数ではアクセス トークンを渡すこともできます。その場合、現在のログオン ユーザー以外のユーザーにアクセスが限定されている既知のフォルダーでもアクセス可能です。
シェル項目プロパティのアクセス
シェル項目のプロパティの値を要求することにより、シェル項目に関する詳しい情報を取得できます。そのためには、IShellItem ではなく、IShellItem2 のメソッドを使用する必要があります。各プロパティは PROPERTKEY 構造体に含まれる値によって識別され、要求できるプロパティの初期化済みの値は propkey.h によって定義されます。プロパティは、文字列、数値、または日時の場合があり、IShellItem2 のメソッドによって該当する値が返されます。たとえばリスト 3 は、PKEY_DateCreated プロパティを使用して項目の作成日にアクセスしています。pItem 変数は IShellItem オブジェクトに割り当て済みであることが前提となっています。
リスト 3 シェル項目によるプロパティのアクセス
IShellItem2* pItem2 = nullptr;
hr = pItem->QueryInterface(&pItem2);
if (SUCCEEDED(hr))
{
FILETIME ft = {0};
pItem2->GetFileTime(PKEY_DateCreated, &ft);
SYSTEMTIME st = {0};
::FileTimeToSystemTime(&ft, &st);
wprintf(
L"Date Created: %04d-%02d-%02d %02d:%02d:%02d\n",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
pItem2->Release();
}
ハンドラー オブジェクトへのバインド
IShellItem および IShellItem2 インターフェイスのメソッドでは、提供されるシェル オブジェクトへのアクセスが制限されていますが、IShellItem::BindToHandler メソッドを呼び出すことにより、ハンドラー オブジェクトを取得して、さらに別のアクセスを得ることができます。ハンドラー オブジェクトには多くの種類があり、シェル項目ごとに使用するハンドラー オブジェクトが異なります。項目がファイルの場合、IStream ハンドラー オブジェクトにアクセスできます。また項目がフォルダーの場合、IShellFolder ハンドラーにアクセスできます。BindToHandler メソッドに、取得したいハンドラー オブジェクトの種類を表す GUID (shlguid.h で定義) を渡すと、このメソッドはそのハンドラー オブジェクトへのポインターを返します。したがって、pItem 変数がリスト 2 で初期化した Libraries フォルダーのシェル項目であると仮定すると、リスト 4 のコードは、列挙子オブジェクトを取得して子項目を列挙し、それらの名前を出力します。
リスト 4 フォルダー シェル項目の項目の列挙
IEnumShellItems* pEnum = nullptr;
hr = pItem->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&pEnum));
if (SUCCEEDED(hr))
{
IShellItem* pChildItem = nullptr;
ULONG ulFetched = 0;
do
{
hr = pEnum->Next(1, & pChildItem, &ulFetched);
if (FAILED(hr)) break;
if (ulFetched != 0)
{
LPWSTR szChildName = nullptr;
child->GetDisplayName(SIGDN_NORMALDISPLAY, &szChildName);
wprintf(L"Obtained %s\n", szChildName);
CoTaskMemFree(szChildName);
pChildItem ->Release();
}
} while (hr != S_FALSE);
pEnum->Release();
}
シェル項目がファイルの場合、ファイルからデータを読み込む方法は開発者が決定できます。ReadFile などの Windows API 関数を使用してファイルからの読み取りを実行する場合、そのファイルのハンドルを取得する必要があります。それには、SIGDN_FILESYSPATH の名前の種類に対して IShellItem::GetDisplayName から返された名前を、CreateFile 関数に渡します。さらに、IShellItem::BindToHandler メソッドの呼び出しで、BHID_Stream ハンドラーを要求することにより、シェル項目をストリーム オブジェクトとして開くこともできます。
共通ファイル ダイアログの使用
その他の API もシェル項目オブジェクトに作用します。たとえば共通ファイル ダイアログを使用して、ファイルを保存したい場所へのパスのあるシェル項目を取得することができます。共通ファイル ダイアログ オブジェクトは、ファイルを開いたり保存したりするわけではなく、ユーザーが識別するシェル項目に関する情報を提供します。リスト 5 は、ファイルを保存するための基本的なコードです。ここに示される pInitialItem 変数は、IFileSaveAsDialog::SetSaveAsItem メソッドの呼び出しにより、[名前を付けて保存] ダイアログに初期的に表示されるファイルを表す、初期化済みのシェル項目オブジェクトです。IFileSaveDialog::Show メソッドはこのダイアログを表示し、ユーザーが [OK] ボタンをクリックした場合、このメソッドは S_OK を返します。ユーザーが [キャンセル] ボタンをクリックした場合には、このダイアログは ERROR_CANCELLED を返します。ユーザーがダイアログでファイルを指定した場合、IFileSaveDialog::GetResult の呼び出しにより、そのファイルに関する情報のあるシェル項目が取得されます。この場合、シェル項目オブジェクトは必ずしも実際のファイルを参照しているわけではなく、ダイアログでユーザーから提供されたパスとファイル名を含んでいるだけです。次にコードでは、pInitialItem シェル項目で参照されるファイルを、pSaveAsItem シェル項目で指定される場所にコピーするための独自のコードを提供する必要があります。
リスト 5 [名前を付けて保存] ダイアログの使用
IFileSaveDialog* pShellDialog;
hr = CoCreateInstance(
CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&pShellDialog));
if (SUCCEEDED(hr))
{
pShellDialog->SetSaveAsItem(pInitialItem);
pShellDialog->Show(nullptr);
if (SUCCEEDED(hr))
{
IShellItem* pSaveAsItem = nullptr;
hr = pShellDialog->GetResult(&pSaveAsItem);
if (SUCCEEDED(hr))
{
CopyFileTo(pInitialItem, pSaveAsItem); // Call the code to Copy copy the actual file…
pSaveAsItem->Release();
}
}
else
{
// ユーザーが [キャンセル] をクリックした場合、戻り値は 0x800704c7、つまり
// HRESULT_CODE(hr) == ERROR_CANCELLED
}
pShellDialog->Release();
}
リスト 5 の [名前を付けて保存] ダイアログでは、Libraries フォルダーもナビゲーション ペインに表示されます。ユーザーがライブラリを選択した場合、IFileSaveDialog::GetResult メソッドから返されるシェル項目は、実際のファイル システムの場所を参照します。必要な場合、ライブラリの既定の保存先が使用されます。
シェル ライブラリ オブジェクトの使用
Windows 7 API では、ライブラリを管理するための COM オブジェクトが提供されます。シェル ライブラリ オブジェクトは IShellLibrary インターフェイスを実装します。また、未初期化オブジェクトを作成するヘルパー メソッド SHCreateLibrary が Windows API によって提供されます。既知のフォルダーを参照するようにオブジェクトを初期化するには、IShellLibrary::LoadLibraryFromKnownFolder を呼び出します。シェル ライブラリ オブジェクトを使用して、ライブラリからのフォルダーの追加または削除、ライブラリ内でのフォルダーの列挙、既定の保存フォルダーの設定を行うことができます。ライブラリ オブジェクトは、ライブラリの説明ファイル (library-ms ファイル) の書き込みに使用されます。ライブラリ設定の変更が終わった時点で、既存のライブラリの場合には IShellLibrary:Commit メソッド、新しいライブラリの場合には IShellLibrary::Save を呼び出す必要があります。
Hilo での Windows 7 ライブラリの使用
Hilo Browser のカルーセルとメディア ペインは、フォルダーと写真をサムネイルで表示します。そのため、これらのクラスのメッセージ ハンドラーは、これらの項目に関する情報を使用して初期化する必要があります。Hilo では、ThumbnailInfo という構造体を使用してこの処理が行われます。この構造体のメンバーは、IShellItem オブジェクトの参照です。ユーザーがカルーセルで特定のフォルダーを選択すると、ウィンドウ メッセージ ハンドラーがそのフォルダー内の項目を列挙し、内側の軌道にサブフォルダーを、メディア ペインに写真を表示します。そのために Hilo では、シェル項目の列挙とフィルター処理を実行する必要があります。
Browser カルーセルの初期化
BrowserApplication クラスには m_currentBrowseLocationItem という変数があります。この変数は Browser カルーセルに表示される初期的なフォルダーに対応するシェル項目です。この変数は Browser が起動された時点で、リスト 6 のコードによって初期化されます。このコードの最初の部分では、SHCreateItemInKnownFolder メソッドを呼び出して Pictures ライブラリのシェル項目オブジェクトを取得します。この呼び出しが成功しなかった場合、コードは SHGetKnownFolderItem を呼び出して、Computer フォルダーのシェル項目を取得し、それによってコンピューターがアクセス可能なすべてのハード ドライブ (外部ドライブを含む) へのアクセスを提供します。
リスト 6 カルーセルの初期的なフォルダーの決定
// 場所が未定義のため、Pictures ライブラリの読み込みのみ実行する
if (nullptr == m_currentBrowseLocationItem)
{
// 既定は Pictures ライブラリ
hr = ::SHCreateItemInKnownFolder(
FOLDERID_PicturesLibrary, 0, nullptr, IID_PPV_ARGS(&m_currentBrowseLocationItem));
}
// Pictures ライブラリが見つからない場合
if (FAILED(hr))
{
// 既知のフォルダー "Computer" を取得する
hr = ::SHGetKnownFolderItem(
FOLDERID_ComputerFolder, static_cast<KNOWN_FOLDER_FLAG>(0), nullptr,
IID_PPV_ARGS(&m_currentBrowseLocationItem));
}
if (SUCCEEDED(hr))
{
ComPtr<IPane> carouselPane;
hr = carouselPaneHandler.QueryInterface(&carouselPane);
if (SUCCEEDED(hr))
{
hr = carouselPane->SetCurrentLocation(m_currentBrowseLocationItem, false);
}
}
SetCurrentLocation メソッドにシェル項目を渡すことにより、カルーセル ペインのメッセージ ハンドラー クラスを現在の場所で更新します。このメソッドは、選択されたフォルダー内のフォルダーを列挙し、カルーセルを更新してこれらのサブフォルダーを表示します。カルーセル ペイン ハンドラー クラスは次に、メディア ペインに対して SetCurrentLocation メソッドを呼び出します。これにより、選択されたフォルダー内の画像ファイルが列挙され、これを使ってメディア ペインにサムネイルが表示されます。
ユーザーがカルーセルまたは履歴リストでフォルダーを選択すると、新しく選択された項目のシェル項目がカルーセル ハンドラーの SetCurrentLocation に渡されます。このようにして、カルーセルとメディア ペインが、選択されたフォルダーの項目を使って更新されます。
フォルダーの列挙
Hilo は (Common プロジェクトにおいて) ShellItemsLoader というユーティリティ クラスを提供しています。このクラスには EnumerateFolderItems というパブリックな静的メソッドが 1 つあります。このメソッドに、列挙するフォルダーのシェル項目オブジェクト、メソッドでフォルダーまたはフォルダー内の画像ファイル項目のどちらを返すかを表す値、そして検索を再帰的に行うかどうかを表すパラメーターを渡します。このメソッドは、要求された項目に対応するシェル項目オブジェクトのベクターを返します。
リスト 7 は、このメソッドの最初の部分です。1 は検索するオブジェクトの種類を表し、itemKinds ベクターに名前付きの値を追加するために使用されます。このメソッドがフォルダー内の項目を列挙するとき、メソッドは各項目の種類を取得し、項目の種類が itemKinds ベクターに含まれるものである場合、その項目が shellItems ベクターに追加され、呼び出し側に返されます。
リスト 7 フォルダーの列挙: 初期化のコード
HRESULT ShellItemsLoader::EnumerateFolderItemsNonRecursive(
IShellItem* currentBrowseLocation, ShellFileType fileType,
std::vector<ComPtr<IShellItem> >& shellItems)
{
std::vector<std::wstring> itemKinds;
if ((fileType & FileTypeImage) == FileTypeImage)
{
itemKinds.push_back(L"picture");
}
if ((fileType & FileTypeImage) == FileTypeVideo)
{
itemKinds.push_back(L"video");
}
if ((fileType & FileTypeImage) == FileTypeAudio)
{
itemKinds.push_back(L"music");
}
// この後に列挙を実行するコードが続く
フォルダーの列挙は、IShellFolder ハンドラー オブジェクトを使用して行われます。リスト 8 は、BindToHandler メソッドを呼び出してこのオブジェクトを取得するコードです。
リスト 8 フォルダーの列挙: ハンドラー オブジェクトの作成
// 現在の検索フォルダー内の全オブジェクトを列挙する
ComPtr<IShellFolder> searchFolder;
HRESULT hr = currentBrowseLocation->BindToHandler(
nullptr, BHID_SFObject, IID_PPV_ARGS(&searchFolder));
if (SUCCEEDED(hr))
{
// 列挙コード、リスト 9 を参照
}
IShellFolder オブジェクトには、IEnumIDList インターフェイスを実装する列挙オブジェクトがあります。このインターフェイスは、シェル項目ではなく、項目 ID を列挙しますが、SHCreateItemWithParent メソッドを呼び出すことにより、ID からシェル項目を作成することも可能です。リスト 9 は、指定されたフォルダー内の項目を列挙するメインのコードです。このコードはまず、SHCONTF フラグを初期化して、検索する項目がフォルダーかファイルかを指定します。このフラグが IShellFolder::EnumObjects メソッドに渡され、それによって項目の列挙オブジェクトが返されます。コードは次に、このオブジェクトに対して IEnumIDList::Next を繰り返し呼び出し、項目の ID を取得します。そして SHCreateItemWithParent メソッドを呼び出し、シェル項目オブジェクトを作成します。
リスト 9 フォルダーの列挙: 項目の列挙
bool const isEnumFolders = (fileType & FileTypeFolder) == FileTypeFolder;
SHCONTF const flags = isEnumFolders ?SHCONTF_FOLDERS : SHCONTF_NONFOLDERS;
ComPtr<IEnumIDList> fileList;
if (S_OK == searchFolder->EnumObjects(nullptr, flags, &fileList))
{
ITEMID_CHILD* idList = nullptr;
unsigned long fetched;
while (S_OK == fileList->Next(1, &idList, &fetched))
{
ComPtr<IShellItem2> shellItem;
hr = SHCreateItemWithParent(nullptr, searchFolder, idList, IID_PPV_ARGS(&shellItem));
if (SUCCEEDED(hr))
{
// 返される項目ベクターに項目を追加すべきかどうかをチェックする
// リスト 10 を参照
}
ILFree(idList);
}
}
}
return hr;
リスト 9 で作成されるシェル項目は、フォルダーまたはフォルダー以外の項目に対応します。検索の対象がフォルダーの場合、以降の処理は不要であり、shellItems ベクターに項目が追加されます。項目がフォルダー以外のオブジェクトである場合、要求された項目の種類の 1 つであるかどうかをチェックする必要があります (リスト 10 にそのコードを示します)。このコードは項目の PKEY_Kind プロパティを読み取り、項目の種類を文字列として取得し、その戻り値を shellItems ベクター内の項目と比較します。
リスト 10 フォルダーの列挙: 項目の種類をチェック
if (isEnumFolders)
{
shellItems.push_back(static_cast<IShellItem*>(shellItem));
}
else
{
// 項目が適正かどうかをチェックする
wchar_t *itemType = nullptr;
hr = shellItem->GetString(PKEY_Kind, &itemType);
if (SUCCEEDED(hr))
{
auto found = std::find(itemKinds.begin(), itemKinds.end(), itemType);
if (found != itemKinds.end())
{
shellItems.push_back(static_cast<IShellItem*>(shellItem));
}
::CoTaskMemFree(itemType);
}
}
まとめ
この章では、シェル API を使用して Windows 7 ライブラリからフォルダーと項目にアクセスする方法を学習しました。次の章では、ユーザーが画像を簡単に編集できる Annotator アプリケーションの概要を説明します。