...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

D言語でMicrosoft Component Object Model (COM) を使う (その18)

D言語 Advent Calendar 2025 23日目の記事です。

その1でCOMインタフェースを使うために必要な宣言や定義の準備ができて、 その2でCOMの初期化と解放ができるようになりました。 その3では実際にファイルを開くダイアログを表示することができ、その4では選択したファイルのパスを取得できました。 その5ではQueryInterfaceを使って別のCOMインタフェースを取得して、ファイル作成日時を取得できました。 その6ではCOMインタフェースをアップキャストもダウンキャストもできないことを説明しました。

その7ではCOMオブジェクトとCOMインタフェースを自作するための準備をしました。 その8とその9では自作COMファクトリをCOMサーバーとして登録し、 自作COMオブジェクトと自作COMインタフェースを作成できました。 その10では既存のCOMインタフェースを拡張した新しいCOMインタフェースとCOMオブジェクトを作成しました。 その11では、COMファクトリクラスを拡張して、新しいCOMインタフェースを使えました。

その12では、AddRefメソッドとReleaseメソッドを自分で呼ばなくていいように ComPtrというスマートポインタを作ることにしましたが、 その13では構築と破棄を同じ回数捉えることができませんでした。 その14ではコピーコンストラクタだけでスマートポインタを作る実験をしました。 その15ではComPtrを実際に使うことができました。 その16ではComPtrにasメソッドを追加してQueryInterfaceを隠ぺいしました。

その17ではCOMの基盤の上に作られているWinRTをD言語で使う準備をしました。

WinRTオブジェクトの作り方

それでは、WinRTの利用側を書いていきます。

MessageDialog関係のMIDLをもう一度見てみます。

C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\winrt\windows.ui.popups.idl
[contract(Windows.Foundation.UniversalApiContract, 1.0)]
[exclusiveto(Windows.UI.Popups.MessageDialog)]
[uuid(33F59B01-5325-43AB-9AB3-BDAE440E4121)]
interface IMessageDialog : IInspectable
{
    [propget] HRESULT Title([out] [retval] HSTRING* value);
    [propput] HRESULT Title([in] HSTRING value);
    [propget] HRESULT Commands([out] [retval] Windows.Foundation.Collections.IVector<Windows.UI.Popups.IUICommand*>** value);
    [propget] HRESULT DefaultCommandIndex([out] [retval] UINT32* value);
    [propput] HRESULT DefaultCommandIndex([in] UINT32 value);
    [propget] HRESULT CancelCommandIndex([out] [retval] UINT32* value);
    [propput] HRESULT CancelCommandIndex([in] UINT32 value);
    [propget] HRESULT Content([out] [retval] HSTRING* value);
    [propput] HRESULT Content([in] HSTRING value);
    HRESULT ShowAsync([out] [retval] Windows.Foundation.IAsyncOperation<Windows.UI.Popups.IUICommand*>** messageDialogAsyncOperation);
    [propget] HRESULT Options([out] [retval] Windows.UI.Popups.MessageDialogOptions* value);
    [propput] HRESULT Options([in] Windows.UI.Popups.MessageDialogOptions value);
}

[contract(Windows.Foundation.UniversalApiContract, 1.0)]
[exclusiveto(Windows.UI.Popups.MessageDialog)]
[uuid(2D161777-A66F-4EA5-BB87-793FFA4941F2)]
interface IMessageDialogFactory : IInspectable
{
    HRESULT Create([in] HSTRING content, [out] [retval] Windows.UI.Popups.MessageDialog** messageDialog);
    HRESULT CreateWithTitle([in] HSTRING content, [in] HSTRING title, [out] [retval] Windows.UI.Popups.MessageDialog** messageDialog);
}

[activatable(Windows.UI.Popups.IMessageDialogFactory, Windows.Foundation.UniversalApiContract, 1.0)]
[contract(Windows.Foundation.UniversalApiContract, 1.0)]
[marshaling_behavior(standard)]
runtimeclass MessageDialog
{
    [default] interface Windows.UI.Popups.IMessageDialog;
}

