次のページ 前のページ 目次へ

4. Windowsでのバイナリ互換

4.1 フォーマットは1つだが3種類ある実行形式ファイル

32ビットWindowsの実行形式ファイルはPEと呼ばれるフォーマットで、WindowsNT系と9x系で共通となっている。

	PEフォーマットの構造

	+--------------------+
	+   DOSヘッダ     +  MS-DOSとの互換性の為に存在。最初の2バイトがASCIIの 'M' 'Z' で始まる。
	+          +  PEヘッダへのオフセットメンバを持ち、続いてPEヘッダを検索する。
	+--------------------+
	+ スタブプログラム +  MS-DOS上で実行されたとき、「このプログラムを実行するにはWindows
	+          +  が必要です」というメッセージを出力するプログラム。
	+--------------------+
	+          +  
	+--------------------+
	+  PEヘッダ   +  これがウインドウズ用のヘッダ。最初の2バイトがASCIIの 'P' 'E' で始まる。
	+          +  現在の32ビットウインドウズでは「イメージファイルヘッダ」という名称の
	+          +  ヘッダの後ろに「イメージオプションヘッダ」というヘッダが存在する。
	+--------------------+
	+ セクションテーブル +  各セクション(古い言葉でいうとセグメント)の情報が入っているヘッダ。
	+          +  
	+--------------------+
	+          +  
	+--------------------+
	+ 各セクションデータ +  各セクションのデータが格納されている
	+          +  VisualC++で作成したEXEでは下記の様なセクションが作成される
	+          +  text , rdata , data , idata , reloc
	+--------------------+

	※PEフォーマットについて詳しく知りたい人は、マイクロソフトのサイトより PEDUMP というプログラムのソースが入手できるので、
	 ソースを解読したり、EXEを作成していろんなコンパイラの生成結果をダンプしてみるとよくわかると思います。
PEはDOSの実行形式ファイルの上位互換となっており、DOS上でも暴走することなく実行できるようになっている。 当然「このプログラムを実行するにはWindowsが必要です」というメッセージを出力して終了するのであるが、 常に上位互換を意識するマイクロソフトらしいと思う。 ただ、フォーマットに関しては1つだが、中身は実行環境を選ぶ3種類の形式が存在する。 1つはデバイスに対して非同期動作を行うプロセス、もう1つは非同期動作を行わないプロセス、 そしてもう1つは、サービスと呼ばれるUNIXのデーモンの様なものである。

4.2 デバイスに対して非同期動作を行うプロセス

「非同期動作」という言葉は非常にわかりにくいが、 要するにデバイス(ハードウェア)に対するアクセスする際のCPU待ち時間を他のプロセスに譲ることのできる動作をいう。 非同期動作を行わないプロセスは、デバイスの動作待ちの間は無限ループを実行しているかの振る舞いをするのに対して、 非同期動作を行うプロセスはデバイスの動作待ちの間スレッドが停止した状態になるのである。

例えば、ハードディスクの内容をRS232Cに出力する場合を考えると、 非同期動作を行わないプロセスは実行中CPUの負担が常に100%であるのに対して、 非同期動作を行うプロセスの実行中CPUの負担はよほどボーレートが高くない限り常に0%近くとなる。 つまり、非同期動作はデバイスをアクセスするプロセスにとってCPU効率を上げる最も効果的な方法である。

4.3 デバイスに対して非同期動作を行わないプロセス

Windows9x系ではWin32サブセットのうち、非同期動作が削除されており、実行できない。 この制限の為に、普通の開発環境(例えばVisualC++)では非同期動作をサポートしない(出来ないこともないが)。 その結果、NT系Windowsのシステムプログラムを除くほとんどは非同期動作を行わないプロセスだといえる。

4.4 サービスと呼ばれるUNIXのデーモンの様なプロセス

Windowsはウインドウを持つプロセスのみが、他プロセスからのメッセージを受け取ることができる。 従って、ウインドウを持たない常駐プログラムの様なプロセスをOSから制御する特別な機構が存在する。 その機構を備えたプロセスをサービスと呼んでいる。これはUNIXではデーモンと呼ばれる常駐プロセスと等価である。 ただ、UNIXではシグナルという特殊なメッセージ通信があり、通常の実行形式プログラムとデーモンの違いはない。 これについても、普通の開発環境では通常サポートしない。

