簡體   English   中英

如何在 SWIG 包裝器庫中將 C++ 異常傳播到 Python?

[英]How do I propagate C++ exceptions to Python in a SWIG wrapper library?

我正在圍繞自定義 C++ 庫編寫 SWIG 包裝器,該庫定義了自己的 C++ 異常類型。 該庫的異常類型比標准異常更豐富、更具體。 (例如,一個類表示解析錯誤並具有一組行號。)如何在保留異常類型的同時將這些異常傳播回 Python?

我知道這個問題已經有幾個星期了,但我是在為自己研究解決方案時才發現的。 所以我會嘗試回答,但我會提前警告它可能不是一個有吸引力的解決方案,因為 swig 接口文件可能比手動編碼包裝器更復雜。 此外,據我所知,swig 文檔從不直接處理用戶定義的異常。

假設您想從 C++ 代碼模塊 mylibrary.cpp 中拋出以下異常,以及要在 Python 代碼中捕獲的一些錯誤消息:

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

MyException 是在別處定義的用戶異常。

在開始之前,請記下如何在您的 Python 中捕獲此異常。 出於我們的目的,假設您有如下 Python 代碼:

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

我認為這描述了您的問題。

我的解決方案包括對您的 swig 接口文件 (mylibrary.i) 進行四項添加,如下所示:

第 1 步:在標頭指令(通常未命名的 %{...%} 塊)中,為指向 Python 感知異常的指針添加一個聲明,我們將其稱為 pMyException。 下面的第 2 步將對此進行定義:

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

第 2 步:添加一個初始化指令(“m”特別令人震驚,但這正是 swig v1.3.40 當前在其構造的包裝文件中所需要的) - pMyException 在上面的第 1 步中聲明:

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

第 3 步:正如在之前的帖子中提到的,我們需要一個異常指令 - 注意“%except(python)”已被棄用。 這會將 c++ 函數“do_something”包裝在一個 try-except 塊中,該塊捕獲 c++ 異常並將其轉換為上面步驟 2 中定義的 python 異常:

%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*);

第 4 步:因為 swig 在 .pyd dll 周圍設置了一個 python 包裝(一個“影子”模塊),我們還需要確保我們的 python 代碼可以“透視”到 .pyd 文件。 以下對我有用,最好直接編輯 swig py 包裝器代碼:

%pythoncode %{
    MyException = _mylibrary.MyException
%}

對原始海報有多大用處可能為時已晚,但也許其他人會發現上面的建議有些用處。 對於小型作業,您可能更喜歡手工編碼的 c++ 擴展包裝器的簡潔性,而不是 swig 接口文件的混亂。


添加:

在上面列出的接口文件中,我省略了標准模塊指令,因為我只想描述使異常起作用所需的附加內容。 但是你的模塊行應該是這樣的:

%module mylibrary

此外,您的 setup.py(如果您使用的是 distutils,我建議至少開始使用它)應該具有與以下類似的代碼,否則當無法識別 _mylibrary 時,步驟 4 將失敗:

/* 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"],)

請注意編譯標志 /EHsc,我在 Windows 上需要它來編譯異常。 您的平台可能不需要該標志; 谷歌有詳細信息。

我已經使用我自己的名字測試了代碼,我在此處將其轉換為“mylibrary”和“MyException”以幫助概括解決方案。 希望轉錄錯誤很少或沒有。 要點是 %init 和 %pythoncode 指令以及

static PyObject* pMyException;

在標頭指令中。

希望能澄清解決方案。

我將在這里添加一點,因為這里給出的示例現在說“%except(python)”已被棄用...

您現在可以(無論如何,從 swig 1.3.40 開始)進行完全通用的、獨立於腳本語言的翻譯。 我的例子是:

%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");
    }
}

這將在任何受支持的腳本語言(包括 Python)中生成 RuntimeError 異常,而不會在其他標頭中獲取特定於 Python 的內容。

您需要將它放在需要此異常處理的調用之前

來自swig 文檔

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

example.h

struct MyBaseException : public std::runtime_error {
  MyBaseException(const std::string& msg)
    : std::runtime_error{msg} {}
};

struct MyDerivedException : public MyBaseException {
  MyDerivedException(const std::string& msg)
    : MyBaseException{msg} {}
};

void foo() { throw MyBaseException{"my base exception"}; }
void bar() { throw MyDerivedException{"my derived exception"}; }
void baz() { throw std::runtime_error{"runtime error"}; }
void qux() { throw 0; }

example.i

%module example
%{
#include "example.h"
%}

%include <std_string.i>
%include <exception.i>

%exception {
  try {
    $action
  } catch (const MyDerivedException& e) {
     PyErr_SetString(SWIG_Python_ExceptionType(SWIGTYPE_p_MyDerivedException), e.what());
     SWIG_fail;
  } catch (const MyBaseException& e) {
     PyErr_SetString(SWIG_Python_ExceptionType(SWIGTYPE_p_MyBaseException), e.what());
     SWIG_fail;
  } catch(const std::exception& e) {
    SWIG_exception(SWIG_RuntimeError, e.what());
  } catch(...) {
    SWIG_exception(SWIG_UnknownError, "");
  }
}

%exceptionclass MyBaseException;

%include "example.h"

test.py

import example

try:
  example.foo()
except example.MyBaseException as e:
  print(e)

try:
  example.bar()
except example.MyDerivedException as e:
  print(e)

try:
  example.baz()
except RuntimeError as e:
  print(e)

try:
  example.qux()
except:
  print("unknown error")

python3 test.py

my base exception
my derived exception
runtime error
unknown error

你也可以使用:

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

例子:

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

它為每個函數生成一個 try catch 塊:

  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");
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM