MinGWでWindowsソフト開発(ウィンドウ編)

(2011/1/1更新)
HOME / MinGWトップへ

■ 概要


前回はウィンドウを伴わない実行ファイル生成を行いました。
今度はWindowsソフトの定番であるGUIソフトを作ってみます。

MinGWではヘッダファイル <windows.h> を取り込むだけで、Windows OSの提供する「Win32API」を利用してWindowsプログラムを書くことができます。
このWin32APIはVisual Cなど他のWindows開発用言語からも使うことから「Win32API 入門」などのキーワードで検索すれば解説ページがたくさんヒットするのでそれを参考に記述することができます。


■ 簡単なメッセージボックス表示


Windowsプログラムを作成するにあたって、まずは簡単なメッセージボックスを表示させてみます。

前回同様、プログラムメニューの[MinGW Shell]からMSYS端末を起動させて作業開始します。

ソースコード [message.c]

見てのとおり、Windowsプログラムは必ず WinMain関数から実行が開始されます。
(CUIではmain関数から始まりました。)



MakefileはCUIで使っていたものを流用します。

[Makefile]

SRCおよびPROGの設定値を変えるだけでそのまま使うことができます。



コンパイル作業

Username@foo-PC ~
$ make
gcc -Wall -O3 -o message.o -c message.c
gcc message.o -o message.exe

Username@foo-PC ~
$ message

makeと打つと、新たにmessage.exeが作成されました。
messageと打って、作成されたexeファイルを実行させてみます。

メッセージボックスが現れたらOKです。


■ ウィンドウを作ってみる


先ほどはメッセージボックスという小さなウィンドウでした。今度はごく一般的なウィンドウを作成してみます。

メインのソースコード [window.c]

だいぶコード量が増えてしまいました。これがWindowsプログラムを作る場合の骨格となります。

ウィンドウクラスを設定して、登録して、作成したら、実際に表示をさせます。
あとはメッセージループに入り、行われた操作に対する処理を繰り返すことになります。

OSからAPIを通じて知らされた操作内容は、DispatchMessage()によってウィンドウプロシージャに送られることで適切な処理(ウィンドウが破棄された等)が行われることになります。

なので、各種操作に対応する処理をウィンドウプロシージャに羅列していくことになります。



メッセージボックスのみの表示では必要なかったですが、ウィンドウを持つプログラムでは、コンパイル時に "-mwindows" オプションを渡してリンクを行わないとリンケージエラーになってしまうので注意が必要です。

