окно win32 в WPF

Недавно наше приложение встретилось со странной проблемой.

Приложение имеет win32 окно в окне WPF, когда изменяют размер окна WPF, проблема произошла.

StackTrace:

Exception object: 0000000002ab2c78
Exception type: System.OutOfMemoryException
InnerException: 
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)+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: 
HResult: 8007000e

Кроме того, я нашел некоторые ссылки по теме:

relatedA

relatedB

  1. Там какой-либо путь состоит в том, чтобы избежать или решить эту проблему?

  2. Как узнать настоящую проблему?

  3. От стека вызовов мы можем решить, что проблема была, прибыл из Платформы.NET?

Спасибо за Ваш ответ или комментарии!

16
задан whunmr 17 January 2010 в 11:33
поделиться

3 ответа

Ваша проблема не вызвана управляемой утечкой памяти. Очевидно, что где-то в неуправляемом коде вы щекочите ошибку.

Метод SyncFlush() вызывается после нескольких вызовов MILCore и, похоже, вызывает немедленную обработку отправленных изменений, а не оставление их в очереди на последующую обработку. Так как вызов обрабатывает все ранее отправленные изменения, ничто в вашем визуальном дереве не может быть исключено из стека отправленных вами вызовов.

Стек вызовов, включающий неуправляемые вызовы, может дать более полезную информацию. Запустите приложение под VS.NET с отладкой, или с помощью windbg, или другого отладчика. Установите отладчик на break на исключении и получите стек вызовов в относительной точке останова.

Стек вызовов, конечно же, спустится в MILCore, и оттуда он может попасть в слой DirectX и в драйвер DirectX. Подсказка о том, какая часть вашего кода вызвала проблему, может быть найдена где-то в этом родном стеке вызовов.

Скорее всего, MILCore передает в DirectX огромное значение некоторого параметра, основываясь на том, что вы ему говорите. Проверьте ваше приложение на предмет чего-либо, что может привести к ошибке, которая заставит DirectX выделить много памяти. Примерами того, на что стоит обратить внимание, могут быть:

  • BitmapSources, которые настроены на загрузку в очень высоком разрешении.
  • Large WritableBitmaps
  • Extreme large (or negative) transform or size values

Another way to attack this problem is to progressively simplify your application until the problem disappears, then look very closedly at what you removed last. Когда это удобно, это может быть полезно сделать как бинарный поиск: Изначально вырезать половину визуальной сложности. Если это работает, отложите назад половину того, что было удалено, в противном случае удалите другую половину. Повторяйте до конца.

Также обратите внимание, что обычно нет необходимости удалять компоненты пользовательского интерфейса, чтобы MILCore не увидел их тогда. Любой Visual with Visibility.Hidden может быть полностью пропущен.

Нет никакого обобщенного способа избежать этой проблемы, но техника поиска поможет вам точно определить, что именно нужно изменить, чтобы исправить это в конкретном случае.

Из стека вызовов можно с уверенностью сказать, что вы нашли ошибку либо в NET Framework, либо в DirectX драйверах для определенной видеокарты.

Что касается второй трассы стека, которую вы разместили

Джон Кноллер прав, что переход от RtlFreeHeap к ConvertToUnicode - это глупость, но делает из нее неверный вывод. Мы видим, что ваш отладчик потерялся при отслеживании стека. Он правильно начал с исключения, но потерялся под фреймом Assembly.ExecuteMainMethod, так как эта часть стека была перезаписана при обработке исключения и вызове отладчика.

К сожалению, любой анализ этой стековой трассы бесполезен для ваших целей, так как она была перехвачена слишком поздно. Мы видим, что во время обработки WM_LBUTTONDOWN возникает исключение, которое преобразуется в WM_SYSCOMMAND, который затем ловит исключение. Другими словами, вы щелкнули по чему-то, что вызвало системную команду (например, изменение размера), которая вызвала исключение. На момент, когда эта трасса стека была перехвачена, исключение уже обрабатывалось. Причина, по которой вы видите вызовы User32 и UxTheme, заключается в том, что они участвуют в обработке нажатия кнопки. Они не имеют никакого отношения к реальной проблеме.

