C#でCOM In-Process Server

# 概要  

**COM(Component Object Model)**を使うと、プログラムをExcelやVBScriptなどから呼び出すことができる。
呼び出される側を**COMサーバ**、呼び出す側を**COMクライアント**と呼ぶ。
COMサーバには**In-Process Server**と**Out-Of-Process Server**の2種類に分けられる。

* In-Process Server: 実体はDLL。単体では動作せず、クライアントから呼ばれた際に、クライアント下にインスタンスが生成される。
* Out-Of-Process Server: 実体はEXE。単体で動作し、クライアントからアクセスできるインタフェースを提供。クライアントから呼ばれた際に、独立した別プロセスが立ち上がる。

> [Component Object Model](https://msdn.microsoft.com/en-us/library/ms680573.aspx)

ここでは、In-Process ServerをC#で実装する方法を紹介する。

# 環境  

* Windows 10 Pro (Ver.1709)  
* Visual Studio 2017
* .NET Framework 4.6.1

# 手順  

### ソリューション・プロジェクト作成  

まずソリューションを作り、その下にC#プロジェクトを作る。
プロジェクト種別は**クラス ライブラリ(.NET Framework)**を選択する。

### インタフェース作成  

COMサーバのインタフェースを定義する。
外部からはこのインタフェース下のメソッドやプロパティを介してアクセスする事となる。

```cs
using System;
using System.Runtime.InteropServices;

namespace Sample
{
    [ComVisible(true)]
    [Guid("502262A7-19EB-4EC5-984C-8F010665A586")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface ITest
    {
        string ToUpper(string str);
    }
}
```


* **ComVisible**属性は、指定したクラスやインタフェースをCOMから見えるようにするもの。  
* **Guid**属性は、唯一のIDを振るためのもの。この後の処理でGUIDがレジストリに登録されるので、必要。
* Guidの生成は、Visual Studioの「ツール」->「**GUIDの作成**」から可能。
* **InterfaceType**属性は、アクセス方法をIDispatchに限るかIUnknownに限るか、両方可能かを設定する。
* 公開するため、アクセス指定子は**public**にする。


----

**備考: IDispatchとIUnknown**

* IDispatch: 遅延バインディングを示す。クライアントはサーバーのインタフェースの情報を持たず、ProgId名とメソッド名を指定して呼び出して動的に解決する。CreateObjectを使う場合はこのケース。
* IUnknown: 事前バインディングを示す。クライアントはサーバーのインタフェースの情報を持ち、それを使って呼び出す。
サーバーのDLLを参照設定に入れる場合はこのケース。

----

### クラス作成

COMサーバのクラスを作成する。  

```cs
using System;
using System.Runtime.InteropServices;

namespace Sample
{
    [ComVisible(true)]
    [Guid("A2666F42-F50D-4F42-9CC7-D7F55300FD95")]
    [ProgId("Sample.Test")]
    [ClassInterface(ClassInterfaceType.None)]
    public class Test : ITest
    {
        public string ToUpper(string str)
        {
            return str.ToUpper();
        }
    }
}
```

* インタフェース同様、**ComVisible**で公開する。
* **Guid**は、インタフェースとは別のIDを新たに生成すること。
* **ProgId**属性は、クライアントが呼び出す際に指定する名前の文字列を定義する。
* **ClassInterface**属性は、インタフェースが明示的に指定されていない場合のデフォルトのアクセス方法を決める属性。今回はITestインタフェースを実装しているので、**None**を指定する。
* 公開するため、アクセス指定子は**public**にする。

#### 参考

> [In-process C# COM server (CSDllCOMServer)](https://code.msdn.microsoft.com/windowsdesktop/CSDllCOMServer-1d5f97a4) 

### アセンブリに署名

Properties -> 「署名」 を開き、「アセンブリに署名する」にチェックを入れ、キーファイルを作成する
(※これをしておかないと、後のRegAsm処理に失敗する) 

### ビルド
構成マネージャで、**x86**または**x64**を選択した後、
プロジェクトをビルドし、dllファイルを生成する

### RegAsm.exeでレジストリに登録する

作成したCOMサーバを利用可能にするには、レジストリに登録する必要がある。
その為の登録処理を行うプログラム「RegAsm.exe」が用意されている。

パスは以下の通り

- 32bitの場合: C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe
- 64bitの場合: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe

(なお、v4.0.30319の部分は.NET Frameworkのバージョンにより異なる)  

管理者権限でPowerShell(またはコマンドプロンプト)を起動し、**/codebase**オプションを付けてRegAsm.exeを実行する
(Sample.dllは対象のdll名)

```
> C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase Sample.dll
Microsoft .NET Framework Assembly Registration Utility 4.7.2556.0
for Microsoft .NET Framework Version 4.7.2556.0
Copyright (C) Microsoft Corporation.  All rights reserved.

型は正常に登録されました。
```

これで、登録が完了した

### 呼び出し  

例えば、ExcelマクロやVBScriptでは、以下のように呼び出すことができる。

```vb
Sub TestCall()
  Dim ret As String
  Dim obj As Object
  Set obj = CreateObject("Sample.Test")
  
  ret = obj.ToUpper("abc")
  MsgBox (ret)
  
  Set obj = Nothing
End Sub
```

なお、COM In-Procサーバーは、呼び出し元のプラットフォーム(x86/x64)と呼び出し先が一致している必要がある?模様。
Excelからの呼び出しでは、x64のIn-Procサーバーを呼び出せず、
VBScriptからの呼び出しでは、x86のIn-Procサーバーを呼び出せなかった。

### 備考: RegAsmでレジストリに何が登録されるのか?  

RegAsmを実行した後、regeditでレジストリを確認したところ、以下の項目が登録されていた。
なお、RegAsmを使わずに直接レジストリに同様の設定を行うこともできる。

|キー|名前|データ|備考|
|---|---|---|---|
|HKEY_CLASSES_ROOT\\**作成したクラスのProgId**|(規定)|**作成したクラスのProgId**| |
|HKEY_CLASSES_ROOT\\**作成したクラスのProgId**\\CLSID|(規定)|**作成したクラスのGUID**| |
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**|(規定)|**作成したクラスのProgId**|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\Implemented Categories\\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}|(規定)|(無し)|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\InProcServer32|(規定)|mscoree.dll|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\InprocServer32|Assembly|**アセンブリ名、Culture, Version, PublicKeyToken**|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\InprocServer32|Class|**作成したクラスのProgId**|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\InprocServer32|CodeBase|**dllのパス**|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\InprocServer32|RuntimeVersion|v4.0.30319|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\InprocServer32|ThreadingModel|Both|x86のみ|
|HKEY_CLASSES_ROOT\\WOW6432Node\\CLSID\\**作成したクラスのGUID**\\ProgId|(規定)|**作成したクラスのProgId**|x86のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**|(規定)|**作成したクラスのProgId**|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\Implemented Categories\\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}|(規定)|(無し)|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\InProcServer32|(規定)|mscoree.dll|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\InprocServer32|Assembly|**アセンブリ名、Culture, Version, PublicKeyToken**|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\InprocServer32|Class|**作成したクラスのProgId**|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\InprocServer32|CodeBase|**dllのパス**|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\InprocServer32|RuntimeVersion|v4.0.30319|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\InprocServer32|ThreadingModel|Both|x64のみ|
|HKEY_CLASSES_ROOT\\CLSID\\**作成したクラスのGUID**\\ProgId|(規定)|**作成したクラスのProgId**|x64のみ|