Home > OS >  How can this warning of InteropServices.SEHException be real?
How can this warning of InteropServices.SEHException be real?

Time:02-12

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.

  • Related