2010/5/24 - 2011/9/18
Pen Jr.D言語なんてやってみそWin32APIC .h to D
Windows SDK
Win32 API
コンソールアプリならば D言語の標準ライブラリだけでも組むことが出来ますが、GUIアプリの場合は簡単なものでも Win32 API を使う必要があります。

API というのは Application Programming Interface ということで、OS に限らず Application の機能を使うためのインターフェイスで、その機能名(関数名)やパラメーターなどはその API 独自のものになるのか普通です。

プログラム言語の標準ライブラリーもある意味 API で、その手順はほとんど同じで、必要な関数の場所と引数などがコンパイラにわかるようにしてやれば良いだけです。

Win32 API が難解なのはその量が膨大なため欲しい機能の使い方を探すのが難解で、しかもその機能を使う場合には初期化だの何だのといろいろな準備が必要なことが多く、特に GUI は単にウィンドウに hello world を表示するだけでも難解です。

さらに異常に古いものが互換性のためか残されていていたり、継承されていたり、混在していたり・・・とさらに難解です。

また、解説やサンプルは C言語ですから、最低限の知識も必要になります。

C言語以外の解説もありますし、結構古いものでも参考になりますが、正確に使うためには msdn で確認した方がよいでしょう。

SDK
API を使うということは、公開された関数を呼び出すことが基本になりますから、関数名やその引数などを知る必要がありますが、Windowsの場合 SDK(ソフトウェア開発キット)が無料公開されてます。

Platform SDK と呼ばれていたものが、.net とかの API と統合されて Windows SDK となったようです。

msdn で公開されてる情報を見ることで何とかなる部分もありますが、少なくとも最新の .lib ファイルといくつかのツールは必要になるでしょう。

Microsoft Windows SDK for Windows 7
D言語で Windows の GUIアプリを作る場合のアプローチはいくつかあるようですが、DMD に標準で添付されるものは、ライブラリなども不十分なので SDK のインストールが必要になります。

最新(2011年1月27日現在 version : 7.1)のものはMicrosoft の Download Center から

のどちらかをダウンロードしますが、iso ファイルの場合は開発環境に合ったものが必要です。

winsdk_web.exe
環境にあったものを自動的にダウンロード、インストールしてくれる。
GRMSDK_EN_DVD.iso
開発環境が 32ビット Windows なら。
GRMSDKX_EN_DVD.iso
64ビット Windows なら。
iso ファイルの場合ダウンロードしたものをメディアに焼くか、仮想ドライブにマウントしてからインストール。
インストール
筆者の環境では iso ファイル を使わないとうまくインストール出来ないことがありましたが、winsdk_web ならダウンロードしたファイルを実行するだけで簡単です。

また、どちらでインストールしても手順はほとんど同じです。

最初のメッセージ

最初にこんなのが出るかもですが OK で OK。

サンプル

Next >

サンプル

大事なことが書いてあるのでしょうが、さらっと I Agree を選んで Next >

次にインストール先を選択できます。

サンプル
Tools
Samples
サンプルコードのインストール先。

次は、インストール項目の選択です。

サンプル
Samples
C++ とかの開発環境があれば別ですが、D言語開発では参考資料でしかありませんし、サンプルコードならネット上にもあります。

また、結構バグとか適当なところが多くて、msdn のサイトにあるやつで修正されてたりもしますから、必要なものをその都度ダウンロードすれば OK。

Windows Headers and Libraries
時に絶対必要になります。
Tools
Common Utilities
筆者は知らないツールだらけで、スタートメニューに登録されたりして邪魔ですが、

Debugging Tool for Windows だけはインストールしておきましょう。

サンプル
Foldef for Tools
デフォルトだと "C:\Program Files\Microsoft SDKs" なんかにインストールされるはずです。
..\Windows\v7.1\Bin
ほとんど何するツールなのかわからないものばかりですが、リソースコンパイラ( rc.exe )などいくつか必要なツーが入ってます。
..\Windows\v7.1\Include
.h ファイル。

