...ing logging 4.0

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

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

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

その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を隠ぺいしました。

さて、前置きが終わりましたので、ここから伏線回収といきましょう。

WindowsAPIセットの歴史

私はDFLを作っている関係で、いまだにWin32の世界にどっぷり浸かっていますが、 WindowsAPIセットは様々な変遷を経ています。

まとめてくれているサイトがあったので引用します。

Short history of all Windows UI frameworks and libraries

Win32 - 1985

Win32 is the Windows built-in API. Using it you can create buttons, windows, scrollbars, access the windows shell, common dialogs and so on, and they will look and behave like native windows. It is pretty old, can be accessed via the C programming language but works nicely still today.

MFC - 1992

A C++ wrapper named MFC for Win32 which makes the API a bit easer to use - the MFC still gets minor updates today from Microsoft and is interestingly still used widely.

WinForms - 2002

Windows Forms was a .NET wrapper over Win32, for use in C# and other .NET languages. It's in maintenance mode and no new features are planned of being added (source).

WPF - 2006

Windows Presentation Foundation introduced XAML and used DirectX to draw vector based components and to be used by .NET languages like C#. It's now open source.

Silverlight - 2007

Used parts of WPF for doing UI on the Web as competition to Adobe Flash. It's discontinued (source).

Xamarin (Forms) - 2014

Xamarin was a cross platform .NET, including the UI framework Xamarin.Forms (similar to WinForms). It ran on Mono instead of .NET, then later on real .NET. Replaced with MAUI, see below.

WinJS - 2012

WinJS was a JavaScript for creating Windows Store apps via HTML5 and JavaScript. It seems not to be developed any further.

WinRT (XAML) - 2012

WinRT is a platform for creating Windows apps for example via a custom C++ dialect (C++/WinRT, C++/CX) which used XAML to create the UserInterface. It was replaced with UWP later.

UWP (XAML) - 2015

Starting with Windows 10 - UWP is the platform for creating "universal apps" based on real C++ but also usable from .NET. This was replaced with WinUI later.

WinUI - 2018

Similar to UWP above but with styles making it look more like Windows 11. Now replaced with WinUI 3.

MAUI - 2020

Framework for creating multi-platform UIs, the successor of Xamarin.Forms (see above). Of course also now based on XAML, only for .NET languages.

WinUI 3 - 2021

Similar to WinUI, but decoupled the UI framework from the OS and put it into the library. Your apps now also work on systems which don't have the latest OS update.

邦訳もありましたので引用します。

WindowsのUIフレームワークの歴史。Win32からWinUI 3まで | ソフトアンテナ

Win32 - 1985: Windowsの組み込みAPIC言語からアクセスでき現在も動作。

MFC - 1992: Win32のC++ラッパー。現在も広く使われている。

WinForms - 2002: Win32の.NETラッパーで、C#や他の.NET言語から使える。新機能の追加は予定されていない。

WPF - 2006: XAMLを導入し、DirectXを使ってベクターベースのコンポーネントを描画できる。現在はオープンソース

Silverlight - 2007: Adobe Flashに対抗してWeb上でUIを実現するためにWPFの一部を使用。廃止された。

Xamarin (Forms) - 2014: クロスプラットフォームの.NETで、UIフレームワークのXamarin.Formsを含んでいた。MAUIに取って代わられた。

WinJS - 2012: HTML5JavaScriptを使ってWindowsストアアプリを作成するためのライブラリ。もう開発されていない。

WinRT (XAML) - 2012: C++/WinRT、C++/CXを使ってWindowsアプリを作成するためのプラットフォーム。UIの作成にXAMLを使用。後にUWPに取って代わられた。

UWP (XAML) - 2015: C++をベースとしながらも.NETからも利用可能な「ユニバーサルアプリ」を作成するためのプラットフォーム。WinUIに取って代わられた。

WinUI - 2018: UWPと似ているがスタイルがWindows 11に近い。現在はWinUI 3に置き換えられている。

