我试图将C ++中的struct
数组传递给C#中的Unity脚本。当我在生产中使用代码时,数组的大小会有很大差异,所以我实际上需要传递一个未知长度的数组。
我的聪明想法是将数组存储在堆上,并将对该数组的引用传递给Unity。我发现StackOverflow的帖子就是如何做到的。但是然后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#中的问题是什么?我没有传递正确的信息,还是我以错误的方式处理正确的信息?
这是我提出的第一个解决方案,主要靠我自己。第二种解决方案更好。
非常感谢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 ++代码中创建的每个对象。我意识到了这一点,接下来我会照顾它。
查看已检查的答案。亚历克斯的答案要好得多,因为它更少使用不必要的指针。我使用了更多指针,因为我只想知道如何在C#中使用它们。
答案 0 :(得分:3)
你的ptrNativeData
是一个结构数组本身的指针,而不是指向结构的指针数组。将它复制到指针数组并访问它们是不正确的。
我还建议在声明互操作函数时使用out
而不是ref
。这更准确,并且不需要编组器将初始值从托管代码复制到本机代码(因为它们在本机代码中初始化):
[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
}
答案 1 :(得分:1)
几年后,我找到了这个答案,它对我有一点帮助。我总要添加一些东西,它更容易使用。您不必担心释放C ++部分中的内存:
C ++
#pragma pack(push, 1)
struct Joint
{
int id = 0;
float posX = 0.0f;
float posY = 0.0f;
float posZ = 0.0f;
};
#pragma pack(pop)
extern "C" void __declspec(dllexport) __stdcall GetJointArray(Joint* newJoints, int* length)
{
Joint theNewJoints[2];
Joint theJoint;
theJoint.id = 20;
theJoint.posX = 21;
theJoint.posY = 22;
theJoint.posZ = 23;
theNewJoints[0] = theJoint;
theNewJoints[1] = theJoint;
memcpy(newJoints, &theNewJoints, 2 * sizeof (theJoint));
*length = 2;
}
C#
[StructLayout(LayoutKind.Sequential, Pack = 0)]
internal unsafe struct Joint
{
[MarshalAs(UnmanagedType.I4)]
public int id;
[MarshalAs(UnmanagedType.R4)]
public float posX;
[MarshalAs(UnmanagedType.R4)]
public float posY;
[MarshalAs(UnmanagedType.R4)]
public float posZ;
}
[DllImport("TheDLL")]
static extern void GetJointArray(Joint[] jointArray, out int length);
int itemsLength = 0;
Joint[] result = new Joint[2]; // Of course you need an additional dll function call to get the size of the array before
GetJointArray( result, out itemsLength);
Debug.Log("ITEMS LENGTH:" + itemsLength);
Debug.Log("JOINT1 ID:" + result[0].id);
要提及的另一件事是:您需要使用此方法进行两次调用...首先从C ++ DLL获取数组的大小,在C#中分配数组,然后让C ++ DLL用额外通话