C#では、DLLタイプのCOMサーバを実装できます。
COMサーバは次の手順で実装できます。
IUnknownまたはIDispatchから継承し、必要なメソッドを追加したインターフェース(interface)を定義する。
定義したインターフェース(interface)を継承したクラス(class)を実装する。
COMサーバは、.NET付属のregasmを利用して、DLLサーバとしてシステムに登録できます。
COMの基本インターフェースである
IUnknownとIDispatchはシステムに組み込まれており、
C#ではinterfaceやclassの属性として指定します。
そのため、これらのインターフェースは実装する必要がありません。
実装することもできません。
interfaceの宣言
C#では、interfaceを使うと、純粋仮想クラスを定義できます。
COMのインターフェースを定義したい場合は、
次のようなCOMに必要な属性を指定してinterfaceを定義します。
属性 | 内容 |
ComVisible | 外部からインターフェースを参照可能とする場合はtrue。 |
InterfaceType | インターフェースの種類(IUnknown、IDispatch、Dual)を指定する。 |
Guid | interfaceのIIDを指定する。 |
例えば、Write()というメソッドを持ち、デュアルインターフェースを備えた
ICSichigo1というインターフェースは次のように定義できます。
[ComVisible(true)]
[Guid("2FBCAFB9-4AEB-4295-87C4-4725BE75BBE3")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICSichigo1
{
void Write(string logstr);
}
これだけで、C++言語でidlファイルを使った定義と同じように、
タイプライブラリ出力可能なしたインターフェースになります。
classの実装
インターフェースを定義した後は、定義したインターフェースを備えたクラスを実装します。
クラスの場合もインターフェースと同様に、COM特有の属性を指定します。
属性 | 内容 |
ComVisible | タイプライブラリ(.tlb)等でクラスを公開する場合はtrue。 |
InterfaceType | 通常はNoneを指定する。それ以外の値を用いると、継承に加えて実装するインターフェースの種類を指定する形になる。 |
Guid | クラスのCLSIDを指定する。 |
ProgId | クラスのProgIdを指定する。ProgIdを指定しておくと、このクラスをCLSIDだけでなくProgIdでも参照できるようになる。 |
ComDefaultInterface | インターフェース指定をしなかった場合に、ここで指定したインターフェースをデフォルトとする。 |
属性指定する点以外は通常のクラスとほとんど同じです。
実装もそれほど難しくありません。
[ComVisible(true)]
[Guid("27BCBDA1-F045-4322-BCB4-10D746E081F4")]
[ProgId("csichigo1.LogWriter")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ICSichigo1))]
public class CSichigo1 : ICSichigo1
{
public void Write(string logstr) {
File.AppendAllText(@"c:/tmp/csichigo1.txt", logstr);
}
}
アセンブリ属性の追加
COMサーバとして公開する.NETのアセンブリでは、ComVisibleにtrueを指定し、
自分のアセンブリのために生成したGUIDを指定します。
アセンブリ属性はSystem.Reflectionに収められており、COMに関連する項目は次の通りです。
属性 | 内容 |
assembly: ComVisible | タイプライブラリ(.tlb)を生成する場合はtrueを指定します。 |
assembly: Guid | 自分で生成したタイプライブラリのGUIDを指定します。他のGUIDをコピーせず、guidgen等で生成したものを指定します。 |
Visual Studioの統合環境では、
これらの情報はAssemblyInfo.csというファイルに収められ、
他のソースコードとリンクされるようになっています。
統合環境を使っている場合、これらの情報はプロジェクトのプロパティで編集できます。
コマンドラインでビルドしている場合は、
他の属性と同じように、ソースファイルに直接追加して問題ありません。
例えば、次のようにアセンブリ属性を指定できます。
[assembly: AssemblyTitle("csichigo1")]
[assembly: AssemblyProduct("csichigo1")]
[assembly: AssemblyDescription("csichigo1")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Ichigopack")]
[assembly: AssemblyCopyright("Copyright (C) 2018 The Ichigopack Project")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(true)]
[assembly: Guid("0F6A07A0-A000-403E-AF5C-8AAF45CC0C07")]
ビルドとシステムへの登録
コマンドラインからビルドする場合は、次のようになります。
regasmはCOMサーバをシステムに登録するコマンドです。
regasmの実行には管理者権限が必要になります。
prompt> csc /target:library csichigo1.cs
prompt> regasm /tlb csichigo1.dll
regasmに/tlbを指定するとタイプライブラリを出力します。
32bit環境では32bit、64bit環境では64bitのCOMサーバになります。
DLLサーバは32bitと64bitが混在しているとCoCreateInstance()で失敗しますので、
実行環境に留意してご登録ください。
クライアント
C#で実装されたCOMサーバはデュアルインターフェースを備えますので、
IDispatchを介さずC++から呼び出せます。
Visual C++では、tlbを#import指定するとヘッダの代わりに使えます。
#import "csichigo1.tlb" no_namespace no_implementation \
no_smart_pointers raw_interfaces_only named_guids
この#import行により、csichigo1.tlbに対応するヘッダが
csichigo1.tlhというファイル名で生成されます。
なお、Write()引数のstringはCOMのBSTR型になります。
生成されるファイルcsichigo1.tlhは以下のようなヘッダファイルです。
struct ICSichigo1 : IDispatch
{
virtual HRESULT __stdcall
Write(BSTR logstr) = 0;
};
struct CSichigo1;
extern "C" const GUID __declspec(selectany) LIBID_CSichigo1 =
{0x0f6a07a0,0xa000,0x403e,{0xaf,0x5c,0x8a,0xaf,0x45,0xcc,0x0c,0x07}};
extern "C" const GUID __declspec(selectany) IID_ICSichigo1 =
{0x2fbcafb9,0x4aeb,0x4295,{0x87,0xc4,0x47,0x25,0xbe,0x75,0xbb,0xe3}};
extern "C" const GUID __declspec(selectany) CLSID_CSichigo1 =
{0x27bcbda1,0xf045,0x4322,{0xbc,0xb4,0x10,0xd7,0x46,0xe0,0x81,0xf4}};
#importにより、C++のコードで、
このヘッダファイルを#includeしてビルドすることに相当します。
したがって、C++からは次のコードで直接呼び出せることがわかります。
説明のため、エラーチェックは省略しています。
ICSichigo1* pObj = NULL;
HRESULT hr = CoCreateInstance(
CLSID_CSichigo1, NULL,
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER |
CLSCTX_LOCAL_SERVER,
IID_ICSichigo1, (LPVOID*)&pObj);
BSTR bstr = SysAllocString(L"ichigopack c# sample 1 from c++\n");
pObj->Write(bstr);
SysFreeString(bstr);
CSichigo1はデュアルインターフェースを備えているため、
C++以外の言語からでも呼び出せます。
例えば、Windows Scriptからは次のように呼び出せます。
<job id="ichigocom1">
<script language="JScript">
var obj = new ActiveXObject("csichigo1.LogWriter")
obj.Write("c# ichigopack COM sample 1\n");
</script>
</job>
もちろん、C++であってもIDispatchを介したアクセスも可能です。
IDispatchの利用はC++からは手間がかかりますが、例えば次のように呼び出せます。
説明のため、エラーチェックは省略しています。
IDispatch* pObj = NULL;
HRESULT hr = CoCreateInstance(
CLSID_CSichigo1, NULL,
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER |
CLSCTX_LOCAL_SERVER,
IID_IDispatch, (LPVOID*)&pObj);
LCID lcid = GetSystemDefaultLCID();
LPOLESTR szFuncNames[1] = {L"Write"};
DISPID dispFunc[1] = {0};
VARIANT v, vret;
VariantInit(&v);
VariantInit(&vret);
hr = pObj->GetIDsOfNames(IID_NULL, szFuncNames, 1, lcid, dispFunc);
v.bstrVal = SysAllocString(
L"ichigopack c# sample 1 from c++ (IDispatch)\n");
DISPPARAMS dp;
EXCEPINFO ei;
UINT uArgErr = 0;
dp.rgvarg = &v;
dp.rgdispidNamedArgs = NULL;
dp.cArgs = 1;
dp.cNamedArgs = 0;
hr = pObj->Invoke(dispFunc[0], IID_NULL, lcid, DISPATCH_METHOD,
&dp, &vret, &ei, &uArgErr);
VariantClear(&v);
VariantClear(&vret);
ソースコード
最後に、csichigo1.csの完全なソースコードを載せておきます。
//
// Copyright (c) 2018 The Ichigopack Project (http://ichigopack.net/).
//
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.IO;
[assembly: AssemblyTitle("csichigo1")]
[assembly: AssemblyProduct("csichigo1")]
[assembly: AssemblyDescription("csichigo1")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Ichigopack")]
[assembly: AssemblyCopyright("Copyright (C) 2018 The Ichigopack Project")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(true)]
[assembly: Guid("0F6A07A0-A000-403E-AF5C-8AAF45CC0C07")]
namespace csichigo1
{
[ComVisible(true)]
[Guid("2FBCAFB9-4AEB-4295-87C4-4725BE75BBE3")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICSichigo1
{
void Write(string logstr);
}
[ComVisible(true)]
[Guid("27BCBDA1-F045-4322-BCB4-10D746E081F4")]
[ProgId("csichigo1.LogWriter")]
[ClassInterface(ClassInterfaceType.None)] // ICSichigo1 only
[ComDefaultInterface(typeof(ICSichigo1))]
public class CSichigo1 : ICSichigo1
{
public void Write(string logstr) {
File.AppendAllText(@"c:/tmp/csichigo1.txt", logstr);
}
}
}
解説一覧
COM/ActiveXの解説ページ 目次
機能とセットアップ
COMクライアント
COMサーバ