この文書では Win16 から Win32 へアプリケーションプログラムを移植する際の注意事項をまとめています。
基本的には、ソースの修正は必要ない。
データ型 | Win16 | Win32 |
---|---|---|
HANDLE | 16bit | 32bit |
UINT | 16bit | 32bit |
BOOL | 16bit | 32bit |
WORD | 16bit | 16bit |
DWORD | 32bit | 32bit |
Win32 は、near アドレスと far アドレスとを区別しない。 大文字の NEAR 型と FAR 型は、WINDEF.H で定義されているために、インクルード ファイルによって自動的に処理され、Win32 では空文字列として再定義される。このような処理が行われるため、結果的に NEAR と FAR は無視されることになる。 小文字のnearとfarを用いている場合は、マクロ定義のFARとNEARに変更する。
修飾子 | Win16 | Win32 |
---|---|---|
near | 16ビットアドレス(セグメントアドレッシング) | × |
far | 32ビットアドレス(セグメントアドレッシング) | |
NEAR | 16ビットアドレス(セグメントアドレッシング) | 32ビットアドレス (リニアアドレッシング) |
FAR | 32ビットアドレス(セグメントアドレッシング) | |
指定無し | 16ビットアドレス(セグメントアドレッシング) |
Windows 3.x には、far ポインタを使ってメモリやファイルを操作する _fxxxx 形式の名前を持つ関数がある。 Win32 では far ポインタがないため、Win32 では、これらの関数名から _f のプリフィックスを取り除いた xxxx の部分と同じ名前の関数に置き換えられている。 ただし、WINDOWSX.H ファイルには _fxxxx の関数名も定義されているため、Win32 でも _fxxxx という関数名は、サポートされている _f プリフィックスのない関数と同じ意味になる。つまり、WINDOWSX.H をインクルードすれば、これらの関数については書き直す必要がない。一部の定義を以下に示す。
#define _fmemcpy memcpy
#define _fstrcpy strcpy
#define _fstrcmp strcmp
#define _fstrcat strcat
Win16 APIでは、メモリ操作関数がnearポインタ用とfarポインタ用の2種類が用意されている。
Win32 | Win16 | |
---|---|---|
nearポインタ | farポインタ | |
memcpy | memcpy | _fmemcpy |
strcpy | strcpy | _fstrcpy |
strcmp | strcmp | _fstrcmp |
strcat | strcat | _fstrcat |
Win16とWin32では、WinMain関数の引数に型に違いがある。これにともない、メッセージのハンドリングにも変更が必要である。
引数 | Win16 | Win32 |
---|---|---|
第1引数 | HWND型(16ビット) | HWND型(32ビット) |
第2引数 | UINT型(16ビット) | UINT型(32ビット) |
第3引数 | WORD型(16ビット) | UINT型(32ビット) |
第4引数 | LONG型(32ビット) | LONG型(32ビット) |
Win32ではWinMain関数の第2引数は常に.0なので、すでに存在するインスタンスのハンドルが0のときにウィンドウクラスを登録するという手順になっているWin16のソースコードに手を加えなくてもよい。
WinMain関数の第2引数には、すでに存在するインスタンスのハンドルが入っている。すでに存在するインスタンスがあれば、RegisterClass関数を呼ばずにインスタンスを共有するようになっている。 RegisterClass()の返り値は、関数の実行が成功(True)したか失敗(False)したかを返す。
必ずRegisterClass()を呼ばなければならない。また、WinMain関数の第2引数は常に0である。RegisterClass()の返り値は、クラスの識別子を返し、失敗した場合は0を返す。
Win16 と Win32 では、WinMain 関数の引数に型に違いがあります。これにともない、メッセージのハンドリングにも変更が必要です。
引数 | 意味 |
---|---|
WPARAM | ウィンドウのID(16ビット) |
LPARAM(HIGH) | ウィンドウハンドル(16ビット) |
LPARAM(LOW) | 通知コード(16ビット) |
引数 | 意味 |
---|---|
WPARAM(HIGH) | ウィンドウのID(16ビット) |
WPARAM(LOW) | 通知コード(16ビット) |
LPARAM | ウィンドウハンドル(32ビット) |
wParam/lParamのパッキングが変わったため、メッセージのハンドリングに注意する必要がある。 メッセージパッキングの問題を局所的に抑えるには、以下のようにローカル変数に取り出すか、関数化すればよい。
switch (msg) {
case WM_COMMAND:
id = LOWORD(wparam);
hWnd = (HWND)(UINT)lParam;
cmd = HIWORD(wParam)
}
また、Win16とWin32でコードを共有するには、以下の方法がある。
switch (msg) {
case WM_PAINT:
return HANDLE_WM_PAINT(hwnd, wParam, lParam, MyWnd_OnPaint);
}
void MyWnd_OnPaint(HWND hwnd) {
// WM_PAINTのハンドリング
}
マクロ | Win16 | Win32 |
---|---|---|
GET_WM_COMMAND_ID(wp, lp) | (wp) | LOWORD(wp) |
GET_WM_COMMAND_HWND(wp, lp) | (HWND)LOWORD(lp) | (HWND)(lp) |
GET_WM_COMMAND_CMD(wp, lp) | HIWORD(lp) | HIWORD(wp) |
Win16では第4引数を指定するにはMakeProcInstanceを用いなければならないが、Win32では直接ダイアログプロシージャ自身のポインタを指定することも可能である。
int DialogBox(HANDLE, LPCSTR, HWND, FARPROC);
int DialogBox(HANDLE, LPCSTR, HWND, DLGPROC);
Win32環境下では、GlobalAllocとLocalAllocは共にmallocと同じ動作をするようになった。また、ロック/アンロックの操作も必要なくなった。
目的 | Win16 | Win32 |
---|---|---|
メモリの確保 | GlobalAlloc() または LocalAlloc() | malloc() |
メモリの解放 | GlobalFree() または LocalFree() | free() |
WinMain関数の型をWINAPI型に変更する。 ウィンドウプロシージャ関数をLRESULT CALLBACK型に変更する。また、_exportは省略できる。 Win32ではAPI関数の型はWINAPI型に統一された。
関数 | Win16 | Win32 |
---|---|---|
WinMain | FAR PASCAL型 | WINAPI型 または APIENTRY型 |
ウィンドウプロシージャ | FAR PASCAL型 | LRESULT CALLBACK型 |
上記のようにWindowsNTやWindows95には、それぞれ固有のAPIがあるが、WindowsNTまたはWindows95に依存したAPIは使用しない。
Win32APIの機能 | Windows95 | WindowsNT |
---|---|---|
32ビット拡張されたWin16相当のAPI | ○ | ○ |
32ビットメモリ管理 | ○ | ○ |
ファイルマッピング | ○ | ○ |
ネットワーキング(NetBios,Windows Sockets) | ○ | ○ |
OLE2.0 | ○ | ○ |
メールスロットと名前付きパイプ | ○ | ○ |
スレッド | ○ | ○ |
拡張GDI(ベジェパス) | ○ | ○ |
リモートプロセスコール | ○ | ○ |
GDIトランスフォーム | ○ | |
イベントログ | ○ | |
セキュリティ | ○ | |
Unicode | △ | ○ |
プラグアンドプレイ | ○ |
Win16 では DLL の初期化時に呼ばれる関数が LibMain で、DLL の終了時に呼ばれる関数が WEP でした。
Win32 では DLL の初期化時と終了時に呼ばれる関数が DllMain という関数に集約されました。
DllMain(HANDLE hInst, ULONG ulReason, LPVOID lpvReserved)
値 | 意味 |
---|---|
DLL_PROCESS_ATTACH | 新しいプロセスが DLL へアクセスしようとしている。単一スレッドとみなす。 |
DLL_THREAD_ATTACH | 既存のプロセスの新しいスレッドが DLL にアクセスしようとしている。この呼び出しは、DLL にアタッチするプロセスの 2 番目のスレッドから行われる。 |
DLL_PROCESS_DETACH | プロセスがDLL からデタッチする。 |
DLL_THREAD_DETACH | プロセスの追加スレッドの 1 つ (最初のスレッド以外) がDLL からデタッチする。 |
DllMain関数には、以下のようなWin16のLibMain関数のような初期化パラメータはない。
DLLのデータセグメント | Win32では必要ない。メモリ空間がフラットで、セグメント化されていないため。 |
DLLのローカルヒープのサイズ | ローカルメモリ管理関数の呼び出しはすべてデフォルトのヒープで行われる。 |
コマンドラインへのポインタ | コマンドラインは、API関数GetCommandLineを呼び出すことにより取得する。 |
必要であれば、DllMain関数を新たに追加する。簡単な移植方法を以下に示す。
#ifdef WIN32
BOOL WINAPI DllMain(HANDLE hInst, ULONG ulReason, LPVOID lpvReserved) {
switch(ulReason) {
case DLL_PROCESS_ATTACH:
return LibMain(hInst, 0, 0, NULL);
case DLL_PROCESS_DETACH:
WEP(0);
break;
}
return TRUE;
}
#endif
_pascal 型、__pascal 型を用いているものは、WINAPI 型に修正する必要がある。 PASCAL 型、pascal 型を用いているものは、修正を施さなくてもコンパイル可能だが、WINAPI 型に修正することが望ましい。
WindowsNTとWindows95ではレジストリのキー項目に違いがある。 Windows95レジストリとWindowsNTレジストリでは、キーに関連するクラス情報の処理のしかたが異なる。 Windows95レジストリはすべてのクラスを同じように処理する。 RegQueryInfoKey に有効な lpszClass バッファポインタは渡すが、有効なlpcchClassポインタは渡さない場合(NULLを渡す場合)には、Windows95は関数呼び出しを続ける。 これに対して、WindowsNTレジストリはクラスを区別する。WidnowsNTでは、RegQueryInfoKeyに有効なlpszClassバッファポインタは渡すが、有効なlpcchClassポインタは渡さない場合(NULLを渡す場合)には、システムはERROR_INVALID_PARAMETERエラー値を返す。
HKEY_CURRENT_USER のサブキー
HKEY_CURRENT_USERのサブキー
Win16 では、アプリケーションの設定状態などは INI ファイルに記録していました。
Win32 では、アプリケーションの設定状態などはレジストリ(登録情報データベース)に記録するようにします。 アプリケーションの状態は、\HKEY_CURRENT_USER\Software\メーカ名\アプリケーション名\ の下に記録するのが慣例です。
文字列パラメータを引数とするWin32関数はUnicode文字列(ワイドキャラクタ)またはANSI文字列のいずれかを処理できるように設計されている。ただし、Windows95はほとんどのWin32関数のUnicodeバージョンを実装していない。若干の例外はあるもののこれらの関数はエラー値を返すスタブとして実装されている。Windows95では以下の関数のUnicodeを実装している。
さらに、Windows 95 は、Unicode との間で文字列を変換するための MultiByteToWideChar および WideCharToMultibyte 関数も実装している。
__stdcall キーワードを使うと、指定した関数の引数が右から左にプッシュされ、名前の前にアンダースコアが付き、後ろに @### が付加されます。 この ### は関数の引数のサイズのバイトサイズです。 前のバージョンとの互換性を保つために、 __stdcall の同義語 _stdcall も用意されています。 関数のプロトタイプがあり、引数の数が一定の場合は、呼び出された関数がスタックをポップします。 可変の場合は、呼び出し側でスタックをポップします。 __stdcall としてマークされ、可変個の引数を取る関数は、__cdecl としてインプリメントされます。 呼び出し規約をプロトタイプまたは宣言で指定すると、コマンド ライン オプションで指定したどの規約より優先されます。
__stdcall 呼び出し規約は、Win32 API 関数の呼び出しに使用します。 スタックは呼び出された側がクリアするので、vararg 関数は __cdecl になります。 この呼び出し規約を使う関数には、関数プロトタイプが必要になります。 次に、この呼び出し規約のインプリメントを示します。
引数は右から左の順番で渡されます。 また、引数は値で渡されます(ポインタ型と参照型を除く)。
呼び出された側の関数が引数をスタックからポップすることで、スタックのクリアを行います。
名前の修飾として、アンダースコア (_) が名前の頭に付けられます。
また、名前の後にはアット マーク (@) と引数リストのバイト数 (10 進) が続きます。
したがって、int func(int a, double b)
と宣言された関数は修飾の結果 _func@12
となります。
大文字小文字の変換はありません。
/Gz コンパイラ オプションを指定すると、他の呼び出し規約で明示的に宣言されていないすべての関数が __stdcall となる。 __stdcall 修飾子とともに宣言された関数は、__cdecl で宣言された関数と同じ方法で値を返す。
指定されたモジュールの参照カウンタをチェックすることにより可能
/* モジュール名よりモジュールハンドルを取得 */
MHndl = GetModuleHandle("OPE.EXE");
/* モジュールハンドルより参照カウンタを取得し
* 2個以上より参照されている時はエラー
*/
if (GetModuleUsage(MHndl) > 1) {
errproc("このアプリケーションは,一度に複数起動できません。" );
exit(1);
}
Win32ではユーザがアプリケーションの複数のインスタンスを起動しているか否かを判定する為の簡単な方法は提供していないが、すべてのインスタンスに単一のグローバル変数を共有させることができれば、実行中のインスタンス数をグローバル変数を使用してアプリケーションが管理し、複数起動チェックを行うことが可能となる。共有化の方法としては以下がある
Sharedという新セクションを追加し、変数を宣言する
#pragma data_seg("Shared")
DWORD abc = 0;
HWND HwndA = NULL;
#pragma data_seg();
#pragma comment(lib,"msvcrt " "-section:Shared,rws)
リンカディレクティブを OBJ ファイルに埋め込む(プロジェクトのリンカスイッチでも指定可能)
HANDLE hMap = CreateFileMapping((HANDLE) 0xFFFFFFFF,....,1024,"SharedData");
あるいは
HANDLE hMap = OpenFileMapping(...."SharedData");
if (hMap == NULL)
return(GetLastError());
.
.
LPVOID lpView = MapViewOfFile(hMap,.....);
.
.
.
UnmapViewOfFile((LPVOID) lpView);
.
.
CloseHandle(hMap);
プロセスは、アプリケーションがCreateProcessを呼び出したときに作成される。 アプリケーション内のスレッドがCreateProcessを呼び出すと、オペレーティングシステムは参照カウンタの初期値として1がセットされたプロセスカーネルオブジェクトを作成する。このプロセスカーネルオブジェクトは、プロセス自体ではなく、オペレーティングシステムがプロセスの管理のために使う小さなデータ構造である。プロセスカーネルオブジェクトは、プロセスについての統計情報から構成される小さなデータ構造と考えることができる。システムは、次に、新プロセスのために4Gバイトの仮想アドレス空間を作成し、そこに実行可能ファイルのコード、データと必要なDLLをロードする。 次に、システムは、新プロセスの新スレッドのためにスレッドカーネルオブジェクトを作成する。スレッドカーネルオブジェクトは、プロセスカーネルオブジェクトと同様に、オペレーティングシステムがスレッド管理のために使う小さなデータ構造である。主スレッドは、Cランタイムのスタートアップルーチンから実行を開始し、このスタートアップルーチンがWinMain関数を呼び出す。システムが新プロセスと主スレッドの作成に成功すると、CreateProcessは、TRUEを返す。 CreateProcessのインタフェースを以下に示す。
BOOL CreateProcess(
LPCSTR lpszImageName,
LPCSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPTSTR lpszCurdir,
LPSTARTUPINFO lpsiStartInfo,
LPPROCESS_INFORMATION lppiProcInfo);
CreateProcess の lppiProcInfo 引数は、PROCESS_INFORMATION 構造体へのポインタである。 この構造体は、呼び出し側アプリケーションで確保しておかなければならない。 CreateProcess は、制御を返す前にこの構造体を初期化する。構造体の定義は次の通りである。
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
プロセスを作成するとプロセスカーネルオブジェクトとスレッドカーネルオブジェクトが作成される。オブジェクトの作成時に、システムはその参照カウンタに1をセットする。そしてCreateProcessは、処理が終わる直前にこれらのオブジェクトをオープンし、PRCESS_INFORMATION構造体の hProcess、hThreadメンバにそのプロセスのみで有効なオブジェクトハンドルをセットする。この処理によって、2つのオブジェクトの参照カウンタはそれぞれインクリメントされて2になる。
このような設計になっているため、Win32システムでプロセスオブジェクトを解放するには、プロセスを終了させる(参照カウンタが1減る)だけでなく、親プロセスでCloseHandle を呼び出す(参照カウンタが1減る)必要がある。スレッドオブジェクトの場合も、親プロセスがハンドルをクローズしないと解放できない。
また、GetExitCodeProcessを使えば、プロセスハンドル(hProcess)に対応するプロセスが終了しているかどうか調べ、終了している場合にはその終了コードを取得することができる。
BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpdwExitCode);
終了コードは、lpdwExitCode 引数が指すDWORD 変数に格納される。プロセスが終了していない場合、このDWORD変数にはSTILL_ACTIVEが格納される。処理に成功すると、 GetExitCodeProcessは、TRUEを返す。
DLLの複数のマッピングの間で変数を共有したい場合は、変数を独自セクションに配置する。変数を独自セクションに配置するには、以下のように記述する。
#pragma data_seg("Shared")
int iShare;
#pragma data_seg()
そして、リンカに対して変数を共有するセクション名を指定する。これは、リンカで次のスイッチを指定すればよい。(Visual C Ver.4.0の場合、プロジェクト設定のC/C++カテゴリのプロジェクトオプションで指定する。)
-SECTION:<名前>,<属性>
指定できる属性にはR(READ),W(WRITE),S(SHARED),E(EXECUTE)がある。 たとえば、Sharedセクションを読み書き可能の共有セクションにしたければ、次のようにスイッチを指定すればよい。
-SECTION:Shared,RWS
また、複数のセクションの属性を変更したい場合には、-SECTIONスイッチを複数回指定する。