4.5 Windowsバイナリ互換ソフトの製作

上記の3つの形態をすべてサポートした実行形式ファイルを製作する。 つまり、そのプロセスがWindowsNT系で実行されたなら非同期動作を行ない、 Windows9x系で実行されたなら非同期動作は行なわない。 さらに、サービスとして実行されたらOSからの制御を受け付ける機構を用意する。 という感じのものです。

・非同期動作を行なえるかどうかの検出は現状はプラットホームIDを得て判断する

	// 非同期動作を行なえるかどうかの判断
	OSVERSIONINFO	structOsVersionInfo;
	structOsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx( &structOsVersionInfo );					// Windowsのバージョンを得る。
	DWORD	dwOsPlatFormID = structOsVersionInfo.dwPlatformId;

	// dwOsPlatFormID == VER_PLATFORM_WIN32s		Windows 3.1. に Win32s を実装したもの
	// dwOsPlatFormID == VER_PLATFORM_WIN32_WINDOWS		Windows 95 or Windows 98 
	// dwOsPlatFormID == VER_PLATFORM_WIN32_NT		Windows NT/Windows 2000

	if( dwOsPlatFormID == VER_PLATFORM_WIN32_NT ) bAsynchronous = TRUE;
	else bAsynchronous = FALSE;

・サービスとして起動されているかどうかは、ユーザ名にて判断する

	CHAR	pcUserName[64]; *pcUserName = '\0';
	DWORD	dwUserNameBufSiz = sizeof(pcUserName);
	GetUserName( pcUserName, &dwUserNameBufSiz);		// ユーザ名を得る
	if( strcmp( pcUserName, "SYSTEM" ) == 0 ){	// サービスの場合
		bWindowsService = TRUE;
	}
	else{						// 通常のプロセスの場合
		bWindowsService = FALSE;
	}

・自動判別された結果に基ずいて実行する部分を切り換える

// メイン関数の抜粋 //////////////////////////////////////////////////////////////

	if( bWindowsService ){
		// サービスとして実行されている場合、ServiceMain() 関数を登録してメイン関数は終了する。
		*pcUserName = '\0';
		pstructServiceTableEntry[0].lpServiceName = pcUserName;
		pstructServiceTableEntry[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
		pstructServiceTableEntry[1].lpServiceName = 0;
		pstructServiceTableEntry[1].lpServiceProc = 0;
		StartServiceCtrlDispatcher( pstructServiceTableEntry );
		return 1;
	}

// ファイルオープン部の抜粋 ////////////////////////////////////////////////////

	if( bAsynchronous ) dwAttr = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
	else                dwAttr = FILE_ATTRIBUTE_NORMAL;

// ファイル読み込み部の抜粋 ////////////////////////////////////////////////////

	if( bAsynchronous ){	// 非同期アクセスの場合
		if( ! ReadFile(hFileHandle, pvBuf, dwReqBytes, &dwRet, &objOverlapped) ){
			if( GetLastError() == ERROR_IO_PENDING ){	// 未完了で制御が返された場合
				if( ! GetOverlappedResult( hFileHandle, &objOverlapped, &dwRet, TRUE) ){
					dwRet = 0;
				}
			}
			else{						// 純粋の読み込みエラー
				dwRet = 0;
			}
		}
		objOverlapped.Offset += dwRet;
	}
	else{			// 同期アクセスの場合
		if( ! ReadFile(hFileHandle, pvBuf, dwReqBytes, &dwRet, 0 ) ){
			dwRet = 0;
		}
	}

上記のコーディングにより、1つの実行形式ファイルで3種類のプロセスに対応させる事ができました。 WindowsNT系では効率的にハードをアクセスできるので高速化が図れ、Windows9xでも動作する。 また、サービスに登録すればそのままサービスになるので、デバッグを通常のプロセスで行ない、デバッグを終了したらサービスにするという使い方が可能です。 これは、巨大なサービスを作成する場合には大変有利で、ソフトウェアの品質が格段に上がりました。


次のページ 前のページ 目次へ