【问题标题】:%typemap and %exception for error codes from C functions for SWIG, Python来自 SWIG、Python 的 C 函数的错误代码的 %typemap 和 %exception
【发布时间】:2012-02-26 20:12:13
【问题描述】:

我有一些 C 代码想要公开给 Python。它有一个这样的调用约定:

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

返回值将是 (a+b) 或其他值,但如果出现问题,我会在 *err 中得到错误代码。从 Python 的角度来看,我想包装这个函数,使其行为如下:

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

这应该很容易吧?但我不这么认为。

这是我有的东西,但请注意myerr3 kludge:

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

这表现得像它应该的那样,例如

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"

但我不能真正使用这个解决方案,因为如果我有另一个类似的功能

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

那么我的myerr3 杂物就会坏掉。

在不改变 C 代码的调用约定的情况下,解决这个问题的更好方法是什么?

【问题讨论】:

  • 为什么加负数会报错?
  • 我怀疑这是一个简单的例子,实际上是一个更复杂的函数,显示了它指示成功/失败的方式
  • 没错。完整的应用程序是ascend4.org/FPROPS,这是一个流体属性库。

标签: python c exception swig


【解决方案1】:

诀窍不是使用%exception,而是定义%typemap(argout)。也不要直接引用您的临时变量。 %typemap(in) 抑制目标语言中的参数并提供局部临时变量,但您仍应在 %typemap(argout) 中引用参数本身。这是原始 .i 文件的修改版本。我还添加了更通用的异常抛出,因此它也适用于其他语言:

%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 <exception.i>

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

%typemap(argout) int* err {
    if(*$1 != 0) {
        SWIG_exception(SWIG_ValueError,err_string(*$1));
    }
}

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

结果如下:

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 "<stdin>", line 1, in <module>
  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 "<stdin>", line 1, in <module>
  File "x.py", line 73, in add
    return _x.add(*args)
RuntimeError: second argument was less than 0

【讨论】:

  • 参数。我希望我能想到这样使用它。比我的任何一个都干净。
【解决方案2】:

如果您愿意接受它不会重入,您可以使用全局而不是myerr3,例如:

%{
static int myerr = 0;
%}

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

%exception{
    $action
    if(myerr != 0){
        PyErr_SetString(PyExc_RuntimeError,err_string(myerr));
        return NULL;
    }
};

另一种选择是稍微滥用freearg 类型映射,而不是%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_SetString(PyExc_RuntimeError,err_string($1));
       SWIG_fail;
    }
}

或者如果你不能使用alloca:

%typemap(in,numinputs=0) int *err {
    $1=malloc(sizeof(int));
    *$1=0;
}

%typemap(freearg) int *err {
    if ($1 && *$1 != 0) {
       PyErr_SetString(PyExc_RuntimeError,err_string($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
}

您可能会使用第三种可能的 (bodge) 方法:

%{
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_SetString(PyExc_RuntimeError,err_string(myerr1|myerr2|myerr3|myerr4|myerr5));
        return NULL;
    }
}

诀窍在于,类型映射中的特定 myerrN 掩盖了 static const 全局变量 - if 语句始终只引用一个局部常量,它是唯一可以非零的常量

【讨论】:

  • 我希望能够使其可重入......而且我无法让这种方法与 C 对象上的方法一起工作,swig.org/Doc1.1/HTML/SWIG.html#n5
  • 嗯,我想不出比__thread 更干净的解决方案了。你能举一个不适用于所有对象的例子吗?仅仅使用throw 并处理它而不是使用指针不是更简单吗?
  • @jdpipe - 我想我找到了一种更好的(?)方法 - 它是可重入的,而是使用 freearg
  • 嗯...这看起来像是一个解决方案,但它不像我希望的那样整洁。也许 __thread 更好?假设我可以更改库的调用约定...那么有没有更简洁的解决方案,例如使用 err = add(int a, int b, int *res); 之类的函数
  • @jdpipe - 我意识到还有另一种方法可以解决它 - 你可以让 if 语句检查所有位置,但除了你正在使用的位置之外,所有位置始终为 0。有点一个笨拙的,但它符合语言规则和 SWIG ;)
【解决方案3】:

来自 Karl Wette,来自 swig-user 邮件列表:

您可以通过移动声明来修改您的“in”类型映射 类型映射中的“myerr”:

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

只要每个函数中只有一个“int *err”参数,这个 应该没事。然后您可以直接使用“myerr”而无需参数 号码。

这似乎是完全正确的解决方案,不需要任何麻烦。谢谢卡尔!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    相关资源
    最近更新 更多