2010/6/2 - 2011/8/14
Pen Jr.D言語なんてやってみそWin32APIHellow World
<<・・Hellow World
・・>>
まずは簡単に
自前の Window は表示するだけでちょっとたいへんですが、MessageBox を使えばコンソールアプリ並に簡単です。

以下 自前ヘッダー をインストール前提です。

import win32.windows;

void main(){
  .MessageBox( null, "Hellow World", "Hellow", MB_OK );
}
※ この文字列リテラルは 0 終端になる

とりあえず、これでOK

適当な作業フォルダに Hellow.d とかで保存して、コマンドプロンプトをそこで開きます。

ビルドも簡単に

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

出来た Hellow.exe を起動すれば

サンプル

日本語は

import win32.windows;

void main(){
  .MessageBox( null, "こんにちは日本", "Hellow", MB_OK );
}

Unicode だとこれで OK 。

基本
MessageBox だけで GUI アプリちゅうわけにはいきませんから、やることは C や C++ とほとんど同じですが、

本家の指示に従うと

import core.runtime;
import win32.windows;

extern( Windows ){
  int MyWndProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){
    switch( msg ){
      case WM_DESTROY:
        .PostQuitMessage( 0 );
        break;
      default:
    }
    return .DefWindowProc( hw, msg, wp, lp );
  }
  
  int WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine, int nCmdShow
    ){
    int ret;
    void exc( Throwable e ){
      throw e;
    }
    try{
      Runtime.initialize( &exc );
      ret = myMain( hInstance );
      Runtime.terminate( &exc );
    }
    catch( Throwable o ){
      .MessageBox( null, cast( LPWSTR )o.toString, "Hellow", MB_OK );
    }
    return ret;
  }
}

MyWndProcWM_DESTROY を拾いたいから( PostQuitMessage は必須 )

myMain

int myMain( HINSTANCE hinst ){
  WNDCLASSEX wc;
  with( wc ){
    cbSize        = WNDCLASSEX.sizeof;
    hInstance     = hinst;
    style         = CS_HREDRAW | CS_VREDRAW;
    lpszClassName = "MyHellow";    // これもちゃんと 0 終端になる
    lpfnWndProc   = &MyWndProc;
    hCursor       = .LoadCursor( null, IDC_ARROW );
    hbrBackground = cast( HBRUSH )( COLOR_WINDOW + 1 );
  }
  
  .CreateWindowEx(
    0,
    cast( LPWSTR ).RegisterClassEx( &wc ), "Hellow",
    WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    CW_USEDEFAULT, CW_USEDEFAULT,
    300, 200,
    null, null, hinst, null
    );
  
  for( MSG msg; .GetMessage( &msg, null, 0, 0 ) > 0; ){
    .TranslateMessage( &msg );
    .DispatchMessage( &msg );
  }
  
  return 0;
}

ビルドをすると

D:\Pen-Jr\My Programs\Hellow win32>dmd -L/exet:nt/su:windows:4.0 hellow.d
OPTLINK (R) for Win32  Release 8.00.12
Copyright (C) Digital Mars 1989-2010  All rights reserved.
http://www.digitalmars.com/ctg/optlink.html
hellow.obj(hellow)
 Error 42: Symbol Undefined _D5win327winuser14tagWNDCLASSEXW6__initZ
--- errorlevel 1

と、エラーになります

自前の ヘッダー( win32.windows ) を import していますが、そのヘッダーのコンパイルとリンクが必要です。

この場合 winuser.d だけをコンパイルしても良いのですが、 libs フォルダ以下にある win32.lib でビルド済みですから、これを同じフォルダに持ってきて

dmd -L/exet:nt/su:windows:4.0 hellow.d win32.lib

win32.lib を追加すれば OK ですが、

Recommend+ を使えば、

最初の import win32.windows;

import windows;

mixin ImpWinAPI;

とすれば OK。

できた Hellow.exe を起動すれば

サンプル

こんなウィンドウが表示されるはずですが

バグがあったりすると、プロセスが終了しなかったりして面倒です。

WinDbg
GUI アプリは安全のため WinDbg などから実行した方がよいでしょう。

最新版は Windows SDK でインストールします。

インストールされていれば、スタートメニューの Debugging Tools for Windows (x64) なんかに居るはずです。

起動すると

サンプル

File -> Open Executable で実行ファイルを開きます。

サンプル
サンプル

Debug -> Go または F5

暴走とかエラーは止まりますが、フリーズしたら Break

次のビルドをする前に stop debugging しておかないと、実行ファイルの書き換えが出来ず、リンカーでエラーになります。

高機能なデバッガーらしくいろいろ出来るのでしょうが、これだけでも十分です。
リソース

Windows の GUI アプリでな何かと使いますから、リソースコンパイラ( rc.exe )が必要。

Windows SDK Tools に入っていますから、インストールして PASS を通してあれば使えます。

試しに、

D:\Pen-Jr\My Programs\Hellow win32>rc /?

Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385
Copyright (C) Microsoft Corporation.  All rights reserved.

