2009-12-10 4 views
4

J'écris un wrapper autour d'une API non gérée assez volumineuse. Presque chaque méthode importée renvoie un code d'erreur commun en cas d'échec. Pour l'instant, je fais ceci:Traitement uniforme des codes d'erreur dans une API non gérée

ErrorCode result = Api.Method(); 
if (result != ErrorCode.SUCCESS) { 
    throw Helper.ErrorToException(result); 
} 

Cela fonctionne très bien. Le problème est, j'ai tellement d'appels de méthode non gérés que cela devient extrêmement frustrant et répétitif. Donc, j'ai essayé de passer à ceci:

public static void ApiCall(Func<ErrorCode> apiMethod) { 
    ErrorCode result = apiMethod(); 
    if (result != ErrorCode.SUCCESS) { 
     throw Helper.ErrorToException(result); 
    } 
} 

Ce qui me permet de réduire tous ces appels à une ligne:

Helper.ApiCall(() => Api.Method()); 

Il y a deux problèmes immédiats avec cela, cependant. Tout d'abord, si ma méthode non gérée utilise les paramètres out, je dois d'abord initialiser les variables locales car l'appel de la méthode est en fait dans un délégué. Je voudrais pouvoir déclarer simplement une destination out sans l'initialiser. Deuxièmement, si une exception est levée, je n'ai vraiment aucune idée d'où elle vient. Le débogueur saute dans la méthode ApiCall et la trace de la pile affiche uniquement la méthode qui contient l'appel à ApiCall plutôt que le délégué lui-même. Comme je peux avoir plusieurs appels d'API dans une seule méthode, cela rend le débogage difficile. J'ai alors pensé à utiliser PostSharp pour encapsuler tous les appels non gérés avec la vérification du code d'erreur, mais je ne suis pas sûr de savoir comment cela serait fait avec les méthodes extern. Si cela finit par créer simplement une méthode wrapper pour chacun d'entre eux, alors j'aurais le même problème d'exception qu'avec la méthode ApiCall, non? De plus, comment le débogueur sait-il comment afficher le site de l'exception levée dans mon code s'il n'existe que dans l'assemblage compilé?

Ensuite, j'ai essayé d'implémenter un marshaler personnalisé qui intercepterait la valeur de retour des appels API et vérifierait le code d'erreur là. Malheureusement, vous ne pouvez pas appliquer un marshaler personnalisé pour renvoyer des valeurs. Mais je pense que cela aurait été une solution vraiment propre si cela avait fonctionné.

[return: 
    MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ApiMethod))] 
public static extern ErrorCode Method(); 

Maintenant, je suis complètement à court d'idées. Quelles sont les autres façons de gérer cela? Que se passe-t-il si vous ne vérifiez pas ErrorCode.SUCCESS?

+0

+1 Semble cool. Désolé, je n'ai pas de réponse pour vous, mais bonne chance! –

Répondre

1

Suivez la classe ErrorHandler à partir du SDK Visual Studio 2010. Il existait dans les versions antérieures, mais le nouveau a CallWithCOMConvention(Action), ce qui peut s'avérer utile en fonction de la façon dont votre API interagit avec d'autres codes gérés.

