% typemap et% exception pour les codes d’erreur des fonctions C pour SWIG, Python

J’ai du code C que je veux exposer à Python. Il a une convention d’appel comme celle-ci:

int add(int a, int b, int *err) 

où la valeur de retour serait (a + b) ou autre chose, mais si quelque chose n’allait pas, j’obtiendrais un code d’erreur dans * err. Je veux envelopper cette fonction afin qu’elle se comporte comme ceci, du sharepoint vue de Python:

 def add(a,b): if something_bad: raise RuntimeError("something bad") return a+b 

Cela devrait être facile, non? Mais je ne le trouve pas si.

Voici quelque chose qui fonctionne, mais cherchez le kludge de myerr3 :

 %module myswig %feature("autodoc","1"); %{ int add(int a, int b, int *err){ if(a < 0)*err = 1; if(b < 0)*err = 2; return a+b; } char *err_string(int err){ switch(err){ case 1:return "first argument was less than 0"; case 2:return "second argument was less than 0"; default:return "unknown error"; } } %} %typemap(in,numinputs=0) int *err (int myerr = 0){ $1 = &myerr; }; %exception{ $action if(myerr3 != 0){ PyErr_SetString(PyExc_RuntimeError,err_string(myerr3)); return NULL; } }; int add(int a, int b, int *err); 

Cela se comporte comme il se doit, par exemple avec

 import myswig print "add(1,1) = " print myswig.add(1,1) # prints '2' print "add(1,-1) = " print myswig.add(1,-1) # raises an exception # we never get here... print "here we are" 

mais je ne peux pas vraiment utiliser cette solution, car si j’ai une autre fonction comme

 int add(int a, int b, int c, int *err) 

alors mon myerr3 kludge va tomber en panne.

Quelle est la meilleure façon de résoudre ce problème, sans changer la convention d’appel du code C?

    L’astuce consiste à ne pas utiliser %exception mais à définir %typemap(argout) . Ne vous référez pas non plus directement à votre variable temporaire. %typemap(in) supprime l’argument dans la langue cible et fournit une variable temporaire locale, mais vous devez toujours faire référence à l’argument lui-même dans %typemap(argout) . Voici une version modifiée de votre fichier .i d’origine. J’ai aussi ajouté plus d’exceptions génériques, ce qui devrait donc fonctionner pour d’autres langages également:

     %module x %feature("autodoc","1"); // Disable some Windows warnings on the generated code %begin %{ #pragma warning(disable:4100 4127 4211 4706) %} %{ int add(int a, int b, int *err){ if(a < 0)*err = 1; if(b < 0)*err = 2; return a+b; } char *err_string(int err){ switch(err){ case 1:return "first argument was less than 0"; case 2:return "second argument was less than 0"; default:return "unknown error"; } } %} %include  %typemap(in,numinputs=0) int *err (int myerr = 0) { $1 = &myerr; } %typemap(argout) int* err { if(*$1 != 0) { SWIG_exception(SWIG_ValueError,err_ssortingng(*$1)); } } int add(int a, int b, int *err); 

    Et voici le résultat:

     Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import x >>> x.add(1,1) 2 >>> x.add(3,4) 7 >>> x.add(-1,4) Traceback (most recent call last): File "", line 1, in  File "x.py", line 73, in add return _x.add(*args) RuntimeError: first argument was less than 0 >>> x.add(3,-1) Traceback (most recent call last): File "", line 1, in  File "x.py", line 73, in add return _x.add(*args) RuntimeError: second argument was less than 0 

    De Karl Wette, via la liste de diffusion swig-user:

    Vous pouvez modifier votre typemap “in” en déplaçant la déclaration de “myerr” dans le typemap:

     %typemap(in,numinputs=0, noblock=1) int *err { int myerr = 0; $1 = &myerr; }; 

    Tant qu’il n’y a qu’un seul argument “int * err” dans chaque fonction, cela devrait aller. Vous pouvez ensuite utiliser “myerr” directement sans le numéro d’argument.

    Cela semble être exactement la bonne solution, aucun kludges requirejs. Merci Karl!

    Si vous êtes prêt à accepter que ce ne soit pas ré-entrant, vous pouvez utiliser un myerr3 global au lieu de myerr3 , par exemple:

     %{ static int myerr = 0; %} %typemap(in,numinputs=0) int *err { $1 = &myerr; }; %exception{ $action if(myerr != 0){ PyErr_SetSsortingng(PyExc_RuntimeError,err_ssortingng(myerr)); return NULL; } }; 

    L’autre alternative est d’abuser légèrement du typemap freearg au lieu du %exception :

     // "" makes sure we don't go inside {}, which means using alloca is sane %typemap(in,numinputs=0) int *err "*($1=alloca(sizeof(int)))=0;" %typemap(freearg) int *err { if (*$1 != 0) { PyErr_SetSsortingng(PyExc_RuntimeError,err_ssortingng($1)); SWIG_fail; } } 

    Ou si vous ne pouvez pas utiliser alloca :

     %typemap(in,numinputs=0) int *err { $1=malloc(sizeof(int)); *$1=0; } %typemap(freearg) int *err { if ($1 && *$1 != 0) { PyErr_SetSsortingng(PyExc_RuntimeError,err_ssortingng($1)); // Don't leak even if we error free($1); $1=NULL; // Slightly ugly - we need to avoid a possible double free SWIG_fail; } free($1); $1=NULL; // even here another arg may fail } 

    Il existe une troisième approche possible (bodge) que vous pouvez utiliser:

     %{ static const int myerr1 = 0; static const int myerr2 = 0; static const int myerr3 = 0; static const int myerr4 = 0; static const int myerr5 = 0; //... %} %typemap(in,numinputs=0) int *err (int myerr = 0){ $1 = &myerr; } %exception{ $action // Trick: The local myerrN from the typemap "masks" the const global one! if(myerr1 != 0 || myerr2 != 0 || myerr3 != 0 || myerr4 != 0 || myerr5 != 0) { PyErr_SetSsortingng(PyExc_RuntimeError,err_ssortingng(myerr1|myerr2|myerr3|myerr4|myerr5)); return NULL; } } 

    Le truc, c’est que le myerrN spécifique du typemap masque les static const globales static const constantes – l’instruction if fait toujours référence à une seule constante locale qui est la seule qui puisse être non nulle.