web-dev-qa-db-fra.com

VS2010 n'affiche pas le message d'exception non géré dans une application WinForms sur une version 64 bits de Windows

Lorsque je crée un nouveau projet, j'obtiens un comportement étrange pour les exceptions non gérées. Voici comment je peux reproduire le problème:

1) créer une nouvelle application Windows Forms (C #, .NET Framework 4, VS2010)

2) ajoutez le code suivant au gestionnaire Form1_Load:

int vara = 5, varb = 0;
int varc = vara / varb;
int vard = 7;

Je m'attendrais à ce que VS rompt et affiche un message d'exception non géré à la deuxième ligne. Cependant, la troisième ligne est ignorée sans aucun message et l'application continue de s'exécuter.

Je n'ai pas ce problème avec mes projets C # existants. Je suppose donc que mes nouveaux projets sont créés avec des paramètres par défaut étranges.

Est-ce que quelqu'un a une idée de ce qui ne va pas dans mon projet ???

J'ai essayé de cocher les cases dans Debug-> Exceptions. Mais alors les exécutions se cassent même si je gère l'exception dans un bloc try-catch; ce qui n'est pas non plus ce que je veux. Si je me souviens bien, il y avait une colonne appelée "exceptions non gérées" ou quelque chose du genre dans cette boîte de dialogue, qui ferait exactement ce que je voulais. Mais dans mes projets, il n'y a qu'une seule colonne ("Thrown").

75
Robert Hegner

Il s’agit d’un vilain problème provoqué par la couche d’émulation wow64 qui permet à du code 32 bits de s’exécuter sur la version 64 bits de Windows 7. Il avale des exceptions dans le code exécuté en réponse à une notification générée par le gestionnaire de fenêtres 64 bits. , comme l'événement Load. Empêcher le débogueur de le voir et d'intervenir. Ce problème est difficile à résoudre, les groupes Windows et DevDiv de Microsoft pointent du doigt. DevDiv ne peut rien y faire, Windows pense que c'est le comportement correct et documenté, aussi mystérieux que cela puisse paraître.

C'est certainement documenté mais à peu près personne ne comprend les conséquences ou pense que c'est un comportement raisonnable. Surtout pas lorsque la procédure de fenêtre est masquée, comme dans tout projet utilisant des classes wrapper pour masquer la tuyauterie de la fenêtre. Comme n'importe quelle application Winforms, WPF ou MFC. Le problème sous-jacent est que Microsoft n'a pas pu comprendre comment redistribuer les exceptions du code 32 bits au code 64 bits qui a déclenché la notification en code 32 bits qui tente de gérer ou de déboguer l'exception.

C'est seulement un problème avec un débogueur attaché, votre code bombardera comme d'habitude sans un. 

Projet> Propriétés> onglet Générer> Plate-forme cible = AnyCPU et décochez l'option Préférer 32 bits. Votre application s'exécutera désormais en tant que processus 64 bits, éliminant ainsi le mode de défaillance wow64. Certaines conséquences, cela désactive Edit + Continue pour les versions de VS antérieures à VS2013 et risque de ne pas toujours être possible lorsque vous dépendez du code 32 bits.

Autres solutions possibles:

  • Déboguer> Exceptions> cochez la case Lancé pour les exceptions CLR pour forcer le débogueur à s'arrêter à la ligne de code qui lève l'exception.
  • Ecrivez try/catch dans le gestionnaire d'événements Load et failfast dans le bloc catch.
  • Utilisez Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException) dans la méthode Main() pour que l'interception des exceptions dans la boucle de message ne soit pas désactivée en mode débogage. Cela rend toutefois difficile à déboguer toutes les exceptions non gérées, l'événement ThreadException est plutôt inutile.
  • Déterminez si votre code appartient vraiment au gestionnaire d'événements Load. Il est très rare d’en avoir besoin, c’est cependant très populaire dans VB.NET et dans une chanson swan, car c’est l’événement par défaut et un double-clic ajoute trivialement le gestionnaire d’événements. Vous ne devez toujours vraiment avoir besoin de Load lorsque la taille réelle de la fenêtre vous intéresse une fois que les préférences de l'utilisateur et la mise à l'échelle automatique sont appliquées. Tout le reste appartient au constructeur.
  • Mise à jour vers Windows 8 ou version ultérieure, le problème wow64 est résolu.
119
Hans Passant

D'après mon expérience, je ne vois ce problème que lorsque je cours avec un débogueur attaché. L'application se comporte de la même manière lorsqu'elle est exécutée de manière autonome: l'exception n'est pas avalée.

