WPF中的win32視窗
最近我們的應用程序遇到了一個奇怪的問題。
應用程序在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另外,我找到了一些相關連結:
- 有沒有辦法避免或處理這個問題?
- 如何找出真正的問題?
- 從呼叫堆棧中,我們能否確定問題出在 .NET Framework 上?
感謝您的回答或評論!
您的問題不是由託管記憶體洩漏引起的。顯然,您在非託管程式碼中的某個地方發癢。
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 呼叫中的斷點開始 - 有關更多詳細資訊,請參見下面的註釋)
- 將調試器設置為在所有異常(託管和非託管)上中斷,然後繼續按 go(F5 或“g”)直到它在您感興趣的記憶體分配異常上中斷。這是首先要嘗試的,因為它很快,但是在使用本機程式碼時它經常會失敗,因為本機程式碼通常會向呼叫本機程式碼返回錯誤程式碼而不是拋出異常。
- 將調試器設置為在所有異常上中斷,並在常見的記憶體分配常式上設置斷點,然後反复按 F5(go)直到發生異常,計算您按了多少 F5。下次執行時,請少用一個 F5,您可能正在執行生成異常的分配呼叫。將呼叫堆棧擷取到記事本,然後從那裡重複 F10(跳過)以查看是否真的是分配失敗。
- 在 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 呼叫正確的時間後,設置您的斷點或按上述說明單步執行。建議
我上面描述的調試技術是勞動密集型的:計劃至少花費幾個小時。正因為如此,我通常會嘗試反复簡化我的應用程序,以在跳入調試器進行此類操作之前準確找出導致錯誤的原因。這有兩個好處:它會給你一個很好的repro來發送顯卡供應商,它會讓你的調試更快,因為顯示更少,因此單步執行的程式碼更少,分配更少等。
因為問題只發生在特定的顯卡上,所以毫無疑問,問題要麼是顯卡驅動程序的錯誤,要麼是呼叫它的 MilCore 程式碼中的錯誤。很可能是在顯卡驅動程序中,但有可能 MilCore 傳遞了大多數顯卡都能正確處理的無效值,但不是這個。我上面描述的調試技術會告訴你情況是這樣的:例如,如果 MilCore 告訴顯卡分配一個 1000000x1000000 像素區域並且顯卡給出了正確的解析度資訊,那麼錯誤就在 MilCore 中。但如果 MilCore 的要求是合理的,那麼這個 bug 就在顯卡驅動程序中。