マネージドコードとアンマネージドコードの間で、構造化されたデータをやりとりしたい事がたまにあるのですが、残念ながら UnitySendMessage
は有効な文字列しか扱うことができません。 UnitySendMessage
の為だけにデータを文字列にシリアライズするのも大げさな気がします。
構造体をやりとりするだけなら、構造体を返すエントリーポイントを作れば良いのですが、安全に Unity のメインスレッドに返すには、やはり UnitySendMessage
を使いたいところです。
文字列しか使えないからといって諦めるのはエンジニアとして負けた気がするので、ちょっと頑張ってみます。
C#
[StructLayout(LayoutKind.Sequential)]
private struct AnyData {
[MarshalAs(UnmanagedType.LPStr)] public readonly string name;
[MarshalAs(UnmanagedType.LPStr)] public readonly string description;
[MarshalAs(UnmanagedType.R8)] public readonly double number;
[MarshalAs(UnmanagedType.Bool)] public readonly bool isActive;
public readonly Int16 shortInteger;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=2)] private readonly string __padding0;
private readonly IntPtr __original;
private readonly IntPtr __padding1;
}
[DllImport(LIBNAME)] static extern void purge_anydata(IntPtr intptr);
void OnSendMessage(string message) {
var intptr = (IntPtr)Convert.ToInt64(message, 16);
var anyData = (AnyData)Marshal.PtrToStructure(intptr, typeof(AnyData));
...
purge_anydata(intptr);
}
C++
struct AnyData {
string name;
string description;
double number;
bool active;
int16_t shortInt;
};
struct CSAnyData {
const char *name;
const char *description;
double number;
int32_t active;
int16_t shortInt;
uint8_t __padding0[2];
const AnyData *__original;
const void* __padding1;
static const CSAnyData *make_data(const AnyData &data) {
return new CSAnyData(new AnyData(data));
}
CSAnyData(const AnyData *data)
: name(data->name.c_str())
, description(data->description.c_str())
, number(data->number)
, active(data->active)
, shortInt(data->shortInt)
, __original(data)
{}
~CSAnyData() {
delete __original;
}
};
void SendToUnity(const AnyData &data) {
char ptr[17];
sprintf(ptr, "%p", CSAnyData::make_data(data));
UnitySendMessage("hogehoge object", "OnSendMessage", ptr);
}
extern "C" purge_anydata(CSAnyData *data) {
if (data) {
delete data;
}
}
構造体のアドレスを文字列として UnitySendMessage
に渡し、Unity 側で構造体を取得しています。また UnitySendMessage
を呼ぶ際に、コード上、構造体のメモリをダングリング状態にすることで、スレッドセーフな実装を省略できます。( new
して UnitySendMesssage
に渡してしまえば、以降 Unity のメインスレッドからしか該当メモリにアクセスされないはず。)注意点は UnitySendMessage
は様々な理由でメッセージが届かない場合があるので、その場合はメモリがリークすること。
また、構造体の定義時には下記の点を意識した方がよい。
- 32bit, 64bit どちらでも都合が悪くならないようにアライメントを明示的に調整する。
- 整数値はビット幅が規定されている型を使う。(int32, int64 ...)
- bool 値は 4バイト型で扱っとく。(アーキテクチャによってサイズが異る)