Help us understand the problem. What is going on with this article?

COMサーバー、忘れてたのでメモ

More than 1 year has passed since last update.

前回書いたような、過去の出荷アプリ(当時の担当者が行方不明)のメンテを任されるような業務が今後も発生しそうです。
最近はずっとC++/Win32だけ知っていれば十分、な世界にいたので、だいぶ記憶も鈍ってしまったようで、今回COMサーバーを復習してみたのでメモっておきます。
メモなんで、内容薄いのと間違っているかもしれません><

復習に際して、以下を参考にしました。

C++でATLを使わずにレジストリフリーのCOMサーバーを作成してWSHから利用する方法 - seraphyの日記

いつものEternalWindowsさん

0. 下記説明で共通して使用するクライアントの例

#include <windows.h>
#include <tchar.h>

#include <comdef.h>
#include "..\COMsampleServer\COMSampleServer.h"

int main()
{
    TCHAR szBuf[256];
    CoInitialize(nullptr);
    {
    HRESULT hr;

    ITestServerA *p;
    hr=CoCreateInstance(CLSID_TestServerA, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&p));
    if(FAILED(hr)){
      wsprintf(szBuf, TEXT("ITestServerAの取得に失敗しました。%08x"),  hr);
      MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
      goto exitproc;
    }
    p->Test();
    p->Release();
  }
 exitproc:
    CoUninitialize();
    return 0;
}

1. 普通なCOMサーバ(inproc_server)

上記のseraphyさんのとこのソースを理解して、ほぼ丸コピーすればOKですね!

<手順>

(1) VisualStudioでdllのプロジェクトを作成します
(2) idlファイルを作成してプロジェクトに追加します
 idlファイルに、GUIDのツールで Interface, TypeLib, CLSID のguidを生成して埋め込み、idlファイル単体でコンパイルしておきます。
 tlbファイルが作成されます。

import "oaidl.idl";
import "ocidl.idl";

// Interface
[object, dual, uuid(8037957A-E0DA-4F34-A580-605FEC8E0D95)]
interface ITestServerA : IDispatch
{
    [id(1)] HRESULT Test();
}

// TypeLib
[uuid(5686C776-212E-4B34-BD46-A5C05397AEC9), version(1.0)]
library LTestServerA
{
    importlib("stdole2.tlb");

  // CLSID
    [uuid(246A7D70-0F7C-4781-BA9D-578FA6404090)]
    coclass TestServerA
    {
        [default] interface ITestServerA;
    }
}

(3) defファイルを作成してプロジェクトに追加します

EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE

(4) rcファイルを作成してプロジェクトに追加します

 (2)で作成したtlbファイルを追記します。

1 TYPELIB "プロジェクト名.tlb"

(5) 実装コードファイル(*.c, *.h) の追加します
 seraphyさんのとこのコードを参考に作成します。
 DllRegisterServer(), DllUnregisterServer()の実装については、EternalWindowsさんとこのコードを参考に作成します。

 ポイントは下記
  DllRegisterServer()にて、(4)のrcファイルによってdllファイルに埋め込んだtlbファイルの読み込みに成功したら、その情報(dllファイルのパス)をレジストリに登録します。
  (LoadTypeLib(), RegisterTypeLib()のパスの引数は、Unicode文字列なことに注意)

  WCHAR szModulePathW[MAX_PATH];
  GetModuleFileNameW(g_hinstDll, szModulePathW, MAX_PATH);
  ITypeLib *pTypeLib;
  if(SUCCEEDED(LoadTypeLib(szModulePathW, &pTypeLib))){
    RegisterTypeLib(pTypeLib, szModulePathW, NULL);
    ...

(6) ビルドして完成、管理者になってレジストリに登録します

> regsvr32 プロジェクト名.dll

 以降、COMサーバーを起動する際には、(5)にてレジストリに記載したdllファイルを実行するようになります。

<評価>

 1.は、レジストリ登録が面倒ですね。
 出来上がったdllファイルをexeファイルと同じ位置にコピーしておくだけで使えるようにしたいですよね
 ということで、2.のレジストリフリーなCOMサーバです。(seraphyさんとこの情報そのまま)

2. レジストリフリーなCOMサーバ(inproc_server)

以下、変更点を追記していきます。

<手順>

(1) VisualStudioでdllのプロジェクトを作成します(1.と同じ)
(2) idlファイルを作成してプロジェクトに追加します(1.と同じ)
(3) defファイルを作成してプロジェクトに追加します(1.と同じ)
(4) rcファイルを作成してプロジェクトに追加します(1.と同じ)
(5) 実装コードファイル(*.c, *.h) の追加(1.と同じですが、DllRegisterServer(), DllUnregisterServer()は使いません)
(6) manifestファイルを作成してプロジェクトに追加します
 プロジェクトのプロパティ->リンカー->マニフェストファイルで、"ユーザーアカウント制御(UAC)を有効にする"をいいえにします。

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity name="プロジェクト名" type="win32" version="1.0.0.0" />
  <file name="プロジェクト名.dll">
    <comClass clsid="{84C36579-EF9B-4DF7-AD52-ED157380520B}" threadingModel="Apartment" progid="COM TestServerA" />
    <typelib  tlbid="{A3F4673F-7328-4034-8F53-E74E7A2C07EB}" version="1.0" helpdir="" />
  </file>
</assembly>

(7) ビルドして完成します
(8) クライアント側にもmanifestファイルを追加してビルドします

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity name="COMsampleClient" type="win32" version="1.0.0.0" /> 
  <!-- -->
  <dependency>
    <dependentAssembly>
      <assemblyIdentity name="プロジェクト名" type="win32" version="1.0.0.0" />
    </dependentAssembly>
  </dependency>
</assembly>

<評価>

 結局、レジストリフリーかどうかという違いは、どのdllファイルを使用するかの指定方法の違いだけでしかないようですね。

3. サロゲートなCOMサーバ(local_server)

通常、COMサーバー(dll)は呼び出し元の実行ファイルと同じプロセス空間で実行するものですが、別プロセスで実行したほうがいい場合があります。
例えば、COMは32bitプロセス、本体は64bitプロセス、のハイブリッドとか、COMがエラー起こしたら本体が介錯してあげるとか、そういう使い方。
サロゲートサーバとは、dllhost.exeがCOMサーバー(dll)を実行してクライアントと通信する形態です。
1.のCOMサーバをそのまま使いまわします。

<手順>

(1) レジストリに以下の項目を追加します
 レジストリのCLSIDの下に、AppIDを追加、そのAppIDに、DllSurrogate=""を追加します。
 登録にregedit.exeを使用する場合、
 64bitOSで、dllhost.exeを32bit(Wow64)で動かしたいときには、
  c:\Windows\syswow64\regedit.exe
 の方を使用します。

Windows Registry Editor Version 5.00 

[HKEY_CLASSES_ROOT\AppID\{308B9966-063A-48B8-9659-4EBA6626DE5D}] 
"DllSurrogate"="" 

[HKEY_CLASSES_ROOT\CLSID\{246A7D70-0F7C-4781-BA9D-578FA6404090}]
"AppID"="{308B9966-063A-48B8-9659-4EBA6626DE5D}" 

(2) クライアント側のCoCreateInstance()を書き換えます

    ITestServerA *p;
    hr=CoCreateInstance(CLSID_TestServerA, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&p));
    // hr=CoCreateInstance(CLSID_TestServerA, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&p));

<評価>

 この変更で、dllhost.exeが起動してCOMサーバdllを実行し、クライアントと通信するようになります。
 予想外にあっさりしてますねw

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away