リンケージエラーメッセージ例
undefined reference to `GetStockObject@4'
collect2: ld returned 1 exit status


以上を踏まえた上でMakefileを書き換えてみます。

makeユーティリティ [Makefile]

前回同様、SRCおよびPROGの設定値を変えることと、今度は "-mwindows" オプションを渡せるようにLDFLAGSのコメントを外しています。



ではこれでコンパイルしてみます。

コンパイル作業

Username@foo-PC ~
$ make
gcc -Wall -O3 -o window.o -c window.c
gcc window.o -mwindows -o window.exe

Username@foo-PC ~
$ window

windowと打って、作成したexeファイルを実行してみます。

ウィンドウが現れたらOKです。


■ ウィンドウにメニューを付ける


Windowsプログラムと言えば、ウィンドウ上部にメニューがあってそれを選んで操作していくのが定番です。

ウィンドウにメニューを組み込んで、それぞれの処理に振り分けることをやってみましょう。

メニューリソース定義(リソーススクリプト)[menu.rc]

ファイル先頭で指定した "WIN_MENU" はメニューを識別するためのIDで、識別できるものを適当に付けておきます。メインプログラム側からはここで付けたIDを使ってこのメニューを指定することができます。

定義したメニューリソースは、MinGW付属のwindresコマンドを使ってオブジェクトファイルにコンパイルします。これはリソースコンパイラと呼ばれるものになります。

メニューリソースのコンパイル作業

Username@foo-PC ~
$ windres menu.rc menu.o

これでメニューリソースのオブジェクトファイルmenu.oを作成することができました。



日本語を扱う上で気を付けなければならないことがあります。
シフトJISの持つ 0x5C問題があるので、「貼」「表」「ソ」などの第2バイトの次にくる文字が勝手にエスケープされないように\(0x5C)を補ってやります。

そうしないと以下のメッセージが出て正しくオブジェクトファイル作成ができません。

$ windres menu.rc menu.o
menu.rc:12: unrecognized escape sequence
menu.rc:14: unrecognized escape sequence
menu.rc:18: unrecognized escape sequence
menu.rc:19: unrecognized escape sequence
menu.rc:20: unrecognized escape sequence
menu.rc:22: unrecognized escape sequence


0x5Cコード位置を\で補わずにコンパイルしてみると、実際には文字化けせずに正しく作成されました。
ただやはり上記メッセージが出てしまうので、これを出さないようにするために "--language=0411" オプションを指定すれば回避できるという情報がありますが、こちらの環境ではうまく抑制することができませんでした。どうやらgcc自身のビルドの問題らしい。


でもせっかくUNIXライクなMSYS環境を構築して使ってるわけですからコマンドで前処理する工夫をしてみます。

Username@foo-PC ~
$ sed 's/\\/\\\\/' menu.rc | windres -o menu.o

sedコマンドを使って\文字を補い、パイプを使ってwindresコマンドへ標準入力から渡せばいいだけです。案外簡単にできます。



これで無事にメニューリソースをオブジェクトファイル化できたら、次はメニューを呼び出すウィンドウ側の作成になります。

メインのソースコード [window.c]

前回のウィンドウ表示と違う部分は、WndProc()関数(ウィンドウプロシーシャ)だけです。メニュー選択肢の数だけswitch文による場合分けが増えました。

LoadMenu()関数の第2引数にメニューリソースを書くときに付けたメニューIDを渡してロードしています。

リソースファイルの時と同様、シフトJISの0x5C問題があるので\(0x5C)文字を補っています。ただし\文字を補わずにコンパイルオプションを渡して回避できる方法があるのでこれをMakefile作成で使うことにします。



オブジェクトファイルwindow.oの作成は前回使ったMakefileでもできますが、menu.oをリンクさせないままexeファイルまで作成してしまうのでひとまず手動で行います。

オブジェクトファイルにコンパイルした後、先に作成しておいたmenu.oとリンクさせる方法で実行ファイルを作成していきます。

コンパイル作業

Username@foo-PC ~
$ gcc -c window.c

Username@foo-PC ~
$ gcc window.o menu.o -mwindows -o menu.exe

Username@foo-PC ~
$

gccコマンドに "-c" オプションを渡すことで、オブジェクトファイル作成までに止めることができます。

その後にオブジェクトファイル同士をリンクさせて実行ファイルを生成しています。



分けないで一度に作成する方法もあります。


Username@foo-PC ~
$ gcc window.c menu.o -mwindows -o menu.exe

Username@foo-PC ~
$


ここまで手打ちでコンパイル作業を行いましたが、makeコマンド一発で作成できるようにMakefileを書き換えてみたいと思います。

makeユーティリティ [Makefile]

RC変数にリソースファイルをセットし、OBJS変数にオブジェクトファイル時のファイル名を追加しています。

また、MENU変数にはwindresコマンドを指定しています。
これで以下の記述が成立するようになります。

%.o: %.rc
	$(MENU) $(RC) $*.o

%.o: %.rcは、「左側」は「右側」から作られてるという意味になるのでこの場合はオブジェクトファイルがリソースファイルから生成されることを意味します。

その下のタブで始まる行が、生成方法を記述している部分になります。

わかりやすいように変数・マクロを展開すると、

menu.o: menu.rc
	windres menu.rc menu.o

このようになります。



このMakefileを使ってコンパイル作業をやってみます。

コンパイル作業

Username@foo-PC ~
$ make
gcc -Wall -O3 -o window.o -c window.c
windres menu.rc menu.o
gcc window.o menu.o -mwindows -o menu.exe

Username@foo-PC ~
$ menu

これでmakeと打っただけで実行ファイルmenu.exeが作成されました。これを実行すると以下のような感じになりました。





どうでしょう。。だいたいWindowsアプリケーションっぽくなってきたと思います。



ここで、メニューリソースファイルやCソースコードをシフトJISでそのまま記述した時に「貼」「表」「ソ」などに含まれる\(0x5C)文字によって起こる問題を回避するMakefileを作成してみます。

makeユーティリティ [Makefile]

メニューリソースのコンパイルのところにも書きましたが、リソースコンパイル部分にはsedコマンドによる前処理を追加しています。

またCソースコードのコンパイルエラーを回避するために、CFLAGS変数には新たに2つのオプション "-finput-charset=cp932" "-fexec-charset=cp932" を追加して、どの文字コードで書かれたソースで実行時にどの文字コードとして扱うのかを教えています。

これでシフトJISの\(0x5C)問題を気にせずに開発を進めていくことができるようになりました。



これを使ってmakeと打つと、以下のようになりました。

コンパイル作業

Username@foo-PC ~
$ make
gcc -Wall -O3 -finput-charset=cp932 -fexec-charset=cp932 -o window.o -c window.c
sed 's/\\/\\\\/' menu.rc | windres -o menu.o
gcc window.o menu.o -mwindows -o menu.exe

Username@foo-PC ~
$

コマンド一発で、エラー・警告のたぐいを一切出さないで実行ファイル生成できるのはいいもんですね。





ここまでくれば、あとは具体的に自分がどのようなアプリケーションを作りたいかを明確にすることが大事です。

そのために必要な機能はどのように書けば実現できるかを調べまくって完成させるところまでを繰り返していくことで身についてくると思います。

とにかく動きのわかるものを作ることは楽しいので、枝葉の部分(ウィンドウの表示位置やサイズ変更や再描画などの細かいこと)はアプリケーションを作っていく過程でついでに覚えていけばいいと思います。



▼ ダウンローダーを作る

では先ずは具体的に『ダウンローダーを作る』を目標に掲げて作っていきましょう。

前回作ったメニュー付きウィンドウのファイル群(001ディレクトリにある)は残しておいて、それを流用できるように新たに002ディレクトリにコピーします。


Username@foo-PC ~
$ cp -r 001 002

Username@foo-PC ~
$ cd 002

Username@foo-PC ~/002
$ ls
./  ../  Makefile  menu.exe  menu.o  menu.rc  window.c  window.o

Username@foo-PC ~/002
$ make clean
rm window.o menu.o

Username@foo-PC ~/002
$ rm menu.exe
rm: remove regular file `menu.exe'? y

