【问题标题】:Is this usage of gcroot safe?这种 gcroot 的使用安全吗?
【发布时间】:2013-03-08 21:07:58
【问题描述】:

我需要使用来自 C++/CLI 的非托管 API。此 API 存储指向任意用户数据的 void 指针和一些回调。然后它最终调用这些回调,将用户数据作为 void* 传递。

到目前为止,我有一个本地类将其“this”指针作为用户数据传递,并使用该指针将 API 调用回该类,即:

static void __stdcall Callback(void* userData) {
    ((MyType*)userData)->Method();
}

class MyType {
public:
    MyType() { RegisterWithApi((void*)this, Callback); }
    void Method();
};

我正在尝试使用托管类来翻译它。我发现 gcroot 类型可用于在本机代码中安全地存储托管引用,所以我现在是这样做的:

// This is called by the native API
static void __stdcall Callback(void* userData) {
    // Cast back to gcroot and call into managed code
    (*(gcroot<MyType^>*)userData)->Method();
}

ref class MyType {
    gcroot<MyType^>* m_self;
public:
    MyType() { 
        m_self = new gcroot<MyType^>;
        RegisterWithApi((void*)m_self, Callback);
    }
    ~MyType() { delete m_self; }
    // Method we want called by the native API
    void Method();
}

虽然这对 C++/CLI 编译器来说似乎很好,但我并不完全放心。据我了解,gcroot 以某种方式跟踪其托管引用,因为它被 GC 移动。当非托管代码存储为 void* 时,它会设法做到这一点吗?这段代码安全吗?

谢谢。

【问题讨论】:

  • 支持 Marshal::GetFunctionPointerForDelegate(),一个例子is here

标签: c++ .net garbage-collection interop c++-cli


【解决方案1】:

这就是我最终做的事情,而且效果很好。 gcroot 的目的是在本机堆上存储托管引用,这正是我在这里所做的。

【讨论】:

    【解决方案2】:

    不!恰恰相反。 gcroot 是一个原生类模板。您可以使用它以使用 clr 支持编译的本机类型存储托管内存的句柄。您通常会使用它将对本机对象成员函数的调用转移到存储在 gcroot 类型成员中的托管对象。

    编辑:我昨天在移动设备上输入代码示例有点尴尬...gcroot&lt;T^&gt; 的预期和典型用法大致如下:

    // ICallback.h
    struct ICallback {
        virtual void Invoke() = 0;
        virtual void Release() = 0;
        protected:
            ~ICallback() {}
    };
    

    这就是您的原生应用程序或库所看到和包含的内容。然后,您有一个使用 CLR 支持编译的混合组件,它实现 ICallback 并将某个托管对象的句柄存储在 gcroot&lt;ManagedType^&gt; 中:

    // Callback.cpp (this translation unit must be compiled with /clr)
    // I did not compile and test, but you get the point...
    template<class T^> class Callback : public ICallback {
        gcroot<T^> m_Managed;
    
        virtual void Invoke()
        {
           m_Managed->Invoke();
        }
    
        virtual void Release()
        {
            delete this;
        }
    public:
        Callback(T^ p_Managed) : m_Managed(p_Managed) {}
    };
    
    __declspec( dllexport ) ICallback* CreateCallback()
    {
        auto t_Managed = gcnew SomeManagedType();
        return new Callback<System::Action^>(
            gcnew System::Action(t_Managed, &SomeManagedType::Method)); 
    }
    

    您的本机应用程序调用CreateCallback,接收ICallback 的实例,当Invoke-d 调用托管类型的方法时,该方法保存在gcroot&lt;System::Action^&gt;...

    【讨论】:

    • 所以问题是原生 API 没有在 clr 支持下编译?因为否则你所描述的正是我正在做的。
    • 使用 gcroot 的原生类必须在编译时支持 clr。我所描述的与您在问题中显示的完全相反:您将本机 gcroot 指针存储在托管对象中。
    • 嗯,使用 gcroot 的函数(“回调”)在 CLR 支持下编译的。 gcroot 存储在显然是允许的非托管堆上。托管类保留指向它的指针会有什么不同?
    • 这不是gcroot 的预期用途。拥有gcroot&lt;T^&gt; 其中T 是托管类型的全部意义在于使用安全的RAII 包装器来处理托管内存。使用在免费存储上分配的gcroot&lt;T^&gt; 类似于std::shared_ptr&lt;T&gt;* p = new std::shared_ptr&lt;T&gt; - 毫无意义。我将编辑我的答案以证明...
    • 如果它解决了我的问题,那它有什么意义呢?我想将托管类的实例作为 void* 传递给我无法控制的 API。在本机堆上创建一个 gcroot 并将其作为 void* 传递似乎可以解决问题,因为当我使用此指针回调时,我可以转换回 gcroot 并转发到托管类。我不是在问什么是典型用途,我是在问这是否能正常工作。
    猜你喜欢
    • 2021-12-09
    • 2011-08-02
    • 2011-06-19
    • 2012-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多