D言語 Advent Calendar 2025 10日目の記事です。
- 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
その1でCOMインタフェースを使うために必要な宣言や定義の準備ができて、 その2でCOMの初期化と解放ができるようになりました。 その3では実際にファイルを開くダイアログを表示することができ、その4では選択したファイルのパスを取得できました。 その5ではQueryInterfaceを使って別のCOMインタフェースを取得して、ファイル作成日時を取得できました。
COMインタフェースの継承関係があればダウンキャストできるか
その5の記事で、
IShellItem2 pItem2; hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2); if (FAILED(hr)) return; scope(exit) pItem2.Release();
というコードがありました。
IShellItem COMインタフェースからIShellItem2 COMインタフェースを取得するために、 QueryInterfaceメソッドを使いました。
でも、ちょっと待てよ。 IShellItemとIShellItem2の宣言を見てみると、IShellItem2インタフェースがIShellItemインタフェースを継承しているから、 QueryInterfaceを使わなくてもダウンキャストできるんじゃないか。
interface IShellItem : IUnknown
interface IShellItem2 : IShellItem
つまり、次のとおりではなくて、
IShellItem2 pItem2; hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2); if (FAILED(hr)) return; scope(exit) pItem2.Release();
単に次のとおり書けないか。
IShellItem2 pItem2 = cast(IShellItem2)pItem;
COMインタフェースはアップキャストもダウンキャストもできない
答えは公式サイトにあります。
Interfaces - D Programming Language
References cannot be upcast to the enclosing class object, nor can they be downcast to a derived interface. Implement QueryInterface() for that interface in standard COM fashion to convert to another COM interface.
邦訳サイトを見てみます。
COM インターフェイス
インターフェイスの一種として、COMインターフェイスがあります。COMインターフェイスは、 WindowsのCOMオブジェクトとして直接適合するように設計されます。全ての COM オブジェクトは COMインターフェイスによって表現でき、COMインターフェイスを持つ全ての D言語のオブジェクトは、外部のCOMクライアントから使用できます。
COMインターフェイスは、std.c.windows.com.IUnknown から派生することで定義します。COMインターフェイスは、 D言語の通常のインターフェイスと以下の点で異なります:
- std.c.windows.com.IUnknown から派生している。
- DeleteExpression の引数として使えない。
- 参照は、周囲のクラスのオブジェクトへのUpcastや、 派生インターフェイスへのDowncastをすることが許されない。 この目的には、COMの標準的なやり方で適切な QueryInterface() が実装されていなければなりません。
- COMインターフェイスから派生したクラスはCOMクラスです。
- COMクラスのメンバ関数のデフォルトのリンケージは is extern(System) です
- vtbl[] の先頭のメンバは InterfaceInfo へのポインタではなく、最初の仮想関数ポインタになります。
さらに詳しい情報については Modern COM Programming in D をご覧下さい。
はい、許されない。
でもやってみる
void main() { HRESULT hr; hr = CoInitializeEx(null, COINIT.COINIT_MULTITHREADED); if (FAILED(hr)) return; scope(exit) CoUninitialize(); IFileOpenDialog pDialog; hr = CoCreateInstance(&CLSID_FileOpenDialog, null, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, cast(void**)&pDialog); if (FAILED(hr)) return; scope(exit) pDialog.Release(); hr = pDialog.Show(null); if (FAILED(hr)) return; IShellItem pItem; hr = pDialog.GetResult(&pItem); if (FAILED(hr)) return; scope(exit) pItem.Release(); LPWSTR outPath; hr = pItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &outPath); if (FAILED(hr)) return; scope(exit) CoTaskMemFree(outPath); import std.conv; writeln(outPath.to!string); IShellItem2 pItem2 = cast(IShellItem2)pItem; // やってはいけない // IShellItem2 pItem2; // hr = pItem.QueryInterface(&IID_IShellItem2, cast(void**)&pItem2); // if (FAILED(hr)) return; // scope(exit) pItem2.Release(); PROPERTYKEY prop = PKEY_DateCreated; FILETIME filetime; SYSTEMTIME time; pItem2.GetFileTime(&prop, &filetime); FileTimeToSystemTime(&filetime, &time); writefln("%s-%s-%s", time.wYear, time.wMonth, time.wDay); }
コンパイルはできたので実行します。
PS C:\d\gitproj\dfl\examples\text2> dub
Starting Performing "debug" build using C:\D\dmd2\windows\bin64\dmd.exe for x86_64.
Up-to-date dfl 0.13.1+commit.1.gff6b658: target for configuration [library] is up to date.
Building text ~master: building configuration [application]
Linking text
Finished To force a rebuild of up-to-date targets, run again with --force
Running bin/text.exe
C:\d\gitproj\dfl\examples\toggleswitch\toggleswitch.code-workspace
2025-10-16
PS C:\d\gitproj\dfl\examples\text2>
何事もなく動きました。 でも、たまたま動いたと受け止めるのが正しいのだと思います。
QueryInterfaceメソッドを使えば、戻り値のHRESULTを見て取得に成功したかどうか分かりますが、 ダウンキャストだったらどうなるか未知数です。
公式に禁止されているので、やらないようにしましょう。