Shortly after publishing my last post on converting native (C++ or Win32) exceptions to managed exceptions, I realized that some clarifications are in place.
Trapping every Win32 exception and indiscriminately translating it to a managed exception is extremely dangerous. There are various scenarios in which a native exception is thrown to be caught, processed and handled – interfering with this process through a vectored exception handler might result in disastrous consequences.
For example, when a managed application attempts to access an object through a null reference, the JIT-ted code causes a Win32 access violation by referencing a null pointer. This exception is caught by the CLR and converted to a NullReferenceException without our handler – we certainly wouldn’t want to interfere with this process.
Another example is the way thread stacks extend on Windows. When a thread is created, a stack space is reserved for it (the default stack size is 1MB). However, only a single page is actually committed – because most threads don’t need a full 1MB of stack. The second page is marked with PAGE_GUARD, and when the thread requires this page a page guard exception is raised and handled to commit the second page to memory. As part of handling this exception, the next page on the stack is marked with PAGE_GUARD, and so on. If we interfere with this process by “converting” the exception to something else or by swallowing it (e.g. using IsBadWritePtr), the thread’s stack won’t be able to grow without a real reason. It is an exception that is intentionally thrown and handled – it’s none of our business to “handle” it.
There are subtler issues with converting exceptions using this approach. It is fairly difficult to determine exactly which exceptions you are interested in and which exceptions require conversion. As I wrote in the previous post, this can be done by deriving all “interesting” exceptions from a common base class, using a custom Win32 exception code, or even inspecting the exception address to see whether it was raised from “interesting” code.
In other words – use this approach with caution, as always when messing around with internals and bringing together the disparate worlds of managed and native code.