托管 C++ 中未处理的托管异常的访问冲突问题; .NET应用程序
这实际上是一个已解决的问题,但它是如此深奥,我想我应该将其分享给其他用户。
也许其他人可能会提出原因?
不管怎样,我正在开发一个用托管 C++ 编写的“混合模式”.NET 应用程序,但与现有本机库有大量链接。
问题是,未处理的托管异常最终会导致 Win32 访问冲突。 我的意思是,我不会显示带有未处理的托管异常的漂亮 .NET 对话框,而是会收到旧样式的“未处理的 win32 异常发生在...”消息。
有趣的是:如果我在调试器中启动应用程序,则抛出的托管异常将被正确拾取。 即,调试器向我显示该行。
但正常执行时,就会变成这种Access Violation。 此时附加调试器将产生很少的有用信息(它甚至不会显示合理的堆栈跟踪)。
因此,对我来说,这表明在未处理的托管异常到达异常处理程序之前,本机代码中发生了一些事情。
因此,无论如何,我通过将我的项目与 Visual Studio 2008 生成的全新 C++ 托管项目进行比较,设法解决了该问题。
解决方法是执行以下操作:
更改 /SUBSYSTEM 标志(项目属性 -> 链接器 - >System->SubSystem) 从 /SUBSYSTEM:WINDOWS 到“未设置”
从使用旧样式 WinMain() 切换到使用新样式 main() .
即过去是
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
现在是
int main(array<System::String ^> ^args)
[为什么我使用这个奇怪的_tWinMain? 这是多年前当您创建示例混合模式 Windows 应用程序时由 Visual Studio .NET IDE 生成的内容。 它一直工作得很好(直到现在)所以我从来没有费心去改变它。 _tWinMain 只是 WinMain 的一个宏]
我做了这个更改,问题就消失了。 未处理的 .NET 异常现在可以正确捕获,因此我现在可以实际调试它们。
我还对干净的示例 C++ 应用程序进行了反向更改,并证明这是原因。
所以,我真正的问题是,到底发生了什么事?
是不是我使用的是旧样式的 WinMain 而不是新的 main(array
?
我应该向微软报告这个问题吗(有人会关心吗;-))?
This is actually a solved problem, but it's so esoteric I thought I'd share it for other users.
Also perhaps others might suggest reasons?
Anyway, I'm working on a "mixed mode" .NET application written in managed C++, but with heavy links into existing native libraries.
The problem was, unhandled managed exceptions ended up as Win32 Access Violations. What I mean by this is that instead of showing the nice .NET dialog one gets with an unhandled managed exception, instead I would get the older style "Unhandled win32 exception occurred in..." message.
Here's the interesting thing: if I start the app in the debugger, then the thrown managed exception gets correctly picked up. i.e., the debugger shows me the line.
However, when executing normally, it would turn into this Access Violation. Attaching the debugger at that point would produce little useful information (it wouldn't even show a sensible stack trace).
So, to me it suggests that something is happening in native code just before the unhandled managed exception reaches the exception handler.
So anyway, I managed to solve the issue by comparing my project to a clean new C++ managed project generated by Visual Studio 2008.
The fix was to do the following:
Change the /SUBSYSTEM flag (Project properties->Linker->System->SubSystem) from /SUBSYSTEM:WINDOWS to "Not Set"
Switched from using old style WinMain() to using the new style main().
i.e. it used to be
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
And it is now
int main(array<System::String ^> ^args)
[Why am I using this weird _tWinMain? This was what got generated by the Visual Studio .NET IDE many years ago when you create a sample mixed mode Windows app. It's always worked fine (until now) so I never bothered changing it. _tWinMain is just a macro to WinMain]
I made this change and the problem disappears. Unhandled .NET exceptions now get properly trapped so I can now actually debug them.
I also made the inverse change to the clean sample C++ app and proved that it was the cause.
So, really my questions is, what the heck is going on?
Was it just that I was using the old style WinMain instead of the new main(array <String^>^)
?
Should I maybe report this to Microsoft (will anyone care ;-) )?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我并不感到惊讶。
如果您在托管代码中编写顶级
main
,则任何将出现的托管异常都将由托管代码处理。 与 C# 中的情况相同。 这是因为操作系统不会直接调用int main(array^args)
函数。 这是通过托管代码完成的,托管代码可以是 .net 的一部分,也可以在编译时插入。 这是注意到并处理任何转义 .net 异常的代码。如果您编写自己的本机
WinMain
,则没有 .net 代码调用此函数,因此也没有能够处理托管异常的托管代码。 当引发托管异常时,它对操作系统来说就像任何其他本机 Windows 异常一样。请注意,这并不是对如何调用 main/WinMain 的准确描述。 这是根据您的问题描述和一点经验进行的有根据的猜测。 我遗漏了一些我知道的细节,可能还有我完全不知道的细节。 但我很确定这就是故事的本质。
I am not really surprised.
If you write the top-level
main
in managed code, any managed exception that will bubble up, will be handled by managed code. The same as it would in C#. This is because the OS does not call directly into yourint main(array<System::String ^> ^args)
function. This is done by managed code which is either part of .net, or inserted at compile time. This is the code that notices and handles any escaping .net exceptions.If you write your own native
WinMain
, there is no .net code calling into this function, so there is also no managed code able to handle the managed exceptions. When a managed exception is thrown, it appears to the OS as any other native windows exception.Note that this is not an exact description of how main/WinMain is invoked. It's an educated guess based upon your problem description, and a little experience. I left out some details that I know about, and probably also detail which I am totally not aware of. But I am quite sure this is the essence of the story.
我没有 .NET 或托管代码方面的经验,但我在本机方面有相当多的经验。
我不明白更改入口点(main vs WinMain)或子系统(win32 vs console vs none)如何或为何会影响这一点。 我并不是说没有,只是说这不是根本原因。 (旁白:我不知道子系统:“未设置”是什么意思。我认为子系统是链接器知道并标记到可执行文件中的东西,必须设置为某物 ,如果入口点是 main(),它可能设置为 console. 控制台子系统应用程序仍然可以以所有正常方式与 GUI 交互,但它们也始终附加一个控制台窗口,并且如果您从外部启动它们。他们创建一个控制台窗口,因此对于要发送给除您自己之外的任何人的应用程序来说,这通常不是一个好的选择。)
在 Win32 API 级别,这种行为由以下因素控制:
也许是旧 WinMain() 中的某些内容,或者从那里调用的某些内容,或者某些 .NET 托管的内容在 WinMain 之前运行(即使在本机 C/C++ 中,使用默认的 MSVC 设置,就链接器而言,WinMain 并不是真正的入口点 - MSVC 的 C 运行时库提供了一个包装函数,这是真正的入口点)入口点并调用 WinMain ——我希望 .NET 会做同样的事情,但更多),正在调用 SetUnhandledExceptionFilter(NULL),而在您的固定版本中,这种情况不再发生。
如果您知道哪个函数负责建立“带有未处理的托管异常的良好 .NET 对话框”,您可以将其传递给 SetUnhandledExceptionFilter(),但在非托管代码中调用它可能不是一个好主意。
所有这一切的另一种理论:“旧式“未处理的 win32 异常发生在...””对话框实际上说的是访问冲突,并且是这个意思吗? .NET 异常处理程序也可能启动,然后由于某种原因实际上崩溃了; 如果是这种情况,应该可以对其进行调试,尽管这需要更多信息。
I don't have experience with this in .NET or managed code but I have a fair amount of experience with the native side.
I don't see how or why changing the entry point (main vs WinMain) or the subsystem (win32 vs console vs none) should affect this. I'm not saying it didn't, just that it's not the root cause. (Aside: I don't know what subsystem:"not set" means. I'd think the subsystem, which is something the linker knows and stamps into the executable file, has to be set to something, and if the entry point is main(), it's probably set to console. Console subsystem apps can still interact with the GUI in all the normal ways, but they also always have a console window attached, and if you launch them from outside a console window they create one, so this is usually not a good choice for apps you're going to ship to anyone but yourself.)
At the Win32 API level, this kind of behavior is controlled by:
Perhaps something in your old WinMain(), or something called from there, or some .NET managed goo that runs before WinMain (even in native C/C++, with the default MSVC setup, WinMain isn't the real entry point as far as the linker is concerned -- there's a wrapper function provided by MSVC's C runtime library, which is the real entry point and which calls WinMain -- I'd expect .NET does the same thing but more so), was calling SetUnhandledExceptionFilter(NULL), and in your fixed version that's no longer happening.
If you know what function is responsible for putting up the "nice .NET dialog one gets with an unhandled managed exception", you could pass that to SetUnhandledExceptionFilter(), but it's probably not a good idea to call that for exceptions in unmanaged code.
An alternate theory for all this: the 'older style "Unhandled win32 exception occurred in..."' dialog actually said Access Violation, and meant it? It's also possible the .NET exception handler was kicking in and then actually crashing for some reason; if that's the case it should be possible to debug that, though that would require a lot more information.