跨本机框架捕获托管异常

时间:2013-11-01 02:52:13

标签: .net exception exception-handling clr clr-hosting

是否可以通过托管代码捕获托管异常但是在调用堆栈上有介入原生帧的情况下?

我在做这件事时遇到了麻烦。该应用程序是32位本机代码并托管MSCLR 2.0(但大多数代码是.NET 3.5。)

除非完成抛出,否则应用程序运行正常,抛出时究竟发生了什么取决于它运行的系统。

实际的应用程序非常复杂,所以至少在最初我会发布一些简单的概念代码仅仅是为了说明。原生程序(我们称之为Native.exe)运行一个托管程序,我们称之为Managed.exe。在Managed.exe内的某处,用C#编写,如下:

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是在混合C ++ / CLI程序集中定义的托管类,我们将其称为Glue.dllMyObjectGlue.dll中另一个类的实例。那里的相关方法看起来像这样:

void DoSomething( void )
{
    _pMyNativeKernel->DoSomething();
}

DoSomethingNative.exe中的C ++函数,虚拟调用。长话短说,它最终会回到Glue.dll中的托管方法,引发MyEvent

如果引发MyEvent且程序在32位Windows XP系统上运行,则其行为符合预期,控制台将显示:

Throwing...
Caught

在Windows 7 64位系统上运行我改为:

Throwing...
Finished

基本上,例外只是消失在空气中;整个事情继续运行,好像它从未发生过。 (例外对应于按下窗口上的关闭按钮,因此它就像没有单击按钮一样。)

通过远程桌面在Windows Server 2012系统上运行,我得到了这个:

Throwing...

然后整个应用程序崩溃,出现一个对话框,说“Native.exe已经停止工作”,这就是:

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

如果我没有try / catch,这就是我所期望的。

如果我在VS2008SP调试器下的那个环境中运行它,调试器会捕获第一次机会异常,如果我继续它,它会将其作为未处理的异常捕获。

我应该注意到,本机DoSomething最终会调用Win32 GetMessage然后调用DispatchMessage,并且在从窗口过程调用的代码中会发生本机到托管的回调。该窗口是使用Direct3D绘制的。托管程序使用Native.exe“内核”进行所有窗口和绘图操作,并且永远不会自己访问Windows。

Native.exe中的任何干预功能都没有捕获任何例外。我不能说Win32 API函数中是否有任何异常处理程序;我不这么认为,但如果有可能可以解释这种行为在系统之间是如何不一致的。

这大致是Server 2012上的实际调用堆栈,重复项目被删除:

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()    

整个系统已经运行了很长时间但我以前从未需要在托管/本机转换中抛出异常。我确实希望托管应用程序代码能够自由地抛出和捕获异常,而不必担心本机主机是否正在执行本机到托管的回调。

我在网上找到的关于在这些转换中抛出异常的所有信息总是关于托管捕获本机异常,反之亦然。这是管理捕获管理,但介入的本机框架使问题复杂化。

所以关于这样投掷的问题一般是:

  • 这项工作?它 在Windows XP上工作,但我不知道这是否是明确定义的行为,或者我是否幸运。

  • 如果工作,那么它可能导致无法在所有系统上运行的原因是什么?

如果它应该工作,那么我想我必须扩充所有托管回调以捕获托管异常,用本机异常包装它们,并在托管包装器中捕获它对于本机函数并抛出原始托管异常。这听起来像很多头发拉![/ p>

1 个答案:

答案 0 :(得分:0)

我处理同样的问题。我有一个表单,调用它的代码(或者更确切地说是调用.ShowDialog()的代码)位于try {}块内,并带有相应的catch {}块。在某些时候点击对话框的按钮会导致异常,但是没有点击!

所以我编辑了代码,然后简单地用它自己的try / catch包围了有问题的语句(OnClick处理程序中的转换)。

'赶上'被击中但在里面有一个简单的投掷;'导致User-Unhandled异常!

如果我查看堆栈,则有几个托管/本机/托管转换。

似乎托管堆栈没有处理程序,并且系统没有通过本机帧将堆栈移动到下一个托管框架,因此它认为没有处理程序。