HRESULT型の判定にSUCCEEDEDやFAILEDは使えない(ことがある)

解説

 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);

	省略
}

カテゴリー「VC++ TIPS」 のエントリー