2012-02-26 63 views
1

我有一些C代碼,我想暴露給Python。它有一個調用約定是這樣的:%typemap和%exception for C語言錯誤代碼爲SWIG,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雜牌:

%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 kludge將分解。

解決這個問題的最好方法是什麼,而不改變C代碼的調用約定?

+0

爲什麼錯誤添加負數? – 2012-02-26 20:34:27

+0

我懷疑這是一個簡單的例子,它實際上是一個更復雜的函數,顯示了它的方式 – Flexo 2012-02-26 20:40:42

+0

是正確的。完整的應用程序是http://ascend4.org/FPROPS,這是一個流體屬性庫。 – jdpipe 2012-02-26 21:02:54

回答

1

從Karl Wette,通過swig-user郵件列表:

你可以通過移動 「myerr中」聲明的類型映射內修改「在」類型表:只要有只有一個「詮釋* ERR」在每個函數參數

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

,這 應該沒問題。然後你可以直接使用「myerr」,而不需要參數 。

這似乎是完全正確的解決方案,不需要kludges。謝謝Karl!

+0

+1我喜歡,我以前沒見過noblock。 – Flexo 2012-02-27 17:46:16

0

如果你願意接受它不會被重入的,你可以用它代替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; 
    } 
} 

訣竅是,從類型映射口罩static const全球那些具體myerrN - if語句總是指只有一個局部常量,這是唯一可以非零的那個

+0

我一直希望能夠重入其中......我也無法完全掌握C方法上的方法,http://www.swig.org/Doc1.1/HTML/SWIG .html#n5 – jdpipe 2012-02-26 21:02:09

+0

嗯,我想不出一個比'__thread'更清潔的解決方案。你能舉出一個不適用於所有物體的例子嗎?只是拋出並處理它而不是使用指針會更簡單嗎? – Flexo 2012-02-26 22:37:22

+0

@jdpipe - 我想我已經制定了一個更好的方法來實現這一點 - 它是可重入的,並使用'freearg'代替 – Flexo 2012-02-26 23:34:40

3

訣竅是不使用%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 
+0

argout。我希望我能想到像那樣使用它。比我的任何一個都乾淨得多。 – Flexo 2012-03-05 11:20:43