MAUI - 2020: Xamarin.Formsの後継となるマルチプラットフォームUIを作成するためのフレームワークXAMLをベースにしているが、.NET言語向けのみ。

WinUI 3 - 2021: WinUIに似ているが、UIフレームワークをOSから切り離し、ライブラリに入れた。

WinRTはCOMでできている

2012年にWindows 8で導入されたWinRTというAPIセットがあります。

Windowsランタイム - Wikipedia

概要

WinRTはC++言語によって実装された、オブジェクト指向設計にもとづく近代的なAPIである (なお前身となる旧来のWin32 APIは、C言語インターフェイスを持つAPIとして設計され、C/C++両方から利用することができた)。 また、COMに基づいたネイティブ(アンマネージ)APIであり、COMのように複数の言語から利用することができる。

WinRTは、COMの基盤の上に作られたAPIであり、デスクトップアプリでも使えるように拡張されてきました。

WinRTはCOMです。だから、D言語からも使えます。

WinRTの基底はIInspactableインタフェース

D言語からWinRTを使うための準備をしていきます。

COMの基底はIUnknownインタフェースでしたが、 WinRTでは、IInspactableインタフェースが基底になります。 IInspactableインタフェースは、IUnknownインタフェースを継承しています。 このことからもWinRTがCOMの基盤の上に作られたものであることが分かります。

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

HSTRINGはWinRTの世界で使われる文字列のハンドルです。 WinRTの世界の文字列はハンドルで扱われます。 文字列ごと(文字ごとではない)にリソース番号が振られているような感じですね。

WindowsCreateString関数はHSTRINGの文字列を作る関数、 WindowsDeleteString関数はHSTRINGの文字列を解放する関数です。 それぞれextern(Windows)のリンケージで外部宣言しておきます。

後々、HSTRINGを作る度にWindowsCreateString関数を使うのは面倒なので、 D言語の文字列wstringからHSTRINGを作成するためのヘルパー関数を作っておきます。

WinRTの初期化はRoInitialize

WinRTの初期化関数などを外部宣言します。

COMの初期化はCoInitializeEx関数でしたが、 WinRTの初期化はRoInitialize関数で行います。

また、COMの解放はCoUninitialize関数でしたが、 WinRTの解放はRoUninitialize関数で行います。

COMのCoCreateInstance関数に相当するのがRoActivateInstance関数、 CoGetClassObject関数に相当するのがRoGetActivationFactory関数です。

ついでにCOMのCOINITに相当するRO_INIT_TYPEも定義しておきます。

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
}

WinRTライブラリの実体はWindowsApp.lib

WinRTの実体はWindowsApp.libなので、冒頭に次のとおり書いて、これをリンクします。 これを忘れるとコンパイルが通ってもリンクに失敗して実行ファイルが作られません。

pragma(lib, "windowsapp");

MIDLからD言語のインタフェースを書く

WinRTのコンポーネントのABI(Application Binary Interface)はMIDLで書かれています。 あるプログラミング言語でWinRTのコンポーネントを使いたいときは、MIDLが中間言語となり、 MIDLを各言語特有のコードに投影(プロジェクション)することによって、 具体的なコードを言語側で自動生成することになっています。

この記事では、WinRTでメッセージダイアログを表示することを目標にします。

調べてみると、WinRTでメッセージダイアログを表示するときには、 C#C++/WinRTではMessageDialogクラスを使うようです。

MIDLでの定義を探してみると、次のWindows SDKにMessageDialogの定義がありました。

C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\winrt\windows.ui.popups.idl
[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;
}

MIDLによれば、MessageDialogはruntimeclassだと定義されていますが、 これはC++/WinRTなどへ投影するときにクラスになるということです。

この記事ではMessageDialogクラスをD言語で作らないで、WinRTインタフェースだけを扱うことにします。

