動作結果
Windows 10にてコンパイルしたEXEファイルを、実機のMS-DOSで実行しました。先ずは、その結果をご覧ください。
ソースコード
MSDOSでは明らかに対応していないwindowsapiを使ったプログラムを作成しました。
#include <windows.h>
// ウィンドウクラス名
const char g_szClassName[] = "myWindowClass";
// メッセージ処理関数
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE: // ウィンドウ閉
DestroyWindow(hwnd);
break;
case WM_DESTROY: // ウィンドウ破棄
PostQuitMessage(0);
break;
case WM_PAINT: // 再描画処理
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 50, 50, "Hello, World!", lstrlen("Hello, World!")); // 文字列描画
EndPaint(hwnd, &ps);
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// エントリーポイント
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// ウィンドウクラス設定
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "ウィンドウクラスの登録に失敗しました!", "エラー!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// ウィンドウ作成
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"Hello, Windows!",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, "ウィンドウの作成に失敗しました!", "エラー!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
gcc hellowin.c -o hellowin.exe -mwindows
MS-DOSは非常に古いOSですが、Windows 10でコンパイルしたEXEを実行したところ、エラーにはならず、次のように表示されました。
This program cannot be run in DOS mode.
PE形式とは
Windowsで使用されるEXEファイルには、ファイルの先頭に「ヘッダー」と呼ばれる領域があります。このヘッダーには、プログラムを正しく読み込み・実行するための様々な情報が詰め込まれており、言わばEXEファイルの設計図のようなものです。
ヘッダーでは、「何バイト目から何バイト目までにどんな情報を入れるか」といった並びが厳密に定められています。これにより、OSは正確にプログラムを読み取ることができます。
PE形式(Portable Executable形式)のEXEファイルは、以下のような構成となっています。
-
DOSヘッダー(先頭)
最初の2バイトに "MZ"(0x4D, 0x5A)という書かれています。
これはMS-DOSで実行できる「旧来のEXEファイル」として認識されるための目印です。 -
MS-DOSスタブ(互換メッセージ)
DOSヘッダーのすぐ後ろには、MS-DOS用の簡易プログラムが存在します。
内容はMS-DOSでこのファイルを実行した場合に「This program cannot be run in DOS mode.」と表示して終了するだけのものです。
- PEヘッダー(本体)
スタブのあとに、いよいよPEヘッダーが続きます。
ここに実際のWindowsプログラムとしての情報(コードセクションの位置、エントリーポイント、必要なDLLなど)が記述されています。
詳細はMicrosoftの公式資料を参照してください。
https://learn.microsoft.com/ja-jp/windows/win32/debug/pe-format
コンパイルしたEXEの構造
バイナリエディタで開くと「This program cannot be run in DOS mode」と書かれているのが確認出来ます。
実験
今回は「This program cannot be run in DOS mode」の部分を変更してみます。変更する際に1バイトでも減増すると後ろのバイトが全てずれてしまい起動できなくなるため、注意が必要です。
「This program cannot be run in DOS mode」の部分を「YOU opened a file that I changed. mode」と書き直しました。
結果
コマンドプロンプトでも実験
MS-DOSの影を色濃く残している「コマンドプロンプト(cmd.exe)」でも、同様の実験を行ってみました。
ご覧の通り、コマンドプロンプトからこのEXEファイルを実行しても、「This program cannot be run in DOS mode」という表示は出ず、別ウィンドウでWindowsアプリとして正しく起動します。
つまり、「This program cannot be run in DOS mode」というメッセージは、MS-DOS環境で実行された場合にだけ表示されるものであり、現代のWindows環境では発動しないコードなのです。
このことからも、PE形式の先頭にあるDOSスタブは、現代のWindowsにおいては実質的に使われない互換用のコードであることがわかります。
最後に
MS-DOSは既に現役を退いて久しいOSですが、それでも現代のWindows(たとえばWindows 11)で作られた実行ファイルがMS-DOSとの互換性を維持していることは驚きです。
今回の実験を通じて分かる様に、PE形式のEXEファイルには「MS-DOS用のコード」が今尚含まれており、MS-DOS環境で起動された場合にはそのコードが実行されて「This program cannot be run in DOS mode.」という文言を表示します。
この仕様は、互換性を重視して設計されたWindowsの歴史を物語っています。