IMessageDialogのUUID、IMessageDialogFactoryのUUIDのほかに、

[exclusiveto(Windows.UI.Popups.MessageDialog)]

という記載があります。

次のとおり、D言語に変換したコードでもコメントにして残してありました。

const IID IID_IMessageDialogFactory = guidFromUUID("2d161777-a66f-4ea5-bb87-793ffa4941f2");

// uuid("2d161777-a66f-4ea5-bb87-793ffa4941f2")
// WinrtFactory("Windows.UI.Popups.MessageDialog")
interface IMessageDialogFactory : IInspectable
{
extern(Windows):
    HRESULT abi_Create(HSTRING content, IMessageDialog* return_messageDialog);
    HRESULT abi_CreateWithTitle(HSTRING content, HSTRING title, IMessageDialog* return_messageDialog);
}

実は、WinRTではこの"Windows.UI.Popups.MessageDialog"という文字列が、 COMにおけるCLSIDの役割を担います。

D言語のコードは次のようになります。

HSTRING msgDialogFactoryClsid = toHSTRING("Windows.UI.Popups.MessageDialog");
scope(exit) WindowsDeleteString(msgDialogFactoryClsid);
ComPtr!IMessageDialogFactory msgDialogFactory;
RoGetActivationFactory(msgDialogFactoryClsid, &IID_IMessageDialogFactory, cast(void**)msgDialogFactory.ptr);

まず、WinRTの文字列はHSTRINGなので、 前に書いておいたtoHSTRING関数でHSTRINGを作成します。

次のscope(exit)は、HSTRINGの解放処理です。

続いて、ComPtr!IMessageDialogFactory msgDialogFactory;で ファクトリオブジェクトを受け取る変数を作ります。 ComPtrに入れない場合は、自分でReleaseメソッドを呼んでmsgDialogFactoryを解放してください。

RoGetActivationFactory関数の第1引数にはHSTRING文字列を渡し、 第2引数にはUUIDからGUIDに変換したIID_IMessageDialogFactoryのアドレスを渡し、 第3引数でファクトリオブジェクトを受け取ります。

これで、COMのCoGetClassObject関数に相当する処理を、 WinRTのRoGetActivationFactory関数を使って書けました。

WinRTインタフェースの取得

次は、ファクトリオブジェクトmsgDialogFactoryのabi_CreateWithTitleメソッドを呼び出して、 IMessageDialogインタフェースを取得します。

HSTRING content = toHSTRING("Hello D/WinRT!");
scope(exit) WindowsDeleteString(content);
HSTRING title = toHSTRING("D/WinRT Dialog");
scope(exit) WindowsDeleteString(title);
ComPtr!IMessageDialog msgDlg;
msgDialogFactory.abi_CreateWithTitle(content, title, msgDlg.ptr);

contentとtitleは、どちらもHSTRINGの文字列を作成しているだけです。

次に、ComPtr!IMessageDialog msgDlg;でWinRTインタフェースを受け取る変数を作ります。 ComPtrに入れない場合は、自分でReleaseメソッドを呼んでmsgDlgを解放してください。

最後に、msgDialogFactory.abi_CreateWithTitle(content, title, msgDlg.ptr);で第3引数のmsgDlgに IMessageDialogインタフェースを受け取ります。

WinRTインタフェースの使用

IMessageDialogインタフェースのabi_ShowAsyncメソッドを使ってみます。

ComPtr!(IAsyncOperation!(IUICommand)) operation;
hr = msgDlg.abi_ShowAsync(operation.ptr);
if (FAILED(hr)) return;

abi_ShowAsyncを呼ぶと、状態がIAsyncOperation!(IUICommand)に帰ってくるので、 その入れ物になる変数を作って、アドレスを渡します。 これもComPtrに入れてあります。

実行してみると例外が飛ぶ

