2006 年 11 月 28 日 23 時 58 分

DllGetClassObject の実装


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


さて、やっと DllGetClassObject を実装する準備が整った。

現時点で公開するクラスは HardLinkIconID だけであり、
DllGetClassObject は CLSID_HardLinkIconID に対応して、
HardLinkIconIDClass クラスオブジェクトを返すことになる。

今回はクラスオブジェクトを static として確保しておき、
常にそれを返すようにすることにしよう。
ま、単純なシングルトンということで。

========== library.cpp ==========

#include "global.hpp"
#include "HardLinkIconIDClass.hpp"

// (…DllMain…)
// (…DllCanUnloadNow…)

STDAPI DllGetClassObject(
        /* [in] */ REFCLSID rclsid,
        /* [in] */ REFIID riid,
        /* [out] */ void** ppvObject) {

    // HardLinkIconIDClass の唯一のインスタンス
    static HardLinkIconIDClass hardLinkClass;

    // ppvObject が NULL なら例外
    if (ppvObject == NULL) return E_INVALIDARG;

    // ppvObject は out なので、
    // ポインタが指す元の値は無視できる
    *ppvObject = NULL;

    IUnknown* classObject = NULL;

    // 対応するクラスオブジェクトを取得
    if (IsEqualCLSID(rclsid, CLSID_HardLinkIconID)) {
        classObject = &hardLinkClass;
    } else {
        return CLASS_E_CLASSNOTAVAILABLE;
    }

    // クラスオブジェクトへの参照を追加
    classObject->AddRef();

    // インタフェースを要求
    HRESULT result = classObject->QueryInterface(riid, ppvObject);

    // 参照を解放
    classObject->Release();

    return result;

}

========== end of library.cpp ==========

HardLinkIconIDClass のインスタンスは、
static として静的に確保しておく。
関数の外に出しても良いのだが、
これは関数の中でしか使わないので、
関数内で定義することにした。

static は常に実体を定義することになる。
HardLinkIconIDClass はクラスなので、
右辺に初期化子を指定する必要はない。
唯一のコンストラクタは引数なしなので、
これが自動的に呼び出されてインスタンスが作成される。

後はいつものパターンだ。
クラスオブジェクトも COM クラスなので、
IUnknown の QueryInterface を呼び出して、
クライアントが要求したインタフェースを得る。

そろそろ見慣れてきたのではないだろうか。
COM のインスタンスを扱う関数は、
大抵同じような形になるのである。

さて、インプロセスサーバとしての最小限の実装が完了した。
後はこれをビルドして登録するだけで使えるようになるが、
関数名について注意する必要がある。

C や C++ には、「装飾名」と呼ばれる仕組みがある。
装飾名とは、コンパイル後のモジュールに含まれる、
「実際の」関数の名前(内部名)であり、
ソースファイルに書く関数名とは異なる。

この内部名は、一意に関数を識別するためのものだ。
なぜ必要かと言うと、C 言語では、
呼び出し規約と名前によって関数を識別するため、
呼び出し規約が違えば異なる修飾名を生成する必要がある。

また、C++ ではオーバーロードが可能となったので、
同じ名前の関数が複数存在することもある。
そのため、更に複雑な修飾名の生成手法が使われている。

DllGetClassObject や、DllCanUnloadNow は、
STDAPI で宣言されているのだが、
STDAPI は「extern "C" HRESULT __stdcall」と展開される。
extern "C" は、C++ ではなく、C 言語の手法を使って、
装飾名を生成する事を意味する。

なので、何もせずにそのままビルドしてしまうと、
DllGetClassObject や、DllCanUnloadNow という関数は、
__stdcall 規約と C 言語の装飾名の規則に沿って、
_DllGetClassObject@12」、「_DllCanUnloadNow@0
という内部名となって DLL に書き込まれてしまうのだ。

COM 基盤の決まりによって、インプロセスサーバは、
前者の名前で関数を提供する必要があるため、
このままでは正式なインプロセスサーバとはならない。

これを解決するためには、モジュール定義ファイルを
プロジェクトに追加する必要がある。

========== LinkIconOverlays.def ==========

EXPORTS
    DllCanUnloadNow PRIVATE
    DllGetClassObject PRIVATE

========== end of LinkIconOverlays.def ==========

モジュール定義ファイルでは、
外部にエクスポートする関数の名前を明示することができる。
関数の名前をそのまま使いたい場合は、
上記のように単に、関数名を並べるだけでよい。

後ろに続く PRIVATE はおまじないだ。
これらの関数は、COM 基盤のみが動的に呼び出すので、
他のソースがこの関数に誤って静的リンクしないように、
インポートライブラリに含めないことを指示するものだ。

ここまでやってビルドしてはじめて、
インプロセスサーバとして機能する DLL が生成されるのだ。

さて、これで「LinkIconOverlays.dll」ができたのだが、
現在、シェルはこの DLL の存在を知らない。
この DLL を呼び出して使ってもらうためには、
レジストリに DLL を登録する必要がある。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.