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.
17 comments:
Thank you for the very helpful post.
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.
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.
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
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
Amit, you can add a reference to the C# project or any .NET assembly from your C++ CLR project.
HTH
It works!! Thank you again..
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
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?
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
I have requirement somewhat similar where I want to call a c# method directly from c++. How can I do it please help, in urgent need of support.
Saurabh, have you tried the approach from my blog post? Another way to do it is wrap your C# code in COM, which then can be called from your C++.
I did try the COM interop path and it worked fine too, unless there was an issue with the .NET version on which the .dll was build and the runtime where it was consumed, which eventually is a scary thing for my feature hence discarded that approach. Trying this reverse pinvoke way and have issue with the following two lines,
ManagedLib::ManagedClass^ c = gcnew ManagedLib::ManagedClass();
Here what is "ManagedLib", I understand the class is the C# class and expected this to be the namespace, but it refuses to compile in my project. Pls comment/help.
IntPtr p = Marshal::GetFunctionPointerForDelegate(c->GetDelegate());
Here the "Marshal" keyword is causing the issue, I believe this is a class too, but does this need any header file inclusion?
Saurabh, Yes ManagedLib is the namespace for the referenced managed class in your .NET assembly. You will need to add reference to it from your C++ (with /clr option) project. If you have the ManagedLib project in the same Visual Studio solution, you can add a project reference from C++ project by right clicking "References" -> "Add Reference...", or you can do the file reference by browsing to the .NET assembly. There is something you need to watch out: the target .NET framework for the C++ project needs to match that of the .NET assembly being referenced. Follow the directions here if you encounter this issue.
Marshal is a managed type defined in System::Runtime::InteropServices; and IntPtr is in System namespace.
Good luck!
Hi, I can follow most of your example. I have a .NET 3.5 C# dll that was compiled with MSVC 2015 just fine and works for other purposes before I tried to use it with reverse p/invoke.
When I created the C++/CLI intermediate DLL, I had to use MSVC 2008 because it was difficult, and impossible for me to figure out how to get the target and plaform compiler correct for .NET 3.5 C++/CLI in MSVC 2015. I also had to deal with the fact that everything had to be x64
My question is: in the native C++ file (which I'm building in MSVC 2015), using your example, how do I properly reference and include the intermediate C++/CLI dll so that I can make the call to fnInteropBridge ? And do I need to re-declare the "struct EventData" portion in the native C++ ?
Thanks for your example and help.
To the last post:
Yes you can use MSVC 2015 just fine. The InteropBridge, which defines the fnInteropBridge, is the C++/CLI project and it needs to export the API like so:
#define INTEROPBRIDGE_API __declspec(dllexport)
INTEROPBRIDGE_API TCHAR* fnInteropBridge(MessageData data)
{
ManagedLibrary::UsefulThing^ c = gcnew ManagedLibrary::UsefulThing();
System::IntPtr p = Marshal::GetFunctionPointerForDelegate(c->GetDelegate());
NativeToManaged funcPointer = (NativeToManaged)p.ToPointer();
// invoke the delegate
return funcPointer(data);
}
Then in your native C++ project, just link to the InteropBridge and yes you will need to declare the struct (perhaps share the same header file between the two projects).
I do have a working sample in MSVC 2015. If you want, you can email me at hugh.ang at gmail.com - I can send it to you.
Cheers
Hugh
Hello, Hugh,
I created a tool to automate generating a native DLL to call .NET assembly from native C++ application without the need to create the mixed mode assembly using C++/CLI. Would yo like to try it and let me know how you feel about it?
xInterop Native C++ to .NET Bridge
Disclainer: I am the author.
Happy coding.
Shawn
Post a Comment