1

我有一个非托管 C++ 库。我想公开 .NET 应用程序的功能。我不确定如何处理一个特定的功能:

typedef void (free_fn*) (void*); void put (void *data, free_fn deallocation_function);

这个想法是您将动态分配的缓冲区传递给函数并提供一个释放函数。库将异步处理数据,并在以后不再需要数据时释放缓冲区:

无效 *p = malloc (100); ... 填充缓冲区... put (p, free);

我怎样才能将这种东西暴露给 .NET 应用程序?

4

7 回答 7

5

执行此操作时要非常小心。.NET 非常非常希望在进入非托管例程的过程中将其对象固定,并在退出时取消固定。如果您的非托管代码持有一个指针值,该指针值在进入的过程中被固定,那么很有可能内存将被移动或垃圾收集或两者兼而有之。

对于编组到函数指针的代表尤其如此(相信我 - 我发现编组的代表正在对我进行垃圾收集 - 我让 Microsoft 的人员为我验证了这一点)。这个问题的最终解决方案是将委托的副本存储在与唯一事务 id 配对的静态表中,然后创建一个非托管函数,该函数在调用时通过事务 id 在表中查找委托,然后执行它。它很丑,如果我有其他选择,我会使用它。

在您的情况下,这是执行此操作的最佳方法 - 因为您的非托管代码使用 set it and forget it 模型,所以您应该使您的 API 更粗。在托管 C++ 中创建一个包装器,该包装器通过非托管例程分配内存,将数据复制到其中,然后将其与指向非托管释放器的指针一起传递。

于 2009-01-22T21:37:02.447 回答
2

通常,您的库的 .NET 使用者不会将动态创建的数组传递给您的函数。据我所知,.NET 中的所有容器都是垃圾收集器。

无论如何,您都需要为非托管代码创建一个托管包装器。关于这方面的教程和文章有很多,这里有一篇从.

在为未管理的代码编写 .NET 包装器时,我发现您希望更多地关注保留功能,而不是让每个函数都可以在 .NET 中访问。在您的示例中,最好让托管包装器将数组复制到非托管内存中并在库中执行您需要的任何操作。这样,您不必为了规避 .NET 运行时的垃圾回收而对托管内存进行任何固定或将托管内存编组到非托管内存。但是,如何实现托管包装器实际上取决于该函数的用途。

如果你真的想在 .NET 中实现这个函数,你需要查看.NET 中的 Marshal 类来控制非托管代码中的托管内存。

对于您的回调函数,您首先需要创建可以在托管代码中分配的 .NET 委托。然后,您需要在库内部创建一个非托管自由函数,该函数由 put 函数的非托管版本调用。如果用户分配了一个,那么这个非托管自由函数将负责调用托管委托。

于 2009-01-22T21:08:08.620 回答
1

之前的一些海报一直在使用已弃用的 MC++。C++/CLI 是一种更为优雅的解决方案。

最好的互操作技术是隐式互操作,而不是显式互操作。我不相信有人对此发表评论。但是,它使您能够从托管<->本机编组您的类型,如果您对类型定义或结构布局进行更改,它不会导致重大更改(显式互操作会这样做)。

这篇维基百科文章记录了一些差异,是了解更多信息的良好起点。

P/Invoke(显式和隐式)

此外,站点marshal-as.net有一些关于这种较新方法的示例和信息(同样,更理想的是,如果重新定义原生结构,它不会破坏您的代码)。

于 2009-02-21T03:44:09.947 回答
1

您绝对不想固定托管缓冲区,因为尝试在非托管代码中释放它似乎是通往疯狂的最短途径。如果您不能在完全托管的代码中重写这部分,那么最好的选择是在包装器中制作数据的副本,或者完全对托管世界隐藏缓冲区管理。

如果您有胆量(和受虐的耐力),您可以将缓冲区固定在包装器中,然后传入取消固定缓冲区的托管函数的编组委托。但是,我不建议这样做。不得不做几个托管包装器教会了我公开绝对最小的非托管功能的价值,即使这意味着您必须在托管代码中重写一些东西。跨越这个边界就像从东德到西德一样容易,更不用说性能打击了。

于 2009-01-22T21:51:15.697 回答
1

大多数回复建议应将数据从托管缓冲区复制到非托管缓冲区。你会怎么做呢?以下实施可以吗?

void managed_put (byte data_ __gc[], size_t size_)
{
    //  Pin the data
    byte __pin *tmp_data = &data_[0];

    //  Copy data to the unmanaged buffer.
    void *data = malloc (size_);
    memcpy (data, (byte*) tmp_data, size_);

    //  Forward the call
    put (data, size_, free);
}
于 2009-01-22T23:24:03.120 回答
0

既然你提到它是异步的,我会这样做。.Net 公开的函数只接受数据但不接受委托。您的代码将固定数据和函数指针传递给将简单地取消固定数据的函数。这会将内存清理留给 GC,但要确保在异步部分完成之前它不会清理它。

于 2009-01-22T23:03:23.943 回答
0

您必须拥有函数本身的托管包装器(如果您想传入托管函数,则必须使用非托管包装器)。否则,将非托管函数指针视为托管世界中的不透明句柄。

于 2009-01-22T20:33:33.713 回答