解説
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;
CComPtr pInstance;
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;
CComPtr pInstance;
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);
省略
}







