Search code examples
c++visual-c++callbackc++-cli

Unable to assigned a member function from a managed class (C++/CLI) to C++ unmanaged class


Unable to assigned a member function from a managed class (C++/CLI) to C++ unmanaged class Hi,

I have been working on a project where I developed a ClassA in C++ has a function pointer (mCallback_reportProgress) as a call back function (or delegate) to report its progress. This function is assigned via another public function - RegisterCallback_reportProgress. There are actually 2 of them - one for any regular function and one is a template that is used to register a member function for a given class instance.

This code working


ClassA {
public:

    template<typename CType>
    bool RegisterCallback_reportProgress(void (CType::* DelegateFunction_)(int, size_t, float), CType& Class_) //member function version
    {
        if (mWritingStarted.load()) return false;
        if (DelegateFunction_ == NULL) return false;

        RegisterCallback_reportProgress(std::bind(DelegateFunction_, &Class_, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        return true;

    }
    bool RegisterCallback_reportProgress(std::function<void(int, size_t, float)> DelegateFunction_);

    void DoProcess()
    {
        while (DataToProcess) 
        {
            //Do some processing
            mCallback_reportProgress(ID, NumberOfItems, percentageValue);
        }
    }
private:
    std::function<void(int, size_t, float)> mCallback_reportProgress;
}

Please note this is a simplified version of my actual class.

So now I have it working in C++ console and I decided to do a GUI form around it. Decided to use CLR using C++/CLI in a different project - great way to do GUI using a C# style way. Created it and then added in ClassA (both header and Cpp file).

Here is the Form1 I created.

public ref class Form1 : public System::Windows::Forms::Form
    {
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
        }
    public:
        void ProgressBarUpdate(int ThreadID_, size_t CountOfNumbersFound_, float ProgressPercentage_)
        {
            //Assign some progress percentage to a GUI element.
        }
    
    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }
    }
    

The real issue occures when I tried to register the form's reporting function (ProgressBarUpdate) to ClassA's RegisterCallback_reportProgress.


#include "Form1.h"
#include "ClassA.h"
using namespace System::Windows::Forms;

delegate void AsyncRunCaller(Form form);
[STAThread]
int main()
{       
    ClassA ProcessingInstance;
    Application::EnableVisualStyles();
    Application::SetCompatibleTextRenderingDefault(false);
    CppCLRWinFormsProject::Form^ FormApplication = gcnew CppCLRWinFormsProject::Form1(); //Interesting, it has to be only created AFTER SetCompatibleTextRenderingDefault. 
                                                                                         //If BEFORE, Run will throw an error.
    ProcessingInstance.RegisterThreadCallbackPercentage(&CppCLRWinFormsProject::Form1::ProgressBarUpdate, FormApplication); //Here is the part where I tried to register ProgressBarUpdate to mCallback_reportProgress in Class A
    Application::Run(FormApplication);

  return 0;
}

This is where it gives the errorm with the error "C++ a pointer-to-member is not valid for a managed class".

ProcessingInstance.RegisterThreadCallbackPercentage(&CppCLRWinFormsProject::Form1::ProgressBarUpdate, FormApplication);

I generally understand why it fails - it won't let my unmanaged class have the pointer to a member function for a managed class instance to prevent the unmanaged class from calling in case the managed class is GC or garbage collected later which would cause an error.

But I don't know how to marshel it so that it ClassA can register the callback function to the managed class. I have been searching around Stack Overflow but none of the solutions work or are not viable to my predicament.

Is there even a way to do that or do I need to create a separate ClassA that accepts delegate using C++/CLI? I would like to continue to use the same Class for both regular C++ and C++/CLI.

Thank you for your time and effort.

I had tried to get the function ProgressBarUpdate to registered with ClassA's RegisterCallback_reportProgress function. But I am not sure how to handle it when it comes to managed classes.


