CloseHandle 系関数に INVALID_HANDLE_VALUE (またはNULL)を渡してはいけない

free や delete に NULL を渡しても問題ないというのは有名な話*1だけど、Win32 API はどうなんだろうという話。


できれば公式情報として「問題ナイヨー」という保証が欲しいところだけど、いまいち MSDN を見てもよく分からない。
INVALID_HANDLE_VALUE に少しでも関係していそうな事といえば、「関数が ERROR_INVALID_HANDLE を返したら(関数が失敗したら) 大抵の場合はCloseHandle しなくてもいいよ」程度しかない。

It is usually not necessary to call CloseHandle if a function that uses a handle fails with ERROR_INVALID_HANDLE, because this error usually indicates that the handle is already invalidated.

http://msdn.microsoft.com/ja-jp/library/ms724211


仕方がないので実際に試してみた結果が以下の通り。*2

関数 戻り値 GetLastError エラーメッセージ
CloseHandle (NULL) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
CloseHandle (INVALID_HANDLE_VALUE) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
FindClose (NULL) 0 (失敗) 0x000003E6 (ERROR_NOACCESS) メモリ ロケーションへのアクセスが無効です。
FindClose (INVALID_HANDLE_VALUE) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
RegCloseKey (NULL) 0x00000006 (ERROR_INVALID_HANDLE) - ハンドルが無効です。
RegCloseKey (INVALID_HANDLE_VALUE) 0x00000006 (ERROR_INVALID_HANDLE) - ハンドルが無効です。
CloseThemeData (NULL) 0x80070006 (E_HANDLE) - ハンドルが無効です。
CloseThemeData (INVALID_HANDLE_VALUE) 0x80070006 (E_HANDLE) - ハンドルが無効です。
UnmapViewOfFile (NULL) 0 (失敗) 0x000001E7 (ERROR_INVALID_ADDRESS) 無効なアドレスにアクセスしようとしています。
UnmapViewOfFile (INVALID_HANDLE_VALUE) 0 (失敗) 0x000001E7 (ERROR_INVALID_ADDRESS) 無効なアドレスにアクセスしようとしています。
FreeLibrary (NULL) 0 (失敗) 0x0000007E (ERROR_MOD_NOT_FOUND) 指定されたモジュールが見つかりません。
FreeLibrary (INVALID_HANDLE_VALUE) 0 (失敗) 0x000000C1 (ERROR_BAD_EXE_FORMAT) %1 は有効な Win32 アプリケーションではありません。
DeleteDC (NULL) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
DeleteDC (INVALID_HANDLE_VALUE) 0 (失敗) 0x00000006 (ERROR_INVALID_HANDLE) ハンドルが無効です。
DeleteObject (NULL) 0 *3 0 -
DeleteObject (INVALID_HANDLE_VALUE) 0 *4 0 -
ReleaseDC (HWND, NULL) 0 *5 0 -
ReleaseDC (INVALID_HANDLE_VALUE) 0 *6 0x00000591 (ERROR_DC_NOT_FOUND) バイス コンテキスト (DC) ハンドルが無効です。


ご覧のように NULL や INVALID_HANDLE_VALUE を渡すと見事にエラーになった。
もっとも、この検証中にプログラムがクラッシュすることはなかったので、エラーを無視するというのもひとつの手かもしれない。
が、お行儀のいいプログラムを書くためにも、できればハンドルをクローズする前に INVALID_HANDLE_VALUE(NULL) のチェックを行うべきとは思う。


そしてこの結果から、以下のようなコードはよろしくないということが分かる。

// これはダメ
std::tr1::shared_ptr<std::tr1::remove_pointer<HANDLE>::type> ptr(CreateFile(...), CloseHandle);


void SafeCloseHandle(HANDLE h)
{
	if(h != INVALID_HANDLE_VALUE) {
		CloseHandle(h);
	}
}

// これならOK
std::tr1::shared_ptr<std::tr1::remove_pointer<HANDLE>::type> ptr(CreateFile(...), SafeCloseHandle);

いちいちラッパー関数を噛ませないといけないのはいまいちだなぁ。

*1:[迷信] free に NULL を渡すとクラッシュする | 株式会社きじねこ

*2:検証環境は WinXP SP3、Visual Studio 2008 SP

*3:不正なハンドルを渡したので 0 が返る

*4:不正なハンドルを渡したので 0 が返る

*5:バイスコンテキストを解放できなかったので 0 が返る

*6:バイスコンテキストを解放できなかったので 0 が返る