无法将成员函数从托管类 (C++/CLI) 分配给 C++ 非托管类

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

提问人:AlexChan 提问时间:7/8/2023 更新时间:7/15/2023 访问量:78

问:

无法将成员函数从托管类 (C++/CLI) 分配给 C++ 非托管类 晉語

我一直在做一个项目,我开发了一个 ClassA,C++它有一个函数指针 (mCallback_reportProgress) 作为回调函数(或委托)来报告其进度。此功能通过另一个公共函数 RegisterCallback_reportProgress 分配。实际上有 2 个 - 一个用于任何常规函数,另一个是用于为给定类实例注册成员函数的模板。

此代码有效


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;
}

请注意,这是我实际课程的简化版本。

所以现在我让它在 C++ 控制台中工作,我决定围绕它做一个 GUI 表单。决定在不同的项目中使用 C++/CLI 使用 CLR - 使用 C# 风格的方式做 GUI 的好方法。创建它,然后添加到 ClassA 中(标头和 Cpp 文件)。

这是我创建的 Form1。

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;
            }
        }
    }
    

当我尝试将表单的报告函数 (ProgressBarUpdate) 注册到 ClassA 的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;
}

这是它给出错误的地方,错误为“C++ 指向成员的指针对托管类无效”。

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

我通常理解它失败的原因 - 它不会让我的非托管类具有指向托管类实例的成员函数的指针,以防止非托管类在托管类是 GC 或稍后收集垃圾的情况下调用,这会导致错误。

但是我不知道如何对其进行 marshel ,以便 ClassA 可以将回调函数注册到托管类。我一直在搜索 Stack Overflow,但没有一个解决方案可以解决或不适合我的困境。

有没有办法做到这一点,或者我是否需要创建一个单独的 ClassA 来使用 C++/CLI 接受委托?我想继续对常规 C++ 和 C++/CLI 使用相同的类。

感谢您的时间和精力。

我曾尝试将函数 ProgressBarUpdate 注册到 ClassA 的 RegisterCallback_reportProgress 函数中。但是当涉及到托管类时,我不确定如何处理它。

C 回调 c++-cli

评论

0赞 Eugene 7/8/2023
创建一个 functor 或 lambda 并将其作为回调传递,而不是 ptr-to-member-function。它必须包括 .然后,您可以在 lambda 或函子中调用托管回调。使用 lambda 的时间更短,但随后需要传递 lambda 捕获,我不确定这是否可能(我现在无法访问编译器)。gcroot<CppCLRWinFormsProject::Form1>gcroot<>
0赞 AlexChan 7/9/2023
老实说,@Eugene,我不知道 gcroot 是什么以及它是如何工作的。我得查一下。谢谢你的信息。

答:

-1赞 AlexChan 7/15/2023 #1

似乎我的问题没有得到答案,所以我不得不自己找到答案,但多亏了这篇文章,GChandle 中的 c++/CLI 错误,我设法为我的问题找到了答案。我已将其发布在这里,以便将来为其他人使用。

我围绕 ClassA 创建了一个包装类。我知道有人提到使用 lambda 函数和 gcroot,但我无法理解我找到的关于此事的文本,并且大量搜索没有产生任何我能理解的东西。

头文件 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;
    };
}

CWrapperClassA.cpp 的 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);
}

因此,在 form1 的构造函数中,我创建了 Wrapper 类,并将 form1 的 ProgressBarUpdate 订阅给 CWrapperClassA 事件处理程序。因此,每当 ClassA 类mCallback_reportProgress时,传递给函数的值将通过事件处理程序传递给 CppCLRWinFormsProject::Form1::P rogressBarUpdate 成员函数,我可以“添加”或订阅(C# 委托发言)


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;
    }

我已经测试了我的项目中的代码,它有效!请注意,我提出的解决方案是简化版本,我知道肯定有比创建包装类更好的解决方案,但我找不到一个,根据我所掌握的信息,这是我能找到的最佳解决方案。

我希望这能帮助到同样面临同样问题的人。

如果其他人能向我展示一个更好的解决方案,我将不胜感激,因为这不是一个具有如此多编码的优雅解决方案,我知道我需要在 CWrapperClassA 中创建一个公共方法来访问 ClassA 中的任何函数,但我可以接受它,直到我找到更好的解决方案。