Gestion des exceptions d’hébergement CLR dans un thread non créé par CLR

Le problème:

Une exception non gérée dans un thread entrant dans le CLR à partir de code non managé ne déclenche pas le traitement CLR des exceptions non gérées “normales”.

Dans le code ci-dessous, appelez CSSimpleObject.GetssortingngLength() partir de C ++ avec

  • “1” lève une exception dans le thread appelant (thread non créé par CLR),
  • “2” lève une exception dans un nouveau Thread () (thread créé par CLR).

Dans le cas “1”

  • CurrentDomain_UnhandledException () n’est jamais appelée.
  • Le domaine d’application et le processus restront chargés et en cours d’exécution, vous obtiendrez seulement un ECHEC.

Dans le cas “2” (comportement attendu)

  • CurrentDomain_UnhandledException () est appelée.
  • Le processus est tué.

La question:

Que faut-il faire pour obtenir le comportement “normal”?

Exemple de code:

Le code ci-dessous est basé sur l’exemple de code ” CppHostCLR ” de Visual Studio 2010 extrait de ” tous les exemples de fusion et d’interopérabilité “.

RuntimeHost (C ++):

 PCWSTR pszStaticMethodName = L"GetSsortingngLength"; PCWSTR pszSsortingngArg = L"1"; //PCWSTR pszSsortingngArg = L"2"; hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath, pszClassName, pszStaticMethodName, pszSsortingngArg, &dwLengthRet); if (FAILED(hr)) { wprintf(L"Failed to call GetSsortingngLength w/hr 0x%08lx\n", hr); goto Cleanup; } 

Code géré (C #):

 public class CSSimpleObject { public CSSimpleObject() { } //------8<---------- public static int GetStringLength(string str) { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); switch (str) { case "1": throw new Exception("exception in non-CLR-created thread"); case "2": Thread thread = new Thread(new ThreadStart(WorkThreadFunction)); thread.Start(); break; } return str.Length; } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("CurrentDomain_UnhandledException:" + e.ToString()); Console.ReadKey(); } public static void WorkThreadFunction() { throw new Exception(""exception in CLR-created thread""); } 

Recherche jusqu’ici:

MSDN implique initialement que les exceptions non gérées dans un thread non créé par CLR doivent se comporter plus ou moins “naturellement” – voir ” Exceptions dans les threads gérés “.

Le common language runtime permet à la plupart des exceptions non gérées dans les threads de se dérouler naturellement. Dans la plupart des cas, cela signifie que l’exception non gérée entraîne la fermeture de l’application. ”

“Most”, ce qui signifie que, dans les threads créés par le CLR, les exceptions internes, l’abandon de thread et les applications déchargées du domaine d’application sont gérées différemment. Dans les threads non-CLR

“ils procèdent normalement, entraînant la résiliation de l’application.”

Des recherches ultérieures m’ont conduit à « Traitement des exceptions non gérées dans le CLR » où j’ai découvert ce qui suit:

“si l’exception n’a pas été gérée … dans la méthode gérée, l’exception quitterait le CLR mais continuerait à propager la stack en tant qu’exception SEH native (les exceptions gérées sont représentées sous la forme d’exceptions SEH natives) … L’exception non gérée du système d’exploitation Le mécanisme de filtrage (UEF) peut ne pas toujours déclencher le traitement des exceptions non gérées par le CLR. En général, cela fonctionnera comme prévu et le traitement des exceptions non gérées par le CLR sera déclenché. Toutefois, dans certains cas, cela peut ne pas se produire. ”

Quel est le problème avec le code ci-dessus ou comment peut-il être modifié de sorte que le traitement des exceptions non gérées du CLR soit déclenché?

Mise à jour (2011-05-31):

Je viens de trouver un ancien rapport de bogue, “UnhandledExceptionEventHandler n’est pas appelé lorsque le code géré est appelé depuis unmanaged et lève une exception – http://tinyurl.com/44j6gvu “, dans laquelle Microsoft confirme qu’il s’agit d’un comportement “buggy”:

Merci d’avoir pris le temps de signaler ce problème. Le comportement est en effet un bogue provoqué par le moteur d’exécution CLR et le tube cathodique en concurrence pour le filtre UnhandledExceptionFilter . L’architecture du CLR a été révisée dans la version 4.0 prenant en charge ce scénario.

Mise à jour (2011-06-06):

Pourquoi est-il important de bien faire les choses?

  • si vous créez un environnement d’hébergement, vos développeurs s’attendent à un comportement cohérent lors de la gestion des exceptions
  • sauf s’il existe un moyen de déclencher la “gestion normale des exceptions CLR” dans un thread natif, cela signifie que vous devez toujours transférer l’exécution vers un thread géré (mise en queue dans un pool de threads, par exemple).
  • il y a toujours ce petit morceau de code qui transfère l’exécution du natif vers le thread géré … qui doit intercepter toutes les exceptions et gérer cette situation différemment

Remarque: modifier le comportement du CLR via SetActionOnFailure() aggrave les choses, dans la mesure où il finit par masquer l’exception d’origine (c.-à-d. Au lieu d’un manque de mémoire, vous finissez par voir threadAborts – sans la moindre idée de l’origine de l’erreur d’origine)!

La mise à jour implique que vous ayez peut-être une solution ici. Cependant, sa solution ne fonctionnera pas dans tous les cas, alors voici quelques informations supplémentaires.

Si vous préférez le comportement d’exception CLR non gérée, définissez-le comme programme le plus externe et appelez le code natif uniquement pour exécuter des fonctions spécifiques. Cela garantira que le CLR obtienne le contrôle du filtre d’exception non géré.

Si vous souhaitez conserver votre structure actuelle et que le code C ++ dont vous disposez est petit, vous pouvez ne plus utiliser le CRT (ce qui vous priverait de tout un tas de choses utiles, notamment des constructeurs statiques et la gestion des exceptions C ++). Cela garantira que le CLR obtienne le filtre d’exception non géré.

Et, bien sûr, vous pouvez simplement appeler SetUnhandledExceptionFilter et obtenir le comportement souhaité.

Cependant, je pense que la meilleure recommandation dans cette situation est de mettre une fonction réelle avec un bloc catch sur la stack d’appels de tout thread où vous voulez faire quelque chose lorsqu’une exception se produit et ne pas compter sur le mécanisme UEF – car dans le contexte d’un système composant, il est toujours fragile, car plusieurs utilisateurs se font concurrence.

Martyn