...ing logging 4.0

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

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

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

その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インタフェースを使いました。

ここまでで基本的なことはできるようになったので、横道に逸れます。

参照カウンタの管理は難しい

一連の記事では、main関数ですべて完結するような単純な構造なので 別に困りませんでしたが、実際の開発ではCOMインタフェースを たくさんのクラスや関数でたらい回しにして使ったり、 関数やクラスメソッドを呼び出された側がCOMインタフェースを取得して、 呼び出し元に返したりするでしょう。

COMインタフェースを取得するときには、QueryInterfaceメソッドが呼ばれて、 その中でAddRefメソッドが1回呼び出されることを前に書きましたが、 AddRefメソッド1回につきReleaseメソッドを1回呼び出す必要があるので、 この管理は結構難しいです。

というわけで、「ComPtr作る編」が始まります。

ComObjectクラスのAddRefとRelease

もう一度、D言語に用意されたComObjectクラスをおさらいします。

COMオブジェクトのクラスを作るときにComObjectを継承すると、 実装を少し楽することができました。

class ComObject : IUnknown
{
extern (Windows):
    HRESULT QueryInterface(const(IID)* riid, void** ppv)
    {
        if (*riid == IID_IUnknown)
        {
            *ppv = cast(void*)cast(IUnknown)this;
            AddRef();
            return S_OK;
        }
        else
        {   *ppv = null;
            return E_NOINTERFACE;
        }
    }

    ULONG AddRef()
    {
        return atomicOp!"+="(*cast(shared)&count, 1);
    }

    ULONG Release()
    {
        LONG lRef = atomicOp!"-="(*cast(shared)&count, 1);
        if (lRef == 0)
        {
            // free object

            // If we delete this object, then the postinvariant called upon
            // return from Release() will fail.
            // Just let the GC reap it.
            //delete this;

            return 0;
        }
        return cast(ULONG)lRef;
    }

    LONG count = 0;             // object reference count
}

QueryInterfaceメソッドでは、このCOMオブジェクトが対応しているIIDを与えられたときに、 AddRefを1回呼び出していることが分かります。

AddRefメソッドでは、atomicOpを使っているので複雑に見えますが、 要するに、LONG型のcountメンバ変数の値を+1しています。

Releaseメソッドでは、countメンバ変数の値を-1しています。

countが参照カウントを表していて、これがゼロになったときに、 誰からも参照されなくなったので不要だから削除する、 というのが基本的な構造です。

COMは、いわゆる参照カウンタ方式の仕組みで成り立っています。

しかし、D言語のComObjectのReleaseメソッドでは、 参照カウントがゼロになっても明示的にCOMオブジェクトを削除せず、 GCに任せているようです。

参照カウンタ式スマートポインタ

私にとって身近な参照カウンタ式スマートポインタは、やはりC++のstd::shared_ptrでしょう。 boost::shared_ptrのときから使っていましたが、 もう無数にstd::shared_ptr<...>とタイプしまくっていたので、かなり疲れましたね。

そういったスマートポインタでは、AddRefやReleaseのように、 参照カウントの増減を自分で明示的に行わなくてもいいようになっています。

スマートポインタのインスタンスがコピーされるときに自動的にAddRefが呼び出され、 スマートポインタのインスタンスが破棄されるときに自動的にReleaseが呼び出されるようなものです。

D言語にもそういうのが欲しいので、作ってしまいます。

次回から数回にわたって、次のひな形を埋めていきます。

struct ComPtr(BaseType : IUnknown)
{
// ...
private:
    BaseType _comObj;
}