Search code examples
c++-cliinteropclr

(C++/CLI) How to get callbacks from Native Code to Managed Code in C++ CLI?


RANT-BEGIN Before jumping right into already answered band wagon, please read this paper about SE outdated answers https://ieeexplore.ieee.org/document/8669958

Things changes after a time, and I am afraid Computer science is one of the most if not the most field out there where APIs and Interfaces change radically very very fast. Needless to say that a solution that might worked last month might not after latest feature added to a platform/framework. I humbly request you to not mark this question as answered with decade old post when many mainstream things did not even existed. If you dont know latest solution dont bother about it and leave question for someone else who might.

For a community representative of Computer Science where innovations is everyday thing, it is very toxic, new comer unfriendly and conservative. END-RANT

This question has already been answered by me and will be accepted tomorrow (SE policy). Thank you for your interest.

Many times you have function pointers in unmanaged context which are called by some kind of events, We will see how it can be achieved with Top-Level Functions and also with member functions of a managed class.

Again, Please dont mark it as answered by linking to a decade old posts.

PS: So many edits due to unstable internet in third world country, yeah bite me!


Solution

  • unmanaged.cpp

    #pragma unmanaged
    
    // Declare an unmanaged function type that takes one int arguments and callbacks
    // our function after incrementing it by 1 
    // Note the use of __stdcall for compatibility with managed code
    // if your unmanaged callback uses any other calling convention you can 
    // UnmanagedFunctionPointerAttribute (check msdn for more info) on your delegate
    
    typedef int(__stdcall* ANSWERCB)(int);//Signature of native callback 
    
    int TakesCallback(ANSWERCB fp, int a) {
    
        if (fp) {
            return fp(a+1);//Native Callback
        }
        // This code will be executed when passed without fp
        return 0;
    }
    
    #pragma managed
    

    managed.cpp

    using namespace System;
    using namespace System::Runtime::InteropServices;
    namespace Callbacks {
        //  Following delegate is for unmanaged code and must match its signature
        public delegate void MyNativeDelegate(int i); 
        //  This delegate is for managed/derived code and ideally should have only managed parameters
        public delegate void MyManagedDelegate(int i); 
    
        public ref class TestCallback {// Our demo Managed class
        private:
            GCHandle gch;// kept reference so that it can be freed once we are done with it
            void NativeCallbackListener(int i);//unmanaged code will call this function
        public:
            void TriggerCallback(int i); // Its here for demo purposes, usually unmanaged code will call automatically
            event MyManagedDelegate^ SomethingHappened;//plain old event
            ~TestCallback();//free gch in destructor as its managed.
        };
    };
    
    void Callbacks::TestCallback::NativeCallbackListener(int i) {
        // Callback from Native code,
        // If you need to transform your arguments do it here, like transforming void* to somekind of native structure.
        // and then pass SomethingHappened::raise with Managed Class/Struct 
        return SomethingHappened::raise(i); // similar to SomethingHappened.Invoke() in c#
    }
    
    void Callbacks::TestCallback::TriggerCallback(int i)
    {
        MyNativeDelegate^ fp = gcnew MyNativeDelegate(this, &TestCallback::NativeCallbackListener);
            // use this if your nativecallback function is not a member function MyNativeDelegate^ fp = gcnew MyNativeDelegate(&NativeCallbackListener);
            gch = GCHandle::Alloc(fp);
            IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
            ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());// (ANSWERCB)ip.ToPointer(); works aswell
            // Simulating native call, it should callback to our function ptr NativeCallbackListener with 2+1;
            // Ideally Native code keeps function pointer and calls back without pointer being provided every time.
            // Most likely with a dedicated function for that.
            TakesCallback(cb, i);
                            
        }
    void Callbacks::TestCallback::~TestCallBack() {
        gch.Free();//Free GCHandle so GC can collect
    }
    

    implementation.cpp

    using namespace System;
    void OnSomethingHappened(int i);
    int main(array<System::String^>^ args)
    {
        auto cb = gcnew Callbacks::TestCallback();
        cb->SomethingHappened += gcnew Callbacks::MyManagedDelegate(&OnSomethingHappened);
        cb->TriggerCallback(1);
        return 0;
    }
    
    void OnSomethingHappened(int i)
    {
        Console::WriteLine("Got call back with " + i);
    }