我在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);
}
答案 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()中删除它。