Solution

  • It seems my question didn't get an answer so I had to find an answer on my own but thanks to this post here c++/CLI error in GChandle I managed to fashion an answer to my problem. I have posted it here for everyone else in the future later on.

    I created a wrapper class around ClassA. I know there was mention of using lambda function and gcroot but I couldn't understand the text I found on the matter and a lot of searching didn't yield anything I could understand.

    Header file CWrapperClassA.h

    namespace Wrapper {
        using namespace System::Runtime::InteropServices;
        using namespace System;
        delegate void ProgressUpdateEventHandler(int, size_t, float);
        public ref class CWrapperClassA
        {
        private:
            ClassA* UnmanagedClass1obj;
            GCHandle delegateHandle_;
            CWrapperClassA(void);
            delegate void EventDelegate(int ID, size_t CountOfNumberFound, float ProgressPercent);
            EventDelegate^ functionNativeCallback_;
            void ProgressUpdate(int ID, size_t CountOfNumberFound, float ProgressPercent);
    
        public:
            CWrapperClassA(int IDCount);
            event ProgressUpdateEventHandler^ EventUpdate;
        };
    }
    

    Cpp file for CWrapperClassA.cpp

    
    #include "CWrapperClassA.h"
    
    Wrapper::CWrapperClassA::CWrapperClassA(void)
    {};
    
    Wrapper::CWrapperClassA::CWrapperClassA(int IDCount)
    {
        UnmanagedClass1obj = new ClassA(ThreadCount);
    
        functionNativeCallback_ = gcnew EventDelegate(this, &CWrapperClassA::ProgressUpdate);
    
        // As long as this handle is alive, the GC will not move or collect the delegate
        // This is important, because moving or collecting invalidate the pointer
        // that is passed to the native function below
        delegateHandle_ = GCHandle::Alloc(functionNativeCallback_);
    
        // This line will actually get the pointer that can be passed to
        // native code
        IntPtr ptr = Marshal::GetFunctionPointerForDelegate(functionNativeCallback_);
    
        // Convert the pointer to the type required by the native code
        UnmanagedClass1obj->RegisterCallback_reportProgress(static_cast<void(__stdcall*) (int, size_t, float)>(ptr.ToPointer()));
    }
    
    void Wrapper::CWrapperClassA::ProgressUpdate(int ThreadId, size_t CountOfNumberFound, float ProgressPercent)
    {
        EventUpdate(ThreadId, CountOfNumberFound, ProgressPercent);
    }
    

    So in the form1's constructor I create the Wrapper Class and subscribe the form1's ProgressBarUpdate to CWrapperClassA Event handler. So anytime ClassA class the mCallback_reportProgress, the value pass to the function will be pass to CppCLRWinFormsProject::Form1::ProgressBarUpdate member function via the event handler and I can "add" or subscribe (C# delegate speak)

    
    public ref class Form1 : public System::Windows::Forms::Form
        {
        public:
            Form1(void)
            {
                InitializeComponent();
                //
                //TODO: Add the constructor code here
                //
                
                mWrapperForClassAInstance = gcnew Wrapper::CWrapperClassA(4);
                mWrapperForClassAInstance->EventUpdate += gcnew Wrapper::ProgressUpdateEventHandler(this, &CppCLRWinFormsProject::Form1::ProgressBarUpdate); //This is the line that register form1's ProgressBarUpdate to ClassA wrapper
            }
        public:
            void ProgressBarUpdate(int ThreadID_, size_t CountOfNumbersFound_, float ProgressPercentage_)
            {
                //Assign some progress percentage to a GUI element.
            }
        
        protected:
            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            ~Form1()
            {
                if (components)
                {
                    delete components;
                }
            }
        private:
            Wrapper::CWrapperClassA mWrapperForAClassInstance;
        }
    

    I have tested the codes in my project and it works! Please note that the solution I presented is a simplified version AND I know there are definitely better solution than creating wrapper classes but I couldn't find one and with the information I have, this was the best solution I could find.

    I hope this helps someone else who are also facing the same issue.

    If someone else can show me a better solution I would be grateful as this isn't an elegent solution with so much coding and I know I would need to create a public method in CWrapperClassA to access any function in ClassA but I can accept it for until I can find a better solution.