Вы на правильном пути, но вам нужно перехватить трассу стека в момент неудачного выделения (или вы можете использовать другой подход, который я предложил выше).

Вы будете знать, что у вас есть правильная трасса стека, когда все управляемые кадры в вашей первой трассе стека появляются в ней, а верхняя часть стека - это неудачное выделение памяти. Обратите внимание, что нас действительно интересуют только неуправляемые кадры, которые появляются выше вызова DUCE+Channel.SyncFlush -- все, что ниже будет NET Framework и код вашего приложения.

Как получить собственную трассу стека в нужное время

Вы хотите получить трассу стека в момент первого сбоя выделения памяти в вызове DUCE+Channel.SyncFlush, который показан на рисунке. Это может быть сложно. Я использую три подхода: (обратите внимание, что в каждом случае вы начинаете с точки останова внутри вызова SyncFlush - подробнее см. ниже)

  1. Установите отладчик на break on all exceptions (managed and unmanaged), затем продолжайте нажимать go (F5, или "g") до тех пор, пока он не сломается на интересующем вас исключении о выделении памяти. Это первое, что можно попробовать, потому что это быстро, но часто не получается при работе с родным кодом, потому что родной код часто возвращает код ошибки вызывающему родному коду вместо того, чтобы бросить исключение.

  2. Установите отладчик на break on all exceptions, а также установите точки останова на обычных процедурах выделения памяти, затем нажмите F5 (go) несколько раз до тех пор, пока не произойдет исключение, считая, сколько 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 и измените размер окна. При первом вызове SyncFlush просто нажмите F5, чтобы убедиться, что при первом вызове SyncFlush исключение не срабатывает (если нет, посчитайте, сколько раз нужно нажать F5 перед тем, как произойдет исключение). Затем отключите точку останова и перезапустите программу. Повторите процедуру, но на этот раз после того, как вы нажмете на вызов SyncFlush в нужное время, установите точки останова или сделаете один шаг, как описано выше.

Рекомендации

Методы отладки, описанные мною выше, являются трудоемкими: Планируйте потратить как минимум несколько часов. В связи с этим я, как правило, многократно пытаюсь упростить свое приложение, чтобы узнать, что именно щекочет баг, прежде чем перепрыгнуть в отладчик для чего-то подобного. Это имеет два преимущества: Это даст вам хороший повод, чтобы послать поставщика видеокарты, и это сделает вашу отладку быстрее, потому что будет меньше отображаться и, следовательно, меньше кода на один шаг, меньше распределений и т.д.

Так как проблема происходит только с конкретной видеокартой, нет никаких сомнений, что проблема либо в ошибке в драйвере видеокарты или в коде MilCore, который вызывает ее. Скорее всего, это ошибка в драйвере видеокарты, но возможно, что MilCore передает недействительные значения, которые корректно обрабатываются большинством видеокарт, но не этой. Методы отладки, которые я опишу выше, скажут вам, что это так: Например, если MilCore говорит графической карте выделить область 1000000x1000000 пикселей, а графическая карта дает правильную информацию о разрешении, то ошибка находится в MilCore. Но если запросы MilCore обоснованы, то ошибка находится в драйвере видеокарты.

.
23
ответ дан 30 November 2019 в 21:28
поделиться

Вот полезная статья об утечках памяти в WPF. Также вы можете рассмотреть что-то вроде ANTS Performance и/или Memory Profiler от RedGate, чтобы помочь в диагностике подобных проблем.

HTH

.
2
ответ дан 30 November 2019 в 21:28
поделиться

Я не уверен, что часть стека (или, по крайней мере, 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 

A Crash in RtlFreeHeap указывает на повреждение кучи, что предполагает, что проблема в неуправляемом коде, но память для управляемых объектов в конечном счёте должна быть выделена из неуправляемой памяти, так что это может быть и так.

Предлагаю искать места, где ваше неуправляемое окно может испортить кучу; несколько свободных объектов одного и того же выделения, или перезаписывать границы выделения.

1
ответ дан 30 November 2019 в 21:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: