2007 年 9 月 2 日 22 時 43 分

C++/CLI #3: gcnew で動的確保


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


次に作るのは、描画コードの呼び出しだ。

Configure メソッドを作成した際は、
「HelloSaver saver;」という形で変数を宣言した。

この場合、saver 変数のスコープは、
Configure メソッドの内部なので、
メソッドから戻る直前に変数がスコープから外れ、
自動的にデストラクタが呼び出されて解放される。

でも、描画コードの呼び出し部分の実装は、
Configure のような単発の呼び出しではなく、
Create => Paint => Destroy の流れとなる。

なので、スクリーンセーバーのインスタンスを作成して、
Create を呼び出した後は、Destroy を呼び出すまで、
そのインスタンスを保持する必要がある。

一般的な C++ でこういうシナリオに対応する場合は、
new 演算子を使って動的にメモリを確保したあと、
そのポインタ(数値)をどこかに記憶しておき、
不要になった段階で delete 演算子で破棄する。

しかし、HelloSaver はマネージクラスである。
C++ では、プログラマが自身でメモリを管理するが、
CLR は自動的なメモリ管理機能をもっており、
そのメモリの取り扱いは全く異なるため、
new 演算子を使ってメモリを割り当てることはできない。

マネージオブジェクトを動的に確保する際には、
new の代わりに、gcnew という演算子を利用する。

gcnew 演算子は CLR の管理下のマネージメモリを確保し、
新しく作成したオブジェクトのハンドルを返す。
戻り値はポインタではなくハンドルである。

マネージメモリ上では、オブジェクトの参照が監視され、
不必要になれば自動的にガベッジコレクションが行われる。
プログラマが delete 演算子を呼び出す必要はない。

では、これに基づいて 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 をウィンドウのユーザデータに記憶する
        SetWindowLongPtr(hwnd, GWLP_USERDATA, saver); // ダメ

    }

ISaver::Create の引数は 3 つ。
例によって hwnd をラップした HwndWrapper の他に、
ウィンドウの幅と高さを渡すことになっている。
そのため、GetClientRect を使ってサイズを計測した。

さて、ISaver::Create を呼び出した後は、
saver の値をどこかに保存しておかなければならない。

通常、Windows SDK のプログラミングでは、
SetWindowLongPtr や SetProp 等の API を使い、
ウィンドウ属性やプロパティとして値を関連付けておく。

これら API が値として保持できるのは、
OS ネイティブのポインタサイズである。
簡単に言うと、32 ビットや 64 ビットの数値だ。

ここで問題となるのは、saver がハンドルということだ。
マネージオブジェクトを示しているハンドルは、
数値やポインタに変換することはできない。

そのため、SetWindowLongPtr に渡すことができないのだ。
当然、無理に (LONG_PTR) にキャストすることもできない。

さて、どうしたものか。



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