VEGA TECH LAB

インテリア×テクノロジー

iOSネイティブコードから構造体をUnityに渡す

ベガコーポレーションでエンジニアをやっているStellarBiblosです。   初回からかなりニッチな所ですが、これに関しての情報があまり無いようで、折角ですから記事にしてみました。

構造体

まずは各々の方で構造体を宣言しますが、変数の宣言順序が異なると正しく渡せません。 新規で作成している場合には考慮しなくていいと思いますが32bitサポートの場合バイト長が違ったりするため注意です。 また、C#だとboolはデフォルトで4バイトに対してC++は1バイトなため[MarshalAs( UnmanagedType.U1)]で明示的に1バイトと宣言しなければダメです。 その後3バイト長のchar/stringを用いてパディングしておいた方がいいと思います。 そうでなくても末尾が余っている場合はパディングを推奨します。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Model {
    [MarshalAs(UnmanagedType.LPStr)]public string name;
    [MarshalAs(UnmanagedType.I4)]public int index;
    [MarshalAs(UnmanagedType.R4)]public float posX;
    [MarshalAs(UnmanagedType.R4)]public float posY;
    [MarshalAs(UnmanagedType.R4)]public float posZ;
}
struct Model_t {
    const char *name;
    int index;
    float posX;
    float posY;
    float posZ;
};

データ受け渡し

正しく構造体を作れていれば構造体のバイト長が一致します。 そのため構造体サイズ分アロケートしたポインタに対してmemcpyすればズレなくデータが取得できます。

class Reciever : MonoBehaviour {
    [DllImport("__Internal")] static extern void getModelStruct(IntPtr ptr);
    [DllImport("__Internal")] static extern void setModelStruct(IntPtr ptr);
   
    Model getModelFromNative() {
        var model = new Model();
        var ptr = IntPtr.Zero;
        try {
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
            try {}
            finally {
                ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Model)));
            }
            GetFurnitureStruct(ptr);
            model = (Model)Marshal.PtrToStructure(ptr, typeof(Model));
        }
        finally {
            if(ptr != IntPtr.Zero) Marshal.FreeCoTaskMem(ptr);
        }
        return model;
    }

    void setModelFromNative(Model model) {
        var ptr = IntPtr.Zero;
        try {
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
            try {}
            finally {
                ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Model)));
            }
            Marshal.StructureToPtr(ptr, model, false);
            setModelStruct(ptr);
        }
        finally {
            if(ptr != IntPtr.Zero) Marshal.FreeCoTaskMem(ptr);
        }
    }
}
Model_t myModel = *new Model_t { "Vega", 0, 0.0, 1.0, 1.2 };

extern "C" void getModelStruct(Model_t *model_t) {
    memcpy(model_t, &myModel, sizeof(Model));
}

extern "C" void setModelStruct(Model_t *model_t) {
    memcpy(&myModel, model_t, sizeof(Model));
}

おまけ

配列も取れます。 予め何らかの方法で配列長を知る必要がある為完璧な実装ではないです。

class ArrayReciever : MonoBehaviour {
    [DllImport("__Internal")] static extern void getModelStructArray(IntPtr ptr);

    Model[] getModeArraylFromNative(int length) {
        var models = new Model[length];
        var ptr = IntPtr.Zero;
        Int64 arrPtr = 0;
        try {
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
            try {}
            finally {
                ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Model)) * length);
            }
            arrPtr = (Int64)ptr;
            getModelStructArray(ptr);
            for(var i=0; i<length; i++) {
                models[i] = (Model)Marshal.PtrToStrucure((IntPtr)arrPtr, typeof(Model));
                arrPtr += (Int64)Marshal.SizeOf(typeof(Model));
            }
        }
        finally {
            if(ptr != IntPtr.Zero) Marshal.FreeCoTaskMem(ptr);
        }
        return models;
    }
}
Model_t myModels[3];

extern "C" void getModelStructArray(Model_t *models_t) {
    memcpy(models_t, &myModels, sizeof(myModels));
}