【问题标题】:Data structure and stack corruption when using the P/Invoke Interop Assistant使用 P/Invoke Interop Assistant 时的数据结构和堆栈损坏
【发布时间】:2017-01-06 19:01:35
【问题描述】:

我在 C 库中有一个结构:

#pragma pack(push, packing)
#pragma pack(1)

typedef struct
{
    unsigned int  ipAddress;
    unsigned char aMacAddress[6];
    unsigned int  nodeId;
} tStructToMarshall;

__declspec(dllexport) int SetCommunicationParameters(tStructToMarshall parameters);

此代码使用cl /LD /Zi Communication.c 编译以生成用于调试的 DLL 和 PDB 文件。

为了在 .Net 应用程序中使用此代码,我使用 P/Invoke Interop Assistant 为包装 DLL 生成 C# 代码:

这会导致显示的 C# 包装器,我对其进行了修改以使用正确的 DLL 而不是 "<unkown>"。另外,我确实想要aMacAddress 的字节数组,而不是字符串(尽管我知道这通常会有帮助):

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public struct tStructToMarshall
{
    /// unsigned int
    public uint ipAddress;
    /// unsigned char[6]
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 6)]
    public byte[] aMacAddress;
        // ^^^^^^ Was "string"
    /// unsigned int
    public uint nodeId;
}

public partial class NativeMethods
{
    internal const string DllName = "lib/Communication.dll";

    /// Return Type: int
    ///parameters: tStructToMarshall->Anonymous_75c92899_b50d_4bea_a217_a69989a8d651
    [System.Runtime.InteropServices.DllImportAttribute(DllName, EntryPoint = "SetCommunicationParameters")]
                                                    // ^^^^^^^ Was "<unknown>"
    public static extern int SetCommunicationParameters(tStructToMarshall parameters);
}

我有两个问题: 1. 当我将结构的值设置为非零值并查找节点 ID 时,它被损坏或损坏。 IP 地址和 MAC 地址很好,但是数组后的任何结构成员(包括其他数据类型)都被破坏了,即使我指定了个位数的值,在 C 输出中也会显示非常大的数字。 2.当我调用该方法时,我收到一条错误消息:

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

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

如何解决这些问题?

【问题讨论】:

    标签: c# c pinvoke calling-convention packing


    【解决方案1】:

    1。结构损坏

    这种“损坏”是由对齐问题引起的。互操作助手忽略了#pragma pack(1) 指令,并使用默认值described here

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

    • 类型的对齐方式是其最大元素的大小(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,内存看起来像这样(忽略字节序问题,如果你得到对齐,这无关紧要对):

    Byte   Value   C expects           .NET Expects:
    0      0x01    \                   \
    1      0xAB     } IP Address        } IP Address
    2      0xCD     |                   |
    3      0xEF    /                   /
    4      0x01    } aMacAddress[0]    } aMacAddress[0]
    5      0x02    } aMacAddress[1]    } aMacAddress[1]
    6      0x03    } aMacAddress[2]    } aMacAddress[2]
    7      0x04    } aMacAddress[3]    } aMacAddress[3]
    8      0x05    } aMacAddress[4]    } aMacAddress[4]
    9      0x06    } aMacAddress[5]    } aMacAddress[5]
    10     0x00    \                   } Padding
    11     0x00     } Node ID          } Padding
    12     0x00     |                  \
    13     0x01    /                    } Node ID
    14     0x??    } Unititialized      |
    15     0x??    } Unititialized     /
    

    请注意,.NET 期望节点 ID(一个 4 字节值)从地址 12 开始,该地址可以被 4 整除。它实际上使用的是未初始化的内存,这会导致您的结果不正确。

    修复:

    将命名参数 Pack=1 添加到您对 StructLayoutAttribute 的调用中:

    [System.Runtime.InteropServices.StructLayoutAttribute(
        System.Runtime.InteropServices.LayoutKind.Sequential, Pack=1, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
                                                          //  ^^^^^^ - Here
    

    2。堆栈不平衡

    这是由不同的calling conventions 引起的。当您调用带有参数的方法时,这些参数会进入堆栈。在某些调用约定下,调用者会在方法返回后清理堆栈。在其他情况下,被调用的函数在返回之前进行清理。

    当您使用cl 编译未注释的函数时,它使用cdecl 约定,其中指出:

    调用者清理堆栈。这允许使用可变参数调用函数,这使其适用于接受可变数量参数的方法,例如printf

    因此是 C 编译器的一个很好的默认值。当您将函数导入 .NET 时,它使用 stdcall 约定,其中指出:

    被调用者清理堆栈。这是使用平台调用调用非托管函数的默认约定。

    这用于 Windows API(这可能是 P/Invoke 最常用的库),因此是 P/Invoke 的一个很好的默认值,但两者不兼容。

    several other questions 中对此进行了一些描述(可能是因为它有一个 Googleable 错误消息,与您的结构损坏不同)并得到了here 的回答。

    修复:

    CallingConvention = CallingConvention.Cdecl 添加到您的 DllImportAttribute:

    [System.Runtime.InteropServices.DllImportAttribute(DllName, EntryPoint = "SetCommunicationParameters", CallingConvention = CallingConvention.Cdecl)]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-02
      • 1970-01-01
      • 2012-10-24
      • 1970-01-01
      • 1970-01-01
      • 2014-03-26
      • 1970-01-01
      相关资源
      最近更新 更多