views:

1560

answers:

3

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.

+1  A: 

.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

Lou Franco
+1  A: 

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.

Rob Walker
So essentially I'd have an unmanaged function pointer call an unmanaged function, then I'd have that unmanaged function call the delegate, which has been stored as a member variable in my managed class?
Reddog
In essence, yes. You need to work out how the StringCallback function accesses the managed delegate. The snippet above just makes it a global variable. StringCallback can also handle translating between types, for example std::string and String^
Rob Walker
A: 

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