5

是否有可能由托管代码抛出捕获托管异常,但调用堆栈上有干预本机帧?

我很难做到这一点。该应用程序是 32 位本机代码并托管 MSCLR 2.0(但大部分代码是 .NET 3.5。)

除非这个 throw 完成,否则应用程序运行良好,并且当它 throw 时究竟会发生什么取决于它运行的系统。

实际的应用程序非常复杂,所以至少在最初我会发布一些简单的概念代码来说明。本机程序(我们将调用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.dll. MyObject是另一个类的实例Glue.dll。那里的相关方法看起来像这样:

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

DoSomething是一个 C++ 函数,在Native.exe其中被虚拟调用。长话短说,它最终会回调到一个托管方法中,在该方法中Glue.dllraises 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最终会调用 Win32GetMessage和 then 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,但我不知道这是定义明确的行为还是我只是幸运。

  • 如果它应该工作,它不能在所有系统上工作的可能原因是什么?

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

4

1 回答 1

0

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

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

好吧,“接球”被击中,但里面有一个简单的“投掷”;导致用户未处理的异常!

如果我查看堆栈,就会发现有几个托管/本机/托管转换。

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

于 2017-08-28T03:51:13.540 回答