我有一个非托管的C ++库。我想公开.NET应用程序的功能。有一个部分功能我不知道如何处理:
typedef void(free_fn *)(void *); void put(void * data,free_fn deallocation_function);
这个想法是你将动态分配的缓冲区传递给函数并提供释放函数。该库将异步处理数据,并在以后不再需要数据时释放缓冲区:
void * p = malloc(100); ......填写缓冲区...... put(p,free);
如何向.NET应用程序公开此类内容?
答案 0 :(得分:5)
这样做时要非常小心。 .NET确实非常希望将其对象固定在非托管例程的路上并在出路时取消固定。如果您的非托管代码保留在指针值上,那时已经固定,那么内存将被移动或垃圾收集,或两者兼而有之。
特别是委托编写功能指针的情况(相信我这一点 - 我发现编组的代表被垃圾收集在我身上 - 我让微软的人验证了这一点)。此问题的最终解决方案是将静态表中的代理副本存储在与唯一事务id配对的静态表中,然后创建一个非托管函数,在调用时通过事务ID查找表中的委托然后执行它。这很难看,如果我有另一种选择,我会用它。
以下是在您的情况下执行此操作的最佳方法 - 因为您的非托管代码使用了一个设置而忘记了它的模型,那么您应该使您的API更加厚实。在托管C ++中创建一个包装器,通过非托管例程分配内存,将数据复制到其中,然后将其与指向非托管解除分配器的指针一起传递。
答案 1 :(得分:2)
通常,库的.NET使用者不会将动态创建的数组传递给您的函数。据我所知,.NET中的所有容器都是垃圾回收。
无论如何,您需要为非托管代码创建托管包装器。有很多教程和文章,here is one to start with。
在为非管理代码编写.NET包装器时,我发现您希望更多地专注于保留功能而不是在.NET中使每个功能都可访问。在您的示例中,最好让托管包装器将数组复制到非托管内存中,并在库中执行您需要的任何操作。这样,您就不必对托管内存进行任何固定,也不必对托管到非托管内存进行编组,以避免.NET运行时的垃圾回收。但是,实现托管包装器的方式实际上取决于该功能的用途。
如果您真的想在.NET中为函数实现此函数,则需要查看Marshal class in .NET以控制非托管代码中的托管内存。
对于回调函数,首先需要创建可以在托管代码中分配的.NET代理。然后,您需要在库内部创建一个非托管自由函数,该函数由put函数的非托管版本调用。这个非托管的免费功能将负责调用托管代表,如果用户分配了一个。
答案 2 :(得分:1)
你肯定不想固定托管缓冲区,因为尝试在非托管代码中释放它似乎是疯狂的最短途径。如果您无法在完全托管代码中重写此部分,那么最好的办法是在包装器中复制数据,或者完全隐藏管理世界中的缓冲区管理。
如果你有胆量(以及受虐狂的耐力),你可以将缓冲区固定在包装器中,然后传入一个托管缓冲区的托管函数的编组委托。但是,我不建议。不得不做几个托管包装器教会了我暴露绝对最小非托管功能的价值,即使这意味着你必须重写托管代码中的一些东西。越过这个边界就像过去从东德到西德一样容易,更不用说性能命中了。
答案 3 :(得分: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);
}
答案 4 :(得分:1)
之前的一些海报一直在使用MC ++,这是不推荐使用的。 C ++ / CLI是一个更优雅的解决方案。
最好的互操作技术是隐式互操作,不是明确的。我不相信任何人对此有评论。但是,它使您能够从托管的< - > native填充您的类型,如果您对类型定义或结构布局进行更改,它将不会导致重大更改(显式互操作会发生)。
这篇wikiepedia文章记录了一些差异,是进一步信息的良好起点。
P/Invoke (explicit and implicit)
此外,网站marshal-as.net有一些关于这个新方法的示例和信息(同样,更理想,因为如果重新定义本机结构,它不会破坏您的代码)。
答案 5 :(得分:0)
您必须拥有函数本身的托管包装器(如果您想传入托管函数,则需要非托管包装器)。或者,将非托管函数指针视为托管世界中的不透明句柄。
答案 6 :(得分:0)
既然你提到它是异步的,我会这样做。 .Net公开的函数只接受数据,但不接受委托。您的代码将固定数据和函数指针传递给一个简单地取消固定数据的函数。这样可以将内存清理到GC,但确保在异步部分完成之前它不会清理它。