3-b3. Windows API プログラミング

初出: 2007/03/07
最新: 2007/03/07
[3. D言語各論] に戻る

Windows API プログラミング

D言語から Win32 API を直接使うプログラムを書くためのチュートリアルです。

準備1

まず、WindowsApi - bindings があると非常に便利です。これは Platform SDK のヘッダファイルを D のモジュールに翻訳するプロジェクトです。 これを import すると、API宣言をいちいち手書きする手間が省けて簡単に Win32 API が使えるようになります。 最新版をダウンロードするには、Subversion を使って、 以下のコマンドを実行します。

svn export http://svn.dsource.org/projects/bindings/trunk/win32

DSSS がインストール済みならついでにsvnコマンドも入っていますので、 それでOKです。「DSSSもSubversionも入れるの面倒!」という場合は、 このページ の一番下の "Zip Archive" のリンクからzip書庫でダウンロードできます。Win32 API 以外の 色々なヘッダファイル移植も全部まとめてくっついてきます。

(※参照:外部ライブラリの使い方)

準備2

GDC だとこれだけで準備完了なんですが、DMD では、 リンクに必要なインポートライブラリがあまり多く添付されていません。 (kernel32.lib や user32.lib 等々はあるけれど、imm32.lib や mpr.lib が無い。) そこで、Platform SDK から自分でライブラリを用意することになります。必要なものは:

です。

coffimplib c:\PlatformSDK\lib\mpr.lib c:\DigitalMars\dmd\lib\mpr.lib
coffimplib c:\PlatformSDK\lib\imm32.lib c:\DigitalMars\dmd\lib\imm32.lib

みたいに使って、ガンガン lib ファイルを変換しましょう。 とりあえず使う分には mpr.lib だけ変換すれば足りるみたいです。

mpr.lib の関数なんて普通は使わないのにこんなPlatform SDK入れたりするのめんどくさい!という方には 裏技として、ダミーのmpr.libを作ってしまう手があります。「1. 空のファイルにmpr.dと名前をつける」 「2. dmd -c mpr.d」「3. lib -c mpr.lib mpr.obj」で中身なしのライブラリが作れちゃいます。

使ってみる

// win32test.d
import win32.core;

void main()
{
    Beep( 440, 1000 );
}

440Hz の音を1秒間鳴らしてみましょう。コンパイルは普通に

dmd win32test.d

でとりあえず動きます。動きますが、できたwin32test.exeをダブルクリックして起動したときも 黒窓が出てちょっと格好悪いです。

黒窓が出ちゃうの図

この余計なものを消すには、コンパイル時に以下のようなオプションを設定します。

dmd -L/exet:nt/su:windows:4.0 win32test.d

これで音だけが鳴る win32test.exe ができます。dsss を使っていればもっと簡単。

dsss build -gui win32test.d

これでOKです。本家のリファレンス に書いてあるような WinMain をわざわざ書く必要は特にないです。

文字コード

ここを読んでおられるような皆様はご存じかと思いますが、Windowsプログラムは ANSI版とUnicode版をコンパイル時のスイッチで切り替えられるように書くのが一般的です。 D言語からWindows APIを使う場合、コンパイル時に -version=Unicode を指定するとUnicode版、 指定しなければANSI版になります。

// dsss build -gui -version=Unicode win32test.d とコンパイルすると、MessageBoxW が呼ばれる
// 単に dsss build -gui win32test.d なら MessageBoxA が呼ばれる
MessageBox(null, "Hello", "World", MB_OK);

文字列リテラルをANSI/Unicode両用するには、C/C++では _T というマクロを使いました。 上のように直に文字列リテラルをAPIに渡すのは良くないスタイルです。 でも何故か Windows API ライブラリではこれが用意されていないので、自前で書いてしまいましょう。

module win32.tchar;
import win32.windef;

version(Unicode)
{
    import std.utf;
    TCHAR* _T( wchar[] str ) { return (str~\0).ptr; }
}
else
{
    import std.windows.charset;
    TCHAR* _T( char[] str ) { return str.toMBSz(); }
}

Unicode版の場合は単純にD言語での内部表現をそのまま返せばいいのですが、ANSI版の場合は、 Dの文字コードは UTF-8、Windows API の期待する文字コードはShift_JISなどなどと食い違いがあります。 そこで、上にあげた _T ではANSI版のときは文字コードを変換しています。 あともう一つ、Dの文字列は\0終端ではないので、\0を付加するようになっています。

