...ing logging 4.0

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

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

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

その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言語で使う準備をしました。 その18では、WinRTインタフェースを使いましたがアプリが一瞬で終了してしまいました。

WinRTは非同期処理が中心

前回ラストの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の多くの機能は非同期処理であり、 msgDlg.abi_ShowAsync(operation.ptr) も非同期処理なのでメッセージダイアログが表示されることなく アプリが一瞬で終了してしまっています。

C#だったらawait、C++/WinRTだったらco_awaitを使うところですが、 D言語にそれそのものはないので、この記事ではメッセージループを作って対応します。

IAsyncInfoインタフェースの取得

abi_ShowAsyncメソッドの引数で返されるIAsyncOperation!(IUICommand)オブジェクトは、 IAsyncInfoインタフェースも実装されています。

IAsyncOperation<TResult> インターフェイス (Windows.Foundation) - Windows UWP applications | Microsoft Learn

実装

IAsyncInfo

IAsyncInfoインタフェースには、メッセージダイアログの状態を取得するget_Statusがあるので、 これを使って、メッセージダイアログが終了されるまで待つことにします。

例によって、MIDLからD言語に変換したコードは、次のとおりです。 MIDLの掲載は省略します。

extern(C) extern const IID IID_IAsyncInfo;

// uuid("00000036-0000-0000-c000-000000000046")
interface IAsyncInfo : IInspectable
{
extern(Windows):
    HRESULT get_Id(uint* id);
    HRESULT get_Status(AsyncStatus* status);
    HRESULT get_ErrorCode(HRESULT* errorCode);
    HRESULT abi_Cancel();
    HRESULT abi_Close();
}

enum AsyncStatus : int
{
    Started = 0,
    Completed, 
    Canceled, 
    Error,
}

利用側の該当部分のコードだけ抜粋すると、こんな感じになります。

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

ComPtr!IAsyncInfo info = operation.as!IAsyncInfo(&IID_IAsyncInfo);

while (true)
{
    AsyncStatus status;
    hr = info.get_Status(&status);
    if (FAILED(hr)) break;
    if (status != AsyncStatus.Started) break;

    MSG msg;
    while (PeekMessage(&msg, null, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    Sleep(10);
}

get_StatusメソッドでAsyncStatus列挙型を取得し、それがAsyncStatus.Startedではなくなったら メッセージループから抜けます。

ちなみにPeekMessageをGetMessageに変えると正しく動きません。

また、Sleep(10);がなくてもマルチプロセッサ環境だからかOSが重たくなったりはしませんでしたが、 ChatGPTが推奨していたので一応書いておきます。

うまくいかなかった方法

別の方法として、IAsyncOperationインタフェースのset_Completedメソッドを使って メッセージダイアログの終了時に呼び出されるイベントハンドラをセットする方法もあるようです。 ただ、私が試した限り、HRESULTにCO_E_NOT_SUPPORTEDが返されて進めないので、 デスクトップアプリでは使えないのかもしれません。

完成版ソースコード全文

さあ、これで正真正銘の完成です!

D言語から生のWinRTを使ってメッセージダイアログを表示することができました。

import core.sys.windows.com;
import core.sys.windows.basetyps;
import core.sys.windows.objbase;
import core.sys.windows.winbase;
import core.sys.windows.windef;
import core.sys.windows.winuser;

pragma(lib, "windowsapp");

extern(Windows)
{
    HRESULT RoInitialize(RO_INIT_TYPE type);
    void RoUninitialize();
    HRESULT RoActivateInstance(HSTRING activatableClassId, IInspectable* thisInstance);
    HRESULT RoGetActivationFactory(HSTRING activatableClassId, REFIID iid, void** factory);
}

enum RO_INIT_TYPE : int
{
    RO_INIT_SINGLETHREADED = 0,
    RO_INIT_MULTITHREADED = 1
}

// uuid("af86e2e0-b12d-4c6a-9c5a-d7aa65101e90")
extern(Windows)
interface IInspectable : IUnknown
{
    HRESULT GetIids(uint* iidCount, GUID** iids);
    HRESULT GetRuntimeClassName(HSTRING* className);
    HRESULT GetTrustLevel(int* trustLevel);
}

alias HSTRING = HANDLE;

extern(Windows)
{
    HRESULT WindowsCreateString(LPCWSTR ptr, UINT32 len, HSTRING* out_);
    HRESULT WindowsDeleteString(HSTRING str);
}

HSTRING toHSTRING(wstring text)
{
    HSTRING h;
    WindowsCreateString(text.ptr, cast(uint)text.length, &h);
    return h;
}

// uuid("33f59b01-5325-43ab-9ab3-bdae440e4121")
// WinrtFactory("Windows.UI.Popups.MessageDialog")
interface IMessageDialog : IInspectable
{
extern(Windows):
    HRESULT get_Title();//(HSTRING* return_value);
    HRESULT set_Title();//(HSTRING value);
    HRESULT get_Commands();//(IVector!(IUICommand)* return_value);
    HRESULT get_DefaultCommandIndex();//(UINT32* return_value);
    HRESULT set_DefaultCommandIndex();//(UINT32 value);
    HRESULT get_CancelCommandIndex();//(UINT32* return_value);
    HRESULT set_CancelCommandIndex();//(UINT32 value);
    HRESULT get_Content();//(HSTRING* return_value);
    HRESULT set_Content();//(HSTRING value);
    HRESULT abi_ShowAsync(IAsyncOperation!(IUICommand)* return_messageDialogAsyncOperation);
    HRESULT get_Options();//(MessageDialogOptions* return_value);
    HRESULT set_Options();//(MessageDialogOptions value);
}

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);
}

