在調用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對象。