在非托管代码中处理托管委托

Handling Managed Delegates in Unmanaged code

本文关键字:处理 非托管代码      更新时间:2023-10-16

我知道我可以在技术上做到这一点,但我想实现尽可能干净的解决方案。情况如下:

我有一个托管库,它封装了一个非托管C样式库。我目前正在包装的C样式库功能进行一些涉及字符串列表的处理。库的客户端代码可以提供委托,这样在列表处理过程中,如果遇到"无效"场景,库可以通过该委托回调到客户端,并允许他们选择要使用的策略(抛出异常、替换无效字符等)

理想情况下,我希望所有托管C++都隔离在一个函数中,然后能够调用一个单独的函数,该函数只接受非托管参数,这样所有本机C++和非托管代码都会在这一点上隔离。事实证明,为这个非托管代码提供回调机制是我的症结所在


#pragma managed
public delegate string InvalidStringFilter(int lineNumber, string text);
...
public IList<Result> DoListProcessing(IList<string> listToProcess, InvalidStringFilter filter)
{
  // Managed code goes here, translate parameters etc.
}
#pragma unmanaged
// This should be the only function that actually touches the C-library directly
std::vector<NativeResult> ProcessList(std::vector<char*> list, ?? callback);

在这个片段中,我想将所有的C库访问保留在ProcessList中,但在处理过程中,它需要进行回调,并且这个回调是以InvalidStringFilter委托的形式提供的,该委托是从我的托管库的某个客户端传入的。

.NET可以自动将委托转换为指向函数的指针(如果声明正确的话)。有两个注意事项

  1. 必须构建C函数STDCALL
  2. 指向函数的指针不算作对对象的引用,因此必须安排保留一个引用,以使底层对象不会被垃圾回收

http://www.codeproject.com/KB/mcpp/FuncPtrDelegate.aspx?display=Print

如果我正确理解了这个问题,您需要在C++/CLI程序集中声明一个非托管回调函数,该函数充当C库和托管委托之间的桥梁。


#pragma managed
public delegate string InvalidStringFilter(int lineNumber, string text);
...
static InvalidStringFilter sFilter;
public IList<Result> DoListProcessing(IList<string> listToProcess, InvalidStringFilter filter)
{
  // Managed code goes here, translate parameters etc.
  SFilter = filter;
}
#pragma unmanaged
void StringCallback(???)
{
  sFilter(????);
}
// This should be the only function that actually touches the C-library directly
std::vector<NativeResult> ProcessList(std::vector<char*> list, StringCallback);

正如所写的,这段代码显然不是线程安全的。如果您需要线程安全性,则需要其他一些机制来在回调中查找正确的托管委托,可以是ThreadStatic,也可以是回调传递了用户提供的可以使用的变量。

你想做这样的事情:

typedef void (__stdcall *w_InvalidStringFilter) (int lineNumber, string message);
GCHandle handle = GCHandle::Alloc(InvalidStringFilter);
w_InvalidStringFilter callback = 
  static_cast<w_InvalidStringFilter>(
    Marshal::GetFunctionPointerForDelegate(InvalidStringFilter).ToPointer()
  );
std::vector<NativeResult> res = ProcessList(list, callback);