鉴于以下带有要求设置缓冲区的回调事件的 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 中进行函数指针类型转换,并隐藏编码非托管->托管的工作方式(调试非常多)更难,我不喜欢不了解现场发生的事情)。刚刚注意到该方法类似于 this但不需要托管 native 类(开销更少)。请告诉我您是否知道如何进一步简化它(维护类型安全和编码控制)并减少开销。
以下是 C++/CLI Wrapper.h
header :
#include <gcroot.h>
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<Library^> *_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<Library^>();
// 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<Library^> & native_handle = *((gcroot<Library^>*)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;
});
关于c++ - 如何在 C++/CLI 中包装 C 库回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5318842/