Username@foo-PC ~/002
$



とりあえず前回生成したオブジェクトファイルと実行ファイルは削除しておきます。



ではプログラム設計にとりかかります。

【必要な機能・部品をリストアップ】
  • URLアドレス入力口(テキストボックス)
  • ダウンロード開始ボタン
  • HTTPリクエストを発行&取得
  • ファイルの保存先指定(コモンダイアログ)
  • ダウンロードの進行状況(インジケーター)

ウェブで言うところのテキストボックスは、エディットボックス(または単にエディット)と呼ばれるようです。
Windowsプログラムではフォーム部品のことをコントロールと呼ぶので、エディットボックスはエディットコントロールと呼ばれるテキストボックスやテキストエリアのようなくくりに属します。(多分)

進行状況はステータスバーを利用します。



【フロー】
[URL入力] ⇒ [ボタン押下] ⇒ [取得  開始] ⇒ [完了]
                             [保存先指定]
                             [進行  状況]

ボタン押下後に取得開始し、並行して保存先指定と進行状況表示を行います。
なので一旦は一時ファイルに保存し、取得完了時に保存先として指定したファイル名にリネームします。

完了時には音を鳴らします。



【メニュー】
[ファイル]  [設 定]  [ヘルプ]
+- 終了     +- 保存先 +- バージョン情報

ダウンロードするだけの機能なので、メニューはシンプルです。



【補足情報】

ウィンドウ位置情報、保存先ディレクトリ情報を最後に一時ファイルに保存しておき、次回はその情報が残っていれば読み出してデフォルト値として使うようにします。

ダウンロードする際に、ダウンロード先と同じドメインのリファラーを送るようにします。(サーバ側プログラム仕様によってはリファラーをチェックする場合があるため)



以上を踏まえて、ウィンドウプロシージャの外形を考えてみます

ウィンドウプロシージャ外形

まだまだつづく。。。


2010(C)Mingw