Windows ベクトル化例外処理 (ベクタ例外処理, VEH)

構造化例外処理では、通常の try-except によるフレームベースの SEH の他、 ベクトル化例外処理 (ベクタ例外処理, Vectored Exception Handling, VEH) もサポートしています。これによって任意の時点での例外ハンドラの登録・解除を可能にします。

ベクトル化例外ハンドラを登録するには、AddVectoredExceptionHandler を使います。 第一引数を非ゼロにすると、例外発生時に最初に呼び出されるハンドラを登録することになります。 第二引数は例外ハンドラ関数のアドレスです。

PVOID WINAPI AddVectoredExceptionHandler(
  __in  ULONG FirstHandler,
  __in  PVECTORED_EXCEPTION_HANDLER VectoredHandler
);

戻り値としては、例外発生時点から実行を続行する場合は EXCEPTION_CONTINUE_EXECUTION を返し、 例外ハンドラを探し続けるなら EXCEPTION_CONTINUE_SEARCH を返します。

例外ハンドラのプロトタイプは次のようになります。

LONG CALLBACK VectoredHandler(
  __in  PEXCEPTION_POINTERS ExceptionInfo
);

典型的な例外フィルタと同様に、EXCEPTION_POINTERS へのポインタを受け取ります。

動作確認

以下の内容を veh1.cpp として保存します。 このコードでは main 関数に入ったときに、"Entering main" というメッセージを出力します。 それから AddVectoredExceptionHandler でベクトル化例外ハンドラを、二つ登録します。 その後、try ブロックにて strcpy (NULL,NULL) としてアクセス違反例外を発生させます。 ちゃんと例外ハンドラが呼び出されるかどうか、確認します。

#include <windows.h>
#include <stdio.h>

LONG WINAPI VectoredHandler1( struct _EXCEPTION_POINTERS *ExceptionInfo ) {
     
     printf("VectoredHandler1\n");

     return EXCEPTION_CONTINUE_SEARCH;
}


LONG WINAPI VectoredHandler2( struct _EXCEPTION_POINTERS *ExceptionInfo ) {
     
     printf("VectoredHandler2\n");
     
     return EXCEPTION_CONTINUE_SEARCH;

}


int main(int argc, char* argv[]) {

     printf("Entering main()...\n");

     PVOID h1 = AddVectoredExceptionHandler (0, VectoredHandler1 );
     PVOID h2 = AddVectoredExceptionHandler (0, VectoredHandler2 );
     
     if( !h1 ){
          printf("Unabled to Add VEH\n");
          return 1;
     }
     
     __try  {
          strcpy( NULL, NULL );
     }
     __except( EXCEPTION_EXECUTE_HANDLER ) {
          printf("__except\n");
     }

     RemoveVectoredExceptionHandler ( h1 );
     RemoveVectoredExceptionHandler ( h2 );

     return 0;
}

makefile は次です。ベクトル化例外処理は Windows XP 以降でのみサポートされるので、 _WIN32_WINNT を正しく設定する必要があります。私は Windows Vista で試しているので、ここでは 0x0600 としました。

TARGETNAME=veh1

OUTDIR=.\chk

LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"

CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/D_WIN32_WINNT=0x0600
		
LINK32_FLAGS=\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.c{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

nmake でビルドすると、サブディレクトリ chk に veh1.exe が出来るはずです。 それを実行してみましょう。

> veh1.exe
Entering main()...
VectoredHandler1
VectoredHandler2
__except

確かに例外ハンドラが呼び出されています。 ベクトル化例外処理ハンドラが登録順に呼び出され、その後、フレームベースの例外ハンドラ (つまり except ブロック) が呼び出されていることに注意してください。

次に AddVectoredExceptionHandler の第一引数に非ゼロを渡して、VectoredHandler2 を登録してみましょう。

veh1.cpp の以下の赤字の部分変更して、リビルドします。


     PVOID h1 = AddVectoredExceptionHandler (0, VectoredHandler1 );
     PVOID h2 = AddVectoredExceptionHandler (1, VectoredHandler2 );

これを実行すると、確かに VectoredHandler2 が先に呼び出されています。

> veh1.exe
Entering main()...
VectoredHandler2
VectoredHandler1
__except

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説