Usage:  rc [options] .RC input file
Switches:
   /r       Emit .RES file (optional)
   /v       Verbose (print progress messages)
   /d       Define a symbol
   /u       Undefine a symbol
   /fo      Rename .RES file
   /l       Default language ID in hex
   /i       Add a path for INCLUDE searches
   /x       Ignore INCLUDE environment variable
   /c       Define a code page used by NLS conversion
   /w       Warn on Invalid codepage in .rc (default is an error)
   /y       Don't warn if there are duplicate control ID's
   /n       Append null's to all strings in the string tables
   /fm      Localizable resource only dll file name
   /q       RC Configuration file for the resource only DLL
   /g       Specify the ultimate fallback language ID in hex
   /g1      Specify if version only MUI file can be created
   /g2      Specify the custom file version for checksum in MUI creation
   /nologo  Suppress startup logo
Flags may be either upper or lower case

アイコンなんかは基本ですから、自作するなり適当なのを拾ってきて

テキストファイルでそのアイコンを指定します

101 ICON        "penguin.ico"  // 101 は任意の ID

こんなのを Hellow.rc で保存したら

D:\Pen-Jr\My Programs\Hellow win32>rc hellow.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.1.7600.16385
Copyright (C) Microsoft Corporation.  All rights reserved.


D:\Pen-Jr\My Programs\Hellow win32>

と、無言で終了すれば hellow.res が出来ますから、ビルドに追加します

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

ビルドしたファイルにはちゃんとアイコンが表示されます。

サンプル

これだけでは、実行時にウィンドウやタスクバーにアイコンは表示されませんから

Hellow.dmyMain にリソースで指定した id を追加します。

  WNDCLASSEX wc;
  with( wc ){
    cbSize        = WNDCLASSEX.sizeof;
    hInstance     = hinst;
    style         = CS_HREDRAW | CS_VREDRAW;
    lpszClassName = "MyHellow";
    lpfnWndProc   = &MyWndProc;
    // 以下を追加
    hIcon         = .LoadIcon( hinst, .MAKEINTRESOURCE( 101 ) );
    //
    hCursor       = .LoadCursor( null, IDC_ARROW );
    hbrBackground = cast( HBRUSH )( COLOR_WINDOW + 1 );
  }
  // 以下省略

ビルドして実行すれば

サンプル

ちゃんとアイコンが表示されます。

D言語で基本的な Windows GUI アプリを作る準備は完了です。
ちょっとモダンに

この程度のプログラムに class だの何だのは必要ありませんが、GUI アプリを自力で作るとコンパクトなものにはなりませんから、汎用性とか拡張性とか考えてみます。

ここまでの様な単純なプログラムでは、誰が書いても同じようなコードになると思いますが、この先はアプローチからして個人差が出ますからあくまでも参考に

汎用モジュールのつもりで追加

module app;

public import core.runtime, std.utf;
import windows;

  // win32.windows を public import して pragma( lib ) を追加
mixin windows.ImpWinAPI;

class CsMain{
  private{
    // 書き換え禁止
    const{
      HINSTANCE hMainInst;
      LPTSTR    lpCmdLine;
    }
  }
  public:
  this( in HINSTANCE hi, in LPTSTR cmd ){
    hMainInst = hi;
    lpCmdLine = cmd;
  }
  ATOM registClass(
    in string nam,
    in int icon,
    HBRUSH hbr,
    WNDPROC proc
    ){
    WNDCLASSEX wc;
    with( wc ){
      cbSize        = WNDCLASSEX.sizeof;
      hInstance     = cast( HINSTANCE )hMainInst;
      style         = CS_HREDRAW | CS_VREDRAW;
      lpszClassName = toUTF16z( nam );
      // UTF8 -> UTF16 ( 0 終端追加 ) std.utf
      lpfnWndProc   = proc;
      hIcon         =
        .LoadIcon(
            cast( HINSTANCE )hMainInst, .MAKEINTRESOURCE( icon )
          );
      hCursor       = .LoadCursor( null, IDC_ARROW );
      hbrBackground = hbr;
    }
    return .RegisterClassEx( &wc );
  }
  HWND createWindow(
    in LPWSTR cnam, in string wnam,
    in DWORD style, in DWORD exstyle,
    in int x, in int y,
    in int cx, in int cy
    ){
    return .CreateWindowEx(
      exstyle, cnam, toUTF16z( wnam ),
      style,
      x, y, cx, cy,
      null, null, cast( HINSTANCE )hMainInst, null
      );
  }
  int opCall(){
    MSG msg;
    while( .GetMessage( &msg, null, 0, 0 ) > 0 ){
      .TranslateMessage( &msg );
      .DispatchMessage( &msg );
    }
    return msg.wParam;
  }
}

  //
  // WinMain のテンプレート
