2007 年 9 月 3 日 18 時 45 分

C++/CLI #4: ハンドルを数値に変換する


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


ハンドル型の値は演算も変換もできないため、
そのままではスコープを超えて、
オブジェクトを保持することが困難である。

グローバル領域に変数を確保し、
ハンドル型の値を保持する手はあるが、
変数の可視性を高めてしまうのは、
保守性に劣り、見通しも悪くなるのでやりたくない。

ではどうすれば良いか。

.NET Framework には、マネージオブジェクトを、
アンマネージメモリに保持するために、
専用の仕組みを用意している。
それが、GCHandle 構造体である。

GCHandle 構造体を使うと、
任意のマネージオブジェクトへのハンドルを、
一意の ID に変換することができるようになる。
逆に、この ID からハンドルを得ることもできるので、
ハンドルと ID の相互変換が可能となる。

得られる ID は、プラットフォーム固有の整数値なので、
アンマネージメモリに格納することができる。

では、Dispatcher::Create を書きなおしてみよう。

    void Dispatcher::Create(HWND hwnd) {

        // ウィンドウのサイズを測る
        RECT rc;
        GetClientRect(hwnd, &rc);

        // スクリーンセーバーを作成
        ISaver ^saver = gcnew HelloSaver();

        // HWND のラッパーを作成
        HwndWrapper window(hwnd);

        // スクリーンセーバーを初期化する
        saver->Create(%window, rc.right, rc.bottom);

        // マネージオブジェクトである saver が
        // 知らぬ間に GC されないように参照を作成
        GCHandle holder = GCHandle::Alloc(saver);

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

        // ID をウィンドウのユーザデータ領域に記憶する
        LONG_PTR value = reinterpret_cast<LONG_PTR>(pointer);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, value);

    }

GCHandle::Alloc を呼び出すと、
アンマネージオブジェクトへのハンドルを示す、
ID を割り当てることができる。

GCHandle::Alloc で ID が割り当てられると、
そのハンドルは、内部的に強参照されるようになり、
ガベージコレクションの対象にならない。

イメージとしては、どこか見えないところに、
ハンドル型の変数があり、そこに値を代入している感じか。
オブジェクトへの有効な参照が存在すると見なされるのだ。

GCHandle::Alloc の戻り値は、GCHandle 構造体である。
GCHandle 構造体は単に IntPtr の ID をカプセル化し、
便利なメソッドを提供しているラッパーに過ぎないため、
この構造体が破棄されたとしても ID は有効のままだ。

GCHandle 構造体を GCHandle::ToIntPtr に渡すことで、
ID を表す IntPtr 型の整数値を得ることができる。
これは ToPointer を呼ぶことで void ポインタに変換できる。
SetWindowLongPtr の引数は LONG_PTR 型なので、
得られた void ポインタをキャストすれば格納可能となる。

このようにすれば、マネージオブジェクトを、
アンマネージメモリ上に保持することができるのだ。



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