unsafeでアンマネージドな構造体でポインタアクセスとポリモーフィズムのテスト

環境

Unity2022.3.1f1

概要

構造体は値渡しとなりアクセスしづらいです。なのでunsafeでポインタを使って直接アクセスしたい。

そしてポリモーフィズム的な事をしたい。NativeArrayで一緒くたにアクセスしたい。

ポリモーフィズムの部分は具体的には以下のような事をしたいです。(Classでの例)

public abstract class Animal
{
    public abstract void Sound();
}

public class Dog : Animal
{
    public override void Sound()
    {
        Debug.Log("ワンワン");
    }
}

public class Cat : Animal
{
    public override void Sound()
    {
        Debug.Log("ナーゴ");
    }
}
var animals = new Animal[]{ new Dog(), new Cat() };
for(int i = 0; i < animals.Length; i++)
    animals[i].Sound();

Marshal.Alloc版(失敗)

for内のインターフェースを取得する部分でエラーになります。

IntPtrからインターフェスの参照を得る方法が見つかりませんでした。

そもそも構造体として生成されていないです。

public interface ITestAnimal
{
    void Sound();
}

public struct TestDog : ITestAnimal
{
    public int value;
    public void Sound()
    {
        Debug.Log($"ワンワン:{value}");
    }
}

public struct TestCat : ITestAnimal
{
    public int value;
    public void Sound()
    {
        Debug.Log($"ナーゴ:{value}");
    }
}

var test3Animals = new NativeArray<IntPtr>(2, Allocator.Persistent);
test3Animals[0] = Marshal.AllocCoTaskMem(sizeof(TestDog));
((TestDog*)test3Animals[0])->value = 321;
test3Animals[1] = Marshal.AllocCoTaskMem(sizeof(TestCat));
((TestCat*)test3Animals[1])->value = 654;

for(int i = 0; i < test3Animals.Length; i++)
{
    ITestAnimal animal = *((ITestAnimal*)test3Animals[i]);
    animal.Sound();
}

for(int i = 0; i < test3Animals.Length; i++)
    Marshal.FreeCoTaskMem(test3Animals[i]);
test3Animals.Dispose();

GCHandle.Alloc版

直接アクセスもポリモーフィズムもできるようになりました。

public interface ITestAnimal
{
    void Sound();
}

public struct TestDog : ITestAnimal
{
    public int value;
    public void Sound()
    {
        Debug.Log($"ワンワン:{value}");
    }
}

public struct TestCat : ITestAnimal
{
    public int value;
    public void Sound()
    {
        Debug.Log($"ナーゴ:{value}");
    }
}

var testAnimals = new NativeArray<GCHandle>(2, Allocator.Persistent);

testAnimals[0] = GCHandle.Alloc(new TestDog(), GCHandleType.Pinned);
testAnimals[1] = GCHandle.Alloc(new TestCat(), GCHandleType.Pinned);

var t0 = (TestDog*)testAnimals[0].AddrOfPinnedObject();
t0->value = 123;
var t1 = (TestCat*)testAnimals[1].AddrOfPinnedObject();
t1->value = 456;

for(int i = 0; i < testAnimals.Length; i++)
    ((ITestAnimal)testAnimals[i].Target).Sound();

for(int i = 0; i < testAnimals.Length; i++)
    testAnimals[i].Free();
testAnimals.Dispose();

内包

内包を継承元と見立てて行ってみました。

直接アクセスもポリモーフィズムもできるようになりました。

インターフェースではないので共通の変数も持てます。

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Test2Animal
{
    public enum AnimalType
    {
        Dog,
        Cat,
    }
    public AnimalType type;
    public delegate void CallBackSound(Test2Animal* pAnimal);
    private IntPtr sound;

    public void SetDelegateSound(CallBackSound func)
    {
        sound = Marshal.GetFunctionPointerForDelegate(func);
    }

    public void Sound()
    {
        var func = Marshal.GetDelegateForFunctionPointer<CallBackSound>(sound);
        fixed(Test2Animal* pAnimal = &this)
        {
            func.Invoke(pAnimal);
        }
    }
}

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Test2Dog
{
    public Test2Animal animal;
    public int iv;

    public void Setup()
    {
        animal.type = Test2Animal.AnimalType.Dog;
        animal.SetDelegateSound(static (pAnimal) => { ((Test2Dog*)pAnimal)->Sound(); });
        iv = 0;
    }

    public void Sound()
    {
        Debug.Log($"ワンワン:{iv}");
    }
}

[StructLayout(LayoutKind.Sequential)]
public unsafe struct Test2Cat
{
    public Test2Animal animal;
    public float fv;

    public void Setup()
    {
        animal.type = Test2Animal.AnimalType.Cat;
        animal.SetDelegateSound(static (pAnimal) => { ((Test2Cat*)pAnimal)->Sound(); });
        fv = 0.0f;
    }

    public void Sound()
    {
        Debug.Log($"ナーゴ:{fv}");
    }
}

var test2Animals = new NativeArray<IntPtr>(2, Allocator.Persistent);
test2Animals[0] = Marshal.AllocCoTaskMem(sizeof(Test2Dog));
((Test2Dog*)test2Animals[0])->Setup();
test2Animals[1] = Marshal.AllocCoTaskMem(sizeof(Test2Cat));
((Test2Cat*)test2Animals[1])->Setup();

((Test2Dog*)test2Animals[0])->iv = 123;
((Test2Cat*)test2Animals[1])->fv = 4.56f;
for(int i = 0; i < test2Animals.Length; i++)
    ((Test2Animal*)test2Animals[i])->Sound();

for(int i = 0; i < test2Animals.Length; i++)
    Marshal.FreeCoTaskMem(test2Animals[i]);
test2Animals.Dispose();

関連

【C#】メモリレイアウトを制御できるStructLayout &LayoutKind.Explicitを用いて別の型として解釈する(あとUnsafe.Asを利用した例も) - はなちるのマイノート