ユーニックス総合研究所

  • home
  • archives
  • windows-rs-text-out

文字列の描画 - Rustで作るWindowsアプリ

  • 作成日: 2023-05-06
  • 更新日: 2023-12-24
  • カテゴリ: windows-rs

文字列を描画する

Windows APIで作成したウィンドウ上に文字列を描画するには以下の関数を使います。

  • GetDC
  • TextOutW
  • ReleaseDC

GetDC関数でデバイス コンテキストのハンドルを取得し、TextOutW関数でそのハンドルに対して文字列を描画します。そして文字列の描画が終わったら後処理としてハンドルをReleaseDC関数でリリースします。

デバイス コンテキストとは?

デバイス コンテキスト(DC)は簡単に言うとグラフィックに関連する構造のことを言います。線画のペンや塗りつぶしとそのためのブラシ、画面の一部をコピーするためのビットマップ、色のパレットなどそういった操作を行うにはこのデバイス コンテキストを通じて行います。
Windowsにはデバイスの独立をサポートするためのライブラリが存在し、その1つがGdi.dllです。これはグラフィックス デバイス インターフェース(GDI)と呼ばれます。アプリはドライバーが読み込まれたらグラフィック関連の準備をするようにGDIに通知し、これらの処理はDCによって維持されます。
DCは簡単に言えば構造体です。つまりグラフィック関連の処理ではこのDCという構造体にデータが保存され参照されるということですね。DCの内容は隠ぺいされており、普通はDCの情報にアクセスするには用意されている関数などを使います。

useする関数

use windows::{  
    core::*,  
    Win32::Foundation::*,   
    Win32::Graphics::Gdi::*,  
    Win32::System::LibraryLoader::*,  
    Win32::UI::WindowsAndMessaging::*,  
};  

上記のようにコードを編集して関連の関数や構造体をuseしてください。

WM_PAINT処理の修正

    WM_PAINT => {  
        // 画面がちらつくテキストの描画  
        let hdc: HDC = GetDC(window);  
        TextOutW(hdc, 10, 10, w!("こんにちは。").as_wide());  
        ReleaseDC(window, hdc);  
        LRESULT(0)  
    }  

ウィンドウプロシージャ関数内のWM_PAINTの処理を上記のように変更します。
この修正を行ったあとにプログラムを実行すると以下のような画面になります。

実行してみるとわかりますが、このコードだと文字列がちらつきます。これの解決にはダブルバッファリングを使うのが一般的です。これの方法は後述します。

GetDC関数

Function windows::Win32::Graphics::Gdi::GetDC  

pub unsafe fn GetDC<P0>(  
    hwnd: P0  // ウィンドウのハンドル  
) -> HDC  
where  
    P0: IntoParam<HWND>,  

この関数は第1引数で指定されたウィンドウのハンドルのクライアント領域または画面全体のデバイス コンテキストのハンドルを得ます。このハンドルは他のGDI関連の関数で使われます。デバイス コンテキストは構造体ですがその内部仕様は不透明になっていて操作するには関数を通じて行います。
引数がNoneの場合、この関数は画面全体のDCを得ます。
返り値は成功した場合はDCのハンドルになります。
ちなみにGetDC関数にNoneを渡すとウィンドウからはみ出した画面全体のコンテキストになります。この状態でテキストを書き込むとディスプレイ画面の好きな位置に文字列を表示できます。なかなか面白いですよね。

HDC

Struct windows::Win32::Graphics::Gdi::HDC  

#[repr(transparent)]  
pub struct HDC(pub isize);  

HDCisizeのラッパーのタプル構造体です。
HDCにはis_invalid()メソッドが実装されています。このメソッドはHDCの整数をチェックして状態が不正かどうかboolで返します。不正の場合は返り値はtrueになります。整数が-1または0だった場合、そのデバイス コンテキストは不正な状態になります。

TextOutW関数

Function windows::Win32::Graphics::Gdi::TextOutW  

pub unsafe fn TextOutW<P0>(  
    hdc: P0,  // デバイス コンテキストへのハンドル  
    x: i32,  // 文字列のx座標  
    y: i32,  // 文字列のy座標  
    lpstring: &[u16]  // 文字列  
) -> BOOL  
where  
    P0: IntoParam<HDC>,  

TextOutW関数はデバイス コンテキストの指定した座標に文字列を書き込みます。返り値は成功した場合は0以外、失敗した場合は0になります。
w!()マクロが返すPCWSTR&[u16]に変換したい場合はw!("こんにちは").as_wide();のようにas_wide()メソッドを使います。
C言語のAPIではTextOutW関数は文字列の長さも指定する必要がありますが、windows-rsTextOutW関数は内部で暗黙的にAPIにlpstringの長さを渡してくれてます。

文字列を変数にしておく

文字列を一度変数として保存しておきたい場合は

    WM_PAINT => {  
        let hdc: HDC = GetDC(window);  
        let s: PCWSTR = w!("こんにちは");  
        TextOutW(hdc, 10, 10, s.as_wide());  
        ReleaseDC(window, hdc);  
        LRESULT(0)  
    }  

というようなコードを書くことで可能です。PCWSTRを使う場合は

use windows::{  
    core::{PCWSTR},  
    ...  
}  

という感じでuseすることも可能です。

ReleaseDC関数

Function windows::Win32::Graphics::Gdi::ReleaseDC  

pub unsafe fn ReleaseDC<P0, P1>(  
    hwnd: P0,  // DCが解放されるウィンドウのハンドル  
    hdc: P1  // DCのハンドル  
) -> i32  
where  
    P0: IntoParam<HWND>,  
    P1: IntoParam<HDC>,  

この関数はほかのアプリケーションが使用できるようにデバイス コンテキスト(DC)を開放します。DCを使い終わったらこの関数で解放するようにしておきましょう。
返り値はDCが解放された場合は1で、解放されなかった場合は0です。