gcc -nostartfiles その4

gcc で -nostartfiles オプションを有効にすると、できあがりの実行ファイルサイズが小さくなる代わりに、処理の流れが変化する。すなわち、コード中で最初に定義した関数がエントリポイントとなる。

どうしよう?

#include <windows.h>
void hello() {
  MessageBox(NULL, TEXT("Hello, world!"), TEXT(""), MB_OK);
}
int WINAPI WinMain(HINSTANCE hi, HINSTANCE hp, LPSTR cl, int cs) {
  MessageBox(NULL, TEXT("I am in WinMain"), TEXT(""), MB_OK);
  return 0;
}

先頭に関数 hello を定義し、続いて関数 WinMain を定義する。このコードをコンパイル&リンクしてできた実行ファイルを起動すると、関数 hello が呼ばれ、WinMain は呼ばれないことが確認できる。

よくしよう

これでは、正常なアプリケーション動作にならないので、コードを変更し、手動で WinMain を呼ぶようにする。

#include <windows.h>
void Main() {
  int exitcode = WinMain(0, 0, "", 0);
  ExitProcess(exitcode);
}
int hello() {
  MessageBox(NULL, TEXT("Hello, world!"), TEXT(""), MB_OK);
  return 0;
}
int WINAPI WinMain(HINSTANCE hi, HINSTANCE hp, LPSTR cl, int cs) {
  MessageBox(NULL, TEXT("I am in WinMain"), TEXT(""), MB_OK);
  return 0;
}

関数 Main をコードの先頭に追加する。これによって、エントリポイントは Main になる。このソースコード hello.c を gcc -mwindows -nostartfiles hello.c でコンパイル&リンクして実行すると、画面上に「I am in WinMain」というメッセージがポップアップする。

I am in WinMain

このメッセージボックスを閉じると、アプリケーションが終了する。

よくなった

新しいエントリポイント関数 Main では、以下の処理を行っている。

  1. 関数 WinMain を実行する。引数として、とりあえずダミーの値を渡す。
  2. 関数 WinMain の戻り値を変数 exitcode に保存する。
  3. 関数 ExitProcess を実行する。引数として、変数 exitcode を渡す。

ひとまず、これで、一般的な Windows アプリケーションの処理の流れをシミュレートすることができた。WinMain には、ダミーの値を渡しているが、WinMain 内で、それらの値を本物と見なして使用することがなければ、問題ない。

アプリケーションの終了は、ExitProcess で行っている。通常、標準スタートアップファイルがこの手の API を自動で叩いてくれるようなのだが、-nostartfiles を適用すると行われないため、手動で叩いている。