...ing logging 4.0

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

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

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

その1でCOMインタフェースを使うために必要な宣言や定義の準備ができて、 その2でCOMの初期化と解放ができるようになりました。

COMオブジェクトの生成

次は、WindowsにIFileOpenDialogのCOMオブジェクトを生成してもらいます。

となれば、CoCreateInstance関数の出番です。

CoCreateInstance 関数 (combaseapi.h) - Win32 apps | Microsoft Learn

CoCreateInstance 関数 (combaseapi.h)

指定した CLSID に関連付けられた クラスの 1 つのオブジェクトを作成し、既定で初期化します。

ローカル システムでオブジェクトを 1 つだけ作成する場合は、 CoCreateInstance を呼び出します。 リモート システムで 1 つのオブジェクトを作成するには、 CoCreateInstanceEx 関数を呼び出します。 1 つの CLSID に基づいて複数のオブジェクトを作成するには、 CoGetClassObject 関数を呼び出します。

シグネチャは次のとおり。

HRESULT CoCreateInstance(
  [in]  REFCLSID  rclsid,
  [in]  LPUNKNOWN pUnkOuter,
  [in]  DWORD     dwClsContext,
  [in]  REFIID    riid,
  [out] LPVOID    *ppv
);

引数の意味は次のとおり。

[in] rclsid

オブジェクトの作成に使用されるデータとコードに関連付けられている CLSID。

[in] pUnkOuter

NULL の場合、 は、オブジェクトが集計の一部として作成されていないことを示します。 NULL 以外の場合は、集計オブジェクトの IUnknown インターフェイス (制御する IUnknown) へのポインター

[in] dwClsContext

新しく作成されたオブジェクトを管理するコードが実行されるコンテキスト。 値は、列挙 CLSCTX から取得されます。

[in] riid

オブジェクトとの通信に使用するインターフェイスの識別子への参照。

[out] ppv

riid で要求されたインターフェイス ポインターを受け取るポインター変数のアドレス。 正常に戻ると、ppv には要求されたインターフェイス ポインターが含まれます。 失敗した場合、ppv には NULL が含まれます。

D言語での宣言は次のようになっています。

HRESULT CoCreateInstance(REFCLSID, LPUNKNOWN, DWORD, REFIID, PVOID*);

IFileOpenDialog COMオブジェクトの取得

まず、IFileOpenDialog COMオブジェクトをWIndowsから受け取りたいので、入れ物を作ります。 IFileOpenDialogはその1の記事で宣言したので、これが書けるようになっています。

IFileOpenDialog pDialog;

次に、CoCreateInstanceを呼び出して、pDialogにCOMオブジェクトを取得します。

HRESULT hr;
hr = CoCreateInstance(&CLSID_FileOpenDialog, null, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, cast(void**)&pDialog);

第1引数は、IFileOpenDialog COMインタフェースを実装しているCOMオブジェクトのCLSIDです。 これもその1の記事で宣言したものです。

extern(C) extern const CLSID CLSID_FileOpenDialog;

第2引数は、nullにします。

第3引数は、CLSCTX_INPROC_SERVERにします。説明は省略。

第4引数は、取得したいインタフェース識別子を与えます。 これもその1の記事で宣言しました。

extern(C) extern const IID IID_IFileOpenDialog;

第5引数は、IFileOpenDialog COMインタフェースを実装しているCOMオブジェクトへの参照を受け取る変数へのアドレスを指定します。 IFileOpenDialogが参照型なのでポインタがひとつ付き、それのアドレスを渡すので、cast(void**)になっています。

CoCreateInstanceが成功すれば、hrにはS_OKが入ってきます。 何かのエラーがあったら、何もしないで終了します。

if (FAILED(hr)) return;

ここまで来れば、pDialog変数を介して、IFileOpenDialogインタフェースにアクセスできるので、Showメソッドを呼び出せます。

hr = pDialog.Show(null);
if (FAILED(hr)) return;

Windowsに生成してもらったCOMオブジェクトを使い終わったら、ユーザーは自分で解放しなければいけません。

hr = pDialog.Show(null);
if (FAILED(hr)) return;

pDialog.Release();

これでもいいですが、せっかくD言語を使っているので、scope(exit)を使って、生成と解放の処理をひとまとめに書いておきます。

