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を作成していろんなコンパイラの生成結果をダンプしてみるとよくわかると思います。 |
「非同期動作」という言葉は非常にわかりにくいが、 要するにデバイス(ハードウェア)に対するアクセスする際のCPU待ち時間を他のプロセスに譲ることのできる動作をいう。 非同期動作を行わないプロセスは、デバイスの動作待ちの間は無限ループを実行しているかの振る舞いをするのに対して、 非同期動作を行うプロセスはデバイスの動作待ちの間スレッドが停止した状態になるのである。
例えば、ハードディスクの内容をRS232Cに出力する場合を考えると、 非同期動作を行わないプロセスは実行中CPUの負担が常に100%であるのに対して、 非同期動作を行うプロセスの実行中CPUの負担はよほどボーレートが高くない限り常に0%近くとなる。 つまり、非同期動作はデバイスをアクセスするプロセスにとってCPU効率を上げる最も効果的な方法である。
Windows9x系ではWin32サブセットのうち、非同期動作が削除されており、実行できない。 この制限の為に、普通の開発環境(例えばVisualC++)では非同期動作をサポートしない(出来ないこともないが)。 その結果、NT系Windowsのシステムプログラムを除くほとんどは非同期動作を行わないプロセスだといえる。
Windowsはウインドウを持つプロセスのみが、他プロセスからのメッセージを受け取ることができる。 従って、ウインドウを持たない常駐プログラムの様なプロセスをOSから制御する特別な機構が存在する。 その機構を備えたプロセスをサービスと呼んでいる。これはUNIXではデーモンと呼ばれる常駐プロセスと等価である。 ただ、UNIXではシグナルという特殊なメッセージ通信があり、通常の実行形式プログラムとデーモンの違いはない。 これについても、普通の開発環境では通常サポートしない。
上記の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でも動作する。 また、サービスに登録すればそのままサービスになるので、デバッグを通常のプロセスで行ない、デバッグを終了したらサービスにするという使い方が可能です。 これは、巨大なサービスを作成する場合には大変有利で、ソフトウェアの品質が格段に上がりました。