将非 Blittable 结构从 C# 编组到 C++
Posted
技术标签:
【中文标题】将非 Blittable 结构从 C# 编组到 C++【英文标题】:Marshalling non-Blittable Structs from C# to C++ 【发布时间】:2012-07-10 15:13:02 【问题描述】:我正在重写我公司的 C# 和 C++ 之间接口的库代码的过度设计和不可维护的部分。我已经开始研究 P/Invoke,但似乎没有太多可访问的帮助。
我们将包含各种参数和设置的结构传递给非托管代码,因此我们定义了相同的结构。我们不需要在 C++ 端更改任何这些参数,但我们确实需要在 P/Invoked 函数返回后访问它们。
我的问题是:
-
传递字符串的最佳方式是什么?有些很短(可以由我们设置的设备 ID),有些是文件路径(可能包含亚洲字符)
我应该将 IntPtr 传递给 C# 结构,还是应该让 Marshaller 通过将结构类型放入函数签名中来处理它?
我是否应该担心任何非指针数据类型,如布尔值或枚举(在其他相关结构中)?我们在 C++ 中设置了将警告视为错误标志,因此我们不能使用枚举的 Microsoft 扩展来强制数据类型。
P/Invoke 真的可行吗?有一些关于隐式 P/Invoke 的 Microsoft 文档说它更安全且性能更高。
作为参考,这是我迄今为止编写的一对结构体:
C++
/**
Struct used for marshalling Scan parameters from managed to unmanaged code.
*/
struct ScanParameters
LPSTR deviceID;
LPSTR spdClock;
LPSTR spdStartTrigger;
double spinRpm;
double startRadius;
double endRadius;
double trackSpacing;
UINT64 numTracks;
UINT32 nominalSampleCount;
double gainLimit;
double sampleRate;
double scanHeight;
LPWSTR qmoPath; //includes filename
LPWSTR qzpPath; //includes filename
;
C#
/// <summary>
/// Struct used for marshalling scan parameters between managed and unmanaged code.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ScanParameters
[MarshalAs(UnmanagedType.LPStr)]
public string deviceID;
[MarshalAs(UnmanagedType.LPStr)]
public string spdClock;
[MarshalAs(UnmanagedType.LPStr)]
public string spdStartTrigger;
public Double spinRpm;
public Double startRadius;
public Double endRadius;
public Double trackSpacing;
public UInt64 numTracks;
public UInt32 nominalSampleCount;
public Double gainLimit;
public Double sampleRate;
public Double scanHeight;
[MarshalAs(UnmanagedType.LPWStr)]
public string qmoPath;
[MarshalAs(UnmanagedType.LPWStr)]
public string qzpPath;
【问题讨论】:
究竟是什么不起作用。使用 non-bittable 术语很奇怪,因为您列出的结构肯定会变成字节数组。我会定义结构中每个字段的长度。 我更多的是寻找使用 P/Invoke 的正确设计模式。我认为任何带有指针的东西都是不可 blittable 的? 【参考方案1】:blittable 类型是一种在托管代码和非托管代码之间具有共同表示的类型,因此可以在它们之间传递很少或没有问题,例如字节、int32 等。
非 blittable 类型没有通用表示,例如System.Array、System.String、System.Boolean 等
通过为非 blittable 类型指定 MarshalAs 属性,您可以告诉编组器应该将其转换为什么。 请参阅此article on Blittable and Non-Blittable Types 了解更多信息
1 - 传递字符串的最佳方式是什么?有些很短(我们可以设置的设备ID),有些是文件路径(可能包含亚洲字符)
StringBuilder 通常被推荐为最容易使用,但我经常使用纯字节数组。
2 - 我应该将 IntPtr 传递给 C# 结构,还是应该让 Marshaller 通过将结构类型放入函数签名中来处理它?
如果该方法需要一个指针,则传递一个 IntPtr,尽管在许多情况下您可能会摆脱一个 ref,具体取决于它将用于什么。如果它需要在同一个地方停留很长时间,那么我将使用 Marshal 手动分配内存并传递生成的 IntPtr。
3 - 我是否应该担心任何非指针数据类型,如 bool 或 enum(在其他相关结构中)?我们在 C++ 中设置了将警告视为错误标志,因此我们不能使用 Microsoft 的枚举扩展来强制数据类型。
一旦你用正确的编组属性设置了所有东西,我不明白你为什么需要担心。如果对属性有疑问,如果该结构只被托管代码使用,那么该属性将不会被使用。
4 - P/Invoke 实际上是要走的路吗?有一些关于隐式 P/Invoke 的 Microsoft 文档说它更安全且性能更高。
无法对此发表评论,您进入了 Visual C++ 领域。
【讨论】:
为什么要在编组属性上使用字节数组?你会怎么做呢?哦,我的函数签名应该在结构类型前面有 ref 吗?对于我要传递的委托/回调,我是否需要它? 我通常做的是,如果该方法需要一个指向某物的指针,那么我使用 Marshal.AllocHGlobal() 来分配一个适当大小的内存块,然后将它的 IntPtr 传递给该方法。然后使用 Marshal.Copy() 或 Marshal.PtrToStructure() 来检索数据。我只是发现这样做通常比花时间摆弄编组托管类型更快。对于回调,您不需要使用指针传递委托,除非该方法再次明确需要指向函数指针的指针。 对于方法签名 ref 和 IntPtr 在某种程度上是可以互换的。因此,当尝试计算 p/invoke 方法签名时,您可以首先尝试将 ref 与 struct 类型一起使用,如果这不起作用,或者如前所述,如果内存指针需要长时间停留在同一个地方,那么你会尝试使用 IntPtr 而不是 ref struct。以上是关于将非 Blittable 结构从 C# 编组到 C++的主要内容,如果未能解决你的问题,请参考以下文章