1

C++/CLI ラッパー経由で C# からアクセスできる C++ のオブザーバー パターンがあります。ガベージ コレクションが期待どおりに機能していないことがわかりました。エラーが発生していますが、私Call has been made on garbage collected delegateが知る限り、 ( listeners_ディクショナリを介して) デリゲートへのマネージド参照を保持しているため、GC されている理由がわかりません。

ここでは、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);
}
4

2 に答える 2

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

掘り下げるには少し手間がかかりますが、それはこのコードだと思います。ハンドラー変数はメソッドのローカル変数であり、デリゲート オブジェクトを格納します次に、それから初期化されたサンクをネイティブ コードに渡します。この直後、そのデリゲート オブジェクトへのライブ参照は残っていません。したがって、次の GC はそれを収集します。そして、コールバックを作成しようとすると、サンクが壊れます。

ハンドラーは、GC から見える場所に格納する必要があります。クラスのフィールド (クラス オブジェクトが十分に長く存続すると仮定) または静的変数のように。また、ネイティブ コードがコールバックを実行できる限り、常に表示されていることを確認する必要があります。

于 2012-10-23T17:22:12.313 に答える
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() で必ず削除する必要があります。

于 2012-10-29T14:51:36.903 に答える