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);
いちいちラッパー関数を噛ませないといけないのはいまいちだなぁ。