マーシャリングだとかアンマネージドコードだとかのキーワードでよくヒットする。難しいことはともかく、C#コードからC++コードを呼び出す必要があったので、その備忘録を残す。
文字列をC++プログラムに渡す
C#側からC++の関数をコールする。
string
を渡してもらう場合は、C++側はconst char*
で受ける。
// *.h
#ifdef FUNC_EXPORT
#define FUNC_API __declspec(dllexport)
#else
#define FUNC_API __declspec(dllimport)
#endif
extern "C" FUNC_API int function1(const char * buf);
// *.cpp
extern "C" FUNC_API int function1(const char * buf) {
string str = buf;
cout<<"str = "<< str <<endl;
return str.size();
}
string
をそのまま渡している。ネットの情報を見ると、
CharSet = CharSet.Ansi
やCharSet = CharSet.Unicode
を指定して、その際は
前者はchar*
、後者はwchar_t*
といったことが記載されいたが、よく分からず。
数値型やバイトデータは割りとすんなり行くが、文字列はハマりそうな予感。
文字列のやり取りはあまりしないほうがいいかも。
特に何もしなくても、自身の環境では、日本語文字列を渡しても文字化けはしなかった。
// *.cs
// .NET 4.5 以降は
// [DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)]
[DllImport("MyDll")]
public static extern int function1(string buf);
using System.Runtime.InteropServices;
static void Main(string[] args)
{
// call C++ function
int retval = function1( "あいうえお" );
Console.WriteLine("retval ={0}", retval); //retval = 10
}
配列を入力するサンプル
C#側で配列をアロケートしてサイズと一緒に入力する。
// *.h
extern "C" FUNC_API float function2(float * data, int length);
// *.cpp
extern "C" FUNC_API float function2(float * data, int length) {
float retval = 0;
for (int i = 0; i < length; i++) {
retval += data[i];
}
return retval;
}
ポイントは、IntPtr
というC++のvoidポインタのような型を使って、アドレスのやり取りで配列を渡す。事前に渡したい配列データを、Marshal.AllocCoTaskMem
やMarshal.Copy
を使って
配列データを、IntPtrの領域にコピーしてそれを渡す。入力後にMarshal.FreeCoTaskMem
で削除する必要がある。
// *.cs
[DllImport("MyDll")]
public static extern float function2( IntPtr array, int length );
static void Main(string[] args)
{
int len = 5;
float[] arr = new float[len];
for(int i = 0; i < len; i++) {
arr[i] = i * 0.23f;
}
// allocate unmanaged array pointer
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(float)) * len);
// arr copy to ptr
Marshal.Copy(arr, 0, ptr, len);
// call C++ function
float retval = function2(ptr, len);
Console.WriteLine("retval ={0}", retval);
// ポインタ削除
Marshal.FreeCoTaskMem(ptr);
}
配列を出力するサンプル
C++側で配列をアロケートして、配列ポインタとサイズと一緒に出力する。
ポイントは、ポインタに変更すること(配列の場合は、ダブルポインタ)
// *.h
extern "C" FUNC_API int function3(float ** data, int * length);
// *.cpp
extern "C" FUNC_API int function3(float ** data, int * length) {
int retval = 0;
int arrsize = 3;
float *ar = new float[arrsize];
ar[0] = 0.1f;
ar[1] = 0.12f;
ar[2] = 1.999f;
*length = arrsize;
*data = ar;
return retval;
}
下記例では、out
を指定しているが、I/Oならref
でも良さそう。
// *.cs
[DllImport("MyDll")]
public static extern float function3( out IntPtr pointer, out int length );
static void Main(string[] args)
{
int ret = 0;
IntPtr pointer;
int arrsize;
ret = function3( out pointer, out arrsize );
float[] arr = new float[arrsize];
Marshal.Copy( pointer, arr, 0, arrsize );
Marshal.FreeCoTaskMem( pointer );
for( int i = 0; i < arrsize; i ++){
Console.WriteLine( string.Format( "arr[{0}] = {1}", i, arr[i] ) );
}
}
構造体を渡す方法なども、ネットではたくさんヒットするが割愛。必要に迫られたらメモを残すことにする。