如何将结构从 C++ 迁移到 C#
Posted
技术标签:
【中文标题】如何将结构从 C++ 迁移到 C#【英文标题】:How to Migrate structs form C++ to C# 【发布时间】:2012-06-21 14:26:12 【问题描述】:我正在编写一个接收复杂数据结构的 UDP 客户端。 我确实有 C++ 中的结构,但我的程序是 C#
Bitfiels 和联合有很多不同的结构。
有什么方法可以不用手动转换结构吗?
还有在 C# 中实现位域和联合的简单方法吗?
现在我正在为位域使用属性,但这是一项艰巨的工作,极有可能出错。
我提供了一个我现在正在做的简化示例,大约有 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...
【问题讨论】:
关于操作位域,您可能需要查看 System.Collections.Specialized.BitVector32。此外,根据您的操作,您可能希望查看使用适当宽度的枚举并使用 Flags 属性对其进行装饰。 好的,这样怎么样:我编写了一个包含所有结构的管理 C++ DLL,这将允许我从 C# 访问它们,不是吗?另外,另一个问题,C++ 编译器是否保持结构的字节和位顺序? 不幸的是,@ChristianElsner,我有同样的想法,这似乎不起作用。这类结构可以编译,但不能从 C# 访问它们的成员。您通过将struct
替换为value class
使其成为托管结构,但随后您会得到位域和联合的编译器错误。
是否可以使用 COM 访问您的 C++ 结构?
【参考方案1】:
简短的回答是否定的,似乎没有一种简单的方法可以将 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);
产生1539
和186, 19
。
这为您提供了联合结构,但它仍然不能解决 BitFields 的主要问题。对此似乎没有达成共识(当然,除了您已经完成的工作;例如,参见 [1])。使用MarshalAs
属性可能会做一些聪明的事情,将自定义 BitField 类映射到底层结构,但在这种努力下,继续编写自定义类可能会更容易。
【讨论】:
【参考方案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。我还在 AutoPtr 代码中添加了 T* operator=(T* rhs) return (m_ptr = rhs);
行(正如 Kerr 帖子的 cmets 中所建议的那样)以使初始化程序工作。
您现在可以在 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, 98
和224
。
大警告
像这样混合托管和非托管代码是危险的,这就是 Kerr 首先编写 AutoPtr 的原因(顺便说一句,他的 blog post 关于这个主题非常值得一读)。这对我有用,但我不能保证它不会导致内存泄漏。您应该明确在部署它之前检查它是否按预期工作。
【讨论】:
以上是关于如何将结构从 C++ 迁移到 C#的主要内容,如果未能解决你的问题,请参考以下文章
如何将带有 unsigned char* 的结构从 C# 传递到 C++?
如何将 System::^array 从 C# 函数转换为 C++ 中的等效数据类型