2013年7月21日日曜日

CからC#を呼ぶ面倒な方法の1つ

今回は、CからC#で作ったDLLを呼ぶ方法について書きます。タイトルにも書きましたが、かなり面倒なやり方なので、みなさんは頑張って回避してください。

さて、手順は次のようになります。

  1. C#でDLLを作成する
  2. 上で作成したDLLをレジストリへ登録する
  3. Cでプログラムを作成する
C#でDLLを作成する
Cから呼び出されるライブラリをC#で作成します。

DLLをレジストリへ登録する
警告が出ますが気にしない。だってサンプルですもの。

Cでプログラムを作成する
さあ、ここからが本番です。先ほど登録したDLLからクラス名とメソッド名を指定してオブジェクトに働いてもらいましょう。大まかなイメージはJavaやC#のリフレクションに近いです。

  1. 初期化
  2. クラス名からCLSIDを取得
  3. インスタンス作成
  4. IDispatchへキャスト
  5. メソッド名からDISPIDを取得
  6. パラメータの設定
  7. メソッドの呼び出し
  8. 終了処理
ここで注意が必要なのが、パラメータの設定です。適切なVariantTypeと適切なプロパティを選ばなくてはいけないところ、さらに、パラメータの順番が逆転するところに注意してください。

// cl callCOM.c ole32.lib oleaut32.lib
#include <Objbase.h>
int main() {
HRESULT hr;
CLSID clsid;
DISPID dispid;
VARIANTARG rgvArg[2];
int result = 0;
DISPPARAMS dispParams = {NULL, NULL, 0, 0};
VARIANT varResult;
EXCEPINFO excepInfo;
UINT uArgErr;
OLECHAR *lpszProgID = L"NamespaceSample.ClassSample";
OLECHAR *rgszNames = {L"MethodSample"};
IUnknown *pUnknown = NULL;
IDispatch *pDispatch = NULL;
// 1. 初期化
hr = CoInitialize(NULL);
if (FAILED(hr)) {
return -10;
}
// 2. クラス名からCLSIDを取得
hr = CLSIDFromProgID(lpszProgID, &clsid);
if (FAILED(hr)) {
CoUninitialize();
return -20;
}
// 3. インスタンス作成
hr = CoCreateInstance(&clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void **)&pUnknown);
if (FAILED(hr)) {
CoUninitialize();
return -30;
}
// 4. IDispatchへキャスト
hr = pUnknown->lpVtbl->QueryInterface(pUnknown, &IID_IDispatch, (void **)&pDispatch);
if (FAILED(hr)) {
pUnknown->lpVtbl->Release(pUnknown);
CoUninitialize();
return -40;
}
// 5. メソッド名からDISPIDを取得
hr = pDispatch->lpVtbl->GetIDsOfNames(pDispatch, &IID_NULL, &rgszNames, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(hr)) {
pUnknown->lpVtbl->Release(pUnknown);
CoUninitialize();
return -50;
}
// 6. パラメータの設定
VariantInit(&rgvArg[0]);
rgvArg[0].vt = VT_I4;
rgvArg[0].lVal = 99;
VariantInit(&rgvArg[1]);
rgvArg[1].vt = VT_BSTR;
rgvArg[1].bstrVal = SysAllocString(L"HOGE");
dispParams.rgvarg = rgvArg;
dispParams.cArgs = 2;
VariantInit(&varResult);
// 7. メソッドの呼び出し
hr = pDispatch->lpVtbl->Invoke(pDispatch, dispid, &IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispParams, &varResult, &excepInfo, &uArgErr);
if (FAILED(hr)) {
pUnknown->lpVtbl->Release(pUnknown);
CoUninitialize();
return -60;
}
// 8. 終了処理
printf("%S\n", varResult.bstrVal);
pUnknown->lpVtbl->Release(pUnknown);
CoUninitialize();
return 0;
}
view raw callCOM.c hosted with ❤ by GitHub
// csc /target:library ClassSample.cs
// regasm /codebase ClassSample.dll
// regasm /u ClassSample.dll
namespace NamespaceSample {
using System.Runtime.InteropServices;
// ComVisibleAttributeをCOM経由で呼び出したいクラスへ設定する
[ComVisible(true)]
public class ClassSample {
public string MethodSample(string arg1, int arg2) {
return string.Format("arg1={0} arg2={1}", arg1, arg2);
}
}
}
view raw ClassSample.cs hosted with ❤ by GitHub
お疲れ様でした。