2012-10-23 36 views
1

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

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

将托管代理转发非托管更新的方式,托管托管代理或我如何实现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); 
} 

回答

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

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

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

+0

它不添加到字典_listeners实现这一目标。如果没有,为什么不呢?我没有存储处理程序的一部分,我正在存储引用本身。'_listeners-> Add(listener,KeyValuePair (handler,System :: IntPtr(&forforder)));' –

+0

这是一个很好的犁过。什么是保持字典引用? –

+0

它是IterationListenerRegistry包装的类成员class:'System :: Collections :: Generic :: Dictionary >^_listeners;' –

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()