2009-12-22 8 views
14

Récemment, notre application a rencontré un problème étrange.fenêtre Win32 dans WPF

L'application a une fenêtre win32 dans la fenêtre WPF, lors du redimensionnement de la fenêtre WPF, le problème est survenu.

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 

Aussi, j'ai trouvé quelques liens connexes:

relatedA

relatedB

  1. Est-il possible d'éviter ou de traiter ce pro blem?

  2. Comment trouver le vrai problème? À partir de la pile d'appel, pouvons-nous déterminer que le problème provenait de .NET Framework?

Nous vous remercions de votre réponse ou de vos commentaires!

+0

Merci pour votre aide et votre analyse. Merci Ray Burns, John Knoeller, Chris Nicol et tous les visiteurs. – whunmr

+0

whunmr, avez-vous déjà été capable de trouver la cause du problème? Si oui, pourriez-vous partager vos résultats? – Charlie

+0

Salut Charlie, je ne pense pas avoir trouvé la véritable cause de ce problème. et il y a longtemps. – whunmr

Répondre

21

Votre problème n'est pas dû à une fuite de mémoire gérée. Clairement, vous chatouillez un bug quelque part dans le code non géré.

La méthode SyncFlush() est appelée après plusieurs appels MILCore, et elle semble provoquer le traitement immédiat des modifications qui ont été envoyées au lieu d'être laissées dans la file d'attente pour un traitement ultérieur. Comme l'appel traite tout ce qui a été envoyé précédemment, rien dans votre arborescence visuelle ne peut être exclu de la pile d'appels que vous avez envoyée.

Une pile d'appels comprenant des appels non gérés peut générer des informations plus utiles. Exécutez l'application sous VS.NET avec un débogage natif ou avec windbg ou un autre débogueur de code natif. Définissez le débogueur pour interrompre l'exception et obtenez la pile d'appels au point d'arrêt relatif. La pile d'appels descendra bien sûr dans MILCore, et à partir de là, elle pourra aller dans la couche DirectX et le pilote DirectX. Un indice quant à la partie du code qui a causé le problème peut être trouvé quelque part dans cette pile d'appels native.

Les chances sont que MILCore passe une valeur énorme de certains paramètres dans DirectX en fonction de ce que vous lui dites. Vérifiez votre application pour tout ce qui pourrait causer un bogue qui permettrait à DirectX d'allouer beaucoup de mémoire. Exemples de choses à rechercher:

  • BitmapSources définies pour le chargement à très haute résolution.
  • Grand WritableBitmaps
  • Très grande (ou négatif) transformer ou de la taille des valeurs

Une autre façon d'attaquer ce problème est de simplifier progressivement l'application jusqu'à ce que le problème disparaisse, regardez alors très closedly ce que vous avez supprimé la dernière . Lorsque cela est pratique, il peut être utile de faire cela comme une recherche binaire: d'abord couper la moitié de la complexité visuelle. Si cela fonctionne, remettre la moitié de ce qui a été retiré, sinon en retirer une autre moitié. Répétez jusqu'à ce que terminé.

Notez également qu'il n'est généralement pas nécessaire de supprimer les composants de l'interface utilisateur pour empêcher MILCore de les voir. Tout Visual avec Visibility.Hidden peut être entièrement ignoré.

Il n'existe pas de manière généralisée d'éviter ce problème, mais la technique de recherche vous aidera à identifier ce qui doit être modifié spécifiquement pour le corriger dans le cas d'espèce.

La pile d'appel indique que vous avez trouvé un bogue dans NET Framework ou dans les pilotes DirectX pour une carte vidéo particulière.

En ce qui concerne la deuxième trace de la pile que vous avez posté

John Knoeller est exact que la transition de RtlFreeHeap à ConvertToUnicode est un non-sens, mais tire la mauvaise conclusion. Ce que nous voyons, c'est que votre débogueur s'est perdu en retraçant la pile. Il a démarré correctement à partir de l'exception, mais s'est perdu en dessous du cadre Assembly.ExecuteMainMethod car cette partie de la pile avait été remplacée car l'exception était gérée et le débogueur était appelé. Malheureusement, toute analyse de cette trace de pile est inutile pour vos besoins car elle a été capturée trop tard. Ce que nous voyons est une exception se produisant pendant le traitement d'un WM_LBUTTONDOWN qui est converti en WM_SYSCOMMAND, qui attrape alors une exception. En d'autres termes, vous avez cliqué sur quelque chose qui a causé une commande système (comme un redimensionnement), ce qui a provoqué une exception. Au moment où cette trace de pile a été capturée, l'exception était déjà gérée. La raison pour laquelle vous voyez des appels User32 et UxTheme est que ceux-ci sont impliqués dans le traitement du clic sur le bouton. Ils n'ont rien à voir avec le vrai problème.

