Can't order array of strings from C ++ to C # in Unity
I am trying to pass an array from struct
from C ++ to Unity script in C #. When I use the code in production, the size of the array will vary greatly, so I really need to pass an array of unknown length.
My smart idea was to store the array on the heap and pass a reference to that array to Unity. I found StackOverflow posts on how to do this. But then Unity complains that the link is null.
Here is a portion of my C ++ code:
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;
}
I tried this with int
instead struct
and it worked.
Now my C # code:
[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);
The statement Marshal.PtrToStructure
states that the SPointers [i] argument is null. I checked with the Debug.Log command and indeed it looks like null: it prints as 0.
But I've tried something similar with an array before int
and it worked. I'm not sure about this: is this my problem in C ++ or C #? Am I not transmitting correct information or am I not processing correct information in the wrong way?
Solution 1
This was the first solution I came up with mostly on my own. The second solution is better.
Thanks completely to Alex Skalozub, I figured it out. One level of pointer directionality is abstracted during sorting. So my C ++ code contains:
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;
}
And my C # code contains:
for (int i = 0; i < itemsLength; i++) {
Debug.Log("Pointer: " + SPointers[i]);
SArray[i] = (S) Marshal.PtrToStructure(SPointers[i], typeof(S));
}
And this! All data has been transferred.
Now this leaves me with a memory management problem: every single object created in C ++ code must be freed . I know about this, and I will take care of it further.
Solution 2
See verified answer. Alex's answer is much better as it uses much less unnecessary pointers. I used more pointers because I just figured out how to use them in C #.
source to share
Yours ptrNativeData
is a pointer to an array of the structures themselves, not an array of pointers to structures. Copying it into an array of pointers and accessing them is wrong.
I also suggest using interop out
instead ref
when declaring a function. This is more accurate and does not require marshaller to copy the initial values ββfrom managed to native code (since they are initialized in native code):
[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
}
source to share
Years later, I find this answer and it helps me a little. Although I have something to add, which is easier and better to use. You don't need to worry about freeing memory in C ++ Part:
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;
}
FROM#
[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);
One more thing worth mentioning: you need two calls with this approach ... first get the size of the array from the C ++ DLL, allocate the array in C # and let the C ++ DLL fill the array allocated in C # extra call
source to share