template TpWinMain( Tc : CsMain, string Tappnam ){
  extern( Windows ){
    int WinMain(
      HINSTANCE hInstance, HINSTANCE hPrevInstance,
      LPTSTR lpCmdLine, int nCmdShow
      ){
      int ret;
      void exc( Throwable e ){
        throw e;
      }
      try{
        Runtime.initialize( &exc );
        ret = ( new Tc( hInstance, lpCmdLine ) )();
        Runtime.terminate( &exc );
      }
      catch( Throwable o ){
        ret = .MessageBox(
            null, toUTF16z( o.toString ), toUTF16z( Tappnam ),
            MB_OK | MB_ICONERROR | MB_TOPMOST
          );
      }
      return ret;
    }
  }
}

これを app.d で保存

Hellow.d

import app;

extern( Windows ){
  int MyWndProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){
    switch( msg ){
      case WM_DESTROY:
        .PostQuitMessage( 0 );
        break;
      default:
    }
    return .DefWindowProc( hw, msg, wp, lp );
  }
}

class CsMyMain : CsMain{
  this( in HINSTANCE hi, in LPTSTR cmd ){
    super( hi, cmd );
  }
  override{
    int opCall(){
      createWindow(
        cast( LPWSTR )registClass(
          "MyMainClass",
          101,
          cast( HBRUSH )( COLOR_WINDOW + 1 ),
          &MyWndProc
          ),
        "Hellow",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0,
        CW_USEDEFAULT, CW_USEDEFAULT,
        300, 200
        );
      
      return super.opCall();
    }
  }
}

mixin TpWinMain!( CsMyMain, "Hellow" );

こう書き換えて

ビルドに app.d を追加

>dmd -L/exet:nt/su:windows:4.0 hellow.d app.d hellow.res
これを実行しても結果は同じですが、まあ
make
一行のコマンドですが、コマンドプロンプトを起動し直すたびに毎回打ち込むのは面倒です。

bat ファイルを使っても良いのですが、make を使えば何かと便利です。

all:hellow.exe

hellow.exe:hellow.d app.d hellow.res
  dmd -L/exet:nt/su:windows:4.0 hellow.d app.d hellow.res

hellow.res:hellow.rc
  rc hellow.rc

clean:
  del hellow.exe
  del hellow.res

これを拡張しなしの makefile で保存すれば

D:\Pen-Jr\My Programs\Hellow win32>make
dmd -L/exet:nt/su:windows:4.0 hellow.d app.d hellow.res


D:\Pen-Jr\My Programs\Hellow win32>
wstring
メッセージを表示するには Hellow.dWM_PAINT の処理を追加します。
enum string MyMsg = "ようこそD言語の世界へ";

extern( Windows ){
  int MyWndProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){
    switch( msg ){
      case WM_DESTROY:
        .PostQuitMessage( 0 );
        break;
      case WM_PAINT:
        PAINTSTRUCT ps;
        RECT rc;
        HDC dc = .BeginPaint( hw, &ps );
        .GetClientRect( hw, &rc );
        .DrawText(
          dc, toUTF16z( MyMsg ), MyMsg.length, &rc,
          DT_SINGLELINE | DT_VCENTER | DT_CENTER
          );
        .EndPaint( hw, &ps );
        return 0;
      default:
    }
    return .DefWindowProc( hw, msg, wp, lp );
  }
}

DrawText では 0 終端ではなく文字数で処理されますが、UTFー8.length を渡すと、

サンプル

文字数にはならず、うまくいきません。

対応策はいろいろ考えられますが、

  // UTF16 にする
enum wstring MyMsg = "ようこそD言語の世界へ";

void drawText( HDC dc, ref RECT rc, in wstring s ){
  .DrawText(
      dc, s.ptr, s.length, &rc,
      DT_SINGLELINE | DT_VCENTER | DT_CENTER
    );
}

extern( Windows ){
  int MyWndProc( HWND hw, UINT msg, WPARAM wp, LPARAM lp ){
    switch( msg ){
      case WM_DESTROY:
        .PostQuitMessage( 0 );
        break;
      case WM_PAINT:
        PAINTSTRUCT ps;
        RECT rc;
        HDC dc = .BeginPaint( hw, &ps );
        .GetClientRect( hw, &rc );
        .DrawText(
            // ptr と length を渡せば OK
            dc, MyMsg.ptr, MyMsg.length, &rc,
            DT_SINGLELINE | DT_VCENTER | DT_CENTER
          );
        .EndPaint( hw, &ps );
        return 0;
      default:
    }
    return .DefWindowProc( hw, msg, wp, lp );
  }
}

サンプル

D言語の標準ライブラリーでもほとんど wstring に対応しているようですから、wstring を使った方が悩みは減るかも知れません。

2010年7月6日

WinAPI に関して
解説はほとんど出来ませんが、WinAPI の関数は .MessageBox のように . を使うようにしてあります。
E-Mail : open@pen-jr.org

ご意見やご指摘は大歓迎ですが、質問など返信は期待しないでください

pen jr.
D言語研究
わかったつもりになるD言語
D言語友の会
News&Days
はじめてのブログ選び