Vous êtes sur la bonne voie, mais vous devrez capturer une trace de pile au moment où l'allocation échoue (ou vous pouvez utiliser l'une des autres approches que j'ai suggérées ci-dessus).

Vous saurez que vous disposez de la trace de pile correcte lorsque toutes les images managées dans votre première trace de pile s'affichent et que le haut de la pile est une allocation de mémoire défaillante. Notez que nous ne sommes intéressés que par les cadres non gérés qui apparaissent au-dessus de l'appel DUCE+Channel.SyncFlush - tout ce qui sera au-dessous sera NET Framework et votre code d'application.

Comment obtenir une trace de la pile native au bon moment

Vous voulez obtenir une trace de pile au moment où le premier échec d'allocation de mémoire dans l'appel DUCE+Channel.SyncFlush indiqué. Cela peut être difficile. Il y a trois approches que j'utilise: (notez que dans chaque cas, vous commencez avec un point d'arrêt dans l'appel de SyncFlush - voir note ci-dessous pour plus de détails)

  1. Réglez le débogueur pour briser sur toutes les exceptions (gérées et non gérées) , puis continuez à aller (F5, ou "g") jusqu'à ce qu'il casse l'exception d'allocation de mémoire qui vous intéresse.C'est la première chose à essayer car elle est rapide, mais elle échoue souvent lorsque l'on travaille avec du code natif car le code natif renvoie souvent un code d'erreur au code natif appelant au lieu de lancer une exception. Définissez le débogueur pour interrompre toutes les exceptions et également définir des points d'arrêt sur les routines d'allocation de mémoire courantes, puis appuyez plusieurs fois sur F5 (go) jusqu'à ce que l'exception se produise, en comptant le nombre de F5 touchés. La prochaine fois que vous exécutez, utilisez un F5 de moins et vous pouvez être sur l'appel d'allocation qui a généré l'exception. Capturez la pile d'appels dans le Bloc-notes, puis F10 (pas à pas) à plusieurs reprises à partir de là pour voir si c'était vraiment l'allocation qui a échoué.

  2. Définissez un point d'arrêt sur la première image native appelée par SyncFlush (c'est wpfgfx_v0300! MilComposition_SyncFlush) pour passer la transition gérée en native, puis F5 pour l'exécuter. F10 (étape par dessus) à travers la fonction jusqu'à ce que EAX contienne un des codes d'erreur E_OUTOFMEMORY (0x8007000E), ERROR_OUTOFMEMORY (0x0000000E), ou ERROR_NOT_ENOUGH_MEMORY (0x0000008). Notez l'instruction "Call" la plus récente. La prochaine fois que vous exécuterez le programme, courez vers là et entrez dedans. Répétez cette opération jusqu'à ce que vous soyez à l'appel d'allocation de mémoire qui a causé le problème et vider la trace de la pile. Notez que dans de nombreux cas, vous vous retrouvez à faire défiler une structure de données volumineuse. Il vous faut donc une certaine intelligence pour définir un point d'arrêt approprié afin de passer la boucle afin de pouvoir vous rendre rapidement. Cette technique est très fiable mais très laborieuse.

Remarque: Dans chaque cas, vous ne voulez pas définir des points d'arrêt ou commencer mode pas à pas jusqu'à ce que votre application est à l'intérieur du défaut DUCE+Channel.SyncFlush appel. Pour cela, démarrez l'application avec tous les points d'arrêt désactivés. Lorsqu'il est en cours d'exécution, activez un point d'arrêt sur System.Windows.Media.Composition.DUCE+Channel.SyncFlush et redimensionnez la fenêtre. La première fois, appuyez sur F5 pour vous assurer que l'exception échoue lors du premier appel SyncFlush (si ce n'est pas le cas, comptez combien de fois vous devez appuyer sur F5 avant que l'exception ne se produise). Ensuite, désactivez le point d'arrêt et redémarrez le programme. Répétez la procédure mais cette fois après avoir appuyé sur l'appel SyncFlush au bon moment, définissez vos points d'arrêt ou effectuez un seul pas comme décrit ci-dessus.

Recommandations

Les techniques de débogage ci-dessus sont que je décris en main-d'œuvre: Prévoyez de passer plusieurs heures au moins. Pour cette raison, j'essaie généralement de simplifier à plusieurs reprises mon application pour savoir exactement ce qui chatouille le bug avant de sauter dans le débogueur pour quelque chose comme ça. Cela a deux avantages: Il vous donnera une bonne repro pour envoyer le fournisseur de carte graphique, et il rendra votre débogage plus rapide car il y aura moins d'affichage et donc moins de code pour passer à travers, moins d'allocations, etc.

