Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# / C++ Callback Class (not function) Interop - howto?

Apologies if there is a duplicate - I have struggled to find an answer (I have found a few questions around C++ functions that use Function callbacks, and some answers that use Classes as callbacks when called from C/C++ but..

  • I am in C#.
  • I am calling a C++ function
  • I can not change the signature of the C++ function.
  • Also, I am using the dynamic method of p/invoke rather then the static binding (my example below);

I can handle the situation where a function takes a simple value, or a struct containing simple values, and returns simple values, but in this instance I have a C function which is taking a call-back object.

Following some ideas online, I tried to make a class that had the same signature, then pinned that class, and passed it in., but I get the C# error of 'Object is non-Blittable' (which it doesn't have any variables in it!).

Header file:

again apologies if there are any mistakes in my example, I've tried to strip all non relevant code and explode the macros, but I hope you understand the essence of what is going on

    struct someData_t
    {
    int length; /**< JSON data length */
    char* pData; /*< JSON data */
    };

    namespace FOO {
    class ICallback
    {
    public: virtual ~ICallback() {}

        virtual void Callback(const someData_t &response) = 0;
    };
    }

    extern "C" __declspec(dllexport) void process(const someData_t *inData, FOO::ICallback *listener);

My C# file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace Scratchpad {
    class Program {
    static void Main(string[] args) {
        Console.Out.WriteLine("I'm in Managed C#...");

        IntPtr user32 = NativeMethods.LoadLibrary(@"somelongpath\my_c.dll");

        IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(user32, "process");

        process proc = (process)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(process));

        String someJson = "{ \"type\":\"someTestJson\"}";
        byte[] rawdata = Encoding.UTF8.GetBytes(someJson);

        someData myData = new someData();
        int dataLength = rawdata.Length * Marshal.SizeOf(typeof(byte)); // I realise byte is size 1 but..

        myData.length = rawdata.Length;

        myData.pData = Marshal.AllocHGlobal(dataLength);
        Marshal.Copy(rawdata, 0, myData.pData, dataLength);

        Console.Out.WriteLine("Size of mydata: " + Marshal.SizeOf(myData));
        IntPtr unmanagedADdr = Marshal.AllocHGlobal(Marshal.SizeOf(myData));

        Marshal.StructureToPtr(myData, unmanagedADdr, true);

        // ################################################################
        // FIXME: This area still working
        Callbacker myCallback = new Callbacker();

        GCHandle gch = GCHandle.Alloc(myCallback, GCHandleType.Pinned);

        IntPtr mycallbackPtr = gch.AddrOfPinnedObject();

        // FIXME: close of working area.
        // ################################################################
        // CALL THE FUNCTION!
        proc(unmanagedADdr, mycallbackPtr);


        myData = (someData) Marshal.PtrToStructure(unmanagedADdr, typeof(someData));

        Marshal.FreeHGlobal(unmanagedADdr);
        Marshal.FreeHGlobal(myData.pData);
        gch.Free();
        unmanagedADdr = IntPtr.Zero;

        bool result = NativeMethods.FreeLibrary(user32);

        Console.Out.WriteLine("Fini!)");
    }

    private delegate void process(IntPtr data, IntPtr callback);

    [StructLayout(LayoutKind.Sequential)]
    private struct someData { 
        public int length; 
        public IntPtr pData; 
    }

    private class Callbacker {
        public void Callback(someData response) {
        Console.WriteLine("callback Worked!!!");
        }
    }

    }

    static class NativeMethods {
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);
    }
}

Any suggestions welcome

like image 316
Jmons Avatar asked Sep 17 '26 16:09

Jmons


1 Answers

You could create some un-managed wrapper for Your managed Callbacker class which implements ICallback interface. Something like this:

typedef void (*PointerToManagedFunctionToInvoke)(const someData_t&);

class UnmanagedDelegate : public FOO::ICallback {
    private:
        PointerToManagedFunctionToInvoke managedCallback;
    public:
        UnmanagedDelegate(PointerToManagedFunctionToInvoke inManagedCallback)
        : managedCallback(inManagedCallback) {}

        virtual void Callback(const someData_t &response)
        {
            managedCallback(response);
        }
};

// Export this to managed part
UnmanagedDelegate* CreateUnmanagedDelegate(PointerToManagedFunctionToInvoke inManagedCallback)
{
    return new UnmanagedDelegate(inManagedCallback);
}

Then at C# part You could create a delegate to marshal as PointerToManagedFunctionToInvoke, pass it to CreateUnmanagedDelegate receive unmanaged implementation of ICallback and use that to pass to your process

Please be aware that managedCallback should stay allocated at C# side while UnmanagedDelegate class object is alive. And You should delete the UnmanagedDelegate object when it is not used anymore.

OR You could use thin C++/CLI to implement this wrapper.

like image 166
AccessViolation Avatar answered Sep 19 '26 07:09

AccessViolation