解説
COMインターフェースは結果をHRESULT型で返すことが多い。しかしその戻り値を単純にFAILEDやSUCCEEDED マクロを使って判定すると実行時に例外などのエラーが発生することがある。それは失敗時にS_FALSEを返す関数があるためだ。この値はSUCCEEDEDマクロでTRUE、FAILEDマクロでFALSE となる「成功」タイプの値だ。成功とは言うもののFALSEという微妙な値になっている。
BOOL型でも同じような問題がある。BOOLの場合はif(ret == TRUE)は正常に判定できないことがあるので if(ret != FALSE)やif(ret)のように成功判定をする。しかしHRESULT型の場合は単純にif(!FAILED(hr))の ようには解決できない。
HRESULT関連の実装
HRESULT型でよく使われるS_OKやSUCCEEDEDマクロなどはヘッダーの中で以下のように定義 されている。ここから分かるようにS_OKもS_FALSEもSUCCEEDEDマクロで真(TRUE)となる。
#define S_OK ((HRESULT)0x00000000L) #define S_FALSE ((HRESULT)0x00000001L) #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0) #define FAILED(Status) ((HRESULT)(Status)<0)
間違えたプログラミング
COMを使ったプログラミングで以下のようなコーディングをすることは多いのではないだろうか?つまり、ある関数の呼び出しに対してif(FAILED(hr))やif(SUCCEEDED(hr))を使ったエラー分岐処理をだ。
このソースコードの中で使われているIEnumWbemClassObject::Next()は戻り値としてHRESULT型を返す。 しかしその値は失敗時にS_FALSEとなる。S_FALSEはSUCCEEDEDマクロでTRUEとなることがエラーの原因だ。
bool Test() { HRESULT hr; CComPtrpInstance; CComPtr pEnumerator; 省略 pInstance = NULL; hr = pEnumerator->Next(WBEM_INFINITE,1,&pInstance,&dwCount); if(FAILED(hr)) return false; hr = pInstance->GetNames(NULL,WBEM_FLAG_ALWAYS | WBEM_MASK_CONDITION_ORIGIN,NULL,&pvNames); // ↑ pInstance == NULLでエラーになることがある 省略 }
エラーにならないプログラミング
IEnumWbemClassObject::Next()はIWbemClassObjectを取得するためのものなので戻り値は無視して IWbemClassObjectで判定すればいい。ここでは例としてIEnumWbemClassObjectという見慣れないインターフェースを使った。しかしQueryInterface など多用される関数の戻り値をチェックする場合も同様にインターフェースへのポインタの値でチェック をした方が安心だ。通常QueryInterfaceなどではS_FALSEが返ることはない。しかしインターフェースの 開発者がうっかりミスなどで(また見た目的にエラーと思われやすいので)S_FALSEを使ってしまってい る可能性があるためだ。
bool Test() { HRESULT hr; CComPtrpInstance; CComPtr pEnumerator; 省略 pInstance = NULL; hr = pEnumerator->Next(WBEM_INFINITE,1,&pInstance,&dwCount); if(pInstance == NULL) return false; hr = pInstance->GetNames(NULL,WBEM_FLAG_ALWAYS | WBEM_MASK_CONDITION_ORIGIN,NULL,&pvNames); 省略 }