Подтвердить что ты не робот

Динамическое переосмысление самоопределенных исключений С++ как исключений Python с использованием SWIG

Ситуация

Я хочу создать привязку языка Python для С++ API с помощью SWIG. Некоторые из функций API могут вызывать исключения. Приложение С++ имеет иерархию самоопределяемых исключений, например, этот пример:

std::exception
  -> API::Exception
    -> API::NetworkException
      -> API::TimeoutException
      -> API::UnreachableException
    -> API::InvalidAddressException

Требуемое поведение выглядит следующим образом:

  • Все типы исключений должны иметь соответствующий класс Python как обертка. Эти классы-оболочки должны быть действительными исключениями Python.

  • Когда вызов API выдает исключение С++, он должен быть пойман. соответствующее исключение Python (т.е. Класс-оболочка пойманного исключения С++) должно быть брошено.

  • Это должен быть динамический процесс: тип исключения Python определяется во время выполнения, только на основе типа среды выполнения пойманного исключения на С++. Таким образом, нет необходимости описывать полную иерархию исключений в файле интерфейса SWIG.

Проблемы и вопросы

  • Классы Wrapper не являются исключениями Python.

    В то время как SWIG создает классы-оболочки для всех самоопределяемых исключений (например, для любого другого класса), эти классы не являются исключениями Python. Обертка базового исключения (API::Exception в примере) расширяет Object вместо BaseException, класс Python, из которого должны быть выведены все исключения в Python.

    Кроме того, не представляется возможным позволить SWIG добавлять родительский класс вручную. Обратите внимание, что это возможно при использовании SWIG с Java через использование %typemap(javabase) (см. документацию SWIG для деталей).

  • Как может API Python C API исключить пользовательское исключение?

    Самый распространенный способ выбросить исключение Python из API Python C - это вызвать PyErr_SetString [reference]. Это также показано в демонстрационном приложении ниже.

    Но это только тривиально со стандартными (встроенными) исключениями Python, потому что ссылки на них хранятся в глобальных переменных [ссылка] в Python C API.

    Я знаю, что есть метод PyErr_NewException [reference], чтобы получить ссылки на самоопределяемые исключения, но я не получил эту работу.

  • Как API Python C > оценивает тип С++ во время выполнения, а затем находит соответствующий класс оболочки Python по имени?

    Я предполагаю, что класс Python можно искать по имени во время выполнения через часть отражения API Python C API. Это путь? И как это делается на практике?

Демо-приложение

Чтобы поэкспериментировать с этой проблемой, я создал крошечный С++ API с одной функцией, которая вычисляет факториал числа. Он имеет минимальную самоопределяемую иерархию исключений, состоящую только из одного класса TooBigException.

Обратите внимание, что это исключение выступает в качестве основного исключения в общей проблеме, и приложение должно работать с любым подклассом. Это означает, что решение может использовать только динамический (т.е. Время выполнения) исключенный захват для повторного его преобразования в Python (см. Ниже).

Полный исходный код демонстрационного приложения выглядит следующим образом:

// File: numbers.h
namespace numbers {
int fact(int n);
}

// File: numbers.cpp
#include "TooBigException.h"
namespace numbers {
int fact(int n) {
    if (n > 10) throw TooBigException("Value too big", n);
    else if (n <= 1) return 1;
    else return n*fact(n-1);
}
}

// File: TooBigException.h
namespace numbers {
class TooBigException: public std::exception {
public:
    explicit TooBigException(const std::string & inMessage,
                             const int inValue);
    virtual ~TooBigException() throw() {}
    virtual const char* what() const throw();
    const std::string & message() const;
    const int value() const;
private:
    std::string mMessage;
    int mValue;
};
}

// File: TooBigException.cpp
#include "TooBigException.h"
namespace numbers {
TooBigException::TooBigException(const std::string & inMessage, const int inValue):
    std::exception(),
    mMessage(inMessage),
    mValue(inValue)
{
}
const char* TooBigException::what() const throw(){
    return mMessage.c_str();
}
const std::string & TooBigException::message() const {
    return mMessage;
}
const int TooBigException::value() const {
    return mValue;
}
}

Чтобы получить привязку Python, я использую следующий файл интерфейса SWIG:

// File: numbers.i
%module numbers
%include "stl.i"
%include "exception.i"

%{
#define SWIG_FILE_WITH_INIT
#include "TooBigException.h"
#include "numbers.h"
%}

%exception {
    try {
        $action
    }
    catch (const numbers::TooBigException & e) {
        // This catches any self-defined exception in the exception hierarchy,
        // because they all derive from this base class. 
        <TODO>
    }
    catch (const std::exception & e)
    {
        SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str());
    }
    catch (...)
    {
        SWIG_exception(SWIG_UnknownError, "C++ anonymous exception");
    }
}

%include "TooBigException.h"
%include "numbers.h"

Таким образом, каждый вызов API завершается блоком try-catch. Первые исключения нашего базового типа пойманы и обработаны. Затем все остальные исключения захватываются и обновляются с помощью библиотеки исключений SWIG.

Обратите внимание, что любой подкласс numbers::TooBigException пойман, и обертки их динамического (т.е. времени выполнения) должны быть выброшены, а не оболочка их статического (т.е. времени компиляции), который всегда TooBigException!

Проект можно легко создать, выполнив следующие команды на машине Linux:

$ swig -c++ -python numbers.i
$ g++ -fPIC -shared TooBigException.cpp numbers.cpp numbers_wrap.cxx \
    -I/usr/include/python2.7 -o _numbers.so

