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

时间:2020-03-06 15:05:34  来源:igfitidea点击:

我知道我可以从技术上解决这个问题,但我想实施最干净的解决方案。情况如下:

我有一个托管库,其中包装了非托管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可以将委托自动转换为指向函数的指针。有两个警告

  • 必须建立C函数STDCALL
  • 该函数的指针不算作对该对象的引用,因此我们必须安排要保留的引用,以免底层对象被垃圾收集

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

如果我正确理解了问题,则需要在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);