Avec l'introduction de KB976038 , vous pouvez faire en sorte que cela fonctionne à nouveau. Je n'ai jamais installé le correctif, donc je suppose qu'il est venu avec Win7 SP1.

Cela a été mentionné dans ce post:

Voici un code qui activera le correctif:

public static class Kernel32
{
    public const uint PROCESS_CALLBACK_FILTER_ENABLED = 0x1;

    [DllImport("Kernel32.dll")]
    public static extern bool SetProcessUserModeExceptionPolicy(UInt32 dwFlags);

    [DllImport("Kernel32.dll")]
    public static extern bool GetProcessUserModeExceptionPolicy(out UInt32 lpFlags);


    public static void DisableUMCallbackFilter() {
        uint flags;
        GetProcessUserModeExceptionPolicy(out flags);

        flags &= ~PROCESS_CALLBACK_FILTER_ENABLED;
        SetProcessUserModeExceptionPolicy(flags);
    }
}

Appelez-le au début de votre application:

    [STAThread]
    static void Main()
    {
        Kernel32.DisableUMCallbackFilter();

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

J'ai confirmé (à l'aide de l'exemple simple ci-dessous) que cela fonctionne, comme vous le souhaitiez.

protected override void OnLoad(EventArgs e) {
    throw new Exception("BOOM");   // This will now get caught.
}

Donc, ce que je ne comprends pas, c’est la raison pour laquelle il était auparavant impossible au débogueur de gérer les trames de pile en mode noyau croisées, mais avec ce correctif, ils ont tout compris.

9
Jonathon Reinhart

Comme Hans le mentionne, compilez l'application et exécutez le fichier exe sans un débogueur attaché.

Pour moi, le problème était de changer un nom de propriété de classe auquel un contrôle BindingSource était lié. En cours d'exécution sans IDE, j'ai pu voir l'erreur:

Impossible de se connecter à la propriété ou à la colonne SendWithoutProofReading sur le fichier La source de données. Nom du paramètre: dataMember

Correction du contrôle BindingSource pour lier au nom de propriété mis à jour a résolu le problème: enter image description here

3
Jeremy Thompson

J'utilise WPF et ai rencontré le même problème. J'avais déjà essayé les suggestions de Hans 1-3, mais je ne les aimais pas parce que studio ne s'arrêtait pas là où était l'erreur (je ne pouvais donc pas voir mes variables et voir quel était le problème). 

J'ai donc essayé la 4ème suggestion de Hans. J'ai été surpris de voir combien de mon code pourrait être déplacé vers le constructeur MainWindow sans aucun problème. Je ne sais pas pourquoi j'ai pris l'habitude de mettre autant de logique dans l'événement Load, mais apparemment, une grande partie de celle-ci peut être réalisée dans le ctor.

Cependant, cela a eu le même problème que 1-3. Les erreurs qui se produisent pendant le ctor pour WPF sont encapsulées dans une exception générique Xaml. (Une exception interne a la vraie erreur, mais encore une fois, je voulais que studio se casse juste au point de trouble réel).

Ce qui a fini par travailler pour moi a été de créer un fil, de dormir 50 ms, de retourner au fil principal et de faire ce dont j'ai besoin ...

    void Window_Loaded(object sender, RoutedEventArgs e)
    {
        new Thread(() =>
        {
            Thread.Sleep(50);
            CrossThread(() => { OnWindowLoaded(); });
        }).Start();
    }
    void CrossThread(Action a)
    {
        this.Dispatcher.BeginInvoke(a);
    }
    void OnWindowLoaded()
    {
        ...do my thing...

De cette façon, le studio se briserait là où une exception non interceptée se produit.

1
Gabe Halsmer

Une solution simple pourrait être si vous pouvez déplacer votre code init vers un autre événement, tel que Form_Shown, appelé plus tard que Form_Load, et utiliser un indicateur pour exécuter le code de démarrage au premier formulaire affiché:

bool firstLoad = true; //flag to detect first form_shown

private void Form1_Load(object sender, EventArgs e)
{
    //firstLoad = true;
    //dowork(); //not execute initialization code here (postpone it to form_shown)
}

private void Form1_Shown(object sender, EventArgs e)
{
    if (firstLoad) //simulate Form-Load
    {
        firstLoad = false;

        dowork();
    }
}

void dowork()
{
    var f = File.OpenRead(@"D:\NoSuchFile756.123"); //this cause an exception!

}
0
S.Serpooshan