将非 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++的主要内容,如果未能解决你的问题,请参考以下文章

C++ 到 C# 回调结构编组

将浮点数组编组到 C#

如何将 C 指针编组到 C# 结构数组

c#编组尝试将结构从cpp dll返回到c#应用程序,该应用程序被分配为它自己的dll

将复杂结构编组到 C#

在 C#/C++ 之间编组复杂结构