ここまでで本質的な処理が書けたので実行してみると、 hr = msgDlg.abi_ShowAsync(operation.ptr); で例外が飛んで動きません。どうしてでしょうか。

void main()
{
    HRESULT hr = RoInitialize(RO_INIT_TYPE.RO_INIT_SINGLETHREADED);
    if (FAILED(hr)) return;
    scope(exit) RoUninitialize();

    HSTRING msgDialogFactoryClsid = toHSTRING("Windows.UI.Popups.MessageDialog");
    scope(exit) WindowsDeleteString(msgDialogFactoryClsid);
    ComPtr!IMessageDialogFactory msgDialogFactory;
    RoGetActivationFactory(msgDialogFactoryClsid, &IID_IMessageDialogFactory, cast(void**)msgDialogFactory.ptr);

    HSTRING content = toHSTRING("Hello D/WinRT!");
    scope(exit) WindowsDeleteString(content);
    HSTRING title = toHSTRING("D/WinRT Dialog");
    scope(exit) WindowsDeleteString(title);
    ComPtr!IMessageDialog msgDlg;
    msgDialogFactory.abi_CreateWithTitle(content, title, msgDlg.ptr);

    ComPtr!(IAsyncOperation!(IUICommand)) operation;
    hr = msgDlg.abi_ShowAsync(operation.ptr); // ★ここで例外が発生してアプリが落ちる
    if (FAILED(hr)) return;
}

IInitializeWithWindowインタフェースでWinRTオブジェクトをウィンドウに紐付ける

実は、WinRTでUIが関係する機能を使うときのお約束として、 WinRTオブジェクトをウィンドウに紐付ける必要があります。

IInitializeWithWindow (shobjidl_core.h) - Win32 apps | Microsoft Learn

次の定義をmain関数の前に追加します。

extern(C) extern const IID IID_IInitializeWithWindow;

// uuid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")
interface IInitializeWithWindow : IUnknown
{
extern(Windows):
    HRESULT Initialize(HWND hwnd);
}

WinRTオブジェクトにオーナーウィンドウのウィンドウハンドルHWND を設定していますね。

では、main関数を次のように書き換えます。

void main()
{
    HRESULT hr = RoInitialize(RO_INIT_TYPE.RO_INIT_SINGLETHREADED);
    if (FAILED(hr)) return;
    scope(exit) RoUninitialize();

    HSTRING msgDialogFactoryClsid = toHSTRING("Windows.UI.Popups.MessageDialog");
    scope(exit) WindowsDeleteString(msgDialogFactoryClsid);
    ComPtr!IMessageDialogFactory msgDialogFactory;
    RoGetActivationFactory(msgDialogFactoryClsid, &IID_IMessageDialogFactory, cast(void**)msgDialogFactory.ptr);

    HSTRING content = toHSTRING("Hello D/WinRT!");
    scope(exit) WindowsDeleteString(content);
    HSTRING title = toHSTRING("D/WinRT Dialog");
    scope(exit) WindowsDeleteString(title);
    ComPtr!IMessageDialog msgDlg;
    msgDialogFactory.abi_CreateWithTitle(content, title, msgDlg.ptr);

    // ★追加 >>>
    ComPtr!IInitializeWithWindow pDesktopWindow = msgDlg.as!IInitializeWithWindow(&IID_IInitializeWithWindow);
    hr = pDesktopWindow.Initialize(GetDesktopWindow());
    if (FAILED(hr)) return;
    // <<< ★ここまで

    ComPtr!(IAsyncOperation!(IUICommand)) operation;
    hr = msgDlg.abi_ShowAsync(operation.ptr);
    if (FAILED(hr)) return;
}

さあ、これで完成と言いたいのですが、まだ罠があります。 このまま実行しても、メッセージダイアログが表示されることなく、 一瞬でアプリが終了してしまいます。

それは、WinRTは非同期関数が中心になっているということです。

次回に続きます。