2009-09-08 3 views
22

J'écris un wrapper SWIG autour d'une bibliothèque C++ personnalisée qui définit ses propres types d'exceptions C++. Les types d'exception de la bibliothèque sont plus riches et plus spécifiques que les exceptions standard. (Par exemple, une classe représente des erreurs d'analyse et possède une collection de numéros de ligne.) Comment est-ce que je propage ces exceptions à Python tout en préservant le type de l'exception?Comment puis-je propager des exceptions C++ à Python dans une bibliothèque d'encapsuleur SWIG?

Répondre

0

De l'swig documentation

%except(python) { 
try { 
$function 
} 
catch (RangeError) { 
    PyErr_SetString(PyExc_IndexError,"index out-of-bounds"); 
    return NULL; 
} 
} 
+0

Ceci est proche. Il préserve le type de l'exception mais comment puis-je encapsuler les types d'exceptions personnalisées? Peut-être que les exemples SWIG couvrent cela. – Barry

15

Je sais que cette question est vieille de quelques semaines mais je l'ai trouvée juste comme je recherchais une solution pour moi-même. Je vais donc essayer de vous répondre, mais je vous préviens à l'avance que ce n'est peut-être pas une solution intéressante puisque les fichiers d'interface swig peuvent être plus compliqués que le codage manuel du wrapper. Aussi, pour autant que je sache, la documentation swig ne traite jamais directement des exceptions définies par l'utilisateur.

Disons que vous voulez jeter l'exception suivante de votre C++ module de code, mylibrary.cpp, avec un petit message d'erreur d'être pris dans votre code python:

throw MyException("Highly irregular condition..."); /* C++ code */ 

MyException est une exception définie par l'utilisateur autre part.

Avant de commencer, notez comment cette exception doit être interceptée dans votre python. Pour notre propos ici, disons que vous avez un code python comme celui-ci:

import mylibrary 
try: 
    s = mylibrary.do_something('foobar') 
except mylibrary.MyException, e: 
    print(e) 

Je pense que cela décrit votre problème.

Ma solution consiste à faire quatre ajouts à votre fichier d'interface rasade (de mylibrary.i) comme suit:

Étape 1: Dans la directive d'en-tête (le% habituellement sans nom {...%} bloc) ajouter un déclaration pour le pointeur vers l'exception python, que nous appellerons pMyException. Étape 2 ci-dessous définira ceci:

%{ 
#define SWIG_FILE_WITH_INIT /* for eg */ 
extern char* do_something(char*); /* or #include "mylibrary.h" etc */ 
static PyObject* pMyException; /* add this! */ 
%} 

Étape 2: Ajouter une directive d'initialisation (le « m » est particulièrement flagrants, mais c'est ce gorgee v1.3.40 a besoin actuellement à ce moment-là dans son fichier d'emballage construit) - pMyException a été déclaré à l'étape 1 ci-dessus:

%init %{ 
    pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL); 
    Py_INCREF(pMyException); 
    PyModule_AddObject(m, "MyException", pMyException); 
%} 

étape 3: Comme mentionné dans un précédent post, nous avons besoin d'une directive d'exception - note "% sauf (python)" est dépréciée. Cela enveloppez le C+++ fonction « do_something » dans un try-except bloc qui attrape le C++ exception et le convertit à l'exception python définie à l'étape 2 ci-dessus:

%exception do_something { 
    try { 
     $action 
    } catch (MyException &e) { 
     PyErr_SetString(pMyException, const_cast<char*>(e.what())); 
     SWIG_fail; 
    } 
} 

/* The usual functions to be wrapped are listed here: */ 
extern char* do_something(char*); 

Étape 4: Parce que rasade met en place un python wrapping (un module 'shadow') autour de la DLL .pyd, nous devons également nous assurer que notre code python peut 'voir à travers' dans le fichier .pyd. Ce qui suit travaillé pour moi et il est préférable de modifier le code wrapper py rasade directement:

%pythoncode %{ 
    MyException = _mylibrary.MyException 
%} 

Il est probablement trop tard pour être d'une grande utilité à l'affiche originale, mais peut-être quelqu'un d'autre trouvera les suggestions ci-dessus d'une certaine utilité .Pour les petits travaux, vous pouvez préférer la netteté d'un wrapper d'extension C++ codé à la main à la confusion d'un fichier d'interface swig.


Ajouté:

