Open8

今更WindowsAPIでデスクトップアプリ

シンプルなウィンドウを表示するためのおおまかな流れ

  1. Window Procedureを定義する
  2. WNDCLASS構造体のパラメータでメインウィンドウの設定を決める
  3. RegisterClass関数を使って、WNDCLASSを登録する
  4. CreateWindow関数を使って、Windowを作成する
  5. ShowWindow関数を使って、Windowを表示する
  6. GetMessage, TranslateMessage, DispatchMessage関数を使ってメッセージループを開始する
  7. 都度呼び出されるWindow Procedure内で受信したメッセージに合わせて適切に処理を行う

Window Procedure

Window Procidureの最小要素は以下のような実装。
typeから始まるHWNDなどの型略称は別になくても良い。

type HWND = nativeint
type WPARAM = nativeint
type LPARAM = nativeint
type LRESULT = int64
type WindowProc = 
    delegate of hwnd : HWND * uMsg : uint * wParam : WPARAM * lParam : LPARAM -> LRESULT

let private wndProc hwnd msg wParam lParam =
    match msg with
    | WM.DESTROY ->
         PostQuitMessage(0)
         0L
    | _ ->
        DefWindowProc(hwnd, msg, wParam, lParam)

.NETから関数ポインタを渡す時はデリゲートを利用する。F#の場合、通常のF#の関数をデリゲートへ変換することが可能。

let mutable wc = WNDCLASS()
wc.WndProc <- WindowProc(winProc)

ProcedureはAPI側から呼び出される。hWndはウィンドウのハンドル(一意な識別子)で.NETの方ならnativeint(IntPtr)となる。
uMsgWM_から始まる定数値で、この値を見て適切なWindows APIの関数を呼び出したりする。
lParamwParamuMsgの値で中身が変わる。

https://docs.microsoft.com/en-us/windows/win32/winmsg/window-notifications
メッセージと各引数の値についてはここら辺にある。

WM_DESTROYは「閉じる」ボタンを押したときに送信されるメッセージ。
PostQuitMessage関数はWM_QUITを送信し、後述するGetMessage関数に0を返させる。

WNDCLASS構造体

https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassw
型のプレフィックスは除いたフィールド名にしています。
F#
[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)>]
type WndClass =
    struct
        val mutable Style: ClassStyle
        val mutable WndProc: WndProc
        val mutable ClsExtra: int
        val mutable WndExtra: int
        val mutable HInstance: HINSTANCE
        val mutable HIcon: HIc
        val mutable HCursor: HCURSOR
        val mutable HBrush HBRUSH
        val mutable MenuName: string
        val mutable ClassName: string
    end

Class Style(Style)

Winuser.hではCS_から始まる定数。Window Classの追加要素を決める値で、OR演算子で複数のスタイルを組み合わせることができる。描画にまつわる設定値が多い他、閉じるボタンを無効化することもできる。

Extra Class Memory(ClsExtra)

システムは、システム内の各ウィンドウクラスに対してWNDCLASSEX構造体を内部的に保持しています。アプリケーションがウィンドウクラスを登録する際には、WNDCLASSEX構造体の末尾に何バイトかのメモリを追加で割り当てて追加するようにシステムに指示することができます。このメモリはエクストラ・クラス・メモリと呼ばれ、そのクラスに属するすべてのウィンドウで共有されます。エクストラ・クラス・メモリには、そのクラスに関連するあらゆる情報が格納されます。

Extra Class Memoryはシステムのローカル・ヒープから割り当てられるため、アプリケーションはExtra Class Memoryを控えめに使用する必要があります。RegisterClassEx 関数は、要求されたExtra Class Memoryの量が 40 バイトを超えると失敗します。アプリケーションが 40 バイト以上のメモリを必要とする場合は、独自のメモリを割り当て、そのメモリへのポインタをExtra Class Memoryに格納する必要があります。

ECMのサイズを設定する。使用しない場合は0
ポインタを持たせて関数呼び出したり…ということができるようだ。
GetClassLong, SetClassLongで設定/取得できる。

Extra Window Memory(WndExtra)

システムは、各ウィンドウの内部データ構造を保持しています。ウィンドウのクラスを登録する際に、アプリケーションはExtra Window Memoryと呼ばれる追加のメモリを指定することができます。そのクラスのウィンドウを作成する際に、システムは指定された量のExtra Window Memoryを確保し、ウィンドウの構造体の最後に追加します。アプリケーションはこのメモリを使用して、ウィンドウ固有のデータを保存することができます。

追加メモリはシステムのローカル・ヒープから割り当てられるため、アプリケーションは追加ウィンドウ・メモリを控えめに使用する必要があります。RegisterClassEx 関数は,要求されたExtra Window Memoryの量が 40 バイトを超えると失敗します。アプリケーションが 40 バイト以上のメモリを必要とする場合は、独自のメモリを割り当て、そのメモリへのポインタをExtra Window Memoryに格納する必要があります。

EWMのサイズを設定する。使用しない場合は0
GetWindowLong, SetWindowLongで設定/取得できる。
概ねECMと同じ? EWM Injectionという攻撃方法が存在する。

Instance handle(HINSTANCE)

アプリケーションや.dllを識別するための値。GetModuleHandleNULLを渡すことで呼び出し元のハンドルを取得できます。

Icon, Cursor, Brush handle(HICON, HCURSOR, HBRUSH)

アイコンやカーソル、背景のブラシのハンドル。
WNDCLASSEX構造体を使うと、大アイコンと小アイコンを設定できる。
ウィンドウにカーソルが重なった時のカーソルの形状を設定したり、背景を設定したりできる。

Class Menu(MenuName)

メニューのクラス名を与えることでウィンドウにメニューを設定できるようだ。NULLを与えた場合、そのウィンドウにはメニューがなくなる。

Class Name(ClassName)

クラス名。同一プロセス内で一意なヌル終端文字列を設定する必要がある。

Windows FormsやWPF

.NET (Framework)でWindows APIをラップしたフレームワーク。
抽象度は Windows API < Windows Forms < WPF

System.WindowsのUnix向けラッパーを用意できればLinuxやmacOSでもこれらが使えるかもしれない…?

ポインタを使わずポインタを引数に持つC++の関数とやり取りするメモ

void SomeFunction(ISomeInterface **pp);

ポインタのポインタはclassinterface型をref,out,inで参照渡しする。

void SomeFunction(ref ISomeInterface pp);
void SomeFunction(ISomeInterface *p);

ポインタはclassinterfaceなどの参照型ならそのまま。
structなどの値型は参照渡しする。

void SomeFunction(ISomeInterface p);
作成者以外のコメントは許可されていません