使用 P/Invoke 时为非托管代码存储数据

Posted

技术标签:

【中文标题】使用 P/Invoke 时为非托管代码存储数据【英文标题】:Storing data for unmanaged code when using P/Invoke 【发布时间】:2017-03-21 08:10:33 【问题描述】:

我有一个此结构的数组(此处显示为 C#,但也存在于 C++ 中):

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct

    IntPtr name;             //pointer to string, char* on C++ side

    long pValues;
    long jValues;
    long eValues;
    long kValues;

    int cost;
;

以及一个在 C++ DLL 中运行的算法,从托管 C# 代码中调用。它占用大量 CPU,因此需要这样做,因为它在 C++ 中的运行速度比 C# 快得多。托管(C#)端永远不必知道结构数据的内容,因为该算法只返回一个整数数组。

那么,在应用程序的生命周期内,我将如何以最有效的方式(即开销最小)存储这些数据?我想我把它缩小到两个选项:

    在 C# 中初始化结构并设置值,使用 GCHandle 固定内存并在我想工作时传递对 C++ 的引用(请参阅 Unity 论坛上的 this post)

    在 C++ 中初始化结构并设置值,让结构在非托管端持久保存在内存中

所以我的问题非常具体:

对于 1,我对编组的工作方式感到困惑。看起来在MSDN: Copying and Pinning 中,您可以通过固定和传递对固定数据的引用来传递结构数组,无需复制或转换任何数据(并且只要结构两边看起来一样)。我读对了吗,它实际上是这样工作的吗?参考 Unity3d 论坛帖子,我看到 Marshal.PtrToStructure 被调用;我认为执行复制操作?由于在这种情况下数据将存储在托管端,因此每次调用 C++ 函数时都必须复制和/或转换数据会导致大量开销,除非我认为这些类型的操作很多比实际价格贵。

对于 2,我想知道是否可以在 C++ 调用之间保持持久性。据我所知,如果您从 DLL 进行 P/Invoking,则不能在非托管端拥有持久数据,因此我不能只在其中定义和存储我的结构数组,从而使唯一的数据在managed 和 unmanaged 由非托管算法产生的 int 数组。这是正确的吗?

非常感谢您花时间阅读和帮助!

【问题讨论】:

如果你的struct 中有一个string,那么你的结构就不是blittable...它会减慢一切,因为struct 将在进入和退出时被“翻译”的 C 函数。见msdn.microsoft.com/en-us/library/ef4c3t39.aspx。如果 C 结构有 LPSTRLPTSTRLPWSTRchar* ,则放置 IntPtr 啊,谢谢,我错过了!固定。 您是否了解过为什么它在 C++ 中的运行速度比 C# 快?它可能很容易修复,例如避免边界检查、更好的缓存局部性等。而且您当然可以将数据保存在非托管端 - DLL 存在于您的进程空间中,它可以随意分配和释放内存。小心固定托管内存 - 它可能会阻止垃圾收集正常工作(特别是,由于显而易见的原因,不可能移动固定内存 - 如果您不小心,这很容易导致过多的堆碎片)。 你说托管代码从不引用这种形式的数据。然而,它作为 C# 结构存在。这里没有加起来。在我看来,您只需要完全在非托管方面使用该结构。如果它需要跨调用持久化,请通过不透明指针引用它。 Luaan:我实际上没有任何关于它更快的硬数据,我只是在 C++ 中完全实现了算法,并且知道 C++ 编译器在低级优化方面要好得多。用这些知识在 C# 中重新实现似乎很愚蠢,它只会让它变慢,不是吗?你有一个链接来解释保持由 DLL 分配的内存吗?我认为在非托管代码中分配的任何数据并且在返回托管时没有正确清理基本上只是泄漏! 【参考方案1】:

如果 C# 代码不需要知道数组和结构的内部结构,请不要将其暴露给 C# 代码。在非托管代码中完成此类型的所有工作并避免编组开销。

基本上,您希望遵循这个基本模式。我相信细节会有所不同,但这应该会给你基本的概念。

C++

MyStruct* newArray(const int len)

    return new MyStruct[len];


void workOnArray(MyStruct* array, const int len)

    // do stuff with the array


void deleteArray(const MyStruct* array)

    delete[] array;

C#

[DllImport(dllname)]
static extern IntPtr newArray(int len);

[DllImport(dllname)]
static extern void workOnArray(IntPtr array int len);

[DllImport(dllname)]
static extern void deleteArray(IntPtr array);

【讨论】:

非常感谢,这正是我需要知道的!我不确定如何在非托管调用之间保留数据 其实,如果你不介意的话,这和使数组静态化有什么区别呢?我一直在试验,我发现在 DLL 中声明为静态的数据在加载 DLL 时仍然存在。那么这是否意味着只要我不卸载 DLL,我就可以将数据保存在静态数组中,而不是使用不透明的指针?还是有我遗漏的细节? 静态数组也可以工作,但会阻止多线程,或者实际上是多个不同的结构实例。还会阻止动态大小的数组。 也感谢您的回答!你是一个很棒的人

以上是关于使用 P/Invoke 时为非托管代码存储数据的主要内容,如果未能解决你的问题,请参考以下文章

.Net调用非托管代码(P/Invoke与C++InterOP)

为非托管代码提供托管控制句柄 - 访问冲突

使用 P/Invoke 从 Unity 高效地与非托管代码对话

P/Invoke C++ DLL 用于填充托管缓冲区

非公共工件的 Maven 存储库托管? [关闭]

P/Invoke之C#调用动态链接库DLL