2007 年 9 月 5 日 18 時 48 分

C++/CLI #6: GCHandle の参照を解放する


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


GCHandle::Alloc でハンドルに ID を割り当てると、
数値として取り扱えるようになるので便利だが、
不要になった時にはちゃんと解放しなければならない。

これを怠ると、ハンドルへの参照が残ってしまうので、
オブジェクトがガベッジコレクションされなくなり、
メモリリークを引き起こす要因となる。

GCHandle::Alloc で参照したハンドルを解放するためには、
どこかで GCHandle::Free を呼ぶ必要がある。

では、Dispatcher::Destroy を作ってみよう。

    void Dispatcher::Destroy(HWND hwnd) {

        // ウィンドウのユーザデータから ID を取得
        LONG_PTR value = GetWindowLongPtr(hwnd, GWLP_USERDATA);
        void *pointer = reinterpret_cast<void *>(value);

        // 参照は void * と相互変換可能
        IntPtr id(pointer);
        GCHandle holder = GCHandle::FromIntPtr(id);

        // 参照が保持するハンドルを取得
        ISaver ^saver = safe_cast<ISaver ^>(holder.Target);

        // 後始末処理を呼び出す
        saver->Destroy();

        // 参照を破棄する
        holder.Free();

    }

まず、昨日の手順を使って、ISaver のハンドルを取得する。
プラグインにも後始末を通知しなければならないので、
ISaver::Destroy を呼び出しておく。
最後に GCHandle::Free を呼んでハンドルを解放する。

重要なのは、holder.Target を取得する前に、
holder.Free を呼んではいけないということだ。

holder.Free の時点でハンドルが解放されるので、
その瞬間にガベッジコレクタが割り込んだ場合、
ISaver が解放されてしまう可能性がある。

さて、上記ではウィンドウ属性に ID が残ったままだが、
悪影響はないのでそのままにしている。
0 を格納しておけば安全確実だろう。

今回のケースにおいては、WM_DESTROY のハンドラだ。
WM_DESTROY の後は、WM_NCDESTROY 位しか届かないし、
すぐにこの後ウィンドウが破棄されるわけなので、
省略してもほとんど問題ないと考えられる。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.