我试图将结构数组从C传递到C#中的Unity脚本.当我在生产环境中使用代码时,数组的大小将有很大的不同,因此我实际上需要传递未知长度的数组.
我的聪明想法是将数组存储在堆上,并将对该数组的引用传递给Unity.我发现了*有关如何执行此操作的帖子.但是然后Unity抱怨引用为空.
这是我的C代码的一部分:
extern "C" struct S; // Not currently used.
struct S {
int i;
float f;
};
extern "C" bool helloWorld(S ** a, int * i);
S * s;
bool helloWorld(S ** a, int * i) {
s = new S[4];
for (int j = 0; j < 4; j++) {
s[j].i = j; // Fill in placeholder
s[j].f = (float) j; // data for now.
}
*a = s; // I don't know if this works.
*i = 4; // Works.
return true;
}
我用int而不是struct尝试了一下,它起作用了.
现在,我的C#代码是:
[StructLayout(LayoutKind.Sequential),Serializable]
public struct S {
int i;
float f;
};
void Start() {
IntPtr ptrNativeData = IntPtr.Zero;
int itemsLength = 0;
bool success = helloWorld(ref ptrNativeData, ref itemsLength);
if (!success) {
return;
}
S[] SArray = new S[itemsLength]; // Where the final data will be stored.
IntPtr[] SPointers = new IntPtr[itemsLength];
Debug.Log("Length: " + itemsLength); // Works!
Marshal.Copy(ptrNativeData, SPointers, 0, itemsLength); // Seems not to work.
for (int i = 0; i < itemsLength; i++) {
Debug.Log("Pointer: " + SPointers[i]); // SPointers[0] prints 0.
Marshal.PtrToStructure(SPointers[i], SArray[i]); // Crashes here. Boom.
}
}
[DllImport("TestUnity")]
private static extern bool helloWorld(ref IntPtr ptrResultVerts,
ref int resultVertLength);
Marshal.PtrToStructure指令说SPointers [i]参数为null.我用Debug.Log命令检查了一下,的确确实是null:它显示为0.
但是我之前尝试过使用多个int数组进行类似的操作,并且奏效了.我不确定的是:我的问题是C还是C#?我没有传递正确的信息,还是我以错误的方式处理了正确的信息?
解决方案1
这是我主要靠自己提出的第一个解决方案.第二种解决方案更好.
完全感谢Alex Skalozub,我弄清楚了.编组期间提取出一级指针间接.所以现在,我的C代码包含:
S ** s; // Not a pointer to Ss, but a pointer to pointers to Ss.
bool helloWorld(S *** a, int * i) { // Pass a triple pointer.
s = new S * [4]; // Create array of pointers.
for (int j = 0; j < 4; j++) {
s[j] = new S; // Actually create each object.
s[j]->i = j;
s[j]->f = (float) j;
}
*a = s;
*i = 4;
return true;
}
我的C#代码包含:
for (int i = 0; i < itemsLength; i++) {
Debug.Log("Pointer: " + SPointers[i]);
SArray[i] = (S) Marshal.PtrToStructure(SPointers[i], typeof(S));
}
就是这样!所有数据都已传输.
现在这确实给我带来了内存管理问题:必须释放在C代码中创建的每个对象.我知道这一点,接下来我将进行处理.
解决方案2
查看已检查的答案.亚历克斯的答案要好得多,因为它减少了不必要的指针的使用.我使用了更多的指针,因为我只是想出了如何在C#中使用它们.
解决方法:
您的ptrNativeData是指向结构本身的数组的指针,而不是指向结构的指针的数组.将其复制到指针数组并访问它们是不正确的.
我还建议在声明互操作函数时使用out而不是ref.这更加准确,并且不需要marshaller将初始值从托管代码复制到本地代码(因为它们是在本地代码中初始化的):
[DllImport("TestUnity")]
private static extern bool helloWorld(out IntPtr ptrResultVerts, out int resultVertLength);
void Start() {
IntPtr ptrNativeData;
int itemsLength;
bool success = helloWorld(out ptrNativeData, out itemsLength);
if (!success) {
return;
}
S[] SArray = new S[itemsLength]; // Where the final data will be stored.
Debug.Log("Length: " + itemsLength);
IntPtr p = ptrNativeData;
for (int i = 0; i < itemsLength; i++) {
Marshal.PtrToStructure(p, SArray[i]);
p += Marshal.SizeOf(typeof(S)); // move to next structure
}
// todo: free ptrNativeData later
}