C#にはCOMクライアントをサポートするための機能が用意されています。
CoCreateInstance()を内部的に呼び出す機能
キャストをQueryInterface()呼び出しに変換する機能
IDispatchを介したメソッド呼び出しのダイナミックバインディング機能
クラスの作成
C#では、CoCreateInstance()によるコクラス(IUnknownを継承したCOMクラス)をnewで作成できます。
次のようにComImportとCLSIDを与えた空のクラスを書くと、コクラスが定義されます。
[ComImport]
[Guid("425C76F4-E126-456C-837D-1EE96DB89126")]
class CIchigo2
{
}
ComImport属性のクラスがnewされると、C#は内部的にCoCreateInstance()を呼び出します。
そのままではIUnknown相当であり、クラスメソッドの呼び出しができません。
作成したコクラスは、使いたいインターフェースにキャストして使います。
Win32 APIを利用してCOMインターフェースへの参照を直接得ることもできます。
この場合、HRESULTを戻り値とするC++の関数にPreserveSig=falseを与えると、C#がHRESULTを例外に変換してくれます。
DllImport("ole32.dll", PreserveSig = false)
public static extern void CreateBindCtx(
uint reserved, out IBindCtx ppbc);
キャストの方法
C#は、キャストをQueryInterface()呼び出しに変換する機能を備えています。
インターフェースは次のいずれかの方法で得られます。
ダイナミックバインディング(IDispatch)を使う
System.Runtime.InteropServices.ComTypesの定義を利用する
コマンドラインツールtlbimpを使ってタイプライブラリを.NETのDLLに変換し、それをusingで読み込む
インターフェースをC#コードとして手動で用意する
利用が簡単な順にダイナミックバインディング、ComTypes、tlbimp、手動となります。
コクラスを使いたい場合は、
いずれかの方法を使ってインターフェースの定義を追加し、
コクラスをnewして、利用するインターフェースにキャストして使うことになります。
ダイナミックバインディングを使う方法
この場合は、変数型としてdynamicを指定するだけです。
IDispatchであるがわかっているインターフェースであれば、
次の例のように、dynamic型の変数に保存すればメソッドを呼び出せます。
メソッド引数の型については、IDispatchが自動的に解決してくれます。
using System.Runtime.InteropServices;
[ComImport]
[Guid("316E547D-CBB4-495F-A839-DC4414842C8F")]
class CIchigo3
{
}
class Ichigo3Call {
public static void Main(string[] args) {
dynamic inst = new CIchigo3();
inst.Write("ichigopack COM sample 3 (c#)\n");
}
}
この方法は簡単ですが、スクリプト言語からアクセスする場合と同様、
IDispatchを介するため、少し遅くなります。
System.Runtime.InteropServices.ComTypesを使う方法
System.Runtime.InteropServices.ComTypesはシステムのCOMインターフェースの一部を与えます。
using System.Runtime.InteropServices.ComTypes;
class Program {
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CreateBindCtx(
uint reserved, out IBindCtx ppbc);
static void Main(string[] args) {
IBindCtx ctx;
CreateBindCtx(0, out ctx);
...
}
}
tlbimpを使う方法
コマンドラインツールtlbimpを使うと、
例として、次のichigo4のidlファイルを考えます。
//
// Copyright (c) 2016 The Ichigopack Project (http://ichigopack.net/).
//
import "unknwn.idl";
import "oaidl.idl";
// IIchigo4 interface
[
object,
dual,
uuid(964FC28B-E237-4C33-A5E6-5C384021145A),
pointer_default(unique)
]
interface IIchigo4 : IDispatch
{
HRESULT Write( BSTR bstrWrite );
};
// Type library
[
version(1.0),
uuid(74e6d628-b3b0-4ea2-89b3-c85be7bb9e5a)
]
library Ichigo4TypeLib
{
importlib("stdole32.tlb"); // import IDispatch
interface IIchigo4;
[ uuid(b899e97f-e7f5-47c3-875d-807be4b5bcf2) ]
coclass Ichigo4
{
interface IIchigo4;
};
};
このidlから生成したタイプライブラリichigo4com.tlbをC#から呼び出したい場合は、
まず次のコマンドを実行します。
c:\> tlbimp ichigo4com.tlb
このコマンドを実行すると、ライブラリ名称Ichigo4TypeLibに対応した、
Ichigo4TypeLib.dllという名前のdllが生成されます。
このdllには、Ichigo4TypeLibという名前空間内に、
IIchigo4インターフェースとIchigo4コクラスが収められています。
したがって、このライブラリとインターフェースは次のコードで呼び出せます。
using System.Runtime.InteropServices;
// tlbimp ichigocom4_coclass_if.tlb
using Ichigo4TypeLib;
class Ichigo4Call {
public static void Main(string[] args) {
Ichigo4 inst = new Ichigo4();
inst.Write("ichigopack COM sample 4 (c#)\n");
}
}
コマンドラインからビルドする場合は、次のようにします。
c:\>csc ichigo4call.cs /r:Ichigo4TypeLib.dll
tlbファイルの代わりにtlbファイルを含むdllファイルがある場合でも、
同じ方法でC#から呼び出せます。
ここではC++で書かれたCOMサーバを例としましたが、
tlbファイルを提供するCOMサーバは、
どんな言語で書かれていても問題ありません。
手動でinterfaceを用意する方法
手動で用意する場合は、
インターフェースがIUnknownとIDispatchのどちらから継承されたクラスであるかを、
属性として与えます。
例えばシステムのクラスであるISequentialStreamを、
自分で与えるには次の例のようにします。
using System;
using System.Runtime.InteropServices;
[ComImport]
[Guid("425C76F4-E126-456C-837D-1EE96DB89126")]
class CIchigo2
{
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("0c733a30-2a1c-11ce-ade5-00aa0044773d")]
interface ISequentialStream
{
void Read([Out,
MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
byte[] pv, uint cb, out uint pcbRead);
void Write([In,
MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
byte[] pv, uint cb, out uint pcbWritten);
}
class Ichigo1Call {
public static void Main(string[] args) {
ISequentialStream inst = (ISequentialStream)new CIchigo2();
uint cbWritten;
string unistr = "ichigopack COM sample 2 (c#)\n";
byte[] data = System.Text.Encoding.ASCII.GetBytes(unistr);
inst.Write(data, (uint)data.Length, out cbWritten);
}
}
C#の引数をNative(COM)の引数に変換する方法として、
何も指定しなければデフォルトの方法が使われますが、
デフォルトの方法で配列などを扱うと正しく動かないでしょう。
引数がデフォルトの振る舞いで都合が悪い場合は、
MarshalAsという属性を追加します。
MarshalAsはC#の引数をNativeの引数に変換する方法を指定します。
引数を配列として扱いたい場合は、属性としてUnmanagedType.LPArrayを与えます。
このときSizeParamIndex=N(Nは0から始まる引数番号)を与えると、配列のサイズを与えることができます。
UnmanagedTypeには多くのタイプが存在します。
以下にその一部を示します。
タイプ | 内容 |
I1 | 1バイトsigned整数 |
I2 | 2バイトsigned整数 |
I4 | 4バイトsigned整数 |
I8 | 8バイトsigned整数 |
U1 | 1バイトunsigned整数 |
U2 | 2バイトunsigned整数 |
U4 | 4バイトunsigned整数 |
U8 | 8バイトunsigned整数 |
R4 | 4バイトfloat |
R8 | 8バイトfloat |
BStr | COM文字列 |
IUnknown | COMのIUnknown型 |
IDispatch | COMのIDispatch型 |
LPArray | 配列。配列のサイズはSizeConst(固定長の要素数)、あるいはSizeParamIndex(変数から要素数を取得)により求められる。 |
LPStr | ANSI文字列 |
LPWStr | Unicode文字列 |
これらの機能はC#からアンマネージドコードを呼び出す時に使われるものです。
COM以外でも使えます。
解説一覧
COM/ActiveXの解説ページ 目次
機能とセットアップ
COMクライアント
COMサーバ