あーるPG - 社会人のデジタル生活

日曜プログラマになろうかなーと思った30代理系社会人の、キャリアアップや趣味(特にデジタル情報)の記録。らーめんとビールが好き。

Unityの自作プラグインを作る(後編)


UnityのC#スクリプトからCのプラグインを呼び出すときの、型の合わせかたです。

標準型の値渡し

言語がサポートしている標準の型にも違いがあります。
参考:http://www.crystal-creation.com/software/technical-information/programming/language-comparison/grammar/type.htm

単純な置き換えで大丈夫です。

//char foo_c(unsigned char a);
[DllImport ("MyPlugin")]
static extern sbyte foo_cs(byte a);	//charはcsで2byteなのに注意.

// short foo_c(unsigned short a);
[DllImport ("MyPlugin")]
static extern short foo_cs(ushort a);

//void foo_c(unsigned int a);
[DllImport ("MyPlugin")]
static extern void foo_cs(uint a);

ポインタ渡し

C#にはポインタ引数は無いので、参照渡しの指定をします。

//void foo_c(int *a);
[DllImport ("MyPlugin")]
static extern void foo_cs(ref int a);

バッファを渡す際もvoid *pを渡すわけにはいかないので、byteにして渡します。

//void foo_c(void *p);
[DllImport ("MyPlugin")]
static extern void foo_cs(ref byte a);

配列渡し

C#では

int a[256]

としたところで、配列要素それぞれが隣接するメモリ上にあるとは限りません。というか無いと考えて良いです。

ただし、そのあたりの整合性を取るやりかたが提供されています。unsafe, fixedです。
unsafeを指定した関数内ではfixedの要素にポインタ経由でアクセスできます。
また、構造体を固定長の配列として定義できます。

//構造体定義.
public unsafe struct FixedBuffer256{
	public fixed byte a[256];

	public byte set(int n, byte m){
		fixed (FixedBuffer256* p = &this){	//ポインタ経由でアクセスする.
			return p->a[n] = m;
		}
	}
}

public static FixedBuffer256 tempbuffer;

[DllImport ("MyPlugin")]
//int foo_c(void *p);
static extern int foo_cs(ref FixedBuffer256 buf);


void main(void){
	foo_cs(ref tempbuffer);

}

参考:
http://programaru.blog.fc2.com/blog-entry-8.html
http://ufcpp.net/study/csharp/sp_unsafe.html
http://msdn.microsoft.com/ja-jp/library/933dxtdw(v=vs.90).aspx

構造体渡し

構造体を引数として取るAPIもあるとは思いますが、なんだかできないみたいです。成功しませんでした。
構造体のポインタ渡しに置き換えます。

また、構造体のアラインメントを指定して互換性を取る必要もあります。
C#にはアラインメントを指定するディレクティブが提供されています。

プラグイン

public struct{
	char a;
    char padding1[15];
	char b;
    char padding2[15];
}MyStruct;

void foo_c(MyStruct a);

//上記では失敗するので、ラッパーを作成.
void foo_c_wrap(MyStruct *a){
	foo_c(*a);
}

スクリプト

//構造体、共有体内のアラインメント指定。
[StructLayout(LayoutKind.Sequential, Pack = 16, Size=32)]
public struct MyStruct{
	[FieldOffset(0)] byte a;
	[FieldOffset(1)] byte b;
};

[DllImport ("MyPlugin")]
//void foo_c(MyStruct a);
static extern void foo_c_wrap(ref MyStruct a);

参考:http://www.ipentec.com/document/document.aspx?page=csharp-struct-layout-alignment


察するに、C#側でrefを使わずに引数を取ると値渡しになり、コールスタックにコピーされてプラグインが呼ばれます。
このとき、構造体の要素ごとに分解されて積まれてしまうのでは、と。
C側ではコールスタック上の第1要素からパックされたものとして読もうとするので、上手く読めないものと思います。

refを使うとポインタのみが渡されるため、ポインタサイズが合っていればそれを参照するだけでOK。バラバラにはなりません。



プラグイン側の引数を無理に使用しようと思わず、臨機応変に擦り合わせるのが合理的です。
無理に使用しようとすると血を吐きます・・・