окно win32 в WPF - PullRequest
       11

окно win32 в WPF

16 голосов
/ 22 декабря 2009

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

Приложение имеет окно 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?

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

Ответы [ 4 ]

23 голосов
/ 27 декабря 2009

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

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

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

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

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

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

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

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

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

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

Относительно второй трассировки стека, которую вы выложили

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

  3. Установите точку останова на первом собственном кадре, который вызывается SyncFlush (это wpfgfx_v0300! MilComposition_SyncFlush), чтобы пропустить переход от управляемого к собственному, а затем F5 для запуска к нему. F10 (шаг за шагом) через функцию it, пока 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 голосов
/ 22 декабря 2009

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

НТН

1 голос
/ 29 декабря 2009

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

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

...