在托管包装器中包装非托管c ++

时间:2022-09-01 20:51:56

I have an unmanaged C++ library. I would like to expose the functionality for .NET applications. There's one partucular function I am not sure how to handle:

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

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

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

The idea is that you pass dynamically allocated buffer to the function and supply a deallocation function. The library will process the data asynchronously and will release the buffer later on when data is no longer needed:

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

void *p = malloc (100); ... fill in the buffer... put (p, free);

void * p = malloc(100); ...填写缓冲区... put(p,free);

How can I expose this kind of thing to .NET applications?

我如何向.NET应用程序公开这种东西?

7 个解决方案

#1


Be very careful when you do this. .NET really, really wants to have its objects be pinned on the way into an unmanaged routine and unpinned on the way out. If your unmanaged code holds onto a pointer value, that had been pinned on the way in then there is very real chance that the memory will be moved or garbage collected or both.

这样做时要非常小心。 .NET确实非常希望将其对象固定在非托管例程的路上并在出路时取消固定。如果你的非托管代码保留在一个指针值上,那么那个已被固定在那里的路上很可能会有内存被移动或被垃圾收集或两者兼而有之。

This is especially the case with delegates marshalled to function pointers (trust me on this - I found that marshaled delegates were being garbage collected on me - I had people at Microsoft verify that for me). The ultimate solution to this problem is to stash away copies of your delegates in a static table paired with a unique transaction id, then create an unmanaged function that when called looks up the delegate in the table via transaction id then executes it. It's ugly and if I had another choice, I would've used it.

对于功能指针的代理人来说情况尤其如此(相信我 - 我发现编组的代表被垃圾收集在我身上 - 我让微软的人验证了这一点)。此问题的最终解决方案是将静态表中的代理副本存储在与唯一事务ID配对的静态表中,然后创建一个非托管函数,在调用时通过事务ID查找表中的委托然后执行它。这很难看,如果我有另一种选择,我会用它。

Here's the best way to do this in your case - since your unmanaged code uses a set it and forget it model, then you should make your API chunkier. Create an wrapper in managed C++ that allocates memory via an unmanaged routine, copies your data into it and then passes it on along with a pointer to an unmanaged deallocator.

以下是在您的情况下执行此操作的最佳方法 - 因为您的非托管代码使用了一个设置而忘记了它的模型,那么您应该使您的API更加厚实。在托管C ++中创建一个包装器,通过非托管例程分配内存,将数据复制到其中,然后将其与指向非托管解除分配器的指针一起传递。

#2


In general, .NET consumers of your library won't be passing dynamically created arrays to your functions. As far as I know, all containers in .NET are garbage collected.

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

Regardless, you will need to make a managed wrapper for your unmanaged code. There are many tutorials and articles on this, here is one to start with.

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

When writing .NET wrappers for unamanged code, I've found that you want to concentrate more on preserving functionality than on making every function accessible in .NET. In your example, it may be better to just have the managed wrapper copy the array into unmanaged memory and perform whatever operations you need to inside the library. This way you don't have to do any pinning of managed memory or Marshalling of managed to unmanaged memory in order to circumvent the .NET runtime's garbage collection. However, how you implement the managed wrapper really depends on what the purpose of that function is.

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

If you really want to implement this function for function in .NET, you will need to look at the Marshal class in .NET for taking control of managed memory in unmanaged code.

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

For your callback function, you will first need to create .NET delegates that can be assigned in managed code. You will then need to make an unmanaged free function internal to your library that is called by the unmanaged version of the put function. This unmanaged free function will then be responsible for calling the managed delegate, if the user assigned one.

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

#3


You definitely don't want to pin the managed buffer, as trying to deallocate it in unmanaged code seems like the shortest route to madness. If you can't rewrite this portion in fully managed code, your best bet is either going to be making a copy of the data in the wrapper, or completely hiding the buffer management from the managed world.

你肯定不想固定托管缓冲区,因为尝试在非托管代码中释放它似乎是疯狂的最短途径。如果你不能在完全托管代码中重写这部分,最好的办法是要么在包装器中复制数据,要么完全隐藏管理世界中的缓冲区管理。

