【发布时间】: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