👏

64bitアプリから32bit DLLを呼ぶ(COMサロゲート)

に公開

やりたいこと

32bitで作られたC++のCOM DLLがあり、これを64bitのWPFから呼びたい。

サンプル
https://github.com/thakaz/ComSurrogateSample


前提

DLLはCOMを想定しているので、以下のようなものとする。

  • COMとして公開されている
  • regsvr32 で登録できる
  • CLSIDを持っている

サンプルとして足し算ができるだけのDLLをATL(Active Template Library)で作成する。



SimpleCalculator.cpp
STDMETHODIMP CSimpleCalculator::Add(LONG a, LONG b, LONG* result)
{
    if (result == nullptr)
        return E_POINTER;

    *result = a + b;
    return S_OK;
}

呼び出し側はこう。

MainWindow.xaml.cs
Guid clsid = new Guid("480B42D8-3934-47FF-B20D-5FC280FEC649");

Type? t = Type.GetTypeFromCLSID(clsid);
if (t == null)
{
    // CLSID未登録
    return;
}

dynamic obj = Activator.CreateInstance(t)!;
int result = obj.Add(2, 3);

dynamicなのでIDispatch経由。型安全ではないけどサンプルなのでこれで。


やること

COMサロゲートを使う。

まずDLLをCOMとして登録する。管理者権限で以下を実行。

regsvr32 SimpleCom32.dll

Visual Studioを管理者権限で起動してビルドすれば自動で登録される(vcxprojのRegisterOutputtrueなので)。

次に、レジストリに以下を追加。(いずれもHKLM)

CLSID側(32bit側)

HKLM\SOFTWARE\Classes\WOW6432Node\CLSID\{CLSID}
    AppID = {CLSID}

AppID側

HKLM\SOFTWARE\Classes\AppID\{CLSID}
    DllSurrogate = ""

空文字にするのがポイント。これでデフォルトのサロゲート(dllhost.exe)が使われる。


結果


DllSurrogate登録前


DllSurrogate登録後

タスクマネージャーではdllhost.exeが存在し、ちゃんと32bitのサロゲートプロセス経由で動いてることが確認できる。

何が起きているか
WPF (64bit)
  ↓ CoCreateInstance
COM ランタイム
  ↓ AppID + DllSurrogate を参照
dllhost.exe(32bit)
  ↓ DLL をロード
SimpleCom32.dll (32bit)

別プロセスに逃がしてるだけ。COMのマーシャリングが間に入るので、呼び出し側からは普通のCOMオブジェクトに見える。

補足

COM DLL限定(CoCreateInstanceで生成できてCLSIDがあるやつ)。
普通のC++ DLL(LoadLibraryで使うやつ)やMFC DLLは同一プロセス前提なので、この方法は使えない。

ATLプロジェクトを作るとProxy/Stub DLL用のプロジェクトが自動で生成される。
COMで別プロセスと通信するときにデータの受け渡し(マーシャリング)を仲介するためのDLLらしいので今回は不要。

まとめ

  • 64bitから32bit DLLはそのままでは扱えない
  • COM DLLならCOMサロゲートで呼べる
  • レジストリにAppIDDllSurrogateを追加するだけ
  • IDispatchベースならProxy/Stub DLLも不要
  • 普通のDLLやMFC DLLには使えない

taskkill /IM dllhost.exe /F

参考サイト

Discussion