MtnManChris
MtnManChris

Reputation: 536

How to call a delegate in C# (Windows forms) from an unmanaged C++ dll

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

Answers (2)

Ahmed AEK
Ahmed AEK

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

MtnManChris
MtnManChris

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

Output screen shot

Hope this helps someone. I know it has been a couple of weeks for me to get to this point.

Upvotes: 0

Related Questions