extern(C) extern const IID IID_IAsyncInfo;

// uuid("00000036-0000-0000-c000-000000000046")
interface IAsyncInfo : IInspectable
{
extern(Windows):
    HRESULT get_Id(uint* id);
    HRESULT get_Status(AsyncStatus* status);
    HRESULT get_ErrorCode(HRESULT* errorCode);
    HRESULT abi_Cancel();
    HRESULT abi_Close();
}

enum AsyncStatus : int
{
    Started = 0,
    Completed, 
    Canceled, 
    Error,
}

interface IAsyncOperation(TResult) : IInspectable
{
extern(Windows):
    HRESULT set_Completed();//(AsyncOperationCompletedHandler!(TResult) handler);
    HRESULT get_Completed();//(AsyncOperationCompletedHandler!(TResult)* return_handler);
    HRESULT get_Results(TResult* return_results);
}

// uuid("4FF93A75-4145-47FF-AC7F-DFF1C1FA5B0F")
interface IUICommand : IInspectable
{
extern(Windows):
    HRESULT get_Label();//(HSTRING* value);
    HRESULT set_Label();//(HSTRING value);
    HRESULT get_Invoked();//(UICommandInvokedHandler* value);
    HRESULT set_Invoked();//(UICommandInvokedHandler value);
    HRESULT get_Id();//(IInspectable* value);
    HRESULT set_Id();//(IInspectable value);
}

extern(C) extern const IID IID_IInitializeWithWindow;

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

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);
}

class ComException : Exception
{
    this(string msg, string file = __FILE__, int line = __LINE__)
    {
        super(msg, file, line);
    }
}

struct ComPtr(BaseType : IUnknown)
{
    this(BaseType comPtr)
    {
        _comObj = comPtr;
    }
    this(ref ComPtr!BaseType comPtr)
    {
        _comObj = comPtr._comObj;
        _comObj.AddRef();
    }
    ~this()
    {
        if (!_comObj) return;
        _comObj.Release();
        _comObj = null;
    }
    ComPtr!TargetType as(TargetType)(IID* riid)
    {
        TargetType target;
        HRESULT hr = _comObj.QueryInterface(riid, cast(void**)&target);
        if (hr < 0)
            throw new ComException("ComPtr.as is failed.");
        return ComPtr!TargetType(target);
    }
    @property BaseType handle()
    {
        return _comObj;
    }
    @property BaseType* ptr()
    {
        return &_comObj;
    }
    alias handle this;
private:
    BaseType _comObj;
}

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;

    ComPtr!IAsyncInfo info = operation.as!IAsyncInfo(&IID_IAsyncInfo);

    while (true)
    {
        AsyncStatus status;
        hr = info.get_Status(&status);
        if (FAILED(hr)) break;
        if (status != AsyncStatus.Started) break;

        MSG msg;
        while (PeekMessage(&msg, null, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        Sleep(10);
    }
}

明日は総括とします。