山内の授業補完のページ/Windowsアプリ

Win32 APIを使ったHello Worldプログラムをもう一度見てみよう。

#include <stdio.h>
main(int argc, char *argv[])
{
     printf("hello, world\n");
}

と比較して、

#include <windows.h>
int WINAPI WinMain(
		HINSTANCE hInstance ,
		HINSTANCE hPrevInstance ,
		PSTR lpCmdLine ,
		int nCmdShow ) {
	MessageBox(NULL , TEXT("Hello World") ,
			 TEXT("My Message Box") , MB_OK);
	return 0;
}

は、main(int argc, char *argv[])と書いていた部分の代わりにWinMain(...) になっているのです。しかも見慣れたargc, argvが無い。

Win32 APIでのargc, argv(「コマンドラインパラメータ」の取出し)は、 WinMainの第3引数 PSTR lpCmdLine になっています。 (「Win32 API 入門」の「コマンドライン引数」参照。) つまり、 argvで取り出せたコマンドラインパラメータは、Win32 APIでは lpCmdLineの指す文字列としてアクセスできる(というかアクセスしなければならない) のです。

それでも、引数の位置が違うだけならまだよいのですが、内容も違う。具体的には、 argvはポインタの配列で各々のコマンドパラメータ(空白が区切りだった)を 指していました。もしfoo.exeというプログラムの呼出しが

foo.exe parameter1 parameter2 parameter3 parameter4

のようになっていると、

argv[0] --> foo.exeという文字列
argv[1] --> parameter1に書かれた文字列
argv[2] --> parameter2に書かれた文字列
argv[3] --> parameter3に書かれた文字列

のように示されていたのが、Win32では

lpCmdLine --> parameter1 parameter2 parameter3 parameter4

のようになっているということです。だから、lpCmdLineから引数を分解しなければ ならない。

分解するプログラムは、区切り文字として空白' 'を使って次々と読み出してゆけば よいわけです。たとえばstrtokなどを使うと、結構コンパクトに書けたりします。

ところが、1つ厄介なことが...。

MS Windowsのファイル名は、その中に空白を許しています。

ということで、Win32には便利関数CommandLineToArgvが用意してあるらしい。 「Windowsのように」うまく分解してくれる(はずの、MSご推薦の)関数です。

じゃ、これ使おう。と思ったら、なんとCommandLineToArgvW、最後にWがついている ではないか? よく見るとunicode用に出来ていて、こんな風な

LPWSTR *CommandLineToArgvW(
    LPCWSTR lpCmdLine,
    int *pNumArgs
);

インターフェースになっています。入力となる文字列がlpCmdLineですが、それの型が LPCWSTRで、出力は文字列の配列ですが、LPWSTRになっています。このWは ダブルバイト(unicode)文字で、その型に合わせないと何かとまずい。

ちなみに、このunicode文字列の型の扱いは、たとえばこの辺に簡単に触れてあるのだけれど、結構やっかい。

とにかく、メクラ滅法にこんなコードでどうだっ!!

#include <windows.h>

int WINAPI WinMain(
		HINSTANCE hInstance ,
		HINSTANCE hPrevInstance ,
		PSTR lpCmdLine ,
		int nCmdShow ) {

   int argc, i;
   PSTR *buf;
   buf = CommandLineToArgvW(GetCommandLine(), &argc);
   for (i=0; i<argc; i++) {
     printf("%d %s\n", argc, buf[i]);  // デバッグ用
     MessageBox(NULL , (PSTR) buf[i] ,
			  "My Message Box" , MB_OK);
   }
   return 0;
}

ということで試してみると、

$ gcc -o commandline.exe -mno-cygwin commandline.c
commandline.c: In function `WinMain':
commandline.c:15: warning: passing arg 1 of `CommandLineToArgvW' from incompatible pointer type
commandline.c:15: warning: assignment from incompatible pointer type

という結果。予想したとおりだけれど、CommandLineToArgvWの型がLPWSTRに 合っていない。

いろいろと考えた結果、こんなコードにしてみました。

#include <windows.h>

int WINAPI WinMain(
		HINSTANCE hInstance ,
		HINSTANCE hPrevInstance ,
		PSTR lpCmdLine ,
		int nCmdShow ) {

    int argc, i;
    LPWSTR *buf;
    MessageBoxW(NULL , GetCommandLineW() ,
			  L"My Message Box" , MB_OK);
    buf = CommandLineToArgvW(GetCommandLineW(), &argc);
    for (i=0; i<argc; i++) {w
      printf("%d %ws\n", argc, buf[i]);
      MessageBoxW(NULL , buf[i] ,
			  L"My Message Box" , MB_OK);
    }
    return 0;
}

ということで、うまくいった。細かい違いを見てゆくと

というわけで、上のような結果だけれど、そこそこ使えそうです。

但し、この議論はあくまでCygwinでコンパイルする、という話であって、MS Visual Studio C++などではすべてがうまくいくはず、だと思います。さすがにunicodeあたり がひっかかるとCygwinではまだ足りていないところがあるのかな。

さてここで、大事なこと。なぜCommandLineToArgvにこだわったか??