Dans mon fichier d'interface liste ci-dessus, j'omis la directive module standard parce que je ne voulais décrire les ajouts nécessaires pour faire des exceptions travail. Mais votre ligne de module devrait ressembler à:

%module mylibrary 

En outre, votre setup.py (si vous utilisez distutils, que je recommande au moins pour commencer) devrait avoir un code similaire à celui-ci, l'étape 4 sinon échouera quand _mylibrary n'est pas reconnu:

/* setup.py: */ 
from distutils.core import setup, Extension 

mylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'], 
        sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],) 

setup(name="mylibrary", 
     version="1.0", 
     description='Testing user defined exceptions...', 
     ext_modules=[mylibrary_module], 
     py_modules = ["mylibrary"],) 

Notez le drapeau compilation/EHsc, que je avais besoin sur Windows pour compiler des exceptions. Votre plateforme peut ne pas avoir besoin de ce drapeau. google a les détails.

J'ai testé le code en utilisant mes propres noms que j'ai convertis ici en "mylibrary" et "MyException" pour aider à généraliser la solution. Espérons que les erreurs de transcription sont peu ou pas. Les principaux points sont les% INIT et% directives pythoncode ainsi que

static PyObject* pMyException; 

dans la directive d'en-tête.

Espérons que cela clarifie la solution.

+0

Cela semble très prometteur - je vais l'essayer. Il n'est pas trop tard, btw, parce que pour l'instant nous sommes en train d'attraper RuntimeError et d'examiner le texte des messages d'erreur - beurk. – Barry

+0

Il est plus générique de remplacer "return NULL" que vous faites après avoir appelé "PyErr_SetString" dans votre exemple avec une macro nommée "SWIG_fail", qui devrait toujours fonctionner, c'est-à-dire quel que soit le type de données renvoyé. – Hermes

8

Je vais ajouter un peu ici, puisque l'exemple donné ici, dit maintenant que « sauf% (python) » est dépréciée ...

Vous pouvez maintenant (en rasade 1.3.40, de toute façon) faire une traduction totalement générique, indépendante du langage script. Mon exemple serait:

%exception { 
    try { 
     $action 
    } catch (myException &e) { 
     std::string s("myModule error: "), s2(e.what()); 
     s = s + s2; 
     SWIG_exception(SWIG_RuntimeError, s.c_str()); 
    } catch (myOtherException &e) { 
     std::string s("otherModule error: "), s2(e.what()); 
     s = s + s2; 
     SWIG_exception(SWIG_RuntimeError, s.c_str()); 
    } catch (...) { 
     SWIG_exception(SWIG_RuntimeError, "unknown exception"); 
    } 
} 

Cela va générer une exception RuntimeError dans un langage de script pris en charge, y compris Python, sans obtenir des trucs spécifiques python dans vos autres en-têtes.

Vous devez mettre ce avant les appels qui souhaitent cette gestion des exceptions.

+2

+1, il suffit de s'assurer que% inclut l'exception.i dans son fichier d'interface SWIG (introduction de la macro SWIG_exception) –

-1

U peut également utiliser:

prises: http://www.swig.org/Doc3.0/SWIGPlus.html#SWIGPlus_catches

Exemple:

%catches(std::exception, std::string, int, ...); 

qui génère pour chaque fonction d'un bloc catch try:

try { 
    result = (namespace::Function *)new namespace::Function ((uint16_t const *)arg1); 
    } 
    catch(std::exception &_e) { 
    SWIG_exception_fail(SWIG_SystemError, (&_e)->what()); 
    } 
    catch(std::string &_e) { 
    SWIG_Python_Raise(SWIG_From_std_string(static_cast<std::string>(_e)), "std::string", 0); SWIG_fail; 
    } 
    catch(int &_e) { 
    SWIG_Python_Raise(SWIG_From_int(static_cast<int>(_e)), "int", 0); SWIG_fail; 
    } 
    catch(...) { 
    SWIG_exception_fail(SWIG_RuntimeError,"unknown exception"); 
    } 
+0

-1: Cela ne fonctionne pas, Python se bloque. '% catches' ne suffit pas sauf si je spécifie' throw() 'dans les déclarations de fonction. Et le pourcentage de captures est superflu dans ce cas. La documentation SWIG liée ne mentionne que l'utilisation de la directive '% catches' avec un nom de fonction. – Melebius

+0

% attrape en fait "wraps d'exception" plus de fonctions enveloppées swig puis% exception. – hugo24

Questions connexes