【问题标题】:win32 window in WPFWPF中的win32窗口
【发布时间】:2009-12-22 06:50:41
【问题描述】:

最近我们的应用遇到了一个奇怪的问题。

应用程序在WPF窗口中有一个win32窗口, 调整 WPF 窗口大小时,出现问题。

堆栈跟踪:

Exception object: 0000000002ab2c78
Exception type: System.OutOfMemoryException
InnerException: <none>
StackTrace (generated):
    SP       IP       Function
    0048D94C 689FB82F PresentationCore_ni!System.Windows.Media.Composition.DUCE+Channel.SyncFlush()+0x80323f
    0048D98C 681FEE37 PresentationCore_ni!System.Windows.Media.Composition.DUCE+CompositionTarget.UpdateWindowSettings(ResourceHandle, RECT, System.Windows.Media.Color, Single, System.Windows.Media.Composition.MILWindowLayerType, System.Windows.Media.Composition.MILTransparencyFlags, Boolean, Boolean, Boolean, Int32, Channel)+0x127
    0048DA38 681FEAD1 PresentationCore_ni!System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean, System.Nullable`1<ChannelSet>)+0x301
    0048DBC8 6820718F PresentationCore_ni!System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean)+0x2f
    0048DBDC 68207085 PresentationCore_ni!System.Windows.Interop.HwndTarget.UpdateWindowPos(IntPtr)+0x185
    0048DC34 681FFE9F PresentationCore_ni!System.Windows.Interop.HwndTarget.HandleMessage(Int32, IntPtr, IntPtr)+0xff
    0048DC64 681FD0BA PresentationCore_ni!System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+0x3a
    0048DC88 68C6668E WindowsBase_ni!MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+0xbe
    0048DCD4 68C665BA WindowsBase_ni!MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)+0x7a
    0048DCE4 68C664AA WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Boolean)+0x8a
    0048DD08 68C6639A WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Boolean, System.Delegate)+0x4a
    0048DD50 68C64504 WindowsBase_ni!System.Windows.Threading.Dispatcher.WrappedInvoke(System.Delegate, System.Object, Boolean, System.Delegate)+0x44
    0048DD70 68C63661 WindowsBase_ni!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Boolean)+0x91
    0048DDB4 68C635B0 WindowsBase_ni!System.Windows.Threading.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority, System.Delegate, System.Object)+0x40
    0048DDD8 68C65CFC WindowsBase_ni!MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)+0xdc

StackTraceString: <none>
HResult: 8007000e

另外,我找到了一些相关链接:

relatedA

relatedB

  1. 有什么办法可以避免或处理这个问题?

  2. 如何找出真正的问题?

  3. 从调用堆栈中,我们能否确定问题来自 .NET Framework?

感谢您的回答或cmets!

【问题讨论】:

  • whunmr,您是否能够找到问题的根本原因?如果是这样,你能分享你的发现吗?
  • 嗨查理,我想我没有找到这个问题的真正原因。这是很久以前的事情了。

标签: .net wpf windbg out-of-memory


【解决方案1】:

您的问题不是由托管内存泄漏引起的。很明显,您在非托管代码的某个地方解决了一个错误。

SyncFlush() 方法在多次 MILCore 调用后被调用,它似乎会导致已发送的更改立即被处理,而不是留在队列中以供以后处理。由于调用会处理之前发送的所有内容,因此可视化树中的任何内容都不能从您发送的调用堆栈中排除。

包含非托管调用的调用堆栈可能会提供更多有用的信息。在 VS.NET 下使用本机调试或使用 windbg 或其他本机代码调试器运行应用程序。设置调试器在异常处中断,并在相对断点处获取调用堆栈。

调用堆栈当然会下降到 MILCore,并从那里进入 DirectX 层和 DirectX 驱动程序。可以在此本机调用堆栈中的某处找到有关代码的哪一部分导致问题的线索。

根据您所说的,MILCore 很有可能将某个参数的巨大值传递给 DirectX。检查您的应用程序是否有任何可能导致导致 DirectX 分配大量内存的错误。要查找的内容示例如下:

  • 设置为以非常高分辨率加载的位图源。
  • 大型可写位图
  • 非常大(或负)的变换或大小值

解决此问题的另一种方法是逐步简化您的应用程序,直到问题消失,然后非常仔细地查看您最后删除的内容。方便时,最好将其作为二分搜索:最初减少一半的视觉复杂性。如果有效,则将移除的一半放回原处,否则移除另一半。重复直到完成。

另外请注意,通常不需要实际删除 UI 组件以防止 MILCore 看到。任何具有 Visibility.Hidden 的 Visual 都可以完全跳过。

没有通用的方法可以避免此问题,但搜索技术将帮助您确定具体需要更改哪些内容以在特定情况下进行修复。

从调用堆栈可以肯定地说,您在 NET Framework 或特定视频卡的 DirectX 驱动程序中发现了一个错误。

关于您发布的第二个堆栈跟踪

John Knoeller 说从 RtlFreeHeap 到 ConvertToUnicode 的转换是无稽之谈是正确的,但从中得出了错误的结论。我们看到的是您的调试器在回溯堆栈时丢失了。它从异常正确启动,但在 Assembly.ExecuteMainMethod 帧下方丢失,因为在处理异常和调用调试器时,堆栈的那部分已被覆盖。

不幸的是,对这个堆栈跟踪的任何分析对于您的目的都是无用的,因为它被捕获得太晚了。我们看到的是在处理 WM_LBUTTONDOWN 期间发生的异常,该异常被转换为 WM_SYSCOMMAND,然后捕获异常。换句话说,您单击了导致系统命令(例如调整大小)的某些内容,从而导致了异常。在捕获此堆栈跟踪时,已经在处理异常。您看到 User32 和 UxTheme 调用的原因是因为它们涉及处理按钮单击。它们与真正的问题无关。

您在正确的轨道上,但您需要在分配失败时捕获堆栈跟踪(或者您可以使用我上面建议的其他方法之一)。

当您的第一个堆栈跟踪中的所有托管帧都出现在其中并且堆栈顶部是失败的内存分配时,您将知道您拥有正确的堆栈跟踪。请注意,我们真正感兴趣的只是出现在 DUCE+Channel.SyncFlush 调用上方的非托管框架 -- 下方的所有内容都是 NET Framework 和您的应用程序代码。

如何在正确的时间获取本机堆栈跟踪

您希望在显示的DUCE+Channel.SyncFlush 调用内第一次内存分配失败时获得堆栈跟踪。这可能很棘手。我使用了三种方法:(请注意,在每种情况下,您都从 SyncFlush 调用中的断点开始 - 有关详细信息,请参阅下面的注释)

  1. 将调试器设置为中断所有异常(托管和非托管),然后继续按 go(F5 或“g”)直到它中断您感兴趣的内存分配异常。这是第一件事尝试是因为它很快,但在使用原生代码时经常会失败,因为原生代码通常会向调用原生代码返回错误代码而不是抛出异常。

  2. 将调试器设置为在所有异常上中断,并在常见的内存分配例程上设置断点,然后重复按 F5(执行)直到异常发生,计算您按了多少 F5。下次运行时,请少用一个 F5,您可能正在执行生成异常的分配调用。将调用堆栈捕获到记事本,然后从那里重复 F10(跳过)以查看是否真的是分配失败。

  3. 在 SyncFlush 调用的第一个本机帧(这是 wpfgfx_v0300!MilComposition_SyncFlush)上设置断点以跳过托管到本机的转换,然后按 F5 运行到它。 F10(跳过)函数直到 EAX 包含错误代码 E_OUTOFMEMORY (0x8007000E)、ERROR_OUTOFMEMORY (0x0000000E) 或 ERROR_NOT_ENOUGH_MEMORY (0x0000008) 之一。注意最近的“呼叫”指令。下次运行该程序时,请运行到那里并单步执行。重复此操作,直到找到导致问题的内存分配调用并转储堆栈跟踪。请注意,在许多情况下,您会发现自己在一个较大的数据结构中循环,因此需要一些智能来设置适当的断点以跳过循环,以便您可以快速到达需要的位置。这种技术非常可靠,但非常耗费人力。

注意:在每种情况下,您都不想设置断点或开始单步执行,直到您的应用程序位于失败的 DUCE+Channel.SyncFlush 调用中。为确保这一点,请在禁用所有断点的情况下启动应用程序。当它运行时,在System.Windows.Media.Composition.DUCE+Channel.SyncFlush 上启用断点并调整窗口大小。第一次只需按 F5 以确保在第一次 SyncFlush 调用时异常失败(如果没有,请计算在异常发生之前您必须按 F5 多少次)。然后禁用断点并重新启动程序。重复该过程,但这次是在您点击 SyncFlush 调用正确的时间后,设置断点或按照上述说明单步执行。

建议

我上面描述的调试技术是劳动密集型的:计划至少花费几个小时。正因为如此,我通常会尝试反复简化我的应用程序,以在跳入调试器进行此类操作之前准确找出导致错误的原因。这有两个好处:它将为您发送给显卡供应商提供一个很好的重现,它将使您的调试更快,因为显示更少,因此单步执行的代码更少,分配更少,等等。

因为问题只发生在特定的显卡上,所以毫无疑问,问题要么是显卡驱动程序的错误,要么是调用它的 MilCore 代码中的错误。很可能是在显卡驱动程序中,但有可能 MilCore 传递了大多数显卡都能正确处理的无效值,但不是这个。我上面描述的调试技术会告诉你情况是这样的:例如,如果 MilCore 告诉显卡分配一个 1000000x1000000 像素区域并且显卡给出了正确的分辨率信息,那么错误就在 MilCore 中。但如果 MilCore 的要求是合理的,那么 bug 就在显卡驱动中。

【讨论】:

  • 谢谢!嗨 Ray,我转储了托管和非托管堆栈:我可以说问题来自“uxtheme!_ThemeDefWindowProc”吗?
  • 不,它与 UxTheme 无关。 UxTheme 代码只是处理您的按钮单击。堆栈跟踪是正确的跟踪类型,但不是在正确的时间进行的。我为我的答案添加了更多解释,以及一些获得良好堆栈跟踪的提示。希望他们有所帮助。
【解决方案2】:

这里有一个有用的article 关于 WPF 中的内存泄漏。您还可以考虑使用 RedGate 的 ANTS Performance 和/或 Memory Profiler 等工具来帮助诊断此类问题。

【讨论】:

  • 是的...蚂蚁内存分析器看起来是我的最佳选择。
【解决方案3】:

我不确定堆栈部分(或至少是 UXTheme 的东西)是否值得信赖。堆栈的底部看起来很正常。我们看到似乎是一个试图进行清理的异常处理程序。然后是对不同层堆管理代码的大量嵌套调用。

但是堆栈从RtlFreeHeap 转换为ConvertToUnicode 的这部分没有任何意义。我怀疑上面的所有内容都是以前使用堆栈时遗留下来的。

0048f40c 6b88f208 mscorwks!_EH_epilog3_GS+0xa, calling mscorwks!__security_check_cookie 
0048f410 6b8a756e mscorwks!SString::ConvertToUnicode+0x81, calling mscorwks!_EH_epilog3_GS 
0048f424 77b4371e ntdll_77b10000!RtlpFreeHeap+0xbb1, calling ntdll_77b10000!RtlLeaveCriticalSection 
0048f42c 77b436fa ntdll_77b10000!RtlpFreeHeap+0xb7a, calling ntdll_77b10000!_SEH_epilog4 

RtlFreeHeap 中的崩溃指向堆损坏,这表明问题出在非托管代码中,但托管对象的内存最终必须从非托管内存中分配,因此也可能是。

我建议您寻找非托管窗口可能损坏堆的地方;同一分配的多个空闲,或覆盖分配的边界。

【讨论】:

  • 感谢您的分析。现在我可以得出结论 UXTheme 工作正常。
  • @John Knoeller:是的,您确定的转换是堆栈覆盖的一部分。您的其余分析是合理的除了,因为原始跟踪显示 OutOfMemoryException 发生在 SyncFlush 内部并响应按钮单击。由于 RtlpFreeHeap 调用完全在所有托管代码之外,包括Application.Run,所以这个调用堆栈显然没有显示与前一个相同的异常。具体来说,它没有显示导致调整大小时引发内存不足异常的错误。
  • 请注意,有问题的堆栈覆盖是由于线程被关闭所致。可以想象,最终退出包含来自 RtlpFreeHeap 的 GP 错误,但堆栈跟踪中没有可见的内容表明事实确实如此。很可能根本没有损坏,我们看到由于未处理的 OutOfMemoryException 而导致正常的异常退出。不过,在这些情况下,检查您的非托管代码是否有可能破坏堆的内容始终是一个好主意。
  • @Ray:好点。我是在这样的假设下进行操作的,即在某些时候,托管分配请求,尤其是大型请求,可能会触发非托管分配。我还假设他并不是真的失忆了。根据我的经验,当您实际上内存不足时,您的进程会严重崩溃,以至于无法在友好的对话框中报告“内存不足”。
【解决方案4】:

如果它可以帮助遇到 SyncFlush 问题的人,我们刚刚解决了我们的问题,这要归功于 Microsoft 的出色支持(通过我的 MSDN 订阅)。事实证明,我们创建的多媒体计时器比使用 timeBeginPeriod 和 timeEndPeriod 调用释放的要多。这些计时器是一种有限的资源,一旦用完,WPF 渲染线程就会因计时器不足而退出工作。

【讨论】:

    猜你喜欢
    • 2011-11-21
    • 1970-01-01
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2011-04-09
    • 2020-10-08
    • 2018-11-10
    相关资源
    最近更新 更多