gcc -nostartfiles 完全版

gcc で C ソースコードをコンパイル&リンクするとき -nostartfiles をつけると、標準スタートアップファイルがリンクされないため、実行ファイルサイズが非常に小さくなる。実行ファイルサイズを小さくしたいときは、この方法を採用するといい。

この方法で実行ファイルを作成すると、一応 Windows アプリケーションとしては動作する。しかしながら、エントリポイントが、常に WinMain 関数になるのではなく、なぜか、コードの先頭に定義した関数になる。

その上、アプリケーションを終了するには、概ね必ず ExitProcess 関数を呼ぶ必要がある。

エントリポイントとなる先頭の関数内では、WinMain 呼び出しと WinMain が終了した際の ExitProcess 呼び出しのみを行うようにすると便利だ。そうすれば、通常通り、標準スタートアップファイルをリンクして実行ファイルを作成した場合でも、同等の振る舞いをするアプリケーションができ上がるからである。

おすすめ

#include <windows.h>
void Main() {
  HINSTANCE hi;
  HINSTANCE hp;
  LPSTR cl;
  STARTUPINFO sui;
  int cs;
  int exitcode;
  GetStartupInfo(&sui);
  hi = GetModuleHandle(NULL);
  hp = 0;
  cl = (LPSTR)__argv;
  cs = sui.wShowWindow;
  exitcode = WinMain(hi, hp, cl, cs);
  ExitProcess(exitcode);
}
int WINAPI WinMain(HINSTANCE hi, HINSTANCE hp, LPSTR cl, int cs) {
  HWND hwnd = CreateWindow(TEXT("BUTTON"), TEXT("BOTAN"), WS_SYSMENU,
    0, 0, 320, 240, 0, 0, hi, 0);
  ShowWindow(hwnd, cs);
  MessageBoxA(NULL, cl, TEXT(""), MB_OK);
  return 0;
}

あとがき

エントリポイントとなる先頭の関数内で、WinMain を呼び出すときは、4 つの引数を渡す必要がある。

  • 第一引数には GetModuleHandle(NULL) を渡す
  • 第二引数には 0 を渡す
  • 第三引数には __argv を渡す

第四引数について

そして、第四引数には、STARTUPINFO 構造体のメンバ wShowWindow を渡す。このメンバの値と、本来の WinMain 第四引数の値は全く同じである。すなわち、ウィンドウを作成し、表示するアプリケーションにおいて、その表示方法を指定する値が入っている。

実際的には、アプリケーションのショートカットを作成し、そのプロパティの項目「実行時の大きさ」を変更して起動したときに効果が表れる。

以下の 2 つの条件を満たしているアプリケーションは、実行時のウィンドウの大きさについて、ユーザーの指定通りにふるまう良いアプリケーションである。

  • 「最大化」に変更されていれば、起動時、最大化したウィンドウで表示する
  • 「最小化」に変更されていれば、起動時、最小化したウィンドウで表示する

この 2 つの条件を満たすために、WinMain の第四引数こと wShowWindow を利用する。

CreateWindow でウィンドウを作成する際には、第三引数に、ウィンドウのスタイル値を渡さなければならないが、そこに WS_VISIBLE が含まれない場合、ウィンドウを表示するには ShowWindow 関数を実行する必要がある。ShowWindow の第一引数に、メインとしたいウィンドウのウィンドウハンドルを渡した場合、の第二引数には、WinMain の第四引数こと wShowWindow を渡すべきである。そうすることで、先述 2 つの条件を満たすことができる。

ちなみに、CreateWindow の第三引数に WS_VISIBLE が含まれている場合は、ShowWindow するまでもなく、ウィンドウが表示されるが、その際、ウィンドウの大きさは、自動的に wShowWindow の値に伸縮する。

エンディング

gcc -nostartfiles を使うと、実行ファイルサイズが小さくなるが、標準スタートアップファイルがリンクされないため、コードのふるまいが変わる。標準スタートアップファイルは、具体的にどんな処理をしているのかを把握していないため、どうしてそのようなふるまいになるのかが不明である。とりあえず、謎のエントリポイント関数でオリジナルのスタートアップルーチンをシミュレートすることで、特に大きなエラーもないアプリケーションを生成することはできているが、背後でまずいことになっている可能性もある。