ホーム 上へ 進む

2.1 VCL が どのように TFont, TPen, TBrush を管理しているか?

フォント、ペンとブラシは 貴重な資源(GDI オブジェクト)です。特に Windows 9X/ME では大変貴重なものです(128KBまで)。NT系のWindowsでも作れる数に制限があります(16384個まで).。

Delphi のプログラミングでは Font, Pen, Brush プロパティ を設計時や実行時によく使用しますが、ほとんどのコンポーネントには Font プロパティ があり、多くのコンポーネントには Canvas プロパティ が有って、Canvas プロパティ には Font, Pen, Brush プロパティ が有るので、Windows に詳しい方は VCL が貴重な GDI オブジェクトを無駄使いしているのではないかという不安を抱かれるでしょう。

実際には VCL は GDI オブジェクトを参照カウント方式で共有することによって、極力無駄な GDI オブジェクトを作らないようにしています。本節では、VCL の GDI オブジェクトの管理法を解説します。

2.1.1 リソースマネージャのしくみ

説明を行うためにまず言葉を定義します。

TFont, TPenとTBrush は TGraphicsObject の派生クラスで、Canvas の Font, Pen, Brush の型です。これらをまとめてグラフィックスオブジェクトと呼ぶことにします。

Windows が管理する 論理フォント、論理ペンと論理ブラシをまとめて GDI オブジェクトと呼ぶことにします。

TFont、TPen と TBrush のインスタンスはそれぞれ、Graphics ユニット内の 3個の TResourceManager 型の静的変数、FontManager, PenManager, BrushManager でそれぞれ管理されています。これらをまとめてリソースマネージャと呼ぶことにします。

グラフィックスオブジェクトの プロパティ値 のほとんどは、実はリソースマネージャ内で管理されている TResouce型のインスタンス内に有ります。この TResource 型のインスタンスをリソースと呼ぶこととします(Windowsではリソースをいろいろな意味で使いますが、ここではこの意味で使います、TResouceは後で説明します)。

リソースマネージャ、グラフィックスオブジェクトとリソースの関係は下図のようになります。


Figure 2.1-1

図 2.1-1 リソースマネージャ、リソース、グラフィックスオブジェクトの関係

VCL の利用者から見ると、グラフィックスオブジェクトが論理フォント等の GDI オブジェクトの様に見えますが、実際にはリソースが GDI オブジェクト(論理フォント、論理ペン、論理ブラシ)に対応します。つまり、リソース内のパラメータから論理フォント等の GDI オブジェクトが作成されます。

図 2.1-1、図 2.1-3で示されているように、グラフィックスオブジェクトはリソースへのポインタを持ち、リソースを共有します。リソースは、可能な限りグラフィックスオブジェクト間で共有されるようになっていますGDI オブジェクトはリソース1個から1個しか生成されないため、GDI オブジェクトの節約になります。

リソースとグラフィックスオブジェクトの構造をを下図に示します。


Figure 2.1-2

図 2.1-2 TResource(リソース)の構造


Figure 2.1-3

図 2.1-3グラフィックスオブジェクトの構造

2.1.2 グラフィックスオブジェクトとリソースマネージャ

以下の説明では、説明を簡単にするため、リソースマネージャとしては FontManager, グラフィックスオブジェクトとしては TFont を用いて、グラフィックスオブジェクトをリソースマネージャがどのように管理しているかを説明します。

TFont のインスタンスがコンストラクタ Create で生成されると、Create は TFontData レコード型 の変数をデフォルト値
DefFontData:
ハンドル=0,
フォントの高さ=スクリーンでの9pt相当のピクセル数
フォントのピッチ=fpDefault
フォントのキャラクタセット= SHIFTJIS_CHARSET
フォントのスタイル=[]
フォント名='MS P ゴシック'
に初期化して FontManager に渡し、FontManager にリソースの作成を頼みます。TFontData 型の構造は図2.1-2 のリソースパラメータの拡大図を参照してください。TFontData レコード型の構造はは一番上の図です。

