【问题标题】:PInvoke Class by value for c++ structPInvoke Class by value for c++ struct
【发布时间】:2016-09-09 17:13:11
【问题描述】:

我有一个关于 P/Invoke 的小问题。 目前,我正在为原始 C# 实现很糟糕的硬件设备实现 C API 的包装器。 我遇到的问题/不便如下:

API 实现了多个结构来获取/设置设备设置。它们具有相似的结构,因此我给出了一个示例实现:

typedef struct _Struct1
{
    bool b1;
    unsigned int ui1;
    unsigned int ui2;
    unsigned int ui3;
    unsigned int ui4;
    unsigned int ui5;
} Struct1;

getter 和 setter 实现如下:

unsigned int SetSetting(bool b1, Struct1 s1);
unsigned int GetSetting(bool b1, Struct1 &s1);

C# DllImport 是:

[DllImport("api.dll", EntryPoint = "GetSetting", CallingConvention = CallingConvention.StdCall]
public static extern uint GetStatus(bool b1, CStruct1 s1);

[DllImport("api.dll", EntryPoint = "SetSetting", CallingConvention = CallingConvention.StdCall]
public static extern uint SetStatus(bool b1, SStruct1 s1);

C# struct 实现是使用 LayoutKind.Sequential 完成的,它准确地表示了 C 结构,并且 P/Invoke 调用工作得很好,除了: 您可能注意到,getter 和 setter DllImports 略有不同,因为 setter 使用 C# 结构 (SStruct1),而 getter 使用 C# 类 (CStruct1):

[StructLayout(LayoutKind.Sequential)]
public struct SStruct1
{
    public bool b1;
    public uint ui1;
    public uint ui2;
    public uint ui3;
    public uint ui4;
    public uint ui5;
}

[StructLayout(LayoutKind.Sequential)]
public class CStruct1 { /* same as struct */ }

我无法让 P/Invoke 仅使用一个类或结构来工作。

对于这个问题的更方便的解决方案,您有什么建议吗?

将结构更改为类,反之亦然,确实会产生 PInvokeStackImbalance 异常。

我猜这与 setter 期望 struct by value 和 getter by reference 的事实有关,因为它是 out-parameter-equivalent 的。我已经尝试过任何我能想到的 Marshal 属性和参数定义,但我遇到了死胡同。 Google 似乎没有帮助。

非常感谢任何帮助。

编辑

我弄错了:API 是 ANSI C,而不是 C++。

更新

我已经尝试了建议的解决方案(SStruct 用于 setter,SStruct 用于 getter),虽然对于结构完全由 uint 组成的函数可以正常工作,但它会为相关结构产生完整的垃圾:

| Name | correct value   | returned value |
| ---- | --------------- | -------------- |
| b1   | False           | True           |
| ui1  | 0               | 3355443223     |
| ui2  | 0               | 1677721600     |
| ui3  | 0               | 0              |
| ui4  | 0               | 0              |
| ui5  | 0               | 0              |

记住:使用前面提到的 DllImports 时,效果很好。

【问题讨论】:

  • 您可以使用 C++ 制作 C 接口,也可以使用 C++/CLI 封装所有数据结构。
  • 首先我不会使用 StdCall 而是使用 C 调用约定,因为您使用的是 c++ 和 c#。标准调用是窗口标准调用约定。结构需要通过引用(指针)而不是值来调用。因此,您必须将结构声明为 IntPtr。所以使用 Marshal StructureToPtr 和 Marshal PtrToStructure。
  • 你需要使用struct,因为结构是按值传递给SetSetting的。然后GetSetting 接受结构的地址,因此使用out 参数或ref 参数作为结构。请忽略@jdweng,根本不需要使用IntPtr 来传递结构。我对这个问题投了反对票,因为它不完整。您未能显示结构声明。
  • getter 参数必须声明为[Out] out CStruct1。使用bool很麻烦,在C++程序中很可能是1字节,而不是C中默认的4字节。使用[MarshalAs(UnmanagedType.U1)]。请验证 C# 中的 Marshal.SizeOf() 是否与 C++ 中的 sizeof 匹配,以便确保结构声明正确。
  • 我已经尝试了@DavidHeffernan 建议的解决方案,但我很困惑:虽然在一个电话上工作正常,但在另一个电话上,它确实给我另一个电话的垃圾。我会尽快更新问题以描述我的问题。

标签: c# class struct pinvoke pass-by-value


【解决方案1】:
  1. 检查 C# 项目的 x86 或 x64 构建设置。
  2. 验证调用约定。我认为在 VC++ 中默认是cdecl,尝试在 DllImport 中指定它。

【讨论】:

    【解决方案2】:

    基于@HansPassant 的评论,您希望使用out SStruct 作为getter 方法。 此外,您需要确保 dotnet 结构和编译后的 api.dll 结构中的 bool 大小相同(结构打包可能是 1 个字节,但不太可能见 here:)。

    另外,您使用CallingConvention=StdCall。使用错误调用约定的症状是堆栈不平衡异常。如果您不知道,您想尝试使用CallingConvention.Cdecl

    大多数情况下,当在依赖中打开 dll 时,如果符号不以 @<some int> 结尾,则约定为 Cdecl,但不能保证。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-22
      • 2013-03-20
      相关资源
      最近更新 更多