c# - 摆脱不安全的代码,从字节编码 uint 数组?

标签 c# struct marshalling

我正在使用其他人编写的项目从 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_detect_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">

这让我删除了“unsafe”和“fixed”关键字,但引起了另一个问题,因为在解析字节数据时,有一个很大的 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 调用上使用。 您的自定义编码器可能如下所示:

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();

关于c# - 摆脱不安全的代码,从字节编码 uint 数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28424696/

相关文章:

c# - 重新编译引用的程序集时常量值不变

c#调用c++ dll传输struct array发生异常

java - JAXB 将多个对象编码到一个文件

java - 将 StartElement 添加到 JAXBContext marshal

reflection - TypeScript - 传递一个类作为参数,以及反射

c# - 使用 IN 运算符的子查询列出 Entity Framework 6

c# - 为什么没有 `DBconnection.CloseAsync()` 而有 `DBconnection.OpenAsync()` ?

c++ - 使用变量访问结构成员

c# - 在某些情况下使用 StructureMap 向构造函数注入(inject)不同的对象

c - 具有指向下一个结构的指针的结构?