C++/CLI中GCHandle的最佳释放实践

4

我在 C 语言中有一些函数,想在 .net 应用程序中使用它们。为此,我编写了一个 C++/cli 的包装类。

C 语言接口中有一个回调函数,并将其包装在 .net 委托中。

但是如何释放回调 gcHandle 的非托管资源呢? 可以在终结器中调用 GCHandle 的 IsAllocated 和 Free 吗? 因为它是托管资源,可能已经被垃圾回收器释放了。

以下是 C 语言接口的代码:

// C functions

#ifdef __cplusplus
extern "C" {
#endif

    typedef void (*my_native_callback)(const uint8_t buffer[], uint32_t buffer_len);

    void register_callback(my_native_callback c, uint32_t* id);

    void remove_callback(uint32_t id);

#ifdef __cplusplus
}
#endif

以下是 .net 封装:

// .net wrapper (c++/cli)
public ref class MyWrapper
{
public:
    MyWrapper()
    {
        RegisterCallback();
    }

    // Destructor.
    ~MyWrapper()
    {
        this->!MyWrapper();
    }

protected:
    // Finalizer.
    !MyWrapper()
    {
        RemoveCallback();       // <- Is this safe?
        // ... release other unmanaged ressorces
    }

private:
    void RegisterCallback()
    {
        uint32_t id = 0;
        callbackDelegate_ = gcnew MyCallbackDelegate(this, &MyWrapper::OnCallback);
        callbackHandle_ = System::Runtime::InteropServices::GCHandle::Alloc(callbackDelegate_);
        System::IntPtr delegatePointer = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(callbackDelegate_);
        register_callback(static_cast<my_native_callback>(delegatePointer.ToPointer()), &id);
        callbackId_ = id;
    }

    void RemoveCallback()
    {
        if (callbackId_)
        {
            remove_callback(callbackId_);
            callbackId_ = 0;
        }
        if (callbackHandle_.IsAllocated)        // It this safe in the finalizer?
        {
            callbackHandle_.Free();             // It this safe in the finalizer?
        }
        callbackDelegate_ = nullptr;            // It this safe in the finalizer?
    }


    void OnCallback(const uint8_t* buffer, uint32_t buffer_len)
    {
        // ...
    }

private:
    [System::Runtime::InteropServices::UnmanagedFunctionPointer(System::Runtime::InteropServices::CallingConvention::Cdecl)]
    delegate void MyCallbackDelegate(const uint8_t* buffer, uint32_t buffer_len);   
    MyCallbackDelegate^ callbackDelegate_;
    System::Runtime::InteropServices::GCHandle callbackHandle_;
    int callbackId_;
    // ... 
};

这段代码是否安全?最佳实践是什么?谢谢。
1个回答

6

不需要添加额外的GCHandle引用到委托对象。您已经正确地在callbackDelegate_字段中存储了一个引用,足以让垃圾回收器相信委托正在使用并且不应该被回收。不需要额外的引用。

只需从代码中删除callbackHandle_即可。


感谢您的快速回复。另一个问题是在终结器中设置callbackDelegate_ = nullptr;是否安全且必要? - mar.na
不需要了,该对象已经被销毁且没有任何引用指向它,因此该字段将不再被使用。只需删除该语句即可。 - Hans Passant
关于重定位怎么办?在这个文档中:https://msdn.microsoft.com/en-us/library/367eeye0.aspx(参见第二个示例),说需要“一个全局的GCHandle实例来防止委托被重定位”。是否可能在`MyWrapper`类的对象首次实例化后,垃圾收集器会重新定位托管委托? - Tarc
它非常明确地表达了这一点:“可能,但不必要”。它并不是。 - Hans Passant
我的意思是第二个例子。你似乎引用了第一个例子,它说“使用pin_ptr可以将委托固定,但不是必需的”,对吧? - Tarc
正如第一个例子中所讨论的那样,使用pin_ptr是不必要的,而且这种引脚是临时的。请接受提供的建议,争论并没有什么用处。如果您想知道为什么不需要它的确切原因,请单击“提问”按钮。 - Hans Passant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接