des méthodes disponibles, je vous recommande de mettre en œuvre les suivantes:

  • Succeeded(int)
    (Failed() est juste !Succeeded(), vous pouvez sauter)

  • ThrowOnFailure(int)
    (émet une exception appropriée pour votre code de retour)

  • CallWith_MyErrorCode_Convention(Action) et CallWith_MyErrorCode_Convention(Func<int>)
    (comme CallWithCOMConvention, mais pour vos codes d'erreur)

  • IsCriticalException(Exception)
    (utilisé par CallWith_MyErrorCode_Convention)

+0

J'aime ce modèle et je vais certainement l'utiliser s'il n'y a pas d'autre moyen. J'espérais vraiment qu'il y avait un certain point de connexion qui couvrirait automatiquement tous les cas, comme le marshaler personnalisé pour la valeur de retour. Malheureusement, comme je l'ai dit, cette idée n'est pas possible. –

1

Votre code va-t-il échouer rapidement et déclencher une exception? Pouvez-vous dire quelle API non gérée a échoué si votre code managé se déclenche? Si c'est le cas, envisagez de ne pas rechercher d'erreurs et de laisser le moteur d'exécution se déclencher lorsque votre API non gérée échoue.

Si ce n'est pas le cas, je suggère de mordre la balle et de suivre votre première idée. Je sais que vous l'avez appelé "frustrant et répétitif", mais après un projet avec une solution macro "intelligente" à un problème similaire, vérifier les valeurs de retour dans les appels de méthodes et les exceptions d'emballage est la porte de la folie. trompeuse, vous ne pouvez pas tracer le code, la performance souffre, votre code devient optimisé pour les erreurs et déraille en cas de succès.

Si une valeur de retour particulière est une erreur, alors une exception unique. Si ce n'est pas une erreur, laissez-le aller et lancez si cela devient une erreur. Vous avez dit que vous vouliez réduire le chèque à une ligne? Votre proposition lève également la même exception si une méthode renvoie le même "code d'erreur commun".C'est un autre cauchemar de débogage; pour les API qui renvoient les mêmes codes d'erreur à partir de plusieurs appels, faites ceci:

switch (int returnValue = Api.Method1()) 
{ 
    case ErrorCode.SUCCESS: break; 
    case ErrorCode.TIMEOUT: throw new MyWrapperException("Api.Method1 timed out in situation 1."); 
    case ErrorCode.MOONPHASE: throw new MyWrapperException("Api.Method1 broke because of the moon's phase."); 
    default: throw new MyWrapperException(string.Format("Api.Method1 returned {0}.", returnValue)); 
} 

switch (int returnValue = Api.Method2()) 
{ 
    case ErrorCode.SUCCESS: break; 
    case ErrorCode.TIMEOUT: throw new MyWrapperException("Api.Method2 timed out in situation 2, which is different from situation 1."); 
    case ErrorCode.MONDAY: throw new MyWrapperException("Api.Method2 broke because of Mondays."); 
    default: throw new MyWrapperException(string.Format("Api.Method2 returned {0}.", returnValue)); 
} 

Verbose? Ouaip. Frustrant? Non, ce qui est frustrant, c'est d'essayer de déboguer une application qui lance la même exception à partir de chaque ligne quelle que soit l'erreur.

+0

Mais c'est ce que ma méthode 'Helper.ErrorToException' représente. Il prend le code d'erreur et le convertit en une exception significative. Ce que je cherche à faire, c'est rendre l'ensemble du processus aussi automatique que possible. Il y a environ 40 ou 50 codes d'erreur possibles, donc écrire une déclaration massive de commutateur à chaque fois sera certainement frustrant. De plus, ce sera un cauchemar de maintenance complet si l'API ajoute ou supprime un code d'erreur. –

+1

La conversion des codes d'erreur en exceptions significatives est bonne. La conversion du même code d'erreur renvoyé par plusieurs fonctions dans plusieurs situations à la même exception avec la même trace de pile n'est pas bonne. Ne vous inquiétez pas du plaisir que vous avez à écrire le code et plus encore du plaisir de le déboguer. –

0

Je pense, la façon facile est d'ajouter la couche aditional.

class Api 
{ 
.... 
    private static ErrorCode Method();//changing Method to private 

    public static void NewMethod()//NewMetod is void, because error is converted to exceptions 
    { 
     ErrorCode result = Method(); 
     if (result != ErrorCode.SUCCESS) { 
      throw Helper.ErrorToException(result); 
    } 

    } 
.... 
} 
0

Créer une propriété privée pour contenir la valeur ErrorCode et jeter l'exception du compositeur.

class Api 
{ 
    private static ErrorCode _result; 
    private static ErrorCode Result 
    { 
     get { return _result; } 
     set 
     { 
      _result = value; 
      if (_result != ErrorCode.SUCCESS) 
      { 
       throw Helper.ErrorToException(_result); 
      } 
     } 
    } 

    public static void NewMethod() 
    { 
     Result = Api.Method(); 
     Result = Api.Method2(); 
    } 
} 
0

Votre code existant est vraiment très, très proche. Si vous utilisez un arbre d'expression pour contenir le lambda, au lieu d'un délégué Func, alors votre Helper.ApiCall peut retirer l'identité de la fonction qui a été appelée et l'ajouter à l'exception qu'il lance. Pour plus d'informations sur les arbres d'expression et quelques très bons exemples, Google Marc Gravell.

Questions connexes