FontManager は TFontData 型データ全体をキーにして、FontManager の管理しているリソースの中に同じリソースが有るか探します。もしあれば、リソースの参照カウント(図 2-2 参照)を増やし、TFont のインスタンスにそのリソースへのポインタをセットします。

無い場合は、リソースをアロケートし、それに TFontData 型データをコピーして、そのポインタを TFont のインスタンスにセットし、リソースの参照カウンタを1にセットします。

TFontData型データを比較する時、いちいち全体を比較すると大変なので、リソース内には リソースパラメータから作成したハッシュ値が格納されていて、高速に探索が行えるようになっています。

フォント名など、TFont の プロパティ で、対応するリソース内のリソースパラメータ の内容が変わるような変更がなされた場合は(例えば Height プロパティ 等)、TFont はリソースへのポインタを使ってリソースから現在のパラメータ(TFontDataレコード型のデータ)を取得し、それに変更を加え FontManager に渡して、「リソースの更新」を頼みます。FontManager は TFont のインスタンスが指していたリソースの参照カウントを減らし、必要ならば(参照カウントが0になったら)リソースを廃棄して、後は「リソースの作成」と同じ事を行います。

TFont のインスタンスが削除されるときは、TFont の Destructor が FontManager に自分が削除されることを報せます。FontManager は該当リソースの参照カウントを減らし、必要なら(参照カウントが0になったら)リソースを廃棄します。

2.1.3 GDI オブジェクトの生成と削除

VCL はリソースの生成時に GDI オブジェクト(論理フォント、論理ペン、論理ブラシ)をすぐ作るわけでは有りません。GDI オブジェクトが必要になるぎりぎりまで待ち、そこで初めて GDIオブジェクトを生成します。例えば、Canvas にテキストを描画する時、Canvas は Font プロパティ(TFont型) の GetHandle メソッドを呼び出して TFont に 論理フォントを作らせます。

生成された GDIオブジェクトのハンドルはリソースの「リソースハンドル」(図 2.1-2 参照)に格納されます。リソース中のリソースパラメータ(TFontData, TPenData, TBrushData)の「ハンドル」(図 2.1-2 参照)には格納されませんので注意してください。ここは、TFont/TPen/TBrush の Handle プロパティ に GDI オブジェクトのハンドル値を代入するとき、そのハンドル値が格納される場所です。2個所にハンドル値を持つ理由は後述します。

「リソースハンドル」に格納されている GDI オブジェクトが削除されるのはリソースの参照カウントが0になって、リソースが廃棄される時です。つまり、あるリソースを使っている全てのグラフィックオブジェクトが無くなると自動的に リソース中の GDI オブジェクトが削除されます。

GDI オブジェクトは一度作成されると、長い寿命を持つ可能性があります。コントロールにはたいてい Font 属性がありますから、各コントロールに多種多様な Font を設定すると、VCL は貴重な GDI リソースを浪費してしまいます。フォームを作成するときは、できるだけ ParentFont を True にしてフォントの種類を減らすように心がけ、また使うフォントの種類を統一するようにしましょう。

2.1.4 グラフィックスオブジェクトの代入(Assign)

2.1.3 までの説明でほとんど自明ですが、グラフィックスオブジェクトを Assign メソッドで代入すると、基本的にはリソースの参照カウントが増えるだけで、リソースが新たに作成されません。リソースは共有され、余分な GDIオブジェクトが作成されないようになっています。しかし、例外がありますので注意してください(後述 2.1.5)。

2.1.5 Handle プロパティ 使用時の注意事項

グラフィックスオブジェクトの Handle プロパティ はGDIオブジェクトのハンドルを参照するのに使いますが、新たに作成した GDI オブジェクトをグラフィックスオブジェクトにセットするのにも使います。通常GDIオブジェクトは グラフィックオブジェクトの各プロパティの値から作成されますが、Handleプロパティに直接GDIオブジェクトを代入した場合は、グラフィックオブジェクトのプロパティ(TFont.Name等)は無視されます。

