Вызов exit () в библиотеке C ++ завершает скрипт python, который обертывает эту библиотеку с помощью swig

Я пишу обертку Swig-Python для библиотеки C ++. При возникновении критической ошибки библиотека вызывает exit(err); , который, в свою очередь, завершает весь скрипт python, который выполняет функции из этой библиотеки.

Есть ли способ обернуть вокруг функции exit() чтобы вернуться к сценарию или выбросить исключение?

2 Solutions collect form web for “Вызов exit () в библиотеке C ++ завершает скрипт python, который обертывает эту библиотеку с помощью swig”

Вы можете собрать массивный взлом для этого, используя longjmp и on_exit , хотя я настоятельно рекомендую избегать этого в пользу решения с несколькими процессами, о чем я расскажу позже в ответе.

Предположим, что у нас есть следующий (заголовок) заголовочный файл:

 #ifndef TEST_H #define TEST_H #include <stdlib.h> inline void fail_test(int fail) { if (fail) exit(fail); } #endif//TEST_H 

Мы хотим его обернуть и преобразовать вызов exit() в исключение Python. Один из способов добиться этого – это что-то вроде следующего интерфейса, который использует %exception чтобы вставить код C вокруг вызова на каждую функцию C из вашего интерфейса Python:

 %module test %{ #include "test.h" #include <setjmp.h> static __thread int infunc = 0; static __thread jmp_buf buf; static void exithack(int code, void *data) { if (!infunc) return; (void)data; longjmp(buf,code); } %} %init %{ on_exit(exithack, NULL); %} %exception { infunc = 1; int err = 0; if (!(err=setjmp(buf))) { $action } else { // Raise exception, code=err PyErr_Format(PyExc_Exception, "%d", err); infunc = 0; on_exit(exithack, NULL); SWIG_fail; } infunc = 0; } %include "test.h" 

Это «работает», когда мы его компилируем:

 swig3.0 -python -py3 -Wall test.i gcc -shared test_wrap.c -o _test.so -I/usr/include/python3.4 -Wall -Wextra -lpython3.4m 

И мы можем продемонстрировать это с помощью:

 Python 3.4.2 (default, Oct 8 2014, 13:14:40) [GCC 4.9.1] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import test >>> test.fail_test(0) >>> test.fail_test(123) Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception: 123 >>> test.fail_test(0) >>> test.fail_test(999) Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception: 999 >>> 

Это очень уродливое, хотя, конечно, не переносное и, скорее всего, неопределенное поведение.


Мой совет был бы не в том, чтобы сделать это и использовать решение с двумя процессами, сообщающимися вместо этого. Мы все еще можем помочь SWIG создать хороший модуль и лучше, но мы можем положиться на некоторые высокоуровневые конструкции Python, чтобы помочь нам в этом. Полный пример выглядит так:

 %module test %{ #include "test.h" static void exit_handler(int code, void *fd) { FILE *f = fdopen((int)fd, "w"); fprintf(stderr, "In exit handler: %d\n", code); fprintf(f, "(dp0\nVexited\np1\nL%dL\ns.", code); fclose(f); } %} %typemap(in) int fd %{ $1 = PyObject_AsFileDescriptor($input); %} %inline %{ void enter_work_loop(int fd) { on_exit(exit_handler, (void*)fd); } %} %pythoncode %{ import os import pickle serialize=pickle.dump deserialize=pickle.load def do_work(wrapped, args_pipe, results_pipe): wrapped.enter_work_loop(results_pipe) while True: try: args = deserialize(args_pipe) f = getattr(wrapped, args['name']) result = f(*args['args'], **args['kwargs']) serialize({'value':result},results_pipe) results_pipe.flush() except Exception as e: serialize({'exception': e},results_pipe) results_pipe.flush() class ProxyModule(): def __init__(self, wrapped): self.wrapped = wrapped self.prefix = "_worker_" def __dir__(self): return [x.strip(self.prefix) for x in dir(self.wrapped) if x.startswith(self.prefix)] def __getattr__(self, name): def proxy_call(*args, **kwargs): serialize({ 'name': '%s%s' % (self.prefix, name), 'args': args, 'kwargs': kwargs }, self.args[1]) self.args[1].flush() result = deserialize(self.results[0]) if 'exception' in result: raise result['exception'] if 'exited' in result: raise Exception('Library exited with code: %d' % result['exited']) return result['value'] return proxy_call def init_library(self): def pipes(): r,w=os.pipe() return os.fdopen(r,'rb',0), os.fdopen(w,'wb',0) self.args = pipes() self.results = pipes() self.worker = os.fork() if 0==self.worker: do_work(self.wrapped, self.args[0], self.results[1]) %} // rename all our wrapped functions to be _worker_FUNCNAME to hide them - we'll call them from within the other process %rename("_worker_%s") ""; %include "test.h" %pythoncode %{ import sys sys.modules[__name__] = ProxyModule(sys.modules[__name__]) %} 

Что использует следующие идеи:

  1. Рассортируйте данные сериализации, прежде чем записывать их по трубам в рабочий процесс.
  2. os.fork чтобы создать рабочий процесс, с os.fdopen создающим более приятный объект для использования в Python
  3. Расширенное переименование SWIG, чтобы скрыть фактические функции, которые мы обернули у пользователей модуля, но все еще обертываем их
  4. Трюк для замены модуля на объект Python, который реализует __getattr__ для возврата функций прокси для рабочего процесса
  5. __dir__ чтобы поддерживать TAB внутри ipython
  6. on_exit чтобы перехватить выход (но не переадресовать его) и сообщить код обратно через предварительно написанный ASCII маринованный объект

Вы можете сделать вызов library_init прозрачным и автоматическим, если хотите. Вам также необходимо обработать случай, когда работник не был запущен или уже вышел лучше (он будет просто заблокирован в моем примере). И вам также нужно будет убедиться, что работник будет очищен при выходе правильно, но теперь он позволяет вам запускать:

 Python 3.4.2 (default, Oct 8 2014, 13:14:40) [GCC 4.9.1] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import test >>> test.init_library() >>> test.fail_test(2) In exit handler: 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/mnt/lislan/ajw/code/scratch/swig/pyatexit/test.py", line 117, in proxy_call if 'exited' in result: raise Exception('Library exited with code: %d' % result['exited']) Exception: Library exited with code: 2 >>> 

и все же быть (несколько) переносимыми, но определенно четко определенными.

Однако без дополнительной информации сложно предложить решение:

Вы писали библиотеку? Если это так, вы можете переделать его, чтобы выбросить logic_error вместо вызова exit ?

Если библиотека вызывает exit , это означает совершенно катастрофический провал. Внутреннее состояние библиотеки может быть непоследовательным (вы должны предположить, что это так!) – вы уверены, что хотите продолжить процесс после этого? Если вы не писали библиотеку, вы не в состоянии рассуждать об этом. Если вы это сделали, см. Выше.

Возможно, вы можете написать процесс обертки вокруг библиотеки, а маршалл вызовет границу процесса? Это будет медленнее в исполнении и более болезненным для записи и поддержки, но это позволит родительскому процессу (python) обнаружить завершение дочернего элемента (обертка библиотеки).

  • Как открыть класс C ++ для Python без создания модуля
  • Время жизни временных объектов в SWIP-пакетах Python (?)
  • Wrap C struct с элементом массива для доступа в python: SWIG? Cython? ctypes?
  • Преобразование std :: vector в массив NumPy без копирования данных
  • Обтекание класса C ++ в Python с использованием SWIG
  • Как избежать утечки памяти с помощью shared_ptr и SWIG
  • Python: SWIG vs ctypes
  • Техника использования std :: ifstream, std :: ofstream в python через SWIG?
  • Python - лучший язык программирования в мире.