开发者

Destructors not called when native (C++) exception propagates to CLR component

开发者 https://www.devze.com 2022-12-24 15:12 出处:网络
We have a large body of native C++ code, compliled into DLLs. Then we have a couple of dlls containing C++/CLI proxy code to wrap the C++ interfaces.

We have a large body of native C++ code, compliled into DLLs.

Then we have a couple of dlls containing C++/CLI proxy code to wrap the C++ interfaces.

On top of that we have C# code calling into the C++/CLI wrappers.

Standard stuff, so far.

But we have a lot of cases where native C++ exceptions are allowed to propagate to the .Net world and we rely on .Net's ability to wrap these as System.Exception objects and for the most part this works fine.

However we have been finding that destructors of objects in scope at the point of the throw are not being invoked when the exception propagates!

After some research we found that this is a fairly well known issue. However the solutions/ workarounds seem less consistent. We did find that if the native code is compiled with /EHa instead of /EHsc the issue disappears (at least in our test case it did). However we would much prefer to use /EHsc as we translate SEH exceptions to C++ exceptions ourselves and we would rather allow the compiler more scope for optimisation.

Are there any other workarounds for this issue - other than wrapping every call acr开发者_JAVA技巧oss the native-managed boundary in a (native) try-catch-throw (in addition to the C++/CLI layer)?


Unfortunately no I do not believe there are any good workarounds. The C++ exception implementation on MS platforms are implemented using SEH exceptions (IIRC). The CLR hooks into SEH handling to catch native exceptions and process them into CLR exceptions. Since it catches them at an SEH level the exception looks like an SEH exception to C++ and destructors are run or not run accordingly.

So as you've noted the best two options are

  • Compile with /EHa
  • Add a try/catch at the entry and exit point of your functions

Ideally you should be doing the second one anyways. In my experience it's considered bad practice to allow C++ exceptions to cross component boundaries.

There is also likely a hacky solution you could achieve using _set_seh_translator (Documentation). However I highly recommend avoiding that function as it can inadventently subvert CLR exception handling and cause a lot of unwanted problems.


You are not doing this right I think. Using _set_se_translator() already requires you to compile with /EHa. From the MSDN Library page:

You must use /EHa when using _set_se_translator.

More seriously, you'll break managed code when you use it. The CLR relies on SEH exceptions to detect various mishaps. It uses SetUnhandledExceptionFilter to trap them. Particularly NullReferenceException and OverflowException (x86) are raised this way. When you inject your own __try block, you'll prevent this exception from flowing into the CLR. Although you'll "handle" it, you won't have any trace of the exact reason for the exception. And managed try/catch blocks can't detect it.

A cure for /EHa efficiency (if it is actually a problem) is 64-bit code. Its function table based stack unwinding is very efficient with zero overhead for a try block.


The MSDN page for the /E compiler switch does state this behaviour:-

http://msdn.microsoft.com/en-us/library/1deeycx5(VS.80).aspx

Here is the relevant quote:-

If you use /EHs, then your catch clause will not catch asynchronous exceptions. Also, in Visual C++ 2005, all objects in scope when the asynchronous exception is generated will not be destroyed even if the asynchronous exception is handled.

Basically /EHsc is the optimistic view - it assumes that the only exceptions are true C++ style ones and will optimise accordingly. /EHa on the other hand takes the pessimistic view and assumes that any line of code could cause an exception to be generated.

If you can guarentee that you'll never cause an Access Violation, or In-Page Error or other SEH then use /EHsc. But If you're writing a service and/or want to provide a "best effort" then /EHa is going to be necessary.

I also agree with @JaredPar's sentiments about not allowing exceptions to cross module boundaries. What @nobugz says about the way the CLR handles exceptions may be true, but I think there is a difference between .Net code calling out directly to native code using P/Invoke and calling into a C++/CLI interop DLL. In the former case the CLR has to handle the situation on your behalf, whereas in the latter you are in control and can translate accordingly.


There is a bug in the 2.0 version of the CLR causing this issue. Running your managed executable on the 4.0 CLR will allow the destructors to be called as expected.

See Boost shared mutex not released after exception thrown for details.

0

精彩评论

暂无评论...
验证码 换一张
取 消