コアのライブラリはC++で作ってDLL化し、上位層をC#で作りたいということがよくあります。
その場合のやり方を調べたのですが、まぁ、なんというかなかなか本質的な情報にたどり着かないですね。フリーランス向けのWeb塾みたいなページで広告が出てきてクリックしないと消せず、最後まで読んでも結局やりたいことが書いていないとか・・
さて、C++でこんな関数を作りたいと思います。
void get_hogehoge(int maxch, double* volts, double* phases)
maxch個の要素を持つvolts[]とphases[]に値をセットして返す関数だと思ってください。
プロトタイプの宣言は
CSZAPI void WINAPI get_hogehoge(int maxch, double* volts, double* phases);
として、頭にCSZAPIを、戻り値と関数名の間にWINAPIを付けておきます。
このCSZAPIは自分のライブラリ用に作った適当なマクロの名前で、 __declspec(dllexport) に解釈されます。
さて、C++の関数の中では、
for(int i=0;i<maxch;i++) volts[i] = ・・・;
などと書いて値をセットして戻ります。
C#のプログラムではDllImportとMarshalを使うために
using System.Runtime.InteropServices;
が必要です。
クラス内の宣言で、DLLの関数を呼び出すために
[DllImport("libほげほげ.dll")]
protected static extern void get_hogehoge(int maxch, IntPtr volts, IntPtr phases);
として、受け取る型はIntPtrにします。最初、out double[]でやとうとしたらクラッシュしました。
プログラムのメインの部分では、
double[] Volts = new double[32];
double[] Phases = new double[32];
として値が格納される配列を作っておき、
IntPtr pVolts = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(double)) * 32);
IntPtr pPhases = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(double)) * 32);
でIntPtr型の変数を作ります。
そして、
get_hogehoge(32, pVolts, pPhases);
Marshal.Copy(pVolts, Volts, 0, 32);
Marshal.Copy(pPhases, Phases, 0, 32);
とします。C#からC++を呼び出すときに、Marshalで作ったIntPtrのメモリにデータを格納して、それを普通に作った配列にコピーするというしくみです。C#のバッファって、たぶん、突然アドレスが変わったりすることが変わるのでしょう。newで普通に確保した配列にDLLの中から値セットしてはいけません。(unsafedとfixedを使えばできなくはない)
使い終わったら、
Marshal.FreeCoTaskMem(pVolts);
Marshal.FreeCoTaskMem(pPhases);
で解放します。
高速性を求めるならunsafeなコードでfixedを使うというやりかたもありますが、そこまで必要でなければMarshalを使うのが安全だと思います。
★補足説明
CSZAPIの定義は
#if defined (__cplusplus)
#ifdef CSZWIN_EXPORTS
#define CSZAPI extern "C" __declspec(dllexport)
#else
#define CSZAPI extern "C" __declspec(dllimport)
#endif
#else
#ifdef CSZWIN_EXPORTS
#define CSZAPI __declspec(dllexport)
#else
#define CSZAPI __declspec(dllimport)
#endif
#endif
#endif
です。
関数のプロトタイプを集めたヘッダファイルの先頭で宣言しておきます。このヘッダファイルをincludeする前にCSZWIN_EXPORTSをあらかじめdefineしておけばdllexportになり、CSZWIN_EXPORTをdefineしていなければdllimportになります。
これで、DLLのソースか、DLLを使うプログラムかで使い分けられるようになっています。
このマクロは、
- C++なら、extern "C" __declspec(dllexport)になり
- Cなら、__declspec(dllexport)になります
これで関数名の先頭に_(アンダーバー)が付いたりすることなく、引数に@数字とか付かず、CとC++で名前が一致するようになります。
最近のコメント