Parce que le problème se produit uniquement avec une carte graphique spécifique, il ne fait aucun doute que le problème est soit un bogue dans le pilote de la carte graphique ou dans le code MilCore qui l'appelle. Il est fort probable que ce soit dans le pilote de la carte graphique, mais il est possible que MilCore transmette des valeurs incorrectes qui sont gérées correctement par la plupart des cartes graphiques, mais pas par celle-ci. Les techniques de débogage que je décris ci-dessus vous diront que c'est le cas: Par exemple, si MilCore dit à la carte graphique d'allouer une zone 1000000x1000000 pixels et que la carte graphique donne des informations de résolution correctes, le bug est dans le MilCore. Mais si les demandes de MilCore sont raisonnables, alors le bug est dans le pilote de la carte graphique.

+1

MERCI! Hi Ray, J'ai jeté la pile gérée et non gérée: Puis-je dire que le problème vient de "uxtheme! _ThemeDefWindowProc"? – whunmr

+1

Non, ce n'est pas lié à UxTheme. Le code UxTheme gère simplement le clic de votre bouton. La trace de la pile est le bon type de trace, mais elle n'est pas prise au bon moment. J'ai ajouté plus d'explications à ma réponse, et quelques conseils pour obtenir une bonne trace de pile. J'espère qu'ils aident. –

2

Voici un article utile sur les fuites de mémoire dans WPF. Vous pouvez également envisager quelque chose comme ANTS Performance et/ou Memory Profiler de RedGate pour aider à diagnostiquer des problèmes comme celui-ci.

HTH

+0

Ouais ... Le profileur de mémoire Ants ressemble à la meilleure option pour moi. – Anvaka

1

Je ne suis pas sûr que la partie de la pile (ou au moins la substance UXTheme) est digne de confiance. Le bas de la pile semble normal. Et nous voyons ce qui semble être un gestionnaire d'exceptions essayant de faire le nettoyage. Ensuite, beaucoup d'appels imbriqués à différentes couches de code de gestion de tas.

Mais cette partie où la pile passe de RtlFreeHeap à ConvertToUnicode n'a aucun sens.Je soupçonne que tout ce qui précède est dû à l'utilisation antérieure de la pile.

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 

un accident points RtlFreeHeap à la corruption du tas, ce qui suggère que le problème est dans le code non géré, mais la mémoire des objets manged doit finalement être allouée de la mémoire non géré, il pourrait être non plus.

Je vous suggère de rechercher les endroits où votre fenêtre non gérée peut corrompre le tas; plusieurs gratuits de la même allocation, ou en écrasant les limites d'une allocation.

+0

Merci pour votre analyse. Maintenant, je peux conclure que UXTheme fonctionne bien. – whunmr

+1

@John Knoeller: Oui, la transition que vous identifiez fait partie d'un remplacement de pile. Le reste de votre analyse est plausible ** sauf ** pour le fait que la trace d'origine montre que l'exception OutOfMemoryException se produit dans SyncFlush et en réponse au clic du bouton. Puisque l'appel RtlpFreeHeap est complètement en dehors de tout le code managé, y compris 'Application.Run', cette pile d'appel ne montre clairement pas la même exception que la précédente. Plus précisément, il n'indique pas l'erreur qui provoque le rejet de l'exception de mémoire insuffisante lors du redimensionnement. –

+0

Notez que l'écrasement de la pile en question est dû à l'arrêt du thread. Il est concevable que la sortie finale comprenne une erreur GP de RtlpFreeHeap, mais il n'y a rien de visible dans la trace de la pile pour indiquer que c'est effectivement le cas. Il est tout aussi probable qu'il n'y ait pas eu de corruption du tout et nous voyons une sortie d'exception normale due à une exception OutOfMemoryException non gérée. Néanmoins, vérifier votre code non géré pour les choses qui peuvent corrompre le tas est toujours une bonne idée dans ces cas. –

0

Dans le cas où cela aide quelqu'un avec un problème SyncFlush, nous venons de résoudre notre problème grâce à l'excellent support de Microsoft (via mon abonnement MSDN). Il s'est avéré que nous créions plus de temporisateurs multimédias que nous ne libérions en utilisant les appels timeBeginPeriod et timeEndPeriod. Ces minuteurs sont une ressource limitée, et une fois qu'ils ont été utilisés, le thread de rendu WPF a été affamé pour les minuteurs et a cessé de fonctionner.