用C#中的并集模拟C++嵌套结构
Mimick C++ nested structs with union in C#
我知道这个问题以前被问过很多次,我试着通读了以前的所有问题,但运气不好。
我正在尝试将下面的C++结构转换为C#,用于套接字通信。
enum class packet_type
{
read_mem,
get_base_addr,
get_pid,
completed
};
struct copy_mem
{
unsigned int dest_process_id;
unsigned long long dest_address;
unsigned int src_process_id;
unsigned long long src_address;
unsigned int size;
};
struct get_base_addr
{
unsigned int process_id;
};
struct get_pid
{
size_t len;
wchar_t name[256];
};
struct completed
{
unsigned long long result;
};
struct PacketHeader
{
//uint32_t magic;
packet_type type;
};
struct Packet
{
PacketHeader header;
union
{
copy_mem copy_memory;
get_base_addr get_base_address;
get_pid get_pid;
completed completed;
} data;
};
这是我目前的C#实现
public enum PacketType
{
read_mem = 0,
get_base_addr = 1,
get_pid = 2,
completed = 3
}
[StructLayout(LayoutKind.Sequential)]
public struct PacketHeader
{
public PacketType type;
}
[StructLayout(LayoutKind.Sequential)]
public struct get_base_addr
{
uint process_id;
};
[StructLayout(LayoutKind.Sequential)]
public struct get_pid
{
public ulong len;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string name;
}
[StructLayout(LayoutKind.Sequential)]
public struct copy_mem
{
public uint dest_process_id;
public ulong dest_address;
public uint src_process_id;
public ulong src_address;
public uint size;
}
[StructLayout(LayoutKind.Sequential)]
public struct completed
{
public ulong result;
};
[StructLayout(LayoutKind.Explicit, Pack = 0, CharSet = CharSet.Unicode)]
public struct Packet
{
[FieldOffset(0)] //
public PacketHeader header;
[FieldOffset(4)]
public copy_mem CopyMem; //28
[FieldOffset(32)]
public get_base_addr GetBaseAddress;
[FieldOffset(36)]
public get_pid GetPid;
[FieldOffset(300)]
public completed Completed;
}
然后,我使用这种方法将结构转换为用于套接字传输的字节数组:
public static byte[] RawSerialize(T item)
{
int rawSize = Marshal.SizeOf(typeof(T));
IntPtr buffer = Marshal.AllocHGlobal(rawSize);
var a = Marshal.SizeOf(item);
var b = Marshal.SizeOf(buffer);
Marshal.StructureToPtr(item, buffer, false);
byte[] rawData = new byte[rawSize];
Marshal.Copy(buffer, rawData, 0, rawSize);
Marshal.FreeHGlobal(buffer);
return rawData;
}
问题是var a = Marshal.SizeOf(item);
报告的大小为312
,但当我在C++中执行sizeof(Packet)
时,实际结构应该是528
字节
您的假设似乎是错误的。首先,wchar_t
类型在不同的机器上可能具有不同的长度。在我的x64 Linux盒子中,它是4字节的——仅此一点就使get_pid
成为1032
字节大小的结构。您可能有兴趣使用char16_t
或char32_t
类型(例如,请参见此处(。
由于Packet
中的union
与所有字段重叠,这也使Packet
成为1040
字节大小的结构:PacketHeader
为4
字节,get_pid
为1032
字节,这是";"最长";struct和4
字节用于填充。遗憾的是,填充是特定于平台的。
要从C/C++编译器中去除填充,您需要使用GCC的__attribute__ ((packed))
或Visual C++的#pragma pack(1)
等属性(例如,请参见此SO答案(。
不过,请注意,C#中的字段偏移也是错误的:除了标头,Packet
中的所有字段偏移都必须是[FieldOffset(4)]
,因为在C++中,它是从字节4
开始的union
(假设零填充(。
为了便于移植,还要注意unsigned long long
也是特定于平台的,唯一的保证是至少为64位长。如果您需要恰好64位,则可能需要使用uint64_t
(例如,请参见此处(。
这是我用来确定大小的代码(Linux x64,GCC 9.3(:
int main() {
std::cout << "packet_type: " << sizeof(packet_type) << std::endl;
std::cout << "copy_mem: " << sizeof(copy_mem) << std::endl;
std::cout << "get_base_addr: " << sizeof(get_base_addr) << std::endl;
std::cout << "get_pid: " << sizeof(get_pid) << std::endl;
std::cout << "completed: " << sizeof(completed) << std::endl;
std::cout << "PacketHeader: " << sizeof(PacketHeader) << std::endl;
std::cout << "Packet: " << sizeof(Packet) << std::endl;
std::cout << "wchar_t: " << sizeof(wchar_t) << std::endl;
return 0;
}
带填充(默认结构(:
packet_type: 4
copy_mem: 40
get_base_addr: 4
get_pid: 1032
completed: 8
PacketHeader: 4
Packet: 1040
wchar_t: 4
无填充(__attribute__ ((packed))
(:
packet_type: 4
copy_mem: 28
get_base_addr: 4
get_pid: 1032
completed: 8
PacketHeader: 4
Packet: 1036
wchar_t: 4
正如注释中所指出的,将Packet
结构的GetPid
字段设置为[FieldAlign(4)]
将导致以下运行时错误:
Unhandled exception. System.TypeLoadException: Could not load type 'Packet' from assembly 'StructSize, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field.
解决此问题的一种方法是定义get_pid
结构,如下所示:
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public unsafe struct get_pid
{
public ulong len;
public fixed byte name[256];
}
这仍然假设名称字符串为128
个字符长,每个字符都是2字节的Unicode。这样一来,name
属性现在的类型为byte*
。要返回字符串,以下两种方法应该有效:
public static unsafe string GetName(get_pid gp) =>
new string((sbyte*) gp.name, 0, 256, Encoding.Unicode);
public static unsafe string GetName(get_pid gp) =>
Marshal.PtrToStringUni(new IntPtr(gp.name), 256);
- 如何使用Google Mock来模拟gettimeofday()
- G锁定铸造到基础上会释放模拟行为
- 有什么好的方法可以让系统调用代理允许在单元测试中进行模拟
- 落砂模拟碰撞检测C++和SFML
- 在gtest.中使用fff.h模拟系统API
- 谷歌模拟和覆盖关键字
- 用C#中的并集模拟C++嵌套结构
- 在同一模拟中使用静脉和静脉_ inet内容时出现运行时错误
- 在模拟器中使用并集来模拟CPU寄存器有多合适
- 我写了一个C++程序来模拟Enigma机器.我没有得到输出
- 如何模拟不同边数的骰子滚动?
- 模拟持久按键
- 使用SIR模型的疾病爆发模拟
- 在 c++ 中模拟输入并在 JAVA 中读取它?
- 转发变量参数列表以模拟 std::thread
- 如何在谷歌模拟中匹配 C 样式数组
- 如何使用兰德随机化模拟点击
- 模拟GPS数据,以便使用Qt与Traccar一起使用
- QKeyPress - 在Qt中模拟按键
- 如何使用不同的谷歌模拟运行相同的谷歌测试用例?