I prevent unmanaged C exceptions from escaping from my C /CLI code by wrapping unmanaged calls with exception frames where I catch const std::exception&
. But I've got a code-path in which the unmanaged C throw
immediately triggers a warning of SEHException
, even though there is clearly a catch clause higher up the stack frame in C /CLI to intercept it.
I do not understand where the SEHException
can be happening. I am wondering if it is real at all.
Here is the C code throwing the exception way down in the call stack.
StringMap ParseTopLevelMap(std::istream& in)
{
StringMap yamlmap;
if (!TryParseTopLevelMap(in, yamlmap))
throw std::runtime_error("Unable to parse map"); // Causes SEHException warning.
return yamlmap;
}
The moment I make the throw
above, I immediately get this output in the output window:
Exception thrown: 'System.Runtime.InteropServices.SEHException' in MyCompany.Sdk_v143.dll
But I very clearly have a catch(const std::exception& ex)
higher up in the call frame in C /CLI and it does get invoked. Here it is (Note that catch
)
bool ScanContext::TryLoad(GsScan^ scan, String^ path, [SRI::Out]ScanContext^% ctx)
{
try
{
ctx = nullptr;
auto sPath= ToSdk(name);
ctx = gcnew ScanContext(scan, gs::LoadScanContext(scan->sdkScan(), sPath));
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl; // This DOES execute
}
return ctx != nullptr; // If we did not throw, this is non-null and we succeeded.
}
So the catch
clause above does get invoked, the error output message gets dumped, and control returns normally back to my managed C# code that called into the C /CLI.
So where did the SEHException
go?
To double-check, I set the Exceptions dialog to actually break on SEHException
. And it sure did.
This is the call stack at the point it breaks. My C code is in the process of throwing std::runtime_error
ntdll.dll!NtWaitForSingleObject() Unknown
KernelBase.dll!WaitForSingleObjectEx() Unknown
ntdll.dll!RtlpExecuteHandlerForException() Unknown
ntdll.dll!RtlDispatchException() Unknown
ntdll.dll!KiUserExceptionDispatch() Unknown
KernelBase.dll!RaiseException() Unknown
> vcruntime140d.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 75 C
gscored_v143.dll!gs::detail::YAML::ParseTopLevelMap(std::basic_istream<char,std::char_traits<char>> & in) Line 298 C
gscored_v143.dll!gs::ScanContext::loadContent(std::basic_istream<char,std::char_traits<char>> & is, const std::string & loadFolder) Line 172 C
gscored_v143.dll!gs::ScanContext::loadContent(std::basic_istream<char,std::char_traits<char>> & is) Line 63 C
gscored_v143.dll!gs::ScanContext::Load(const std::shared_ptr<gs::Scan> & scan, const std::string & name) Line 884 C
gscored_v143.dll!gs::LoadScanContext(const std::shared_ptr<gs::Scan> & scan, const std::string & name) Line 310 C
[Managed to Native Transition]
MyCompany.Sdk_v143.dll!MyCompany::Sdk::ScanContext::TryLoad(MyCompany::Sdk::Scan^ scan, System::String^ name, MyCompany::Sdk::ScanContext^% ctx) Line 660 C
MyCompany.Services.dll!MyCompany.Services.ScanService.GetScanContext(MyCompany.Sdk.Scan scan, string name) Line 1725 C#
Is this all just a false warning?
CodePudding user response:
Interesting.
It's not at all obvious from the documentation exactly what's going on here, but there's an interesting article over at Code Project which explains how (unmanaged) C exceptions are handled by the Microsoft compiler. Basically, when you call throw
an SEH exception is generated (via RaiseException
) which is then caught and mapped to a C exception by the runtime library.
The question now is how that is mapped to a managed C exception so that you can catch it in managed code, and the mechanics behind that is not at all clear, but SEHException
obviously fits into it somehow.
Perhaps the .NET framework catches your unmanaged throw
via its own try
... catch
block and calls RaiseException
again with a different exception code (one that corresponds to / results in an SEHException
), and that's what you're trapping in the debugger. It then catches that (via __try
... __except
) and uses some sort of magic to generate a managed code exception. Something like that, anyway.
As for whether you need to be concerned about this, the documentation goes on to say:
Note that the SEHException class does not cause unmanaged C exception destructors to be called. To ensure that unmanaged C exception destructors are called, use the following syntax in the catch block.
C#
catch
{
// Handle catch here.
}
Now I'm really not sure what they mean by 'C exception destructors' (I didn't know there was such a thing) but it may be that yamlmap
doesn't get properly destroyed. I'd be surprised if that were true, but it might be worth checking. It also doesn't make sense to me to handle the exception in managed code. Maybe that's just a typo.
Returning to that Code Project article, it cites the fact that the exception code for the SEH exception raised by an unmanaged C throw
is 0xE06D7363
. If you break on that, you might learn a bit more about all this. And then again, maybe not.