Текущая реализация

Моя текущая реализация все еще (успешно) выдает фиксированное стандартное исключение Python. Приведенный выше код <TODO> заменяется на:

PyErr_SetString(PyExc_Exception, (std::string("C++ self-defined exception ") + e.what()).c_str());
return NULL;

Что дает следующее (ожидаемое) поведение в Python:

>>> import numbers
>>> fact(11)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: C++ self-defined exception Value too big
4b9b3361

Ответ 1

Похоже, кто-то ответил на ваш основной вопрос в списке swig-users...

%exception {
  try {
    $action
  } catch (MyException &_e) {
    SWIG_Python_Raise(SWIG_NewPointerObj(
            (new MyException(static_cast<const MyException& >(_e))),  
            SWIGTYPE_p_MyException,SWIG_POINTER_OWN),
        "MyException", SWIGTYPE_p_MyException); 
    SWIG_fail;
  } 
}

Это предполагает, что вы создали оболочки для ваших классов исключений, я полагаю.

Ответ 2

Пример для вашей иерархии

std::exception
  -> API::Exception
    -> API::NetworkException
      -> API::TimeoutException
      -> API::UnreachableException
    -> API::InvalidAddressException

example.i:

%module example
%include "stl.i"
%include "exception.i"

%{
#define SWIG_FILE_WITH_INIT
#include "example.cpp"
%}

%{

#define CATCH_PE(Namespace,Exception) \
    catch(const Namespace::Exception &e) \
    { \
       SWIG_Python_Raise(SWIG_NewPointerObj(new Namespace::Exception(e), \
            SWIGTYPE_p_##Namespace##__##Exception,SWIG_POINTER_OWN), \
            #Exception, SWIGTYPE_p_##Namespace##__##Exception); \
       SWIG_fail; \
    } \
/**/

// should be in "derived first" order
#define FOR_EACH_EXCEPTION(ACTION) \
   ACTION(API,UnreachableException) \
   ACTION(API,TimeoutException) \
   ACTION(API,InvalidAddressException) \
   ACTION(API,NetworkException) \
   ACTION(API,Exception) \
/**/
// In order to remove macros, need traits:
// http://swig.10945.n7.nabble.com/traits-based-access-to-swig-type-info-td12315.html
%}

%exception {
    try {
        $action
    }
    FOR_EACH_EXCEPTION(CATCH_PE)
    catch (const std::exception & e)
    {
        SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str());
    }
    catch (...)
    {
        SWIG_exception(SWIG_UnknownError, "C++ anonymous exception");
    }
}

%include "example.cpp"

example.cpp:

#include <exception>
#include <stdexcept>

namespace API
{
    struct Exception: std::exception
    {
        virtual const char* what() const throw()
        {
            return "It is API::Exception";
        }
    };
    struct NetworkException: Exception
    {
        virtual const char* what() const throw()
        {
            return "It is API::NetworkException";
        }
    };
    struct TimeoutException: NetworkException
    {
        virtual const char* what() const throw()
        {
            return "It is API::TimeoutException";
        }
    };
    struct UnreachableException: NetworkException
    {
        virtual const char* what() const throw()
        {
            return "It is API::UnreachableException";
        }
    };
    struct InvalidAddressException: Exception
    {
        virtual const char* what() const throw()
        {
            return "It is API::InvalidAddressException";
        }
    };

    inline void select(int i)
    {
        switch(i)
        {
            case 0: throw Exception();
            case 1: throw NetworkException();
            case 2: throw TimeoutException();
            case 3: throw UnreachableException();
            case 4: throw InvalidAddressException();
            default: throw std::runtime_error("It is std::runtime_error");
        }
    }
}

Сложение:

swig -c++ -python example.i &&
g++ -fPIC -shared -lpython2.7 example.cpp example_wrap.cxx -I/usr/include/python2.7 -o _example.so

test.py:

#!/usr/bin/env python2.7

from exceptions import BaseException
from example import *

def catch(i):
    try:
        select(i)
    except UnreachableException as e:
        print "Caught UnreachableException"
        print e.what()
        print e
    except TimeoutException as e:
        print "Caught TimeoutException"
        print e.what()
        print e
    except InvalidAddressException as e:
        print "Caught InvalidAddressException"
        print e.what()
        print e
    except NetworkException as e:
        print "Caught NetworkException"
        print e.what()
        print e
    except Exception as e:
        print "Caught Exception"
        print e.what()
        print e
    except BaseException as e:
        print "Caught BaseException"
        print str(e)
    print "_"*16

for i in xrange(6):
    catch(i)

Выход:

Caught Exception
It is API::Exception
<example.Exception; proxy of <Swig Object of type 'API::Exception *' at 0x7f9f54a02120> >
________________
Caught NetworkException
It is API::NetworkException
<example.NetworkException; proxy of <Swig Object of type 'API::NetworkException *' at 0x7f9f54a02120> >
________________
Caught TimeoutException
It is API::TimeoutException
<example.TimeoutException; proxy of <Swig Object of type 'API::TimeoutException *' at 0x7f9f54a02120> >
________________
Caught UnreachableException
It is API::UnreachableException
<example.UnreachableException; proxy of <Swig Object of type 'API::UnreachableException *' at 0x7f9f54a02120> >
________________
Caught InvalidAddressException
It is API::InvalidAddressException
<example.InvalidAddressException; proxy of <Swig Object of type 'API::InvalidAddressException *' at 0x7f9f54a02120> >
________________
Caught BaseException
C++ std::exception: It is std::runtime_error
________________

На основе ответ в maillist.