3

我正在编写一个接收复杂数据结构的 UDP 客户端。我确实有 C++ 中的结构,但我的程序是 C#

Bitfiels 和联合有很多不同的结构。

有什么方法可以让我不必手动转换结构?

还有一种简单的方法可以在 C# 中实现位域和联合吗?

现在我正在为 Bitfields 使用属性,但这是一项艰巨的工作,极有可能出错。

我提供了一个我现在正在做的简化示例,大约有 50 个结构,每个结构有 100 行代码。

示例 C++:

typedef struct Message_s
{
    unsigned char var : 2;
    unsigned char var2 : 6;

    union
    {
         struct
         {
            unsigned char sVar;
            unsigned char sVar2;
         }
         char arr[32];
     }    
}Message_t;

示例 C#:

 [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct Message_s
{
    private byte Field1
    public byte var
    {
       get
       {
           return (byte)(Field1 & 0x03);
       }
       set
       {
           Field1 = (byte)((Field1 & ~0x03) | value);
       }
    public byte var2
    {
       get
       {
           return (byte)(Field1 & 0xFC);
       }
       set
       {
           Field1 = (byte)((Field1 & 0x03) | value<<2);
       }
     }
//unions may be possible with properties...
}
4

2 回答 2

4

简短的回答是否定的,似乎没有一种简单的方法可以将 C++ 结构转换为 C# 格式。但是,有一种方法可以在 C# 结构中进行联合;这是否是一个好主意,或者是否最好从头开始上课,将取决于您。

要在 C# 结构中获取联合,请使用LayoutKind.Explicit以下FieldOffset属性:

[StructLayout(LayoutKind.Explicit)]
struct StructTest
{
    // A byte at the beginning of the struct.
    [FieldOffset(0)]
    public byte byte1;

    // Another byte immediately after it.
    [FieldOffset(1)]
    public byte byte2;

    // Now an integer which covers both.
    [FieldOffset(0)]
    public Int16 int1;
}

您可以按照您的期望使用它:

        var foo = new StructTest();
        foo.byte1 = 3;
        foo.byte2 = 6;
        Console.WriteLine("{0}", foo.int1);

        var bar = new StructTest();
        bar.int1 = 5050;
        Console.WriteLine("{0}, {1}", bar.byte1, bar.byte2);

产生1539186, 19

这为您提供了联合结构,但它仍然不能解决 BitFields 的主要问题。对此似乎没有达成共识(当然,除了您已经完成的工作;例如,参见 [ 1 ])。可以通过MarshalAs属性做一些巧妙的事情来将自定义 BitField 类映射到底层结构,但是在那个级别的努力下,像以前一样继续编写自定义类可能会更容易。

于 2012-06-21T15:24:36.773 回答
2

作为替代方案,有一种方法可以在代码中使用非托管 C++ 类型,但它仍然涉及一些类型。在 VS C++ 项目中,创建非托管结构:

struct Date_s
{
    union
    {
        struct
        {
            unsigned nWeekDay  : 3;    // 0..7   (3 bits)
            unsigned nMonthDay : 6;    // 0..31  (6 bits)
            unsigned           : 0;    // Force alignment to next boundary.
            unsigned nMonth    : 5;    // 0..12  (5 bits)
            unsigned nYear     : 8;    // 0..100 (8 bits)
        };
        unsigned bob;
    };
};

现在我们创建一个包含此结构的托管类型:

public ref struct Date_ms
{
    AutoPtr<Date_s> data;
    Date_ms()
    {
        data = new Date_s();
    }

    property unsigned nWeekDay
    {
        unsigned get() { return data->nWeekDay; }
        void set(unsigned value) { data->nWeekDay = value; }
    }
    property unsigned nMonthDay
    {
        unsigned get() { return data->nMonthDay; }
        void set(unsigned value) { data->nMonthDay = value; }
    }
    property unsigned nMonth
    {
        unsigned get() { return data->nMonth; }
        void set(unsigned value) { data->nMonth = value; }
    }
    property unsigned nYear
    {
        unsigned get() { return data->nYear; }
        void set(unsigned value) { data->nYear = value; }
    }
    property unsigned bob
    {
        unsigned get() { return data->bob; }
        void set(unsigned value) { data->bob = value; }
    }
};

那里有一些复制和粘贴要做。我在这里还使用了一个外部项目:它是来自 Kerr的AutoPtr 。我还在T* operator=(T* rhs) { return (m_ptr = rhs); }AutoPtr 代码中添加了这一行(正如 Kerr 帖子的评论中所建议的那样),以使初始化程序工作。

您现在可以在 C# 代码中使用这个新结构,并且一切都应该按预期工作:

var test = new Date_ms();
test.nMonth = 3;
test.nMonthDay = 28;
test.nYear = 98;
Console.WriteLine("{0}, {1}, {2}", test.nMonth, test.nMonthDay, test.nYear);
Console.WriteLine("{0}", test.bob);

结果3, 28, 98224

巨大的警告

像这样混合托管和非托管代码是危险的,这就是 Kerr 首先编写 AutoPtr 的原因(顺便说一句,他关于该主题的博客文章非常值得一读)。这对我有用,但我不能保证它不会导致内存泄漏。在部署它之前,您应该明确检查它是否按预期工作。

于 2012-06-22T10:51:21.320 回答