如何在C ++ / CLI中包装C库回调

给定以下C库以及要求设置缓冲区的回调事件,如何以类型安全的方式编写适当的C ++ / CLI包装器?

// The callback signature typedef void (__cdecl *BUFFERALLOCATOR)(void *opaque, void **buffer); // A struct that contains the context of the library struct lib_context_base_s { // The stored callback function pointer BUFFERALLOCATOR buffer_allocator; // Opaque pointer that contain the local context. Needed in C because // C doesn't have closures (functions that knows the context where // they are defined) void* opaque; }; typedef struct lib_context_base_s lib_context_base; // Init the base context lib_context_base* new_lib_context_base() { return malloc(sizeof(lib_context_base)); } // Free the base context void free_lib_context_base(lib_context_base *lib_context_base) { free(lib_context_base); } // Set the buffer allocation callback void set_allocate_buffer_callback(lib_context_base *lib_context_base, BUFFERALLOCATOR allocate_buffer, void* opaque) { lib_context_base->buffer_allocator = allocate_buffer; lib_context_base->opaque = opaque; } 

使用delegate void BufferAllocator(ref IntPtr buffer) ,托管代码可以使用该库。

我将坚持类型安全原则:我知道已经有Marshal.GetFunctionPointerForDelegate但是需要在C ++ / CLI中Marshal.GetFunctionPointerForDelegate函数指针类型并隐藏编组非托管 – >托管的工作方式(调试要困难得多,我不喜欢不理解什么是正在发生的事情)。 刚刚注意到这种方法与此类似,但不需要托管本机类(更少的开销)。 请告诉我你是否知道如何进一步简化它(保持类型安全和编组控制)并减少开销。

以下是C ++ / CLI Wrapper.h标头:

 #include  using namespace System; using namespace System::Runtime::InteropServices; namespace LibraryWrapper { // Declare the cdecl function that will be used void cdecl_allocate_buffer(void *opaque, void **buffer); public ref class Library { public: // The BufferAllocator delegate declaration, available to any clr language // [In, Out] attributes needed (?) to pass the pointer as reference delegate void BufferAllocator([In, Out] IntPtr% buffer); internal: // The stored delegate ref to be used later BufferAllocator ^_allocate_buffer; private: // Native handle of the ref Library class, castable to void * gcroot *_native_handle; // C library context lib_context_base *_lib_context_base; public: Library(); ~Library(); // The clr callback setter equivalent to the C counterpart, don't need // the context because in CLR we have closures void SetBufferAllocateCallback(BufferAllocator ^allocateBuffer); }; } 

遵循C ++ / CLi Wrapper.cpp定义:

 #include "wrapper.h" namespace LibraryWrapper { Library::Library() { // Construct the native handle _native_handle = new gcroot(); // Initialize the library base context _lib_context_base = new_lib_context_base(); // Null the _allocate_buffer delegate instance _allocate_buffer = nullptr; } Library::~Library() { free_lib_context_base(_lib_context_base); delete _native_handle; } void Library::SetBufferAllocateCallback(BufferAllocator ^allocateBuffer) { _allocate_buffer = allocateBuffer; // Call the C lib callback setter. Use _native_handle pointer as the opaque data set_allocate_buffer_callback(_lib_context_base, cdecl_allocate_buffer, _native_handle); } void cdecl_allocate_buffer(void *opaque, void **buffer) { // Cast the opaque pointer to the hnative_handle ref (for readability) gcroot & native_handle = *((gcroot*)opaque); // Prepare a IntPtr wrapper to the buffer pointer IntPtr buffer_cli(*buffer); // Call the _allocate_buffer delegate in the library wrapper ref native_handle->_allocate_buffer(buffer_cli); // Set the buffer pointer to the value obtained calling the delegate *buffer = buffer_cli.ToPointer(); } } 

可以这种方式使用(C#):

 // Allocate a ~10mb buffer in unmanaged memory. Will be deallocated // automatically when buffer go out of scope IntPtr _buffer = Marshal.AllocHGlobal(10000000); // Init the library wrapper Library library = new Library(); // Set the callback wrapper with an anonymous method library.SetBufferAllocateCallback(delegate(ref IntPtr buffer) { // Because we have closure, I can use the _buffer variable in the outer scope buffer = _buffer; });