GDI オブジェクトのハンドル値が代入されるとグラフィックスオブジェクトはその GDI オブジェクトを使用します。グラフィックスオブジェクトの プロパティ 群は 本来GDIオブジェクトを作成するために必要なものよりもだいぶ簡略化されていますので、より細かな指定を行いいときに便利です。ただし、Handle プロパティ にはいろいろな制約があります

TPen を例にとって説明します。TPen の Handle プロパティ に論理ペンのハンドルを代入すると、 TPen は、TPenData型の変数にデフォルトのパラメータ(DefPenData)をセットし、そのメンバ「ペンハンドル」に論理フォントのハンドルをセットして、PenManager にリソースの更新を頼みます。 通常は TPen のインスタンスに対応するリソースのリソースパラメータの「ペンハンドル」は0なので、TPenのHandle プロパティ にGDIオブジェクトハンドルを代入すると必ず新しいリソースが作られます。

VCL はこの状態になった TPen のインスタンスに対し、非常に冷たい対応をします。例えば、Width プロパティを読むと 必ず 1 が返ってきてしまいます。論理ペンからペン幅を抽出するのではなく、リソース内のデフォルト状態の TPenData からペン幅を読んでしまうからです。これは他のグラフィックスオブジェクトのいろいろな プロパティ(TFontのプロパティは除く) でも同様です。つまり、Handle プロパティ に GDI オブジェクトハンドルを代入した時は、他のほとんどのプロパティの値を信用してはいけないのです(但し、TFont だけ扱いが異なります。2.2 TFont で詳述します)。

また、例えば Width プロパティ に代入を行うと、TPen のインスタンスは デフォルト状態になっている TPenData 型変数にリソースパラメータをコピーし、そのメンバ Width の値を更新し、「ハンドル」を0にしたものを使ってリソースを更新してしまいます。この時「ペンの幅」以外の TPenData 内のメンバはデフォルト値ですから、以前の論理ペンとはまったく無関係のペンができてしまいます。従って、Handle プロパティ をセットした直後に他のプロパティを変更することは、無意味であり、やってはいけません。(TFont の色とインチ当たりのピクセル数、TPen のペンモード等は例外です。これらはリソース内に格納されないからです。また、 TFont ではかなり処理が異なります。 2.2 TFont を見てください)。

Handle プロパティ に代入を行った後、そのグラフィックオブジェクトを Assignメソッドでコピーしたらどうなるでしょうか? TPen や TBrush では代入先と代入元のグラフィックスオブジェクトが同じリソースを共有するようになります。しかし、TFont でもほとんどの場合そうなりますが、場合によっては不可解な動きになることが有ります(詳細は 2.2 TFont を見てください)。

最後に Handle プロパティ を使う上での注意点をもう一つ挙げておきます。GDIオブジェクトは Handle プロパティ に代入されるとリソースマネージャの管理下に入ります。従って Handle プロパティ に代入された GDI オブジェクトを DeleteObject で勝手に削除してはいけません

2.1.6 まとめ

TFont, TBrush, TPen の管理方法を以下にまとめます。

  1. TFont/TBrush/TPen のパラメータのほとんどはリソースマネージャが管理します。リソースマネージャ は同じデータを二重に登録できない、リソースパラメータを登録するためのデータベースとして働きます。同じパラメータを持つグラフィックオブジェクトはリソースマネージャ内のリソースを共有します。GDI オブジェクト(論理フォント、論理ペン、論理ブラシ)は各リソースに対して1個づつしか作られず、アプリケーション内に Font/Pen/Brush プロパティ がたくさん有っても、グラフィックスオブジェクトのバリエーションが少なければ、実際に作られる GDI オブジェクトの数は非常に少なく抑えられます。
  2. グラフィックスオブジェクトの Handle プロパティ はグラフィックスオブジェクトにきめの細かい指定をするときに便利ですが、セットされた GDI オブジェクトが消えてしまわないようにするには、各グラフィックスオブジェクトの性質とリソースマネージャの管理法を把握して十分に注意を払う必要が有ります。

ホーム 上へ 進む

inserted by FC2 system