Passing a Managed Function Pointer to Unmanaged Code


I was working with a customer a while back and they had a situation where they wanted to be able to register a managed callback function with a native API.  This is not a big problem and the sample that I extended did this already.  The question that arose was – How do I pass a managed object to the native API so that it can pass that object to the callback function.  Then I need to be able to determine the type of the managed object because it is possible that any number of objects could get passed to this call back function.  I took the code sample that is provided on MSDN and extended it a bit to handle this.  Here is the original sample - http://msdn.microsoft.com/en-us/library/367eeye0.aspx 


The first commend here is that this is some DANGEROUS territory.  It is really easy to corrupt things and get mess up.  For those that are used to manage code this is very much C++ territory where you have all the power and with that comes all of the rope that you need to hang your self.


As I mentioned about the customer wanted to be able to pass different types of objects in the lpUserData so the callback can handle different types of data.  So the way to do this is with a GCHandle.  Basically this gives you a native handle the object.  You pass that around on the native size and when it arrives on the managed side you can “trade it in” for the managed object.


So let’s say you have some sort of managed class.  In this case I called it “callbackData” and you want to pass it off. 


callbackData^ cd = gcnew callbackData();

First you need to get a “pointer” to the object that can be passed off:



GCHandle gch2 = GCHandle::Alloc(cd);
IntPtr ip2 = GCHandle::ToIntPtr(gch2);
int obj = ip2.ToInt32();

You need to keep the GCHandle around so you can free it up later.  It is very possible to leak GCHandles.  In addition GCHandles are roots to CLR objects and therefore you are rooting any memory referenced by the object you have the GCHandle for so the GC will not release any of that memory.  This however is not as bad as pinning the memory.  The GCHandle is a reference to the object that can be used later to retrieve the address of the actual object when needed.


From the GCHandle you can convert that to a an IntPtr.  Once you have an Int from the IntPtr that you can pass around to the lpUserData parameter.  You could also just directly pass the IntPtr but I just wanted to demonstrate taking it all the way down to the int. 


Then you can pass that value as part of an LPARAM or whatever you want to the native code. 


int answer = TakesCallback(cb, 243, 257, obj);

That will get passed to your callback that uses your delegate to call into your managed function.  The native code might do something simple like this:



typedef int (__stdcall *ANSWERCB)(int, int, int);
static ANSWERCB cb;
 
int TakesCallback(ANSWERCB fp, int n, int m, int ptr) {
   cb = fp;
   if (cb) {
      printf_s("[unmanaged] got callback address (%d), calling it...\n", cb);
      return cb(n, m, ptr);
   }
   printf_s("[unmanaged] unregistering callback");
   return 0;
}

In inside your callback function you simply get back an object:



public delegate int GetTheAnswerDelegate(int, int, int);
 
int GetNumber(int n, int m, int ptr) {
   Console::WriteLine("[managed] callback!");
   static int x = 0;
   ++x;
 
   IntPtr ip2(ptr);
   GCHandle val = GCHandle::FromIntPtr(ip2);
   System::Object ^obj = val.Target;
   Console::WriteLine("Type - " + obj->GetType()->ToString() );
 
   return n + m + x;
}

Once you have the object you can cast it or do whatever you need to move forward.  This approach allows you to bridge across the managed and native world when you have a native function that makes a callback.  I will attach a complete sample for you to play with if you interested and let me know if there are any questions.  There are other ways to approach this I am sure but this seemed to work pretty well and as long as you manage you rGCHandles and callback references you should be good to go!


Thanks,


Zach


REFERENCES


DelegateTestMixedMode.zip

Comments (0)

Skip to main content