Reputation: 536
I've seen a few examples of how to do this from C++ an unmanaged dll, to a C++ managed CLR dll. I currently have a C++ dll (NetPcapWrap), that references a C dll (npcap.dll). Since the npcap.dll is written in C I cannot compile with CLR so NetPcapWrap is unmanaged. My .Net Forms application references the NetPcapWrap.dll using pInvoke to gets information from the npcap.dll.
Within the npcap.dll is a looping function that writes out data to std::out what I need to do instead call a delegate of the .Net windows Forms app.
I've seen one potential solution https://www.codeproject.com/tips/695387/calling-csharp-net-methods-from-unmanaged-c-cplusp but this would require yet another dll that is compiled with CLR enabled.
Is there a way to do "reverse" pInvoke from an unmanaged C++ dll to a .Net Winforms application?
Sorry, I don't have any code to post because I can't figure out how to write the potion. I can share how I pInvoke the C++ dll but that is not my question.
Thanks
Upvotes: 0
Views: 258
Reputation: 18090
you need the C# code to send a delegate to a C function that expects a function pointer, then store it in some global.
and C# needs to also store this delegate in a static variable to prevent the GC from cleaning it.
on C/C++ side.
// C++ file
using myfunctype = void (*)(void);
// replace with typedef in C
// typedef void (*myfunctype)(void);
extern "C"
__declspec( dllexport )
myfunctype func1;
myfunctype func1 = 0;
extern "C"
__declspec( dllexport )
int SetFunction(myfunctype func_ptr)
{
func1 = func_ptr;
return 5;
}
extern "C"
__declspec( dllexport )
int CallFunction()
{
func1();
return 5;
}
then on C# side
// csharp file
using System.Runtime.InteropServices;
namespace myspace;
public delegate void CFunc();
public class NativeLib
{
public static void CsharpFunc()
{
Console.WriteLine("hello world from Csharp!");
}
public static CFunc func_static = CsharpFunc;
[DllImport("Clib", CallingConvention = CallingConvention.Cdecl)]
public static extern int SetFunction(CFunc func);
[DllImport("Clib", CallingConvention = CallingConvention.Cdecl)]
public static extern int CallFunction();
}
then your main app needs to call them.
// csharp calling application
using myspace;
class Program
{
public static int Main(String[] args)
{
NativeLib.SetFunction(NativeLib.func_static);
NativeLib.CallFunction();
return 0;
}
}
as a bonus, this also works on linux/macos where managed C++ dlls don't exist. (tested with dotnet 6 and 8)
while this small example works, you may need to pin any involved C# object from moving using GCHandle.
note that you cannot allow C++ exceptions to leak into C#, you also cannot allow C# exceptions to leak into C++.
Upvotes: 2
Reputation: 536
Thanks to @Ahmed AEK above and @ulatekh in this link ESP failure issue.
Below is code for a C++ unmanaged dll and a C# console Application. The goal was to call C# from C++. @Ahmed code worked well except for when I had to call C# and pass a parameter to the delegate. In the link above there is a good explanation of why this was failing (see the answer by @Rob), the short comment by @ulatekh provided the final piece to the puzzle.
//C# code
using System;
using System.Runtime.InteropServices;
namespace SimpleApp
{
internal class Program
{
public delegate void CFunc();
public delegate void CFunc2();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CFunc3([MarshalAs(UnmanagedType.I4)] int t);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CFunc4(IntPtr ip);
public static CFunc func_static = CSharpFunc;
public static CFunc2 func_static2 = CSharpFunc2;
public static CFunc3 func_static3 = CSharpFunc3;
public static CFunc4 func_static4 = CSharpFunc4;
public static void CSharpFunc()
{
Console.WriteLine("Hello from C++ via func");
}
public static void CSharpFunc2() // C++ calls this the parameter we passed in, which is 5 times
{
Console.WriteLine("Hello from C++ Func2. Passing an int from C# to C++");
}
public static void CSharpFunc3([MarshalAs(UnmanagedType.I4)] int t) // C++ passes t which is 2 times
{
for (int i = 0; i < t; i++)
Console.WriteLine("Hello from C++ Func3 passing an int from C++ to C# " + i.ToString());
}
public static void CSharpFunc4(IntPtr ip)
{
string s = Marshal.PtrToStringAnsi(ip);
Console.WriteLine(s);
}
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SetFunction(CFunc func);
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SetFunction2(CFunc2 func2);
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SetFunction3(CFunc3 func3);
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SetFunction4(CFunc4 func4);
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I4)]
static public extern int DoSomething();
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I4)]
static public extern int DoSomething2(int j);
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I4)]
static public extern int DoSomething3();
[DllImport("C:\\Users\\xx\\Dev\\Visual C++\\NetworkStuff\\Debug\\SimpleDll.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I4)]
static public extern int DoSomething4();
static void Main(string[] args)
{
int p = SetFunction(func_static);
Console.WriteLine(p.ToString());
DoSomething();
int p2 = SetFunction2(func_static2);
Console.WriteLine(p2.ToString());
DoSomething2(2);
int p3 = SetFunction3(func_static3);
Console.WriteLine(p3.ToString());
DoSomething3();
int p4 = SetFunction4(func_static4);
Console.WriteLine(p4.ToString());
DoSomething4();
Console.Write("Press and key to end.");
Console.ReadKey();
}
}
}
And the C++ unmanaged DLL Code
//C++ .h file
#pragma once
#include <atlstr.h>
#ifdef SIMPLEDLL_EXPORTS
#define SIMPLEDLL_EXPORTS __declspec(dllexport)
#else
#define SIMPLEDLL_EXPORTS __declspec(dllimport)
#endif
class SimpleClass
{
public:
}
//C++ the .cpp file
#include "pch.h"
#include "SimpleClass.h"
using myFuncType = void (*)();
extern "C" SIMPLEDLL_EXPORTS myFuncType func1;
myFuncType func1 = 0;
using myFuncType2 = void (*)();
extern "C" SIMPLEDLL_EXPORTS myFuncType2 func2;
myFuncType2 func2 = 0;
using myFuncType3 = void (*)(int);
extern "C" SIMPLEDLL_EXPORTS myFuncType3 func3;
myFuncType3 func3 = 0;
using myFuncType4 = void (*)(LPCSTR);
extern "C" SIMPLEDLL_EXPORTS myFuncType4 func4;
myFuncType4 func4 = 0;
extern "C" SIMPLEDLL_EXPORTS int SetFunction(myFuncType func_ptr);
extern "C" SIMPLEDLL_EXPORTS int SetFunction2(myFuncType2 func_ptr);
extern "C" SIMPLEDLL_EXPORTS int SetFunction3(myFuncType3 func_ptr);
extern "C" SIMPLEDLL_EXPORTS int SetFunction4(myFuncType4 func_ptr);
extern "C" SIMPLEDLL_EXPORTS int DoSomething();
extern "C" SIMPLEDLL_EXPORTS int DoSomething2(int j);
extern "C" SIMPLEDLL_EXPORTS int DoSomething3();
extern "C" SIMPLEDLL_EXPORTS int DoSomething4();
SIMPLEDLL_EXPORTS int SetFunction(myFuncType func_ptr)
{
func1 = func_ptr;
return 0;
}
SIMPLEDLL_EXPORTS int SetFunction2(myFuncType2 func_ptr)
{
func2 = func_ptr;
return 2;
}
SIMPLEDLL_EXPORTS int SetFunction3(myFuncType3 func_ptr)
{
func3 = func_ptr;
return 3;
}
SIMPLEDLL_EXPORTS int SetFunction4(myFuncType4 func_ptr)
{
func4 = func_ptr;
return 4;
}
SIMPLEDLL_EXPORTS int DoSomething()
{
for (int i = 0; i < 2; i++)
func1();
return 0;
}
SIMPLEDLL_EXPORTS int DoSomething2(int j)
{
for (int i = 0; i < j; i++)
func2();
return 0;
}
SIMPLEDLL_EXPORTS int DoSomething3()
{
int x = 3;
func3(x);
return 0;
}
SIMPLEDLL_EXPORTS int DoSomething4()
{
CStringA s = "Hello C# from C++ sending a string from C++ to C#";
func4((LPCSTR)s);
return 0;
}
The Output
Hope this helps someone. I know it has been a couple of weeks for me to get to this point.
Upvotes: 0