はじめに
C プラグイン側にこういう関数があって、これを Unity (C#) 側から呼びたいとします。
__declspec(dllexport) __m128d nextDouble2(rng_t *rng, __m128d min, __m128d max) { // do something... }
なお、 __m128d
は double
型を 2 つつなげた SIMD 用の型です。 float
4 つの __m128
と uint64_t
2 つの __m128i
もあります。
Burst のヘルプ を見ると、
For all
DllImport
and internal calls, you can only use the following types as parameter or return types:
...
Unity.Burst.Intrinsics.v128
とあるので、 Burst を入れて v128
型で受ければよさそうに思えます。
v128
は 128 bit の union っぽい構造体で、例えば v128.ULong0
で下位 64 bit の ulong
値を取得できたりします。
というわけで C# 側で DllImport
を書きましょう:
// not working :( [DllImport("DllName")] private static extern v128 nextDouble2(IntPtr rng, v128 min2, v128 max2);
ところがこれは動きません。
呼び出すとランダムな値が返ったり、 Unity がフリーズしたりします。
解決策
前述のヘルプの下のほうを見ると、
Passing structs by value isn't supported; you need to pass them through a pointer or reference.
というわけで、サポートされていません。
いや私もこの部分は読んでいたのですが、まさか v128
も対象だとは思わず…
だって v128
(__m128
) はビルトイン型みたいなものじゃないですか…
というわけで、呼べるようにするためにはポインタか ref
参照にしなければなりません。
ポインタにすると unsafe になって面倒なので、 ref
でやります。
まずは C プラグイン側にラッパーを生やします。
__declspec(dllexport) void nextDouble2ForUnity (rng_t* rng, __m128d* min2, __m128d* max2, __m128d* output) { *output = nextDouble2(rng, *min2, *max2); }
そして、 C# 側からこのようにして参照します。
// it works fine :) [DllImport("DllName")] private static extern void nextDouble2ForUnity (IntPtr rng, in v128 min2, in v128 max2, out v128 output);
ref
の代わりに in
と out
を使うこともできるので、適宜使い分けます。
最後に C# 側のラッパーを書けば完成です。
private static v128 nextDouble2(IntPtr rng, v128 min2, v128 max2) { nextDouble2ForUnity(rng, min2, max2, out var result); return result; }
おわりに
ググっても何も出てこないのでさっぱりわからず、 n 週間塩漬けにしていました…
私のようにうっかり勘違いをする人がいるかもしれないので残しておきます…