Reverse P/Invoke

Posted by Hugh Ang at 9/30/2008 07:24:00 PM

While researching for ongoing/upcoming projects that will need approaches for interop between .NET and native code, specifically, for native code to call into .NET code, Reverse P/Invoke has come to my attention as a viable option. Of course there is the official Microsoft recommendation to expose .NET classes as COM components, which are then callable from native code that talks COM.

The Reverse P/Invoke approach allows native code to call into .NET delegate using a function pointer. So it could work well for my requirement, for which I need a way to fire an event from the native app to the .NET app, for instance, an application context change on the native side must be reflected on the .NET side.

The blog by Junfeng, however, does not give a concrete example of such Reverse P/Invoke approach. So I came up with a POC, where I had a VS.NET solution with three projects: (1) a native console application (C++ project) (2) a managed class library (C# project) and (3) a mixed mode dll library with exported C++ function (C++/CLI project).

So this POC is trying to simulate a native application (#1) that needs to notify managed code (#2) of data changes. I came up with a dll library compiled with /clr switch to handle the interop details. Both the native app and the managed code requires very minimum changes.

On the .NET side, we have a managed class that has a Foo() function and a GetDelegate() function that hands out a delegate to Foo to its caller.


public class ManagedClass

{

    private CallBackDelegate _delegate;

 

    public ManagedClass()

    {

        _delegate = new CallBackDelegate(this.Foo);

    }

 

    public CallBackDelegate GetDelegate()

    {

        return _delegate;

    }

 

    public void Foo(EventData data)

    {

        Debug.WriteLine(data.I);

        Debug.WriteLine(data.Message);

    }

}



The EventData is a data structure that shares the same binary layout as the one that will be created and marshaled from the native code.


[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

public struct EventData

{

    public int I;

    public string Message;

}



And here is the delegate definition. Note the attribute UnmanagedFunctionPointer with the calling convention.


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]

public delegate void CallBackDelegate(EventData data);



In the mixed mode dll, here is the definition of the EventData data structure and function pointer:


#pragma once

 

#include <windows.h>

 

// data structure for the callback function

struct EventData

{

    int I;

    TCHAR* Message;

};

 

// callback function prototype

typedef void (*NativeToManaged)(EventData data);



And the exported function is defined as in the following. Note how the .NET delegate gets invoked through the function pointer.


#define INTEROPBRIDGE_API __declspec(dllexport)

 

INTEROPBRIDGE_API void fnInteropBridge(EventData data)

{

    ManagedLib::ManagedClass^ c = gcnew ManagedLib::ManagedClass();

    IntPtr p = Marshal::GetFunctionPointerForDelegate(c->GetDelegate());

 

    NativeToManaged funcPointer = (NativeToManaged) p.ToPointer();

 

    // invoke the delegate

    funcPointer(data);

}



Now in the native app, I have code that creates a copy of EventData and invokes the .NET code through the exported dll function fnInteropBridge:


// forward definition of the API function

void fnInteropBridge(EventData data);

 

int _tmain(int argc, _TCHAR* argv[])

{

    EventData data;

    data.I = 50;

    data.Message = L"Hello from native code!";

 

    fnInteropBridge(data);

 

    return 0;

}



In summary, I like this approach it that it provides quite an easy and non-invasive way for native code to call into managed code. It should especially work well in my scenario, where application context changes initiated from the native app needs to be propagated to the managed code. Furthermore, besides polishing this up, I think I will add code to raise a .NET event from inside ManagedClass.Foo(). Then all interested .NET citizens on the managed app side can subscribe to it.

10 comments:

Anonymous said...

Thank you for the very helpful post.

David Levine said...

Thanks for the post, it helped me solve a problem, and you saved me countless hours by providing an easy example of how to do this.

One change I made was that I called a static method in the managed side, and I did not need a delegate; I just called the managed method directly. The C++/CLI runtime takes care of the details of marshaling the call from native-to-managed.


Also, you don't need to provide a delegate or special marshaling instructions to call from the mixed DLL to the pure managed DLL.

In other words, from the mixed mode DLL, rather than do this...
ManagedClassProvider::ManagedClass^ c = gcnew ManagedClassProvider::ManagedClass();
IntPtr p = Marshal::GetFunctionPointerForDelegate(c->GetDelegate());
NativeToManaged funcPointer = (NativeToManaged) p.ToPointer();
funcPointer(data);


all I needed to do was this...


ManagedClass::ManagedClass^ c = gcnew ManagedClassProvider::ManagedClass();
ManagedClass::EventData data; // defined in managed dll
mdata.I = 50;
mdata.Message = gcnew String("Hello from Mixed Mode");
c->Trace(data);


...and it worked.

One thing that surprised me was that I did not need to host the CLR before calling into it. Apparently the C++/CLI runtime provides a default host and AppDomain (DefaultDomain) if the CLR is not already running, so it just worked.

Hugh Ang said...

Hi David, Thanks for your comments. I am glad that you found the post helpful. You were right that managed functions can be invoked directly by the C++/CLI code. I was exploring how to invoke .NET delegate from native code so I can bubble native "event" to all .NET listeners. I haven't tried this but it should work just fine if the delegate invoked by the C++/CLI code is chained.

Anonymous said...

Thank you for this helpful information!

I tried to do everything you said, but I have a problem in the CLR project (that connect between c++ and c#..)

you defined there:
ManagedLib::ManagedClass but it defined in the c# code so this class is not recognized!

What should I do?

thank you again!

Amit

Anonymous said...

Hello,

Thank you very much for you helpful post!

I misunderstood something.

In the CLR project (that connect between c# and c++). I cant see any connection to the c# project.

you use the class:
ManagedLib::ManagedClass c = gcnew ManagedLib::ManagedClass();
but ManagedLib::ManagedClass is defined in the C# code and not in my CLR project.
do I have to add an include or somwthing else?

thank you again :)

Amit

Hugh Ang said...

Amit, you can add a reference to the C# project or any .NET assembly from your C++ CLR project.

HTH

Anonymous said...

It works!! Thank you again..

Anonymous said...

I would love to have this code and the projects. I use VS 2008 and need to implement this same kind of approach. Please let me know where I can get the code.
Thanks for a wonderful article.
Wyatt

Anonymous said...

You have instructions with how to compile mixed mode dll using a c++ project application. Would you know if you can use mixed mode dll when your c++ is configured in a makefile? Specifically, adding the references?

Sujay Ghosh said...

Hello Hugh,

Thanks for the helpful post .

I have a manged DLL ( C++/CLI ) , which has also a wrapper of the C# library .

Now I want to call the C++/CLI dll from a native C++ code. I want to get a pointer to the wrapper , from my C++ appliation , and would like to call the function , rather than exporting all the function.

It shall be very helpful if you can provide an example