D言語 Advent Calendar 2025 22日目の記事です。
- 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作る編
- D言語でMicrosoft Component Object Model (COM) を使う (その7) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その8) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その9) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その10) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その11) - ...ing logging 4.0
- ComPtr作る編
- D言語でMicrosoft Component Object Model (COM) を使う (その12) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その13) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その14) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その15) - ...ing logging 4.0
- D言語でMicrosoft Component Object Model (COM) を使う (その16) - ...ing logging 4.0
その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を隠ぺいしました。
さて、前置きが終わりましたので、ここから伏線回収といきましょう。
WindowsのAPIセットの歴史
私はDFLを作っている関係で、いまだにWin32の世界にどっぷり浸かっていますが、 WindowsのAPIセットは様々な変遷を経ています。
まとめてくれているサイトがあったので引用します。
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の組み込みAPI。C言語からアクセスでき現在も動作。
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: HTML5とJavaScriptを使って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セットがあります。
概要
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(); }