D言語で使うには翻訳(移植) が必要。

..\Windows\v7.1\Lib
.lib ファイル。

dmd にも標準添付されていますが完全ではありませんから、ここのある .lib ファイルが必要になる場合があります。

そのままでは使えませんから、coffimplib で変換します。

Folder for Samples
..\Windows\v7.1\Samples

サンプルプログラム。

最新 API の基本的な使い方は無料の情報源が少なく、このサンプルから学ぶ必要があることが多い。

Debugging Tools for Windows
ポーティング( 移植 )
MSDN
D では C の関数を直接呼び出すことが出来ますから、MSDN のリファレンスを見ながら宣言して呼び出すことも出来ます。

例えば、RegisterClassEx を呼ぶ場合、MSDN では

ATOM WINAPI RegisterClassEx(
  __in  CONST WNDCLASSEX *lpwcx
);

となっていますから

extern( Windows ){
  ATOM RegisterClassEx( WNDCLASSEX* );
}

と宣言して User32.lib をリンクすれば使えるわけですが、その前に ATOMWNDCLASSEX なんかの宣言も必要になります。

WNDCLASSEXRegisterClassEx のところにも リンク があって

typedef struct tagWNDCLASSEX {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
  HICON     hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;

なんてのは D でもほとんど同じで、

struct WNDCLASSEX {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
  HICON     hIconSm;
}
alias WNDCLASSEX* PWNDCLASSEX;

と定義すれば OK ですが、

UINTWNDPROCHINSTANCE ・・・ と、なんかとっても面倒です。

また、これは ANSI の定義ですから、Unicode の場合、LPCTSTRLPCWSTR にする必要があったりもします。

MSDN の情報は重要ですが、定義・宣言をするための情報ではないようです。
.h ファイル
Windows Headers

RegisterClassExWinUser.h

WINUSERAPI
ATOM
WINAPI
RegisterClassA(
    __in CONST WNDCLASSA *lpWndClass);
WINUSERAPI
ATOM
WINAPI
RegisterClassW(
    __in CONST WNDCLASSW *lpWndClass);
#ifdef UNICODE
#define RegisterClass  RegisterClassW
#else
#define RegisterClass  RegisterClassA
#endif // !UNICODE

WNDCLASSEXWinUser.h

typedef struct tagWNDCLASSEXA {
    UINT        cbSize;
    /* Win 3.x */
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
    /* Win 4.0 */
    HICON       hIconSm;
} WNDCLASSEXA, *PWNDCLASSEXA, NEAR *NPWNDCLASSEXA, FAR *LPWNDCLASSEXA;
typedef struct tagWNDCLASSEXW {
    UINT        cbSize;
    /* Win 3.x */
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR     lpszMenuName;
    LPCWSTR     lpszClassName;
    /* Win 4.0 */
    HICON       hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;
#ifdef UNICODE
typedef WNDCLASSEXW WNDCLASSEX;
typedef PWNDCLASSEXW PWNDCLASSEX;
typedef NPWNDCLASSEXW NPWNDCLASSEX;
typedef LPWNDCLASSEXW LPWNDCLASSEX;
#else
typedef WNDCLASSEXA WNDCLASSEX;
typedef PWNDCLASSEXA PWNDCLASSEX;
typedef NPWNDCLASSEXA NPWNDCLASSEX;
typedef LPWNDCLASSEXA LPWNDCLASSEX;
#endif // UNICODE

で、もちろん C言語 ですから D言語 への翻訳が必要です。

ほとんど手作業で移植するのが基本ですが、WinUser.h だけでも 13千行 ほどありますし、必要なファイルもまだまだまだまだあります。

標準 windows.di
最小限の定義・宣言は組み込みのやつにありますから、それで足りる場合 std.c.windows.windowsimport すれば OK。

「定義・宣言が最小限」と言うこと以外の問題はほとんどありません。

Bindings for the Windows API
.h ファイルのポーティング・プロジェクトです。

download a snapshot of latest revision in SVN (.zip)

をダウンロードして使えます。

D で Windows API 扱うならこれ」ちゅう感じで、筆者もお世話になりましたし、組み込みのやつより作りが丁寧で、移植の良いお手本にもなります。

最新( Windows7 )への対応がされていれば( 2011年8月11日現在まだ )うれしかったのですが、

自前ヘッダー
自動変換プロジェクトですが、変換済みサンプルを使えば OK。

2011年8月11日現在、最新対応で不足も最少のはず・・・

.lib ファイル
リンカー・エラー
.lib の問題はリンカー・エラーになりますが、慣れないと原因の特定が困難です。
Linker Error

こんなことになることがありますが、

OPTLINK (R) for Win32  Release 8.00.12
Copyright (C) Digital Mars 1989-2010  All rights reserved.
http://www.digitalmars.com/ctg/optlink.html

こんなメッセージ以下はリンカー・エラーです。

File Not Found

最初の

win7.lib
 Warning 2: File Not Found win7.lib

は読み込み指定されてる .lib がないと出ます。

この場合の win7.lib は、構成が変わって不要になった .lib ですから指定を削除すれば良いのですが、Window API の .lib のファイルの場合 SDK にあるファイルを変換 してやる必要があります。

Symbol Undefined

未定義シンボルとか言われても、リンカーのメッセージは記号付きで使った覚えのないものに化けているのが普通です。

hellow.obj(hellow)
 Error 42: Symbol Undefined _CoInitialize@4
hellow.obj(hellow)
 Error 42: Symbol Undefined _CoUninitialize@0
hellow.obj(hellow)
 Error 42: Symbol Undefined _CoCreateInstance@20

まずは、こんな @ 付きの奴ら

この手は外部参照シンボルが解決できないと出るらしく、大抵は Windows API の Function です。

例えば _CoInitialize@4CoInitialize が未定義シンボルで、この手はネット検索で CoInitialize Function なんかにたどり着けば解決の糸口がつかめます。

hellow.obj(hellow)
 Error 42: Symbol Undefined _D5win328uiribbon17IUICommandHandler11__InterfaceZ
hellow.obj(hellow)
 Error 42: Symbol Undefined _D5win328uiribbon14IUIApplication11__InterfaceZ

ちょっと暗号な奴らは以外とわかりやすくて

_D5win328uiribbon17IUICommandHandler11__InterfaceZ

win32.uiribbon.IUICommandHandler のことで、interfacestruct なんかの定義がコンパイルされていないとエラーが出ます。

これは win32.uiribbon をコンパイル・リンクすることで解決しますが、

hellow.obj(hellow)
 Error 42: Symbol Undefined _CLSID_UIRibbonFramework
hellow.obj(hellow)
 Error 42: Symbol Undefined _IID_IUIFramework
hellow.obj(hellow)
 Error 42: Symbol Undefined _IID_IUIRibbon
hellow.obj(hellow)
 Error 42: Symbol Undefined _IID_IUICommandHandler
hellow.obj(hellow)
 Error 42: Symbol Undefined _IID_IUIApplication

これはも import したモジュールのコンパイルが必要です。

それぞれ頭の '_' 以下のシンボルが対象で、モジュールを特定する材料はありませんが、

coffimplib( COFFIMPLIB
Windows API の library file は COFF format で、D では OMF format ということですから、変換が必要になります。

ftp://ftp.digitalmars.com/coffimplib.zip からダウンロード。

coffimplib.exe を適当な場所にインストール。

Windows SDK の必要な .lib ファイルを

coffimplib ole32.lib .\new\ole32.lib
ヘッダーファイルのビルド
組み込みの std.c.windows.windows 以外の移植ファイルを使う場合、その .d のコンパイルとリンクが必要になります。

ポーティング・ファイルに限らず、インポート先の定数以外を参照する場合にはそのインポート先ファイルのビルドは必至です。

ビルド時にそのファイルをいっしょに指定してしまっても何とかなりますが、その場合は import とは違いそのファイルのパス指定が必要になり面倒です。

必要な ポーティング・ファイル は全てコンパイル( .lib 化 )して、リンカーが検索できる場所に置いておくのが基本です。

Recommend

ただでさえ Windows API は難解で試行錯誤が必要ですから、最新 OS の API を試すにも準備ばかりに手間をかけるのは勉強にはなりますがうんざりするほどですし、ビルドでつまずいたりするとD言語がいやになったりするかも?

単純で覚えやすい構成で再構築やメンテナンスがしやすく、トラブルフリーな環境を作ってやれば OK。

自前ヘッダー
HtoD.zip のダウンロード

.\HtoD 以下

  • .\win32( Unicode )
  • .\win32a( ANSI )
  • .\win64( 64bit )

.h の変換済み .d 及び .di ファイルが、

.\libs に各ビルド済み .lib ファイルがあります。

.d 及び .di ファイルのモジュール名には、各フォルダと同名のパッケージ名が付けられていますから、フォルダごとコピーするか、同名フォルダを作って .di をコピーします。
最新 .lib

何しろ Windows SDK.lib は大量にありますから、必要なやつと、必要になるだろうやつをピックアップ・・・ とかは面倒です。

要は、最新 SDK.lib ファイルを 全部変換 して、リンカーには 最初 にそのファイル群を検索してもらえば良いわけです。

coffimplib では、変換ファイルを1つずつ指定する必要があるようですから、*.lib を変換するには、

プログラム組んじゃうのが簡単で面白いでしょう。

適当なフォルダ作って convlib.d とかで

import std.stdio, std.file, std.path, std.process, std.string;

void main( in string[] args ){
  // デフォルト値
  string indir = r".\lib", outdir = r".\winlib";
  int i = args.length - 1;
  
  // 変換先フォルダ
  switch( i ){
    case 0:
      break;
    case 2:
      outdir = std.string.format( r".\%s", args[ i ] );
      i--;
      goto case;
    case 1:
      indir = std.string.format( r".\%s", args[ i ] );
      break;
    default:
      assert( 0 );
  }
  // フォルダ確認
  if( !std.file.exists( indir ) ){
    assert( 0 );
  }
  if( !std.file.exists( outdir ) ){
    std.file.mkdir( outdir );
  }
  
  //----------------------------
  //
  // coffimplib.exe で変換
  void coff( in string s ){
    writeln( s );
    std.process.system(
        std.string.format(
          r"coffimplib %s\%s %s\%s",
          indir, s, outdir, s
        )
      ); 
  }
  
  // indir の .lib ファイルを列挙する
  foreach( string s; std.file.dirEntries( indir, SpanMode.shallow ) ){
    // 大文字 "LIB" とかも考慮
    if( !std.string.icmp( std.path.getExt( s ), "lib" ) ){
      coff( std.path.basename( s ) );
    }
  }
}

こんなんをコンパイルして convlib.exe をビルド

  1. 同じフォルダに coffimplib.exe をインストール
  2. 最新 Windows SDK の .\lib をフォルダごとコピー
  3. convlib.exe を引数なしで実行

変換できないやつがいていくつかエラーが出ますが、 .\winlib に変換済み .lib ファイルが出来るはずです。

初期設定ファイル

変換した .di ファイルを .\dmd2\\src\druntime\import に、.lib ファイルを .\dmd2\\windows\lib にコピーしても Ok ですが、D コンパイラ の 初期設定ファイル( sc.ini ) を書き換えることで、メンテナンスしやすい任意の場所に置くことも出来ます。

HOME 環境変数
コンパイラは、通常 .\dmd2\windows\binsc.ini を読み込みますから、その sc.ini を書き換えることでもカスタマイズできますが、コンパイラのアップデート時に上書きされてしまいます。

sc.ini に限らず、.\dmd2 の中は一切いじらずに済む様にした方が良いでしょう。

コンパイラは、

  1. 実行時のカレントディレクトリー
  2. 環境変数 HOME で指定されたディレクトリー
  3. dmd.exe のあるディレクトリー

の順で sc.ini を検索しますから、環境変数 HOME 使って任意の初期設定ファイルを読み込ませることが出来ます。

%@P%

初期設定ファイルでは %@P% という変数を使えます。

%@P% は読み込んだ sc.ini のあるディレクトリーのフルパスに置き換えられます。

.\dmd2\windows\bin にある素の sc.ini では

[Version]
version=7.51 Build 020

[Environment]
LIB="%@P%\..\lib";\dm\lib
DFLAGS="-I%@P%\..\..\src\phobos" "-I%@P%\..\..\src\druntime\import"
LINKCMD=%@P%\link.exe

%@P%.\dmd2\windows\bin ( フルパス )に置き換えられます。

dmd2 と 最新 SDK の変換済み .lib.di 群と sc.ini を1つのパッケージにして、そこを %HOME% にしてやれば OK。

%HOME% パッケージ
筆者の環境では D:\Program Files\D にインストールされていますから、

PATH と同様に HOME を設定します。

環境変数

%HOME% ディレクトリーに import フォルダを作って HtoDwin32win32awin64 をフォルダごとコピー。

winlib フォルダを作って、HtoD.\lib.lib と、変換済み Windows SDK の最新 .lib を全部コピー。

インストールフォルダ
sc.ini 編集

sc.ini

[Version]
version=7.51 Build 020

[Environment]
LIB="%@P%\winlib";"%@P%\dmd2\windows\lib";
DFLAGS="-I%@P%\dmd2\src\phobos" "-I%@P%\dmd2\src\druntime\import" "-I%@P%\import"
LINKCMD=%@P%\dmd2\windows\bin\link.exe
Recommend+
module windows;

enum : string{
  ShlObj = "shlobj",
  WinCodec = "wincodec",
  D2D1 = "d2d1",
  D3D10 = "d3d10_1",
  UiRibbon = "uiribbon",
  Richedit = "richedit"
}

  /*****************************
  *
  *     文字列合成 Template
  *
  */
template ImpJoin( string TOP, string S, T ... ){
  const:
  static if( T.length > 1 ){
    string ImpJoin = ImpJoin!( TOP ~ S ~ T[ 0 ] ~ ", ", S, T[ 1 .. $ ] );
  }
  else{
    string ImpJoin = TOP ~ S ~ T[ 0 ] ~ ";";
  }
}

template ImpJoinBy( string S, T ... ){
  const string ImpJoinBy = ImpJoin!( "public import ", S, T );
}

  /*************************************************************
  *
  *
  *
  */
template ImpWinAPIOf( bool isPrg, T ... ){
  version( ANSI ){
    static if( isPrg ){
      pragma( lib, "win32a.lib" );
    }
    mixin( ImpJoinBy!( "win32a.", T ) );
  }
  else version( Win64 ){
    static if( isPrg ){
      pragma( lib, "win64.lib" );
    }
    mixin( ImpJoinBy!( "win64.", T ) );
  }
  else{
    static if( isPrg ){
      pragma( lib, "win32.lib" );
    }
    mixin( ImpJoinBy!( "win32.", T ) );
  }
}

template ImpWinAPI( T ... ){
  mixin ImpWinAPIOf!( true, "windows", T );
}

template ImpWinAPIEx( T ... ){
  mixin ImpWinAPIOf!( false, T );
}

2010年6月23日

E-Mail : open@pen-jr.org

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

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