Is it possible to have a managed exception thrown and caught by managed code but where there are intervening native frames on the call stack?
I'm having trouble doing this. The app is 32-bit native code and hosts the MSCLR 2.0 (but most of the code is .NET 3.5.)
The application runs fine unless this throw is done, and what exactly happens when it does throw depends on the system it's running on.
The actual application is quite complex so at least initially I'll post some simple conceptual code just for illustration. The native program (we'll call Native.exe
) runs a single managed program which we'll call Managed.exe
. Somewhere within Managed.exe
, written in C#, is the following:
class MyException : Exception {}
...
void OuterManaged()
{
MyObject.MyEvent += ( s, a ) =>
{
Console.WriteLine( "Throwing..." );
throw new MyException();
}
try
{
MyKernel.DoSomething();
Console.WriteLine( "Finished" );
} catch( MyException )
{
Console.WriteLine( "Caught" );
}
}
MyKernel
is a managed class defined in a mixed C++/CLI assembly which we'll call Glue.dll
. MyObject
is an instance of another class in Glue.dll
. The relevant method in there looks something like this:
void DoSomething( void )
{
_pMyNativeKernel->DoSomething();
}
DoSomething
is a C++ function in Native.exe
which is called virtually. To make a long story short, it eventually winds up calling back into a managed method in Glue.dll
which raises MyEvent
.
If MyEvent
is raised and the program is running on a 32-bit Windows XP system, it behaves as intended and the console will show:
Throwing...
Caught
Running on a Windows 7 64-bit system I instead get this:
Throwing...
Finished
Basically, the exception just vanishes into thin air; the whole thing continues to run as though it had never happened. (The exception corresponds to hitting the close button on a window, so it just acts as though the button wasn't clicked.)
Running on a Windows Server 2012 system over remote desktop, I get this:
Throwing...
And then the whole application crashes with a dialog saying "Native.exe has stopped working" and this:
Description:
Stopped working
Problem signature:
Problem Event Name: CLR20r3
Problem Signature 01: Native.exe
Problem Signature 02: 0.0.0.0
Problem Signature 03: 5267c484
Problem Signature 04: 0
Problem Signature 05: 1.0.0.0
Problem Signature 06: 5272e299
Problem Signature 07: 208
Problem Signature 08: f
Problem Signature 09: MyException
OS Version: 6.2.9200.2.0.0.144.8
Locale ID: 1033
This is what I would have expected had I not had the try
/catch
.
If I run it in that environment under the VS2008SP debugger, the debugger catches the first-chance exception, and if I continue it, it then catches it as an unhandled exception.
I should note that the native DoSomething
eventually ends up calling the Win32 GetMessage
and then DispatchMessage
, and the native-to-managed callback occurs in code called from a window procedure. That window is drawn with Direct3D. The managed program uses the Native.exe
"kernel" for all windowing and drawing operations and never accesses Windows on its own.
None of the intervening functions in Native.exe
catch any exceptions at all. I can't say if there are any exception handlers in the Win32 API functions; I wouldn't think so but if there are that could conceivably explain how the behaviour was inconsistent between systems.
This is roughly the actual call stack on Server 2012, with repetitious items cut out:
Managed!MyGame.ReInitDisplay.AnonymousMethod(object s = {Engine.Display}, System.EventArgs a = {System.EventArgs}) C# // throw site
Glue.dll!CDisplayBridge::OnClosePressed() C++
[Native to Managed Transition]
Native.EXE!EngineKern::CGfxDisplay_a::HandleClosePress() C++
Native.EXE!EngineKern::CGfxDisplay::WindowProc(HWND__ * hwnd=0x000610ac, unsigned int uMsg=16, unsigned int wParam=0, long lParam=0) C++
user32.dll!74a477d8()
[Frames below may be incorrect and/or missing, no symbols loaded for user32.dll]
user32.dll!74a47c44()
ntdll.dll!773e2f02()
user32.dll!74a48fed()
uxtheme.dll!7422254d()
user32.dll!74a475e7() // DefWindowProc
Native.EXE!EngineKern::CGfxDisplay::WindowProc(HWND__ * hwnd=0x000610ac, unsigned int uMsg=274, unsigned int wParam=61536, long lParam=4261024) C++
user32.dll!74a48a66() // DispatchMessage
Native.EXE!EngineKern::CKernel::DoEvent() C++
[Managed to Native Transition]
Glue.dll!Engine::Kernel::DoEvent() C++ // DoSomething in the example
MyClassLib!MyClassLib.Game.MainLoop() C#
MyClassLib!MyClassLib.Game.Play() C#
Managed!MyGame.Play() C#
Managed!Program.Main(string[] args = {string[0]}) C#
mscorlib.dll!System.AppDomain.ExecuteAssemblyByName(string assemblyName, System.Security.Policy.Evidence assemblySecurity, string[] args)
mscorlib.dll!System.AppDomain.ExecuteAssemblyByName(string assemblyName)
Glue.dll!__Engine__::AppDomainManager::Main(int pEngineKern = 15760932) C++
[Native to Managed Transition]
Native.EXE!EngineGlue::IManagedEntryPoint::Main(long pEngineKern=15760932) C++ // calls in by COM interop
Native.EXE!CClrHost::Run() C++
Native.EXE!wWinMain(HINSTANCE__ * hInstance=0x00e40000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x01462a80, int nCmdShow=5) C++
Native.EXE!__tmainCRTStartup() C
Native.EXE!wWinMainCRTStartup() C
kernel32.dll!74ca8543()
ntdll.dll!773fac3c()
This whole system has been working fine for a long time but I never needed to throw exceptions across the managed/native transitions before. I do want the managed application code to be able to throw and catch exceptions freely without worrying about whether the native host is doing a native-to-managed callback.
All the information I find on-line about throwing exceptions across such transitions is always about having managed catch a native exception or vice-versa. This is managed catching managed, but the intervening native frames are complicating matters.
So my questions in regards to throwing like this in general are:
Should this work? It does work on Windows XP but I don't know if this was well-defined behaviour or if I was just lucky.
If it should work, what are possible reasons why it's not working on all systems?
If it's not supposed to work, then I guess I'd have to augment all managed call-backs to catch managed exceptions, wrap them with a native exception, and catch that in the managed wrapper for the native function and throw the original managed exception. That sounds like a lot of hair pulling!
I'm dealing with the same issue. I have a form, the code that calls it (or rather the code that calls .ShowDialog()) sits inside a try { } block with a corresponding catch { } block. At some point a button click on the dialog causes an exception BUT the catch isn't hit!
So I edited the code and then simply surrounded the offending statement (a conversion, in the OnClick handler) with it's own try/catch.
Well the 'catch' is hit but inside there a simple 'throw;' leads to the User-Unhandled exception!
If I look at the stack, there are several managed/native/managed transitions.
It seems that the managed stack has no handler and that the system does not walk the stack through the native frames to the next managed frame, so it considers there to be no handler.