import win32.core;
import win32.tchar; // ↑をwin32/tchar.dとして保存しておく

void main()
{
    MessageBox( null, _T("こんにちは"), _T("たいとる"), MB_OK );
}

めっせーじぼっくす

これで正しいWindowsプログラムになりました。

D言語からWindowsに文字列を渡すときは、これで文字コード変換するようにすれば大丈夫です。 ただし、逆に Windows から文字列を受け取るときには注意してください。Windows は Shift_JIS 等々 環境ごとの文字コードで文字列を渡してきますので、D言語の文字列処理関数で扱うためには、 変換してやる必要があります。…こういう面倒を避けるには、全部Unicode版にしてしまうのが楽かもしれません。

もう少し気合いの入った例

7-zip32.dll を使って Zip 書庫を展開するアプリなんてのを作ってみました。

実行イメージ

起動するとウインドウが開いて、そこにZip書庫や7z書庫をドラッグ&ドロップすると、 展開されます。

import win32.core;
import win32.shellapi; // DragQueryFile 等
import win32.tchar;
import std.windows.charset;

// 7-zip32.dll にコマンドを送る関数
int SevenZip( HWND wnd, char[] cmd )
{
    HINSTANCE h = LoadLibrary("7-zip32.dll");
    scope(exit) FreeLibrary(h);

    extern(Windows) int function(HWND,LPCSTR,LPSTR,DWORD) SevenZip;
    SevenZip = cast(typeof(SevenZip)) GetProcAddress( h, "SevenZip" );

    return SevenZip( wnd, toMBSz(cmd), null, 0 );
}

// "WINAPI" や "CALLBACK" の代わりは extern(Windows) です
extern(Windows)
LRESULT WndProc( HWND wnd, UINT msg, WPARAM wp, LPARAM lp )
{
    switch( msg )
    {
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT        rc;
            HDC hdc = BeginPaint( wnd, &ps );
            GetClientRect(wnd,&rc);
            TCHAR* str = _T("Zip書庫をドロップしてね");
            DrawText( hdc, str, lstrlen(str), &rc, DT_SINGLELINE|DT_VCENTER|DT_CENTER );
            EndPaint( wnd, &ps );
            break;
        }
    case WM_DROPFILES:
        {
            // ドロップされたファイル名情報を受け取り
            HDROP dr = cast(HDROP) wp;
            TCHAR[] buf = new TCHAR[MAX_PATH];
            DragQueryFile( dr, 0, buf.ptr, buf.length );
            // 外から受け取った文字列は即 UTF-8 に変換
            // TtoC は上の w32tchar.zip に入ってます
            char[] name = TtoC(buf.ptr);
            // コマンド発行
            SevenZip( wnd, "x " ~ name );
            DragFinish( dr );
            break;
        }
    case WM_DESTROY:
        {
            PostQuitMessage(0);
            break;
        }
    default:
        return DefWindowProc( wnd, msg, wp, lp );
    }
    return 0;
}

void main()
{
    WNDCLASSEX wc;
    wc.cbSize        = WNDCLASSEX.sizeof;
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = &WndProc;
    wc.lpszClassName = _T("MyDLanguageSample");
    wc.hInstance     = GetModuleHandle(null);
    wc.hCursor       = LoadCursor( null, IDC_ARROW ); 
    wc.hbrBackground = cast(HBRUSH)(COLOR_WINDOW+1);

    RegisterClassEx( &wc );
    CreateWindowEx(
        WS_EX_ACCEPTFILES, wc.lpszClassName, _T("窓です"),
        WS_OVERLAPPEDWINDOW|WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, 200, 100,
        null, null, wc.hInstance, null );

    for(MSG msg; GetMessage(&msg,null,0,0)>0; )
    {
        TranslateMessage(&msg);
        DispatchMessage (&msg);
    }
}

RegisterClassEx ~ CreateWindow ~ GetMessageのループ ~ WndProc の定番の流れは こんな感じになります、という例でした。 落とし穴の話も含めて文字列の扱いさえ気をつければ、 あとはCやC++から使うときと特に変わりはありません。 DLLの解放にscope文を使っている辺りがちょっとしたDっぽさですかね。

[3. D言語各論] に戻る

presented by k.inaba   under NYSDL.