Reddog
Reddog

Reputation: 91

Handling Managed Delegates in Unmanaged code

I know I can get this to technically work but I'd like to implement the cleanest possible solution. Here's the situation:

I have a managed library which wraps an unmanaged C-style library. The C-style library functionality I'm currently wrapping does some processing involving a list of strings. The library's client code can provide a delegate, such that during the list processing, if an "invalid" scenario is encountered, the library can callback to the client via this delegate and allow them to choose the strategy to use (throw an exception, replace the invalid characters, etc.)

What I'd ideally like to have is all of the managed C++ isolated in one function, and then be able to call a separate function which takes only unmanaged parameters so that all of the native C++ and unmanaged code is isolated at that one point. Providing the callback mechanism to this unmanaged code is proving to be the sticking point for me.


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

In this snippet, I want to keep all of the C-library access within ProcessList, but during the processing, it will need to do callbacks, and this callback is provided in the form of the InvalidStringFilter delegate which is passed in from some client of my managed library.

Upvotes: 4

Views: 4611

Answers (3)

Rasmus Faber
Rasmus Faber

Reputation: 49619

You want to do something like this:

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

Upvotes: 0

Rob Walker
Rob Walker

Reputation: 47452

If I am understanding the problem correctly you need to declare an unmanaged callback function in your C++/CLI assembly that acts as the bridge between your C library and managed delegate.


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

As written this code is clearly not thread-safe. If you need thread safety then some other mechanism would be needed to look up the correct managed delegate in the callback, either a ThreadStatic, or perhaps the callback gets passed a user supplied variable you could use.

Upvotes: 2

Lou Franco
Lou Franco

Reputation: 89152

.NET can auto-convert the delegate to a pointer to function if it is declared right. There are two caveats

  1. The C function must be built STDCALL
  2. The pointer to function does not count as a reference to the object, so you must arrange for a reference to be kept so that the underlying object is not Garbage collected

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

Upvotes: 2

Related Questions