MS Windowsのファイル名は、ファイルやフォルダ名に空白を含んでもよい、という ことがあります。たとえばディレクトリ名 Documents and Settings などというのが 許されてしまう。こうすると、コマンドパラメータのように「空白で区切る」という ルールと両立しなくなりますよね。(ファイル名の中の空白か、パラメータ間の 空白かが区別つかない)そのために、実は空白を含むファイル名があると、 コマンドパラメータでは1つずつのパラメータを"..."と二重引用符で囲むことに なっているようです。これは、もしファイル名(絶対パスのパス名を含めて)の中に 空白があれば引用符でくくり、なければくくらない、という、へんてこりんな ルールになっているようで、いつもくくられているわけでもないようです。

その辺をきちんと処理するのはかなり面倒なので、出来合いの仕掛けを探したら、 CommandLineToArgvに行き当たったというのが本音です。それでMSのテクニカル サイトを探したら、WのついていないCommandLineToArgvが取りざたされているにも かかわらず(当然喜びましたよ)、関数名の説明を探すとWしかない。 そこでCygwinで試してみると、Wが無い方の関数を呼び出すと、「無いよ」と 言ってくるではないか。

で、仕方なくW付きを使おう、と思ったとたん、UNICODEのごちゃごちゃに (Cygwinのややこしさも含めて)巻き込まれた、というわけです。

二重引用符の確認をしておきましょう。先ほどのプログラムで、ファイル名の引数 を実験するために、こんなことをしました。まず、このプログラムcommandline.exe のショートカットを作ります。(エクスプローラで右クリックして「ショートカット の作成」をクリックすると、同じ場所にショートカットが出来ます。これを切り取って デスクトップに貼ります。このショートカットを起動するために、いくつかの ファイルをマウスで掴んで、commandline.exeのショートカット上にドラッグアンド ドロップします。複数ファイルの掴み方は、コントロール(Ctr)キーを押しながら マウス左クリックで選択、です。

2つのファイル、を選択してドロップした結果を表示します。

right,around

1つ目は最初のメッセージウィンドウで、GetCommandLineの結果全体を表示して います。これでわかるとおり、GetCommandLineの結果は

"D:\cygwin\home\yamanouc\testwin32api\commandlineW.exe" 
  "D:\Documents and Settings\yamanouc\デスクトップ\新規テキスト ドキュメント.txt"
  "D:\Documents and Settings\yamanouc\デスクトップ\test.txt"

となっています。

right,around

2つ目はCommandLineToArgvWで分解した第1番目の引数で、argv[0]に当ります。つまり このプログラムのファイル名をフルパスで書いています。

right,around

3つ目は分解した第2番目の引数で、argv[1]に当ります。1番目のファイル名 「新規テキスト ドキュメント.txt」です。デスクトップにおいてある ので、Documents and Settingsの下ですから、1つ目のメッセージウィンドウでは 二重引用符で囲まれていますが、切り出したここでは引用符はありません。自動的に 引用符を削ってくれたのです。また、パス途中に「デスクトップ」という unicodeが入っていますが、正しく処理されています。

right,around

4つ目は分解した第3番目の引数で、argv[2]です。

なお、参考までに、もしこのプログラムを、引数に空白なしファイル名しかないような 条件で使うと、二重引用符が見えない、というところを示しておきます。

right,around

2番目のファイル名(argv[1]、1番目のコマンドライン引数)には引用符が 付いていませんね。先頭のファイル名(プログラムのexeファイルのファイル名) には引用符が付いているのにね。

最後に、やっかいな処理を人任せにした結果、コマンドラインパラメータを切り分けた 結果はunicodeになってしまいました。これは、Cygwin環境ではとても不便なので、 普通のコードに直してしまいます。

unicodeとマルチバイトコードとの変換は、関数WideCharToMultiByteとMultiByteToWideCharを使います。たとえばここ を参照。

プログラムはこんな風になりました。

#include <windows.h>
#include <stdio.h>

int WINAPI WinMain(
		HINSTANCE hInstance ,
		HINSTANCE hPrevInstance ,
		PSTR lpCmdLine ,
		int nCmdShow ) {
    int argc, i, n, status;
    LPWSTR *buf;
    char cbuf[256];
    char xbuf[256];
    MessageBoxW(NULL , GetCommandLineW() ,
			  L"My Message Box" , MB_OK);
    buf = CommandLineToArgvW(GetCommandLineW(), &argc);
    for (i=0; i<argc; i++) {
      printf("%d %ws\n", argc, buf[i]);
      MessageBoxW(NULL , buf[i] ,
			  L"My Message Box" , MB_OK);
    }
    n = WideCharToMultiByte(0, 0, buf[1], -1, cbuf, sizeof(cbuf), NULL, &status);
    printf("n: %d, status: %d, %ws\n", n, status, cbuf);
    MessageBox(NULL , cbuf ,
			  "My Message Box" , MB_OK);

    return 0;
}

変換後のcbufの内容を表示するのに、printfで表示できています。また ここには載せませんが、このコマンドライン引数をファイル名として 使ってfopenすることができています。(unicodeだとWin32 APIの CreateFile, RedFile, WriteFileを使うことになり、Linux/UNIXで作った コードを書き換えなければならず、ちょっと面倒。)


添付ファイル: filecommandline_5.jpg 602件 [詳細] filecommandline_4.jpg 585件 [詳細] filecommandline_3.jpg 596件 [詳細] filecommandline_2.jpg 609件 [詳細] filecommandline_1.jpg 627件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-07-22 (日) 19:04:20 (4439d)