If you had the guts (and the masochistic stamina) you could pin the buffer in the wrapper, then pass in the marshaled delegate of a managed function that unpins the buffer. However, I wouldn't suggest it. Having had to do a couple of managed wrappers has taught me the value of exposing the absolute minimum unmanaged functionality, even if it means you have to rewrite some things in managed code. Crossing that boundary is about as easy as going from East Germany to West Germany used to be, to say nothing of the performance hits.

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

#4


Most replies suggest that the data should be copied from managed buffer to unmanaged buffer. How exactly would you do that? Is following implementation OK?

大多数回复表明数据应该从托管缓冲区复制到非托管缓冲区。你究竟会怎么做?以下实施可以吗?

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);
}

#5


Some of the previous poster's have been using MC++, which is deprecated. C++/CLI is far more elegant of a solution.

以前的一些海报一直在使用MC ++,这是不推荐使用的。 C ++ / CLI是一个更优雅的解决方案。

The BEST, technique for interop, is implicit interop, not explicit. I dont believe anybody has commented on this yet. However, it gives you the ability to marshal your types from managed<->native where if you make a change to your type definition or structure layout, it will not result in a breaking change (which explicit interop does).

最好的互操作技术是隐式互操作,不是明确的。我不相信任何人对此有评论。但是,它使您能够从托管的< - > native填充您的类型,如果您对类型定义或结构布局进行更改,则不会导致重大更改(显式interop会发生)。

This wikiepedia article documents some of the differences and is a good starting point for further information.

这篇wikiepedia文章记录了一些差异,是进一步信息的良好起点。

P/Invoke (explicit and implicit)

P / Invoke(显式和隐式)

Also, the site marshal-as.net has some examples and information as to this newer method (again, more ideal as it will not break your code if the a native struct is re-defined).

此外,网站marshal-as.net提供了一些关于这种新方法的示例和信息(同样,更理想,因为如果重新定义本机结构,它将不会破坏您的代码)。

#6


You'd have to have managed wrappers for the functions themselves (or unmanaged wrappers if you want to pass in managed functions). Or else, treat the unmanaged function pointers as opaque handles in the managed world.

您必须拥有函数本身的托管包装器(如果要传递托管函数,则需要非托管包装器)。或者,将非托管函数指针视为托管世界中的不透明句柄。

#7


Since you mentioned it was asyncronous, I'd do it this way. The .Net exposed function only takes the data but doesn't take a delegate. Your code passes the pinned data and a function pointer to a function that will simply unpin the data. This leaves the memory cleanup to the GC, but makes sure the it won't clean it up till the asyncronous part is done.

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

#1


Be very careful when you do this. .NET really, really wants to have its objects be pinned on the way into an unmanaged routine and unpinned on the way out. If your unmanaged code holds onto a pointer value, that had been pinned on the way in then there is very real chance that the memory will be moved or garbage collected or both.

这样做时要非常小心。 .NET确实非常希望将其对象固定在非托管例程的路上并在出路时取消固定。如果你的非托管代码保留在一个指针值上,那么那个已被固定在那里的路上很可能会有内存被移动或被垃圾收集或两者兼而有之。

This is especially the case with delegates marshalled to function pointers (trust me on this - I found that marshaled delegates were being garbage collected on me - I had people at Microsoft verify that for me). The ultimate solution to this problem is to stash away copies of your delegates in a static table paired with a unique transaction id, then create an unmanaged function that when called looks up the delegate in the table via transaction id then executes it. It's ugly and if I had another choice, I would've used it.

对于功能指针的代理人来说情况尤其如此(相信我 - 我发现编组的代表被垃圾收集在我身上 - 我让微软的人验证了这一点)。此问题的最终解决方案是将静态表中的代理副本存储在与唯一事务ID配对的静态表中,然后创建一个非托管函数,在调用时通过事务ID查找表中的委托然后执行它。这很难看,如果我有另一种选择,我会用它。

Here's the best way to do this in your case - since your unmanaged code uses a set it and forget it model, then you should make your API chunkier. Create an wrapper in managed C++ that allocates memory via an unmanaged routine, copies your data into it and then passes it on along with a pointer to an unmanaged deallocator.

以下是在您的情况下执行此操作的最佳方法 - 因为您的非托管代码使用了一个设置而忘记了它的模型,那么您应该使您的API更加厚实。在托管C ++中创建一个包装器,通过非托管例程分配内存,将数据复制到其中,然后将其与指向非托管解除分配器的指针一起传递。

