2009-12-22 7 views
14

Недавно наше приложение столкнулось с какой-то странной проблемой.win32 window в 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

  1. Есть ли способ избежать или справиться с этой про я проблема?

  2. Как найти реальную проблему?

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

Благодарим за ваш ответ или комментарий!

+0

Спасибо за вашу помощь и анализ. Спасибо Рэй Бернс, Джон Кнуллер, Крис Никол и все посетители. – whunmr

+0

whunmr, вы когда-нибудь могли найти основную причину проблемы? Если да, можете ли вы поделиться своими выводами? – Charlie

+0

Привет, Чарли, я не думаю, что нашел истинную причину этой проблемы. И это уже давно. – whunmr

ответ

21

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

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

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

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

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

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

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

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

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

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

Что касается второго трассировки стеки вы публикуемой

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

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

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

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

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

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

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

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

+1

СПАСИБО! Hi Ray, Я сбросил управляемый и неуправляемый стек: Могу ли я сказать, что проблема cames из «uxtheme! _ThemeDefWindowProc»? – whunmr

+1

Нет, это не связано с UxTheme. Код UxTheme просто обрабатывает нажатие кнопки. Трассировка стека - это правильный вид трассы, но не в правильное время. Я добавил больше объяснений в свой ответ и несколько советов для получения хорошей трассировки стека. Надеюсь, они помогут. –

2

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

HTH

+0

Да ... Профайлер памяти муравьев выглядит как лучший вариант для меня. – Anvaka

1

Я не уверен, что часть стека (или, по крайней мере, материал 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 до кучи коррупции, которая предполагает, что проблема заключается в неуправляемом коде, но память для сведущих объектов должна в конечном счете быть выделена из неуправляемой памяти, так что это может быть.

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

+0

Благодарим вас за анализ. Теперь я могу заключить, что UXTheme отлично работает. – whunmr

+1

@John Knoeller: Да, переход, который вы идентифицируете, является частью перезаписи стека. Остальная часть вашего анализа правдоподобна **, за исключением ** того факта, что исходная трассировка показывает, что OutOfMemoryException происходит внутри SyncFlush и в ответ на нажатие кнопки. Поскольку вызов RtlpFreeHeap полностью выходит за пределы всего управляемого кода, включая 'Application.Run', этот стек вызовов явно не показывает того же исключения, что и предыдущий. В частности, он не показывает ошибку, вызывающую исключение из памяти для изменения размера. –

+0

Обратите внимание, что проблема с пересылкой стека связана с отключением потока. Можно предположить, что окончательный выход включал ошибку GP из RtlpFreeHeap, но в трассировке стека ничего не видно, чтобы указать, что это действительно так. Вполне вероятно, что коррупции вообще не было, и мы видим нормальный выход из-за необработанного исключения OutOfMemoryException. Тем не менее, проверка неуправляемого кода для вещей, которые могут повредить кучу, всегда является хорошей идеей в этих случаях. –

0

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