C#からC++の関数をコールしてデータをやり取りする方法

マーシャリングだとかアンマネージドコードだとかのキーワードでよくヒットする。難しいことはともかく、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.AnsiCharSet = 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.AllocCoTaskMemMarshal.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] ) );
  }
  
}

構造体を渡す方法なども、ネットではたくさんヒットするが割愛。必要に迫られたらメモを残すことにする。