#2


In general, .NET consumers of your library won't be passing dynamically created arrays to your functions. As far as I know, all containers in .NET are garbage collected.

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

Regardless, you will need to make a managed wrapper for your unmanaged code. There are many tutorials and articles on this, here is one to start with.

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

When writing .NET wrappers for unamanged code, I've found that you want to concentrate more on preserving functionality than on making every function accessible in .NET. In your example, it may be better to just have the managed wrapper copy the array into unmanaged memory and perform whatever operations you need to inside the library. This way you don't have to do any pinning of managed memory or Marshalling of managed to unmanaged memory in order to circumvent the .NET runtime's garbage collection. However, how you implement the managed wrapper really depends on what the purpose of that function is.

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

If you really want to implement this function for function in .NET, you will need to look at the Marshal class in .NET for taking control of managed memory in unmanaged code.

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

For your callback function, you will first need to create .NET delegates that can be assigned in managed code. You will then need to make an unmanaged free function internal to your library that is called by the unmanaged version of the put function. This unmanaged free function will then be responsible for calling the managed delegate, if the user assigned one.

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

#3


You definitely don't want to pin the managed buffer, as trying to deallocate it in unmanaged code seems like the shortest route to madness. If you can't rewrite this portion in fully managed code, your best bet is either going to be making a copy of the data in the wrapper, or completely hiding the buffer management from the managed world.

你肯定不想固定托管缓冲区,因为尝试在非托管代码中释放它似乎是疯狂的最短途径。如果你不能在完全托管代码中重写这部分,最好的办法是要么在包装器中复制数据,要么完全隐藏管理世界中的缓冲区管理。

If you had the guts (and the masochistic stamina) you could pin the buffer in the wrapper, then pass in the marshaled delegate of a managed function that unpins the buffer. However, I wouldn't suggest it. Having had to do a couple of managed wrappers has taught me the value of exposing the absolute minimum unmanaged functionality, even if it means you have to rewrite some things in managed code. Crossing that boundary is about as easy as going from East Germany to West Germany used to be, to say nothing of the performance hits.

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

#4


Most replies suggest that the data should be copied from managed buffer to unmanaged buffer. How exactly would you do that? Is following implementation OK?

大多数回复表明数据应该从托管缓冲区复制到非托管缓冲区。你究竟会怎么做?以下实施可以吗?

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);
}

#5


Some of the previous poster's have been using MC++, which is deprecated. C++/CLI is far more elegant of a solution.

以前的一些海报一直在使用MC ++,这是不推荐使用的。 C ++ / CLI是一个更优雅的解决方案。

The BEST, technique for interop, is implicit interop, not explicit. I dont believe anybody has commented on this yet. However, it gives you the ability to marshal your types from managed<->native where if you make a change to your type definition or structure layout, it will not result in a breaking change (which explicit interop does).

最好的互操作技术是隐式互操作,不是明确的。我不相信任何人对此有评论。但是,它使您能够从托管的< - > native填充您的类型,如果您对类型定义或结构布局进行更改,则不会导致重大更改(显式interop会发生)。

This wikiepedia article documents some of the differences and is a good starting point for further information.

这篇wikiepedia文章记录了一些差异,是进一步信息的良好起点。

P/Invoke (explicit and implicit)

P / Invoke(显式和隐式)

Also, the site marshal-as.net has some examples and information as to this newer method (again, more ideal as it will not break your code if the a native struct is re-defined).

此外,网站marshal-as.net提供了一些关于这种新方法的示例和信息(同样,更理想,因为如果重新定义本机结构,它将不会破坏您的代码)。

#6


You'd have to have managed wrappers for the functions themselves (or unmanaged wrappers if you want to pass in managed functions). Or else, treat the unmanaged function pointers as opaque handles in the managed world.

您必须拥有函数本身的托管包装器(如果要传递托管函数,则需要非托管包装器)。或者,将非托管函数指针视为托管世界中的不透明句柄。

#7


Since you mentioned it was asyncronous, I'd do it this way. The .Net exposed function only takes the data but doesn't take a delegate. Your code passes the pinned data and a function pointer to a function that will simply unpin the data. This leaves the memory cleanup to the GC, but makes sure the it won't clean it up till the asyncronous part is done.

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