IFileOpenDialog pDialog;
hr = CoCreateInstance(&CLSID_FileOpenDialog, null, CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, cast(void**)&pDialog);
if (FAILED(hr)) return;
scope(exit) pDialog.Release();

実行してみる

ここまでで実行すると、ファイル選択ダイアログが表示され、IFileOpenDialog COMインタフェースが使用できました。

以下、ソース全文です。

import core.sys.windows.windef;
import core.sys.windows.com;
import core.sys.windows.objbase;

extern(C) extern const IID IID_IModalWindow;

// uuid("b4db1657-70d7-485e-8e3e-6fcb5a5c1802")
interface IModalWindow : IUnknown
{
extern (Windows):
    HRESULT Show(HWND hwndOwner);
}

extern(C) extern const IID IID_IFileDialog;

// uuid("42f85136-db7e-439c-85f1-e4075d135fc8")
interface IFileDialog : IModalWindow
{
extern (Windows):
    HRESULT SetFileTypes();//(UINT cFileTypes, const COMDLG_FILTERSPEC* rgFilterSpec);
    HRESULT SetFileTypeIndex();//(UINT iFileType);
    HRESULT GetFileTypeIndex();//(UINT* piFileType);
    HRESULT Advise();//(IFileDialogEvents pfde, DWORD* pdwCookie);
    HRESULT Unadvise();//(DWORD dwCookie);
    HRESULT SetOptions();//(FILEOPENDIALOGOPTIONS fos);
    HRESULT GetOptions();//(FILEOPENDIALOGOPTIONS * pfos);
    HRESULT SetDefaultFolder();//(IShellItem psi);
    HRESULT SetFolder();//(IShellItem psi);
    HRESULT GetFolder();//(IShellItem* ppsi);
    HRESULT GetCurrentSelection();//(IShellItem* ppsi);
    HRESULT SetFileName();//(LPCWSTR pszName);
    HRESULT GetFileName();//(LPWSTR pszName);
    HRESULT SetTitle();//(LPCWSTR pszTitle);
    HRESULT SetOkButtonLabel();//(LPCWSTR pszText);
    HRESULT SetFileNameLabel();//(LPCWSTR pszLabel);
    HRESULT GetResult(IShellItem* ppsi);
    HRESULT AddPlace();//(IShellItem psi, FDAP fdap);
    HRESULT SetDefaultExtension();//(LPCWSTR pszDefaultExtension);
    HRESULT Close();//(HRESULT hr);
    HRESULT SetClientGuid();//(REFGUID guid);
    HRESULT ClearClientData();
    HRESULT SetFilter();//(IShellItemFilter pFilter);
}

extern(C) extern const IID IID_IFileOpenDialog;

// uuid("d57c7288-d4ad-4768-be02-9d969532d960")
interface IFileOpenDialog : IFileDialog
{
extern(Windows):
    HRESULT GetResults();//(IShellItemArray* ppenum);
    HRESULT GetSelectedItems();//(IShellItemArray* ppsai);
}

extern(C) extern const IID IID_IShellItem;

// uuid("43826d1e-e718-42ee-bc55-a1e261c37bfe")
interface IShellItem : IUnknown
{
extern(Windows):
    HRESULT BindToHandler();//(IBindCtx pbc, REFGUID bhid, REFIID riid, void** ppv);
    HRESULT GetParent();//(IShellItem* ppsi);
    HRESULT GetDisplayName(SIGDN sigdnName, LPWSTR* ppszName);
    HRESULT GetAttributes();//(SFGAOF sfgaoMask, SFGAOF* psfgaoAttribs);
    HRESULT Compare();//(IShellItem psi, SICHINTF hint, int* piOrder);
}

enum SIGDN : int
{
    SIGDN_NORMALDISPLAY = 0,
    SIGDN_PARENTRELATIVEPARSING = 0x80018001,
    SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
    SIGDN_PARENTRELATIVEEDITING = 0x80031001,
    SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
    SIGDN_FILESYSPATH = 0x80058000,
    SIGDN_URL = 0x80068000,
    SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
    SIGDN_PARENTRELATIVE = 0x80080001,
    SIGDN_PARENTRELATIVEFORUI = 0x80094001
}

extern(C) extern const CLSID CLSID_FileOpenDialog;

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