D言語 Advent Calendar 2025 12日目の記事です。
- COM使う編
- D言語でMicrosoft Component Object Model (COM) を使う (その1) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その2) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その3) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その4) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その5) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その6) - ...ing logging 4.0
- COM作る編
その1でCOMインタフェースを使うために必要な宣言や定義の準備ができて、 その2でCOMの初期化と解放ができるようになりました。 その3では実際にファイルを開くダイアログを表示することができ、その4では選択したファイルのパスを取得できました。 その5ではQueryInterfaceを使って別のCOMインタフェースを取得して、ファイル作成日時を取得できました。 その6ではCOMインタフェースをアップキャストもダウンキャストもできないことを説明しました。
その7ではCOMオブジェクトとCOMインタフェースを自作するための準備をしました。
QueryInterfaceメソッドを実装する
前回、次のとおり、COMオブジェクトのひな形を作りました。
// uuid("0b425ee1-5991-4dbd-89e7-a12bf13fbaef"); class FilePicker : ComObject, IFilePicker { extern (Windows): override HRESULT QueryInterface(const(IID)* riid, void** ppv) { // ... return S_OK; } HRESULT pickFile(LPWSTR* path, SYSTEMTIME* time) { // ... return S_OK; } }
まずは、QueryInterfaceメソッドを実装しますが、 その5の記事で使ったQueryInterfaceメソッドをおさらいします。
QueryInterfaceメソッドの役割は、取り出したいCOMインタフェースのIIDを受け取って、 COMオブジェクトがそのCOMインタフェースを実装しているならば、 そのCOMインタフェースを返すことでした。
IIDをriid変数で受け取っているので、自分自分のインスタンス(COMオブジェクト)がこのIIDに対応しているか判定して、 ppvに自分自身を代入すればいいです。
また、自分自身への参照を新たにひとつ作ることになるので、AddRefメソッドを1回呼び出します。
対応していないIIDを与えられたときは、 もしかしたら親クラスが対応しているかもしれないので、 親クラスのQueryInterfaceを呼び出しておきましょうか。
これで自クラスでも親クラスでも実装されていないCOMインタフェースが指定された場合は、 実装されていないCOMインタフェースが指定されたことを示すエラー値E_NOINTERFACEが返されます。
ここまでをまとめると、QueryInterfaceメソッドの実装は、次のとおりになります。
override HRESULT QueryInterface(const(IID)* riid, void** ppv) { if (*riid == IID_IFilePicker) { *ppv = cast(void*)cast(IFilePicker)this; AddRef(); return S_OK; } else { return super.QueryInterface(riid, ppv); } }
pickFileメソッドを実装する
pickFileメソッドでは、「COM使う編」で作ったCOMを使ったコードをまるごと書きます。
HRESULT pickFile(LPWSTR* path, SYSTEMTIME* time)
{
HRESULT hr;
IFileOpenDialog pDialog;
hr = CoCreateInstance(&CLSID_FileOpenDialog, null, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, cast(void**)&pDialog);
if (FAILED(hr)) return hr;
scope(exit) pDialog.Release();
hr = pDialog.Show(null);
if (FAILED(hr)) return hr;
IShellItem pItem;
hr = pDialog.GetResult(&pItem);
if (FAILED(hr)) return hr;
scope(exit) pItem.Release();
LPWSTR outPath;
hr = pItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &outPath);
if (FAILED(hr)) return hr;
scope(exit) CoTaskMemFree(outPath);
// import std.conv;
// *path = outPath.to!string;
import core.stdc.wchar_;
wcscpy(*path, outPath);
IShellItem2 pItem2;
hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2);
if (FAILED(hr)) return hr;
scope(exit) pItem2.Release();
PROPERTYKEY prop = PKEY_DateCreated;
FILETIME filetime;
pItem2.GetFileTime(&prop, &filetime);
FileTimeToSystemTime(&filetime, time);
return S_OK;
}
変更点としては、ファイルパスを引数のpathで、ファイル作成時間を引数のtimeに書き込むようにするところです。
先にtime変数から説明すると、メソッド内でSYSTEMTIME変数を宣言するのではなく、 引数で宣言されたtime変数に書き込まれるように修正しています。
次にpath変数を説明すると、COMインタフェースで取得したファイルパスをoutPathに一旦受け取っておいて、 それを引数のpath変数にwcscpy関数でコピーします。 outPathはCOMインタフェースで取得した文字列のメモリを指していて、 使い終わったらCoTaskMemFree関数で解放しないといけないので、 このメソッド内で解放まで終わらせるためにoutPathをpathにコピーしています。
メソッド内で文字列をコピーしないなら、 メソッドの呼び出し元でCoTaskMemFree関数を呼んでもらうように決めてもいいですが、 ここではこっちの方法は採用しないことにします。
自作したCOMインタフェースとCOMオブジェクトを使う
IFilePicker COMインタフェースとFilePicker COMオブジェクトが完成したようなので、 main関数でこれらを使うコードを書いていきます。
まずはCoCreateInstance関数でCLSID_FilePickerとIID_IFilePickerを指定して、 pPicker変数にCOMインタフェースを取得します。
IFilePicker pPicker; hr = CoCreateInstance(&CLSID_FilePicker, null, CLSCTX_INPROC_SERVER, &IID_IFilePicker, cast(void**)&pPicker); if (FAILED(hr)) return; scope(exit) pPicker.Release();
続いて、pickFileメソッドのpath引数に与える文字列用にメモリを確保します。
LPWSTR path = cast(wchar*)new wchar[MAX_PATH]; SYSTEMTIME time; hr = pPicker.pickFile(&path, &time); if (FAILED(hr)) return;
最後に、path変数をコンソールに出力するコードを追加して次のとおりになりました。
void main() { HRESULT hr; hr = CoInitializeEx(null, COINIT.COINIT_MULTITHREADED); if (FAILED(hr)) return; scope(exit) CoUninitialize(); IFilePicker pPicker; hr = CoCreateInstance(&CLSID_FilePicker, null, CLSCTX_INPROC_SERVER, &IID_IFilePicker, cast(void**)&pPicker); if (FAILED(hr)) return; scope(exit) pPicker.Release(); LPWSTR path = cast(wchar*)new wchar[MAX_PATH]; SYSTEMTIME time; hr = pPicker.pickFile(&path, &time); if (FAILED(hr)) return; import std.conv; writeln(path.to!string); writefln("%s-%s-%s", time.wYear, time.wMonth, time.wDay); }
完成したが・・・?
以下、ここまでのソース全文を掲載します。 果たしてこれで動作するでしょうか。
import std.stdio; import core.sys.windows.windef; import core.sys.windows.com; import core.sys.windows.objbase; import core.sys.windows.winbase; extern(C) extern const IID IID_IModalWindow; // uuid("b4db1657-70d7-485e-8e3e-6fcb5a5c1802") interface IModalWindow : IUnknown { extern (Windows): HRESULT Show(HWND hwndOwner); } extern(C) extern const IID IID_IFileDialog; // uuid("42f85136-db7e-439c-85f1-e4075d135fc8") interface IFileDialog : IModalWindow { extern (Windows): HRESULT SetFileTypes();//(UINT cFileTypes, const COMDLG_FILTERSPEC* rgFilterSpec); HRESULT SetFileTypeIndex();//(UINT iFileType); HRESULT GetFileTypeIndex();//(UINT* piFileType); HRESULT Advise();//(IFileDialogEvents pfde, DWORD* pdwCookie); HRESULT Unadvise();//(DWORD dwCookie); HRESULT SetOptions();//(FILEOPENDIALOGOPTIONS fos); HRESULT GetOptions();//(FILEOPENDIALOGOPTIONS * pfos); HRESULT SetDefaultFolder();//(IShellItem psi); HRESULT SetFolder();//(IShellItem psi); HRESULT GetFolder();//(IShellItem* ppsi); HRESULT GetCurrentSelection();//(IShellItem* ppsi); HRESULT SetFileName();//(LPCWSTR pszName); HRESULT GetFileName();//(LPWSTR pszName); HRESULT SetTitle();//(LPCWSTR pszTitle); HRESULT SetOkButtonLabel();//(LPCWSTR pszText); HRESULT SetFileNameLabel();//(LPCWSTR pszLabel); HRESULT GetResult(IShellItem* ppsi); HRESULT AddPlace();//(IShellItem psi, FDAP fdap); HRESULT SetDefaultExtension();//(LPCWSTR pszDefaultExtension); HRESULT Close();//(HRESULT hr); HRESULT SetClientGuid();//(REFGUID guid); HRESULT ClearClientData(); HRESULT SetFilter();//(IShellItemFilter pFilter); } extern(C) extern const IID IID_IFileOpenDialog; // uuid("d57c7288-d4ad-4768-be02-9d969532d960") interface IFileOpenDialog : IFileDialog { extern(Windows): HRESULT GetResults();//(IShellItemArray* ppenum); HRESULT GetSelectedItems();//(IShellItemArray* ppsai); } extern(C) extern const IID IID_IShellItem; // uuid("43826d1e-e718-42ee-bc55-a1e261c37bfe") interface IShellItem : IUnknown { extern(Windows): HRESULT BindToHandler();//(IBindCtx pbc, REFGUID bhid, REFIID riid, void** ppv); HRESULT GetParent();//(IShellItem* ppsi); HRESULT GetDisplayName(SIGDN sigdnName, LPWSTR* ppszName); HRESULT GetAttributes();//(SFGAOF sfgaoMask, SFGAOF* psfgaoAttribs); HRESULT Compare();//(IShellItem psi, SICHINTF hint, int* piOrder); } enum SIGDN : int { SIGDN_NORMALDISPLAY = 0, SIGDN_PARENTRELATIVEPARSING = 0x80018001, SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, SIGDN_PARENTRELATIVEEDITING = 0x80031001, SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, SIGDN_FILESYSPATH = 0x80058000, SIGDN_URL = 0x80068000, SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, SIGDN_PARENTRELATIVE = 0x80080001, SIGDN_PARENTRELATIVEFORUI = 0x80094001 } extern(C) extern const CLSID CLSID_FileOpenDialog; extern(C) extern const IID IID_IShellItem2; struct PROPERTYKEY { GUID fmtid; DWORD pid; } alias REFPROPERTYKEY = PROPERTYKEY*; // uuid("7e9fb0d3-919f-4307-ab2e-9b1860310c93") interface IShellItem2 : IShellItem { extern(Windows): HRESULT GetPropertyStore();//(GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv); HRESULT GetPropertyStoreWithCreateObject();//(GETPROPERTYSTOREFLAGS flags, IUnknown *punkCreateObject, REFIID riid, void **ppv); HRESULT GetPropertyStoreForKeys();//(const PROPERTYKEY *rgKeys, UINT cKeys, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv); HRESULT GetPropertyDescriptionList();//(REFPROPERTYKEY keyType, REFIID riid, void **ppv); HRESULT Update();//(IBindCtx *pbc); HRESULT GetProperty();//(REFPROPERTYKEY key, PROPVARIANT *ppropvar); HRESULT GetCLSID();//(REFPROPERTYKEY key, CLSID *pclsid); HRESULT GetFileTime(REFPROPERTYKEY key, FILETIME *pft); HRESULT GetInt32();//(REFPROPERTYKEY key, int *pi); HRESULT GetString();//(REFPROPERTYKEY key, LPWSTR *ppsz); HRESULT GetUInt32();//(REFPROPERTYKEY key, ULONG *pui); HRESULT GetUInt64();//(REFPROPERTYKEY key, ULONGLONG *pull); HRESULT GetBool();//(REFPROPERTYKEY key, BOOL *pf); } const PROPERTYKEY PKEY_DateCreated = { { 0xB725F130, 0x47EF, 0x101A, [0xA5, 0xF1, 0x02, 0x60, 0x8C, 0x9E, 0xEB, 0xAC ]}, 15 }; GUID guidFromUUID(string uuidString) { static import std.uuid; std.uuid.UUID uuid = std.uuid.UUID(uuidString); ubyte[8] data = uuid.data[8 .. $]; return GUID(uuid.data[0] << 24 | uuid.data[1] << 16 | uuid.data[2] << 8 | uuid.data[3], uuid.data[4] << 8 | uuid.data[5], uuid.data[6] << 8 | uuid.data[7], data); } extern(C) const IID IID_IFilePicker = guidFromUUID("f3488441-b094-430e-b34a-c18a5088b01e"); // uuid("f3488441-b094-430e-b34a-c18a5088b01e"); interface IFilePicker : IUnknown { extern (Windows): HRESULT pickFile(LPWSTR* path, SYSTEMTIME* time); } extern(C) const CLSID CLSID_FilePicker = guidFromUUID("0b425ee1-5991-4dbd-89e7-a12bf13fbaef"); // uuid("0b425ee1-5991-4dbd-89e7-a12bf13fbaef"); class FilePicker : ComObject, IFilePicker { extern (Windows): override HRESULT QueryInterface(const(IID)* riid, void** ppv) { if (*riid == IID_IFilePicker) { *ppv = cast(void*)cast(IFilePicker)this; AddRef(); return S_OK; } else { return super.QueryInterface(riid, ppv); } } HRESULT pickFile(LPWSTR* path, SYSTEMTIME* time) { HRESULT hr; IFileOpenDialog pDialog; hr = CoCreateInstance(&CLSID_FileOpenDialog, null, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, cast(void**)&pDialog); if (FAILED(hr)) return hr; scope(exit) pDialog.Release(); hr = pDialog.Show(null); if (FAILED(hr)) return hr; IShellItem pItem; hr = pDialog.GetResult(&pItem); if (FAILED(hr)) return hr; scope(exit) pItem.Release(); LPWSTR outPath; hr = pItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &outPath); if (FAILED(hr)) return hr; scope(exit) CoTaskMemFree(outPath); // import std.conv; // *path = outPath.to!string; import core.stdc.wchar_; wcscpy(*path, outPath); IShellItem2 pItem2; hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2); if (FAILED(hr)) return hr; scope(exit) pItem2.Release(); PROPERTYKEY prop = PKEY_DateCreated; FILETIME filetime; pItem2.GetFileTime(&prop, &filetime); FileTimeToSystemTime(&filetime, time); return S_OK; } } void main() { HRESULT hr; hr = CoInitializeEx(null, COINIT.COINIT_MULTITHREADED); if (FAILED(hr)) return; scope(exit) CoUninitialize(); IFilePicker pPicker = null; hr = CoCreateInstance(&CLSID_FilePicker, null, CLSCTX_INPROC_SERVER, &IID_IFilePicker, cast(void**)&pPicker); if (FAILED(hr)) return; scope(exit) pPicker.Release(); LPWSTR path = cast(wchar*)new wchar[MAX_PATH]; SYSTEMTIME time; hr = pPicker.pickFile(&path, &time); if (FAILED(hr)) return; import std.conv; writeln(path.to!string); writefln("%s-%s-%s", time.wYear, time.wMonth, time.wDay); }
実は、CoCreateInstance関数がエラーを返してきて、このままではCOMオブジェクトを生成できません。
hr = CoCreateInstance(&CLSID_FilePicker, null, CLSCTX_INPROC_SERVER, &IID_IFilePicker, cast(void**)&pPicker);
原因は、勝手に自作したCLSIDを、WIndowsが知らないからです。
CoCreateInstance関数を呼び出すと、指定したCOMオブジェクトをWindowsが返してくれますが、 ユーザーが勝手に作ったCLSIDでは、それができません。
では、どうしたらいいのか?
次の記事に続きます。