SelectObjectでGDIオブジェクトをデバイスコンテキストに選択する
Windows GDIで線や図形、文字、ビットマップを描画するときは、デバイスコンテキスト(DC)に使用するGDIオブジェクトを選択します。その中心となるAPIがSelectObjectです。HDCとHPEN、HBRUSH、HFONT、HBITMAPなどを結び付けることで、後続の描画関数が参照する色、太さ、書体、描画対象を切り替えられます。
SelectObjectは単にオブジェクトを登録する関数ではありません。呼び出し前に選択されていたハンドルを戻り値で返すため、描画後に元のオブジェクトを復元する処理まで含めて使う必要があります。選択状態とオブジェクトの寿命を正しく管理することが、GDIリークや予期しない描画結果を防ぐ基本になります。
SelectObjectの役割と基本動作
基本形は次のようになります。
HGDIOBJ oldObject = SelectObject(hdc, hObject);
第1引数には対象のデバイスコンテキスト、第2引数には選択したいGDIオブジェクトを渡します。たとえば、作成済みのペンを選択してからLineToを呼び出すと、そのペンの色や線幅が線の描画に反映されます。ブラシを選択してRectangleやEllipseを呼び出せば、内部の塗りつぶしにそのブラシが使われます。
戻り値は置き換えられたオブジェクトです。描画が終わったら、通常は次のように元のオブジェクトを戻します。
HGDIOBJ oldPen = SelectObject(hdc, hPen);
// 線を描画する
SelectObject(hdc, oldPen);
失敗時にはNULL、またはオブジェクトの種類によっては別のエラー状態が返る可能性があります。選択前後でハンドルの種類が一致しているか、対象DCが有効かを確認すると、原因を切り分けやすくなります。
ペン、ブラシ、フォントを切り替える方法
ペンは輪郭線の属性を、ブラシは塗りつぶしの属性を、フォントは文字の外観を担当します。たとえばCreatePenで作成したペンを選択してRectangleを描くと、四辺の輪郭にペンが適用されます。内部を塗るブラシも同時に選択されているため、輪郭と塗りつぶしを別々に設計できます。
GDIオブジェクトの作成方法や色、スタイルの指定については、ペンとブラシの作成方法も関連資料として参照できます。作成したハンドルをすぐ削除せず、まずDCへ選択し、描画が終わって元のオブジェクトへ戻した後にDeleteObjectを呼ぶ流れが重要です。
フォントの場合はCreateFontやCreateFontIndirectで作成したHFONTを選択し、TextOutやDrawTextを実行します。ビットマップでは、メモリDCへHBITMAPを選択してからBitBltやStretchBltを使う構成が一般的です。異なる種類のハンドルを誤って渡しても、期待どおりに機能するとは限りません。
デバイスコンテキストとの組み合わせ
画面へ直接描画するウィンドウDCと、オフスクリーン描画に使うメモリDCでは、オブジェクトの扱いが少し異なります。メモリDCへビットマップを選択すると、そのビットマップが描画先になります。作成直後のメモリDCには既定のビットマップが入っているため、交換前のハンドルを保存しておくと後で復元できます。
複数の属性を一時的に変更する場合は、SaveDCでDCの状態を保存し、描画後にRestoreDCで戻す方法も便利です。これにより、ペン、ブラシ、フォント、背景色、マッピングモードなどを個別に復元する負担を減らせます。ただし、SaveDCはGDIオブジェクトそのものの所有権や削除順序を管理する機能ではありません。
WM_PAINT内でBeginPaintが返したHDCを使う場合は、EndPaintまでを一つの描画処理として扱います。GetDCで取得したDCはReleaseDCが必要です。取得方法に応じた解放関数を使わないと、描画内容が正しくてもリソース管理上の問題が残ります。
選択解除とハンドルの寿命
GDIオブジェクトを削除する前に、そのオブジェクトがどのDCにも選択されていない状態へ戻す必要があります。特にビットマップは、メモリDCへ選択したままDeleteObjectを呼ぶと削除できない、または想定外の状態になる場合があります。元のハンドルを保存しておけば、処理の最後に安全に差し替えられます。
HBITMAP oldBitmap =
static_cast<HBITMAP>(SelectObject(memDC, bitmap));
// memDCを使った転送処理
SelectObject(memDC, oldBitmap);
DeleteObject(bitmap);
DeleteDC(memDC);
アプリケーション全体で何度も使うペンやブラシは、毎回作成と削除を繰り返すより、ウィンドウや描画コンテキストに関連付けて保持する設計が適しています。一方、短時間だけ必要なオブジェクトは、作成箇所と削除箇所を近づけると所有関係を追いやすくなります。
ストックオブジェクトを取得した場合は、通常のCreate系APIで作成したオブジェクトと同じようにDeleteObjectしてはいけません。SelectObjectの戻り値を無条件に削除せず、自分で作成したハンドルか、システムが提供するハンドルかを区別してください。
実装時の確認項目と使い分け
描画コードをレビューするときは、API呼び出しの順番だけでなく、失敗時の処理と後片付けまで確認します。Windows APIに関する周辺資料を探す場合は、Windows APIの資料も参照先の一つになります。小さなサンプルでも、ハンドルの所有者を明確にしておくと、機能追加時の不具合を抑えられます。
選択前に確認する項目は次のとおりです。
- HDCが有効で、描画対象の種類が合っている
- SelectObjectへ渡すGDIオブジェクトの型が正しい
- 戻り値の元オブジェクトを保存している
- 選択中のオブジェクトを削除しない
- 失敗時にNULLやエラー状態を確認している
描画後の後片付けでは、次の点を確認します。
- 保存した元のオブジェクトをDCへ再選択する
- 作成したペン、ブラシ、フォントをDeleteObjectで削除する
- メモリDCを使った場合はDeleteDCを呼び出す
- GetDCとBeginPaintで適切な解放関数を使い分ける
- 長時間実行時にGDIハンドル数が増え続けていない
| 対象 | 主な用途 | 選択先の例 | 後処理の注意 |
|---|---|---|---|
| HPEN | 輪郭線、直線 | ウィンドウDC、メモリDC | 元のペンへ戻してから削除 |
| HBRUSH | 塗りつぶし | ウィンドウDC、メモリDC | ストックブラシは削除しない |
| HFONT | 文字の書体やサイズ | 文字描画用DC | 元のフォントへ戻してから削除 |
| HBITMAP | ビットマップ描画先 | メモリDC | 選択解除後に削除 |
| HRGN | クリッピング領域 | DCのクリップ操作 | DeleteObjectの対象を確認 |
実装上の実用的な要点は、SelectObjectの呼び出しを描画処理の近くに置き、戻り値を必ず保存することです。作成、選択、描画、復元、削除という順序を一貫させれば、GDIオブジェクトを安全に切り替えながら、線・塗り・文字・ビットマップを意図したデバイスコンテキストへ適用できます。