我正在使用他人编写的项目来从Parrot AR无人机接收一些数据。很多数据都是以字节数组的形式出现的,我使用的这个库使用一堆结构进行解析。总的来说,我对编组真的很陌生。
我有一个结构,看起来像这样:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public unsafe struct navdata_vision_detect_t
{
public ushort tag;
public ushort size;
public uint nb_detected;
public fixed uint type [4]; // <Ctype "c_uint32 * 4">
public fixed uint xc [4]; // <Ctype "c_uint32 * 4">
public fixed uint yc [4]; // <Ctype "c_uint32 * 4">
public fixed uint width [4]; // <Ctype "c_uint32 * 4">
public fixed uint height [4]; // <Ctype "c_uint32 * 4">
public fixed uint dist [4]; // <Ctype "c_uint32 * 4">
public fixed float orientation_angle [4]; // <Ctype "float32_t * 4">
}
然而,如果我试图访问navdata_vision_dedetect_t的实例并获得固定的uint值,我必须使用"fixed"关键字,这似乎真的很混乱:
unsafe private void drawTagDetection()
{
int x, y;
if (_detectData.nb_detected > 0)
{
fixed (uint* xc = _detectData.xc)
{
x = (int)xc[0];
}
fixed (uint* yc = _detectData.yc)
{
y = (int)yc[0];
}
}
我希望能够像访问普通c#数组一样访问uint数组。我想我应该能够使用编组,但我无法让它发挥作用。我试过类似的东西:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public uint[] type; // <Ctype "c_uint32 * 4">
这让我删除了"不安全"one_answers"固定"关键字,但也造成了另一个问题,因为在解析字节数据时,有一个大的switch语句会对各种结构进行强制转换,比如
private static unsafe void ProcessOption(navdata_option_t* option, ref NavdataBag navigationData){
var tag = (navdata_tag_t) option->tag;
switch (tag)
{
//lots of other stuff here
case navdata_tag_t.NAVDATA_VISION_TAG:
navigationData.vision = *(navdata_vision_t*) option;
break;
}
}
所以我仍然需要在另一个不安全的函数中有一些指向这个结构的指针。如何使这些结构中的数组"安全",同时允许另一个不安全的函数将我的对象强制转换为结构?
谢谢你能提供的任何帮助!
首先,作为UnmanagedType.ByValArray
进行编组是行不通的:我们有指针而没有值。建议的并集模拟也将失败:数组是C#中的引用类型,使用[FieldOffset(x)]
将无法按预期工作。
我认为你可以这样做:
1) 将你的结构转换为这样的指针结构:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct _navdata_vision_detect_t
{
public ushort tag;
public ushort size;
public uint nb_detected;
public IntPtr type; // <Ctype "c_uint32 * 4">
public IntPtr xc; // <Ctype "c_uint32 * 4">
public IntPtr yc; // <Ctype "c_uint32 * 4">
public IntPtr width; // <Ctype "c_uint32 * 4
public IntPtr height; // <Ctype "c_uint32 * 4">
public IntPtr dist; // <Ctype "c_uint32 * 4">
public IntPtr orientation_angle; // <Ctype "float32_t * 4">
}
2) 围绕这个结构包装一个类。类应包含以上结构作为成员。它还应该以"安全的方式"镜像所有结构成员。
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public class navdata_vision_detect_t
{
public ushort tag;
public ushort size;
public uint nb_detected;
public uint[] type; // <Ctype "c_uint32 * 4">
public uint[] xc; // <Ctype "c_uint32 * 4">
public uint[] yc; // <Ctype "c_uint32 * 4">
public uint[] width; // <Ctype "c_uint32 * 4
public uint[] height; // <Ctype "c_uint32 * 4">
public uint[] dist; // <Ctype "c_uint32 * 4">
public float[] orientation_angle; // <Ctype "float32_t * 4">
internal _navdata_vision_detect_t data;
}
3) 现在,您将需要一个自定义整理器来手动将数据从指向的数据传输到实际的数组。请记住(不幸的是)不能在结构上使用自定义编组器,只能在P/Invoke调用上使用。您的自定义Marshaller可能如下所示:
public class myCustomMarshaler : ICustomMarshaler
{
[ThreadStatic]
private navdata_vision_detect_t marshaledObj;
private static myCustomMarshaler marshaler = null;
public static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
{
marshaler = new myCustomMarshaler();
}
return marshaler;
}
public int GetNativeDataSize()
{
return Marshal.SizeOf(typeof(_navdata_vision_detect_t));
}
public System.IntPtr MarshalManagedToNative(object managedObj)
{
if (!(managedObj is navdata_vision_detect_t))
{
throw new ArgumentException("Specified object is not a navdata_vision_detect_t object.", "managedObj");
}
else
{
this.marshaledObj = (navdata_vision_detect_t)managedObj;
}
IntPtr ptr = Marshal.AllocHGlobal(this.GetNativeDataSize());
if (ptr == IntPtr.Zero)
{
throw new Exception("Unable to allocate memory to.");
}
Marshal.StructureToPtr(this.marshaledObj.data, ptr, false);
return ptr;
}
public object MarshalNativeToManaged(System.IntPtr pNativeData)
{
marshaledObj.tag = marshaledObj.data.tag;
marshaledObj.size = marshaledObj.data.size;
marshaledObj.nb_detected = marshaledObj.data.nb_detected;
for (int i=0; i<3; i++)
{
Int32 _type = Marshal.ReadInt32(this.marshaledObj.data.type, i * sizeof(Int32));
this.marshaledObj.type[i] = Convert.ToUInt32(_type)
Int32 _xc = Marshal.ReadInt32(this.marshaledObj.data.xc, i * sizeof(Int32));
this.marshaledObj.xc[i] = Convert.ToUInt32(_xc)
Int32 _yc = Marshal.ReadInt32(this.marshaledObj.data.yc, i * sizeof(Int32));
this.marshaledObj.yc[i] = Convert.ToUInt32(_yc)
Int32 _width = Marshal.ReadInt32(this.marshaledObj.data.width, i * sizeof(Int32));
this.marshaledObj.width[i] = Convert.ToUInt32(_width)
Int32 _height = Marshal.ReadInt32(this.marshaledObj.data.height, i * sizeof(Int32));
this.marshaledObj.height[i] = Convert.ToUInt32(_height)
Int32 _dist = Marshal.ReadInt32(this.marshaledObj.data.dist, i * sizeof(Int32));
this.marshaledObj.dist[i] = Convert.ToUInt32(_dist)
// Marshal class doesn't have ReadFloat method, so we will read Int32 and convert it to float
Int32 _orientation_angle = Marshal.ReadInt32(this.marshaledObj.data.orientation_angle, i * sizeof(Int32));
byte[] tmpBytes = BitConverter.GetBytes(_orientation_angle);
this.marshaledObj.orientation_angle[i] = BitConverter.ToFloat(tmpBytes);
}
// Here is your safe "structure"
return this.marshaledObj;
}
public void CleanUpManagedData(object managedObj)
{
}
public void CleanUpNativeData(System.IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
}
}
4) 在外部方法中使用您的结构和自定义编组器,如以下所示:
[DllImport("legacy.dll", CharSet = CharSet.Ansi)]
public static extern short doLegacyStuff(
[In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(myCustomMarshaler))]
navdata_vision_detect_t navdata);
5) 您可以使用PtrToStructure()以"安全的方式"将字节数组转换为结构:
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
navdata_vision_t data = (navdata_vision_t)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(navdata_vision_t));
handle.Free();