在调用catch块之前,把当前异常保存在exception_storage对象中,并注册一个专用于catch块的异常处理程序,,C++异常处理程序必须继续传就能得到exception_storage对象。
现在重新回到C++异常处理这个主题上来。调用catch块时,它可能重新抛出异常或抛出新异常。前一种情况下,C++异常处理程序必须继续传播 (propagate)当前异常;后一种情况下,它需要在继续之前销毁原来的异常。
此时,处理程序要面对两个难题:"如何知道异常是源于catch块还是 程序的其他部分"和"如何跟踪原来的异常"。我的解决方法是:在调用catch块之前,把当前异常保存在exception_storage对象中,并注 册一个专用于catch块的C++异常处理程序——catch_block_protector。调用get_exception_storage()函数,就能得到exception_storage对象:
- namespace my_handler
- {
- __declspec(dllexport) exception_storage* get_exception_storage() throw ()
- {
- void * p = TlsGetValue(dwstorage);
- return reinterpret_cast (p);
- }
- }
- BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
- {
- using my_handler::exception_storage;
- exception_storage *p;
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- //主线程(第一个线程)不会收到DLL_THREAD_ATTACH通知,所以,
- //与其相关的操作也放在这了
- dwstorage = TlsAlloc();
- if (-1 == dwstorage)
- return FALSE;
- p = new exception_storage();
- TlsSetValue(dwstorage, p);
- break ;
- case DLL_THREAD_ATTACH:
- p = new exception_storage();
- TlsSetValue(dwstorage, p);
- break;
- case DLL_THREAD_DETACH:
- p = my_handler::get_exception_storage();
- delete p;
- break ;
- case DLL_PROCESS_DETACH:
- p = my_handler::get_exception_storage();
- delete p;
- break ;
- }
- return TRUE;
- }
这样,当catch块(重新)抛出异常时,程序将会执行catch_block_protector。如果是抛出了新异常,这个函数可以从 exception_storage对象中分离出前一个异常并销毁它;如果是重新抛出原来的异常(可以通过ExceptionInformation数组 的前两个元素知道是新异常还是旧异常,后一种情况下着两个元素都是0,参见下面的代码),就通过拷贝ExceptionInformation数组来继续 传播它。下面的代码就是catch_block_protector()函数的实现。
在单线程程序中,这是一个完美的实现。但在多线程中,这就是个灾难了,想象一下多个线程访问它,并把异常对象保存在里面的情景吧。由于每个线程都有自己的 堆栈和C++异常处理链,我们需要一个线程安全的get_exception_storage实现:
每个线程都有自己单独的 exception_storage,它在线程启动时被创建,并在结束时被销毁。Windows提供的线程局部存储(thread local storage,TLS)可以满足这个要求,它能让每个线程通过一个全局键值来访问为这个线程所私有的对象副本,这是通过TlsGetValue()和 TlsSetValue这两个API来完成的。
Excptstorage.cpp中给出了get_exception_storage()函数的实现。它会被编译成动态链接库,因为我们可以籍此知道线 程的创建和退出——系统在这两种情况下都会调用所有(当前进程加载的)dll的DllMain()函数,这让我们有机会创建特定于线程的数据,也就是 exception_storage对象。
【编辑推荐】