Cannot marshal a struct that contains a union
I have a C++ struct that looks like this:
struct unmanagedstruct
{
int开发者_JS百科 flags;
union
{
int offset[6];
struct
{
float pos[3];
float q[4];
} posedesc;
} u;
};
And I'm trying to Marshal it like so in C#:
[StructLayout(LayoutKind.Explicit)]
public class managedstruct {
[FieldOffset(0)]
public int flags;
[FieldOffset(4), MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 6)]
public int[] offset;
[StructLayout(LayoutKind.Explicit)]
public struct posedesc {
[FieldOffset(0), MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 3)]
public float[] pos;
[FieldOffset(12), MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] q;
}
[FieldOffset(4)]
public posedesc pose;
}
However, when I try loading data into my struct only the first 3 elements of the offset array are there (the array's length is 3). I can confirm that their values are correct - but I still need the other 3 elements. Am I doing something obviously wrong?
I'm using these functions to load the struct:
private static IntPtr addOffset(IntPtr baseAddress, int byteOffset) {
switch (IntPtr.Size) {
case 4:
return new IntPtr(baseAddress.ToInt32() + byteOffset);
case 8:
return new IntPtr(baseAddress.ToInt64() + byteOffset);
default:
throw new NotImplementedException();
}
}
public static T loadStructData<T>(byte[] data, int byteOffset) {
GCHandle pinnedData = GCHandle.Alloc(data, GCHandleType.Pinned);
T output = (T)Marshal.PtrToStructure(addOffset(pinnedData.AddrOfPinnedObject(), byteOffset), typeof(T));
pinnedData.Free();
return output;
}
Loading example:
managedstruct mystruct = loadStructData<managedstruct>(buffer, 9000);
Let me know if you need more information.
I'm not 100% sure about this but I believe that the Union means that the same memory is used for both members. In the case of the C++ structure, an int[] or a posedesc structure. So the size of the structure will be sizeof(int) + sizeof(posedisc). Meaning, Union doesn't mean you'll have both an int[] and a posedisc you'll have shared memory that can be either of those types in C++ land but only one or the other in managed land.
So I think you probably need two managed structures, one that has offset and one that has posedisc. You can pick one or the other in your call to LoadStruct. Optionally you could create a byte[] field and have calculated properties that convert those bytes into the desired types.
A possible cause for problem might be the fact that in C++, int
has a size that depends on the architecture.
In other words, in some circumstances, your offset
array in the C++ struct may be actually an array of 64 bit values.
Now, in your C# struct, whenever you used the MarshalAs
attribute, you do not specify the ArraySubType
parameter.
According to the documentation, when you omit ArraySubType
, the struct is supposed to be marshalled according to the type of the managed array, but maybe the fact that the offset
array in the C++ struct is not spread accros 48 bytes instead of 24, is causing the problem you encountered.
My suggestion would be to try to change all int
to long
in the C++ struct to ensure the size is always the same, and also add the ArraySubType
parameter in all your MarshalAs
attributes for arrays.
Kinda late to the party, but my best guess is that your offset
array gets overwritten by pos
array when the marshaller reaches the field, and it will be the reference that gets overwritten, not the actual elements. Therefore, offset
will always be of length 3 (and try GetType
on it, it should return Single[]
. This is what you get for overlapped references.).
So either you can delete q
and set pos
' size to 7, or you can use fixed
arrays (although I'm not sure how it will get marshalled).
精彩评论