C# 使用P/Invoke Interop Assistant时数据结构和堆栈损坏

C# 使用P/Invoke Interop Assistant时数据结构和堆栈损坏,c#,c,pinvoke,calling-convention,packing,C#,C,Pinvoke,Calling Convention,Packing,我在C库中有一个结构: #pragma pack(推送、打包) #布拉格语包(1) 类型定义结构 { 无符号整数IP地址; 无符号字符AmacadAddress[6]; 无符号整数节点ID; }马歇尔; __declspec(dllexport)int SetCommunicationParameters(tStructToMarshall参数); 这段代码是用cl/LD/Zi Communication.c编译的,以生成DLL和PDB文件进行调试 为了从.Net应用程序中使用此代码,我使用为

我在C库中有一个结构:

#pragma pack(推送、打包)
#布拉格语包(1)
类型定义结构
{
无符号整数IP地址;
无符号字符AmacadAddress[6];
无符号整数节点ID;
}马歇尔;
__declspec(dllexport)int SetCommunicationParameters(tStructToMarshall参数);
这段代码是用
cl/LD/Zi Communication.c
编译的,以生成DLL和PDB文件进行调试

为了从.Net应用程序中使用此代码,我使用为包装器DLL生成C#代码:

这将导致显示C#包装器,我将其修改为使用正确的DLL,而不是
。另外,我确实希望为
amacadAddress
创建一个字节数组,而不是一个字符串(尽管我知道这通常会有帮助):

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential,CharSet=System.Runtime.InteropServices.CharSet.Ansi)]
公共结构tStructToMarshall
{
///无符号整数
公共uint IP地址;
///无符号字符[6]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr,SizeConst=6)]
公共地址;
//^^^^^^^^是“字符串”
///无符号整数
公共单元节点ID;
}
公共部分类NativeMethods
{
内部常量字符串DllName=“lib/Communication.dll”;
///返回类型:int
///参数:tStructToMarshall->Anonymous_75c92899_b50d_4bea_a217_a69989a8d651
[System.Runtime.InteropServices.DllImportAttribute(DllName,EntryPoint=“SetCommunicationParameters”)]
//^^^^^^^^是“”
公共静态外部设置通信参数(tStructToMarshall参数);
}
我有两个问题: 1.当我将结构的值设置为非零并查找节点ID时,它已损坏。IP地址和MAC地址都很好,但是数组之后的任何结构成员(包括其他数据类型)都会被破坏,即使我指定了一位数,C输出中也会显示非常大的数字。 2.当我调用该方法时,我得到一个错误,该错误表示:

对PInvoke函数“”的调用使堆栈不平衡。这可能是因为托管PInvoke签名与非托管目标签名不匹配。检查PInvoke签名的调用约定和参数是否与目标非托管签名匹配

尝试调用不带参数的方法不会生成此异常。我非常确定它与目标签名匹配,因为我就是这样生成它的

如何解决这些问题?

1。结构腐败 这种“损坏”是由对齐问题引起的。interop assistant正在忽略
#pragma pack(1)
指令,并使用默认值

类型实例的字段使用以下规则对齐:

  • 类型的对齐方式是其最大元素的大小(1、2、4、8等,字节)或指定的打包大小,以较小者为准

  • 每个字段必须与自身大小的字段(1、2、4、8等字节)或类型对齐,以较小者为准。由于类型的默认对齐方式是其最大元素的大小,该元素大于或等于所有其他字段长度,这通常意味着字段按其大小对齐。例如,即使类型中最大的字段是64位(8字节)整数或Pack字段设置为8,字节字段也会在1字节边界上对齐,Int16字段在2字节边界上对齐,Int32字段在4字节边界上对齐

  • 在字段之间添加填充以满足对齐要求

您已在C中指定字段应在1字节边界上对齐。但是,您的C#代码假定存在不存在的填充,特别是在您的6字节结构之后:

使用IP地址0x01ABCDEF、MAC地址{0x01、0x02、0x03、0x04、0x05、0x06}和节点ID 0x00000001,内存看起来是这样的(忽略endian ness问题,如果对齐正确则无所谓):

字节值C需要.NET需要:
0 0x01\\
1 0xAB}IP地址}IP地址
2 0xCD||
3 0xEF//
4 0x01}AmacadAddress[0]}AmacadAddress[0]
5 0x02}AmacadAddress[1]}AmacadAddress[1]
6 0x03}AmacadAddress[2]}AmacadAddress[2]
7 0x04}AmacadAddress[3]}AmacadAddress[3]
8 0x05}amacadress[4]}amacadress[4]
9 0x06}AmacadAddress[5]}AmacadAddress[5]
10 0x00\}填充
11 0x00}节点ID}填充
12 0x00|\
13 0x01/}节点ID
14 0x??}单元化|
15 0x??}单元化/
请注意,.NET希望节点ID(一个4字节的值)从地址12开始,地址12可以被4整除。它实际上使用了未初始化的内存,这会导致错误的结果

修复方法: 将命名参数
Pack=1
添加到对StructLayoutAttribute的调用中:

[System.Runtime.InteropServices.StructLayoutAttribute(
System.Runtime.InteropServices.LayoutKind.Sequential,Pack=1,CharSet=System.Runtime.InteropServices.CharSet.Ansi)]
//^^^^^^-这里
2.堆栈不平衡 这是由不同的原因造成的。当您使用参数调用方法时,这些参数将进入堆栈。在某些调用约定下,调用方在方法返回后清理堆栈。在其他情况下,被调用函数在返回之前进行清理

使用
cl
编译未注释函数时,它使用