Окно win32 в WPF

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

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

StackTrace:

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

  • Есть ли способ избежать или решить эту проблему?

  • Как узнать реальную проблему?

  • Из стека вызовов Можем ли мы определить, что проблема возникла из .NET Framework?

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

Ответы

Ответ 1

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

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

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

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

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

  • Bitmap Источники, которые настроены на загрузку с очень высоким разрешением.
  • Большие WritableBitmaps
  • Чрезвычайно большие (или отрицательные) значения преобразования или размера

Другим способом атаки этой проблемы является постепенное упрощение вашего приложения до тех пор, пока проблема не исчезнет, ​​а затем посмотрите очень внимательно на то, что вы удалили последним. Когда это удобно, это может быть полезно сделать в виде бинарного поиска: сначала вырезать половину визуальной сложности. Если он работает, верните половину того, что было удалено, иначе удалите еще одну половину. Повторяйте до конца.

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

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

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

Что касается второй трассировки стека, опубликованной

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

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

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

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

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

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

  • Установите отладчик для разрыва всех исключений (управляемых и неуправляемых), затем продолжайте движение (F5 или "g" ) до тех пор, пока он не сломается на выделение выделения памяти, которое вас интересует. Это первое, что нужно сделать чтобы попытаться, потому что это быстро, но часто не работает при работе с собственным кодом, потому что собственный код часто возвращает код ошибки в вызывающий собственный код вместо того, чтобы бросать исключение.

  • Установите отладчик для разрыва всех исключений, а также установите точки останова в общих подпрограммах распределения памяти, затем нажмите F5 (go) несколько раз, пока не произойдет исключение, подсчитав, сколько F5s вы нажмете. В следующий раз, когда вы запустите, используйте еще одно 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 в нужное время, установите точки останова или выполните однократное нажатие, как описано выше.

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

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

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

Ответ 2

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

НТН

Ответ 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 

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

Я предлагаю вам искать места, где ваше неуправляемое окно может испортить кучу; множественные, не содержащие одного и того же распределения, или переписывание границ выделения.

Ответ 4

В случае, если это помогает кому-то с проблемой SyncFlush, мы просто решили нашу проблему благодаря превосходной поддержке Microsoft (через мою подписку MSDN). Оказалось, что мы создаем больше мультимедийных таймеров, чем освобождаемся, используя вызовы timeBeginPeriod и timeEndPeriod. Эти таймеры являются ограниченным ресурсом, и как только они были использованы, поток рендеринга WPF был голоден для таймеров и прекратил работать.