垃圾收集委托在C ++ / CLI中

时间:2012-10-23 16:48:53

标签: delegates interop c++-cli

我在C ++中有一个观察者模式,我允许通过C ++ / CLI包装器从C#访问。我发现垃圾收集没有按预期工作。我收到Call has been made on garbage collected delegate错误,但据我所知,我 AM 持有对委托的托管引用(通过listeners_字典),所以我不明白为什么这是GC'd。

这里我只是展示了C ++ / CLI包装器代码,它实现了与包装的C ++代码相同的接口(例如我将其置于“本机”命名空间中)。

将非托管更新转发给托管代理的方式,我如何保留托管代理,或者我是如何实现addListener / removeListener函数,是否有问题?

using namespace System::Runtime::InteropServices;
using namespace System::Collections::Generic;
typedef boost::shared_ptr<native::IterationListener> IterationListenerPtr;

public ref struct IterationListener
{
    enum class Status {Ok, Cancel};

    ref struct UpdateMessage
    {
        UpdateMessage(int iterationIndex, int iterationCount, System::String^ message);

        property System::String^ message;
        property int iterationIndex;
        property int iterationCount;
    };

    IterationListener();

    virtual Status update(UpdateMessage^ updateMessage) {return Status::Ok;}
};

public delegate IterationListener::Status IterationListenerUpdate(IterationListener::UpdateMessage^ updateMessage);


#define DEFINE_INTERNAL_BASE_CODE(CLIType, NativeType) \
public:   System::IntPtr void_base() {return (System::IntPtr) base_;} \
internal: CLIType(NativeType* base, System::Object^ owner) : base_(base), owner_(owner) {} \
          CLIType(NativeType* base) : base_(base), owner_(nullptr) {} \
          virtual ~CLIType() {if (owner_ == nullptr) {SAFEDELETE(base_);}} \
          !CLIType() {delete this;} \
          NativeType* base_; \
          System::Object^ owner_; \
          NativeType& base() {return *base_;}


public ref class IterationListenerRegistry
{
    DEFINE_INTERNAL_BASE_CODE(IterationListenerRegistry, native::IterationListenerRegistry);
    System::Collections::Generic::Dictionary<IterationListener^,
                                             KeyValuePair<IterationListenerUpdate^, System::IntPtr> >^ _listeners;

    public:

    IterationListenerRegistry();

    void addListener(IterationListener^ listener, System::UInt32 iterationPeriod);
    void addListenerWithTimer(IterationListener^ listener, double timePeriod); // seconds
    void removeListener(IterationListener^ listener);

    IterationListener::Status broadcastUpdateMessage(IterationListener::UpdateMessage^ updateMessage);
};






IterationListener::IterationListener()
{
}


IterationListener::UpdateMessage::UpdateMessage(int iterationIndex,
                                                int iterationCount,
                                                System::String^ message)
{
    this->iterationIndex = iterationIndex;
    this->iterationCount = iterationCount;
    this->message = message;
}


struct IterationListenerForwarder : public native::IterationListener
{
    typedef IterationListener::Status (__stdcall *IterationListenerCallback)(IterationListener::UpdateMessage^);
    IterationListenerCallback managedFunctionPtr;

    IterationListenerForwarder(void* managedFunctionPtr)
        : managedFunctionPtr(static_cast<IterationListenerCallback>(managedFunctionPtr))
    {}

    virtual Status update(const UpdateMessage& updateMessage)
    {
        if (managedFunctionPtr != NULL)
        {
            IterationListener::UpdateMessage^ managedUpdateMessage =
                gcnew IterationListener::UpdateMessage(updateMessage.iterationIndex,
                                                       updateMessage.iterationCount,
                                                       ToSystemString(updateMessage.message));
            return (Status) managedFunctionPtr(managedUpdateMessage);
        }

        return Status_Ok;
    }
};


IterationListenerRegistry::IterationListenerRegistry()
{
    base_ = new native::IterationListenerRegistry();
    _listeners = gcnew Dictionary<IterationListener^, KeyValuePair<IterationListenerUpdate^, System::IntPtr> >();
}


void IterationListenerRegistry::addListener(IterationListener^ listener, System::UInt32 iterationPeriod)
{
    IterationListenerUpdate^ handler = gcnew IterationListenerUpdate(listener, &IterationListener::update);
    IterationListenerPtr forwarder(new IterationListenerForwarder(Marshal::GetFunctionPointerForDelegate(handler).ToPointer()));
    _listeners->Add(listener, KeyValuePair<IterationListenerUpdate^, System::IntPtr>(handler, System::IntPtr(&forwarder)));
    base().addListener(forwarder, (size_t) iterationPeriod);
}


void IterationListenerRegistry::addListenerWithTimer(IterationListener^ listener, double timePeriod)
{
    IterationListenerUpdate^ handler = gcnew IterationListenerUpdate(listener, &IterationListener::update);
    IterationListenerPtr forwarder(new IterationListenerForwarder(Marshal::GetFunctionPointerForDelegate(handler).ToPointer()));
    _listeners->Add(listener, KeyValuePair<IterationListenerUpdate^, System::IntPtr>(handler, System::IntPtr(&forwarder)));
    base().addListenerWithTimer(forwarder, timePeriod);
}


void IterationListenerRegistry::removeListener(IterationListener^ listener)
{
    base().removeListener(*static_cast<native::IterationListenerPtr*>(_listeners[listener].Value.ToPointer()));
    _listeners->Remove(listener);
}


IterationListener::Status IterationListenerRegistry::broadcastUpdateMessage(IterationListener::UpdateMessage^ updateMessage)
{
    std::string message = ToStdString(updateMessage->message);
    native::IterationListener::UpdateMessage nativeUpdateMessage(updateMessage->iterationIndex,
                                                            updateMessage->iterationCount,
                                                            message);
    return (IterationListener::Status) base().broadcastUpdateMessage(nativeUpdateMessage);
}

2 个答案:

答案 0 :(得分:1)

IterationListenerUpdate^ handler = gcnew IterationListenerUpdate(listener, &IterationListener::update);
IterationListenerPtr forwarder(new IterationListenerForwarder(Marshal::GetFunctionPointerForDelegate(handler).ToPointer()));

有点夸张,但我认为这是代码。您的处理程序变量是方法的局部变量,并存储委托对象。然后,您将从其初始化的thunk传递给本机代码。在此之后,该委托对象没有实时引用。所以下一个GC将收集它。当它试图进行回调时,它会破坏thunk。

您需要将处理程序存储在GC可以查看的位置。就像你的类的一个字段(假设类对象足够长)或一个静态变量。只要本机代码可以进行回调,您必须确保它保持可见。

答案 1 :(得分:0)

确实存在问题:

IterationListenerUpdate^ handler = gcnew IterationListenerUpdate(listener, &IterationListener::update);
IterationListenerPtr forwarder(new IterationListenerForwarder(Marshal::GetFunctionPointerForDelegate(handler).ToPointer()));
_listeners->Add(listener, KeyValuePair<IterationListenerUpdate^, System::IntPtr>(handler, System::IntPtr(&forwarder)));
base().addListener(forwarder, (size_t) iterationPeriod);

我在堆栈上创建了一个shared_ptr(IterationListenerPtr),并将其地址保存在_listeners字典中。一旦shared_ptr超出范围,该地址就不再有效。什么可能阻止所有地狱破坏的事实是,shared_ptr的内容通过将其传递给本机代码(保留shared_ptr的副本,而不是它的地址)而保持活跃。

但是,当我在完全调试模式下运行时,我仍然不确定为什么这不会触发某种警报。我还不确定为什么它会触发委托已经被垃圾收集的MDA,因为我很确定这不是问题。在我看来,这正是使用调试分配器和本机运行时检查应该找到的那种错误。 :(

修复很简单:

IterationListenerUpdate^ handler = gcnew IterationListenerUpdate(listener, &IterationListener::update);
IterationListenerPtr* forwarder = new IterationListenerPtr(new IterationListenerForwarder(Marshal::GetFunctionPointerForDelegate(handler).ToPointer()));
_listeners->Add(listener, KeyValuePair<IterationListenerUpdate^, System::IntPtr>(handler, System::IntPtr(forwarder)));
base().addListener(*forwarder, (size_t) iterationPeriod);

我必须确保在析构函数或removeListener()中删除它。