ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

DOS窓を開かないコンソールアプリケーションを作る

日曜日にGaucheのnoconsole版の作り方を教わったのでまとめてみました。
https://twitter.com/#!/anohana/status/204144556902584320
https://twitter.com/#!/SaitoAtsushi/status/204143669828268033

Gaucheのnoconsole版

Windows版のGaucheには、通常のgosh.exeに加えてgosh-noconsole.exeが含まれています。Windows環境ではGaucheスクリプトファイルを実行する時にnoconsole版を使う事で、DOS窓コマンドプロンプト)を表示せずにスクリプトを実行出来ます。Gaucheでnoconsole版の実行ファイルを作っているところは、配布ファイルの中のsrcディレクトリにあるMakefileに記述されています。

gosh$(EXEEXT) : $(LIBGAUCHE).$(SOEXT) $(gosh_OBJECTS) 
	@rm -f gosh$(EXEEXT)
	$(LINK) $(gosh_LDFLAGS) -o gosh$(EXEEXT) $(gosh_OBJECTS) $(gosh_LDADD) $(LIBS)

gosh-noconsole$(EXEEXT) : $(LIBGAUCHE).$(SOEXT) $(gosh_noconsole_OBJECTS) 
	@rm -f gosh-noconsole$(EXEEXT)
	$(LINK) $(gosh_LDFLAGS) -o gosh-noconsole$(EXEEXT) $(gosh_noconsole_OBJECTS) $(gosh_LDADD) $(LIBS) -Wl,--subsystem,windows

gosh-noconsole版には、リンカのオプションに --subsystem,windows が追加されています。--subsystem,windowsのオプション付きでリンクした時は、GUIを持つWindowsアプリケーションになり、実行時にDOS窓を開きません。

Microsoft Portable Executable フォーマット

通常のWindowsアプリケーションは、Microsoft Portable Executable (PE)にしたがって作成されます。フォーマットの詳細はマイクロソフトから入手可能です。
http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
実行ファイルがDOS窓を表示するかどうかは、PEフォーマットの省略可能なヘッダーに含まれているWindows Subsystemに記録されています。現在のバージョンで許される値は、このようになっています。

Constant Value Description
IMAGE_SUBSYSTEM_UNKNOWN 0 An unknown subsystem
IMAGE_SUBSYSTEM_NATIVE 1 Device drivers and native Windows processes
IMAGE_SUBSYSTEM_WINDOWS_GUI 2 The Windows graphical user interface (GUI) subsystem
IMAGE_SUBSYSTEM_WINDOWS_CUI 3 The Windows character subsystem
IMAGE_SUBSYSTEM_POSIX_CUI 7 The Posix character subsystem
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 Windows CE
IMAGE_SUBSYSTEM_EFI_APPLICATION 10 An Extensible Firmware Interface (EFI) application
IMAGE_SUBSYSTEM_EFI_BOOT_ SERVICE_DRIVER 11 An EFI driver with boot services
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 An EFI driver with run-time services
IMAGE_SUBSYSTEM_EFI_ROM 13 An EFI ROM image
IMAGE_SUBSYSTEM_XBOX 14 XBOX

今回出てくるのは、この2 The Windows graphical user interface (GUI) subsystemと3 The Windows character subsystemです。

コンソールアプリケーションを作る

PEフォーマットを調べる前準備として簡単なプログラムを作りました。
コンソールにHello Worldを出力し、カレントディレクトリにHello Worldと書かれたfoo.txtを作成します。

#include <stdio.h>

int main(int argc, char* argv[])
{
	FILE *fp;
	fp = fopen("foo.txt", "w");
	fprintf(fp, "Hello World\n");
	fclose(fp);

	printf("Hello World\n");

	return 0;
}

Visual StudioでWin32コンソールアプリケーションとしてコンパイルし、実行するとDOS窓が開いてHello Worldと表示されるはずです。これをhello-console.exeとします。

noconsole アプリケーションを作る

次は同じソースでnoconsole版を作ってみます。noconsole版を作るには、Visual Studioのリンカ設定で2カ所変更が必要です。

一つ目はリンカ→システムからサブシステムを、Windowsに変更します。このときリンカに与えられるオプションが、/SUBSYSTEM:WINDOWSで、Gaucheの--subsystem,windowsに対応します。

もう一つはリンカー→詳細設定から、エントリーポイントをmainに設定します。この設定が無いと、WinMainをエントリーポイントとしてリンクしようとします。

この2カ所を変更して、Visual Studioから実行すると、今度はDOS窓がでないでプログラムが実行されます。foo.txtを消し再度実行し、もう一度foo.txtができていればコンソール無しでプログラムが実行されていることが分かります。Visual Studioの出力ウィンドウにも「スレッド 'Win32 スレッド' (0xbbb8) はコード 0 (0x0) で終了しました。プログラム '[47764] hello.exe: ネイティブ' はコード 0 (0x0) で終了しました。」と表示され、正しく起動して終了し0を返していることが分かります。こちらは、hello-noconsole.exeにします。

PEヘッダーを覗く

早速、通常のhello-console.exeとnoconsole版のhello-noconsole.exeをバイナリエディタで覗いていましょう。左がhello-console.exe、右がhello-noconsole.exeです。

図の青枠で囲まれているところがPEシグネチャです。そこから順番に進んでいって赤枠で囲んでいるところが、Windows Subsystemです。左のhello-consoleはWindows Sybsystemが3(The Windows character subsystem)、右のhello-noconsole.exeは2(The Windows graphical user interface (GUI) subsystem)になっています。
正確にPEヘッダーの情報を得るには、dumpbin.exe を使用します。

>dumpbin /headers hello_console.exe
Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file hello_console.exe

PE signature found

File Type: EXECUTABLE IMAGE

省略

             400 size of headers
               0 checksum
               3 subsystem (Windows CUI)
            8140 DLL characteristics

省略

ちなみに、コンソール版のhello-console.exeのsubsystemを、バイナリエディタで3から2に書き換えると、DOS窓を開かずにプログラムが実行できます。

参考文献

Linkers & Loaders
Linkerや実行ファイルフォーマットに関して、最初の一歩を調べるにはとても役に立つ本です。今回もPEフォーマットを調べるのに役に立ちました。流石に情報は古くなっているので、この本を参考に公式のドキュメントを読むのが良いです。