SelectObjectでデバイスコンテキストにオブジェクトを選択する基本
Windows APIのGDIで描画を行うとき、ペンやブラシ、フォントなどのオブジェクトをデバイスコンテキスト(HDC)に「選択」する作業が必要です。その中心となるのがSelectObject関数で、古くからWin32プログラミングを支えてきた基本APIの一つです。
HDCはディスプレイやプリンタ、メモリビットマップを表す抽象ハンドルで、描画命令を発行しても見た目は定まりません。どのペンで線を引くか、どのフォントで文字を描くかは、SelectObjectでオブジェクトを関連づけて初めて決まります。Windows 9xから最新のWindows 11まで、互換性は保たれています。
この関数の特徴として、戻り値で「以前選択されていたオブジェクト」が返る点が挙げられます。Create系で生成したオブジェクトを後で解放する設計と密接に関係し、手順を誤るとリソースリークや描画不良を引き起こします。シンプルながらも、取り扱いに注意が必要な関数です。
GDIプログラミングではSelectObjectだけでなく、再描画のタイミング制御も重要です。InvalidateRectとUpdateWindowを使った無効化処理と合わせて理解しておくと、WM_PAINTとの関係を整理しやすくなります。Windows API全般を扱うための情報をhozugawa.netの記事群からも参照できます。
関数の宣言と戻り値の意味
SelectObjectの宣言はHGDIOBJ SelectObject(HDC hdc, HGDIOBJ h)という形で、第1引数にHDC、第2引数に選択するGDIオブジェクトを取ります。成功時には以前そのスロットに入っていたオブジェクトのハンドルが返され、これを使って元の状態へ戻せます。
DC内部のオブジェクトスロットは種類ごとに独立しています。ペン、ブラシ、フォント、ビットマップ、リージョンは別々のスロットに格納されるため、複数種類を同時に選択できます。一方、同一種別は1つしか保持できないため、戻り値を保存して元に戻す手順が重要になります。戻り値がNULLやHGDI_ERRORの場合は、DCとオブジェクトの不一致や、削除済みハンドルの指定などが疑われます。
選択できるオブジェクトの種類
SelectObjectで扱えるオブジェクトはWin32で定義された定数で識別され、用途もそれぞれに固有です。ペンとブラシは描画の中核、フォントは文字列出力、ビットマップはメモリDCへの転送、リージョンはクリッピングに使われます。
ペンとブラシは戻り値を別々に受け取って管理するのが基本です。リージョンはペンやブラシと役割が大きく異なるため、コード中では明確に区別して扱うと可読性が高まります。ストックオブジェクトも含めて、すべてのGDIオブジェクトは最終的にDeleteObjectで解放するか、最初から標準で用意されたものを使うかのどちらかに整理できます。
オブジェクト種別の比較
各オブジェクトの特性と用途を表にまとめると、DCへの選択時に混乱しにくくなります。
| オブジェクト種別 | 識別定数 | 主な用途 | DCでの保持数 |
|---|---|---|---|
| ペン | OBJ_PEN | 線・輪郭描画 | 1 |
| ブラシ | OBJ_BRUSH | 領域塗りつぶし | 1 |
| フォント | OBJ_FONT | テキスト描画 | 1 |
| ビットマップ | OBJ_BITMAP | メモリDCへの転送 | 1 |
| リージョン | OBJ_REGION | クリッピング・ヒットテスト | 1 |
ペンとブラシは描画で日常的に使われ、フォントはテキストを伴うUIで必須です。ビットマップはオフスクリーン描画、リージョンはクリッピング領域やヒットテストに利用されます。いずれも同じSelectObjectで選択するものの、スロットは独立しています。
典型的なコードパターン
最も基本的な流れは、ペンやブラシをCreatePenやCreateSolidBrushで生成し、SelectObjectでHDCに選択、描画後に戻り値で保存しておいた以前のオブジェクトへ戻してから、最後にDeleteObjectで生成したオブジェクトを解放するというものです。
HGDIOBJ hOldPen = SelectObject(hdc, hPen);
Rectangle(hdc, 0, 0, 100, 100);
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
GetStockObjectで取得した標準オブジェクトは削除が不要です。Create系で生成したものだけ解放する、と覚えておくと管理が楽になります。WM_PAINTのハンドラ内ではBeginPaint/EndPaintで囲み、その間でだけGDIオブジェクトを使うと、安全に描画できます。
メモリ管理と設計上の注意点
SelectObjectの戻り値を保存せずに、生成したばかりのオブジェクトを直ちにDeleteObjectすると、DCが参照している実体も失われ、以降の描画が破綻します。Create、Select、Deleteは必ずこの順序で行い、戻り値は必ず保存するという原則を守るのが安全です。
メモリリークを防ぐ設計の要点
- ループ内で毎回オブジェクトを生成し直さず、外で一度だけ作成する
SaveDC/RestoreDCでDCの状態をまとめて保存・復元する- Create系の戻り値は
DeleteObjectでの解放まで変数で保持する - ストックオブジェクトとカスタムオブジェクトを取り違えない
アンチパターンとしては、ループ内で毎回ブラシを生成・選択・破棄するコードが代表的です。オブジェクトを一度だけ作成し、終了後にまとめて削除する形に書き直すと、リーク防止と高速化の両面で効きます。
関連APIと描画システム全体
SelectObjectは単体で完結するわけではなく、メモリDCの作成にはCreateCompatibleDC、ビットマップはCreateCompatibleBitmap、転送にはBitBlt、テキスト描画にはDrawTextなど、多くのGDI APIと組み合わせて使います。GDIだけでなく入力イベントをフックするSetWindowsHookExの基礎を押さえておけば、Windows API全体の設計思想が見えてきます。
SelectObjectと組み合わせて使うAPI
CreateCompatibleDC/CreateCompatibleBitmap: メモリDCとビットマップを生成GetStockObject: ペン・ブラシ・フォントの標準オブジェクトを取得SaveDC/RestoreDC: DC全体の状態を保存・復元BeginPaint/EndPaint: WM_PAINT内での正規描画シーケンスDeleteObject: Create系で生成したGDIオブジェクトを解放
これらのAPIを併用することで、メモリ上の描画、画面への転送、テキスト表示といったGDIプログラミングの主要なパターンを構成できます。
SelectObjectを使いこなすための要点は、戻り値を必ず受け取って以前の状態に戻し、Create系で生成したオブジェクトは最終的にDeleteObjectで解放するというシンプルな原則を守ることです。DCの種類とオブジェクトの組み合わせを一致させ、ループ内でオブジェクトを再生成しない構造にしておくと、メモリリークも描画崩れも起きにくくなります。SelectObjectは単純ながらも、Windows描画システムの土台を支える重要なAPIです。基本動作を確実におさえておけば、長年にわたって動作するGDIコードを書く土台になります。