[default] interface Windows.UI.Popups.IMessageDialog;とあって、 MessageDialogはIMessageDialogインタフェースを実装しているようなので、 IMessageDialogインタフェースを探すと同じMIDLファイルに定義がありました。 IMessageDialogFactoryインタフェースも一緒に見つかりました。

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

それでは、「COM使う編」でやったように、D言語のインタフェースに書き換えます。 *1

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

以前の記事で書いたとおり、D言語では参照型になる型は、ポインタをひとつ外します。 基本的には、参照型の引数であれば、[propget]と書かれているメソッドの引数は戻り値として使われるのでポインタをひとつ付けて、 [propput]と書かれているメソッドの引数はポインタを付けない、で大体うまくいきます。

メッセージダイアログを表示するためにはIMessageDialogインタフェースのabi_ShowAsyncメソッドだけ使うので、 完全な宣言を書きましたが、ここでIAsyncOperationとIUICommandの定義も必要だと分かりました。

ただ、IAsyncOperationはテンプレートなので、MIDLを見ても定義がよく分かりません。 そのときは、C++/WinRTのヘッダーファイルを見るといいです。

C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\winrt\windows.foundation.collections.h
template <class TResult>
struct IAsyncOperation_impl : IInspectable
{
    private:
        typedef typename Windows::Foundation::Internal::GetAbiType<TResult>::type     TResult_abi;
        typedef typename Windows::Foundation::Internal::GetLogicalType<TResult>::type TResult_logical;
    public:

    // For all types which are neither InterfaceGroups nor RuntimeClasses, the
    // following three typedefs are synonyms for a single C++ type.  But for
    // InterfaceGroups and RuntimeClasses, they are different types:
    //   T_logical: The C++ Type for the InterfaceGroup or RuntimeClass, when
    //              used as a template parameter.  Eg "RCFoo*"
    //   T_abi:     The C++ type for the default interface used to represent the
    //              InterfaceGroup or RuntimeClass when passed as a method parameter.
    //              Eg "IFoo*"
    //   T_complex: An instantiation of the Internal "AggregateType" template that
    //              combines T_logical with T_abi. Eg "AggregateType<RCFoo*,IFoo*>"
    // See the declaration above of Windows::Foundation::Internal::AggregateType
    // for more details.
        typedef TResult                                                                 TResult_complex;

        virtual HRESULT STDMETHODCALLTYPE put_Completed( IAsyncOperationCompletedHandler<TResult_logical> *handler) = 0;
        virtual HRESULT STDMETHODCALLTYPE get_Completed( IAsyncOperationCompletedHandler<TResult_logical> **handler) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetResults(  TResult_abi *results) = 0;

    };

publicメンバのところだけ書き出して、D言語に書き換えます。 後でget_Resultsメソッドを使うのでこれだけは完全に記載します。

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

続いてIUICommandです。こっちはMIDLを元にします。

[contract(Windows.Foundation.UniversalApiContract, 1.0)]
[uuid(4FF93A75-4145-47FF-AC7F-DFF1C1FA5B0F)]
interface IUICommand : IInspectable
{
    [propget] HRESULT Label([out] [retval] HSTRING* value);
    [propput] HRESULT Label([in] HSTRING value);
    [propget] HRESULT Invoked([out] [retval] Windows.UI.Popups.UICommandInvokedHandler** value);
    [propput] HRESULT Invoked([in] Windows.UI.Popups.UICommandInvokedHandler* value);
    [propget] HRESULT Id([out] [retval] IInspectable** value);
    [propput] HRESULT Id([in] IInspectable* value);
}

同じようにD言語に書き換えますが、この記事ではIUICommandのメソッドを使うところまで進みませんので、 関数名だけ適当に宣言しておきます。

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

とりあえずコンパイルしてみる

いくつかimport文を追加したら、コンパイル可能になります。

guidFromUUID関数、ComExceptionクラス、ComPtr構造体は、 これまでの記事からそのまま引き継いでいます。

次回は、main関数の中身を実装します。

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

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

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

*1:自動生成について実例を追いかけたい方はD/WinRTをどうぞ。