如何通过 Python 中 C++ 虚函数的不正确重载从 SWIG 获取正确的错误
How to obtain correct error from SWIG with incorrect overloading of C++ virtual function in Python
我正在封装以下 C++ 代码:
// content of cpp.h
class A
{
public:
virtual int fnA() = 0;
virtual ~A() { }
};
class B
{
public:
int fnB(A &a)
{
return a.fnA();
}
};
SWIG 使用 SWIG 包装器:
// content of swigmodule.i
%module(directors="1") swigmodule
%feature("director");
%feature("director:except") {
if ($error != NULL) {
fprintf(stderr, "throw\n");
throw Swig::DirectorMethodException();
}
}
%exception {
try { $action }
catch (Swig::DirectorException &e) { fprintf(stderr, "catch\n"); SWIG_fail; }
}
%{
#include "cpp.h"
%}
%include "cpp.h"
其中异常处理是从 SWIG 手册中复制的。
使用它我生成了 SWIG 包装器:
“痛饮-c++-pythonswigmodule.i;
g++ -shared -fPIC -I/usr/include/python2.7 swigmodule_wrap.cxx -o _swigmodule.so"
当在 Python 中使用 "void fnA()" 错误地重载 "int fnA()" 函数时,问题就来了。
# content of useit.py
from swigmodule import A, B
class myA(A):
def fnA(self):
print("myA::fnA")
b = B();
a = myA();
print("%d"% b.fnB(a) )
生成的 SWIG 包装器在运行时正确地将其标记为错误; fnA(self) returns None 这不是一个整数。但是,控制台的输出是:
$ python useit.py
myA::fnA
catch
Traceback (most recent call last):
File "useit.py", line 12, in <module>
print("%d"% b.fnB(a) )
File "/home/schuttek/tmp/swigmodule.py", line 109, in fnB
def fnB(self, *args): return _swigmodule.B_fnB(self, *args)
TypeError: SWIG director type mismatch in output value of type 'int'
这是误导性的,因为它表明错误在 B::fnB 中,而实际错误是在重载 A::fnA.
中
如何让 SWIG 对发生错误的位置提供有意义的诊断?在我的真实代码(这是一个简化版本)中,我不得不使用 GDB 来捕获 Swig::DirectorException class 的构造函数。这是不需要的,因为实际错误在 Python 域中(执行了不正确的覆盖)并且我想保护未来的 Python 用户免受 GDB 及其使用以及 SWIG 内部这样的作为 DirectorException。
您可以使用 %typemap(directorout)
在一定程度上改善错误,例如:
// content of swigmodule.i
%module(directors="1") swigmodule
%feature("director");
%feature("director:except") {
if ($error != NULL) {
fprintf(stderr, "throw\n");
throw Swig::DirectorMethodException();
}
}
%exception {
// $parentclassname
try { $action }
catch (Swig::DirectorException &e) { fprintf(stderr, "catch\n"); SWIG_fail; }
}
%{
#include "cpp.h"
%}
%typemap(directorout,noblock=1,fragment=SWIG_AsVal_frag(int)) int (int swig_val) {
int swig_res = SWIG_AsVal(int)($input, &swig_val);
if (!SWIG_IsOK(swig_res)) {
//%dirout_fail(swig_res, "$type"); // This line expands into the default call
Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_res)), "in output of $symname value of type '""$type""'");
}
$result = %static_cast(swig_val,$type);
}
%include "cpp.h"
它替换了 int
的 directorout 类型映射。似乎 directorout 没有得到一些 special variables from %exception
的应用,所以我们从 SWIG 本身得到的最好的是 $symname,它不是完全合格的,但确实让你将 fnA
滑入错误消息.
您可以使用 __PRETTY_FUNCTION__
来更明确地了解导致它的原因,但现在类型中将包括 "SwigDirector_" - 可能没什么大不了的:
%typemap(directorout,noblock=1,fragment=SWIG_AsVal_frag(int)) int (int swig_val) {
int swig_res = SWIG_AsVal(int)($input, &swig_val);
if (!SWIG_IsOK(swig_res)) {
//%dirout_fail(swig_res, "$type");
const std::string msg = std::string("in output of ") + __PRETTY_FUNCTION__ + " ($symname) value of type '""$type""'";
Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_res)), msg.c_str());
}
$result = %static_cast(swig_val,$type);
}
不过,如果您想对所有可能的 return 类型执行相同的操作,那将变得棘手 - directorout 是由 SWIG 库中的一系列宏生成的,因此有一个 return按值(包括 const/reference/etc. 变体),一种用于字符串,一种用于非原始类型,因此在任何地方都需要大量工作。 (您可能需要修补核心 SWIG 库才能在任何地方正确执行此操作)。
为所有 return 类型修复此问题的一种方法是破解 SWIG 生成的包装文件。我通过以下方式完成了此操作:
cp swigmodule_wrap.cxx x
cp myRaise.h swigmodule_wrap.cxx
sed 's/Swig::DirectorTypeMismatchException::raise(/myRaise(__PRETTY_FUNCTION__,swig_get_self(),/' < x >> swigmodule_wrap.cxx
即通过调用单独的函数替换所有引发 DirectorTypeMismatchException 的地方,并提供 __PRETTY_FUNCTION__
和 swig_get_self()
额外信息以生成所需的额外信息。
myRaise.h 文件中提供了单独的函数(作为宏):
#include <string>
#include <Python.h>
// ReplaceString taken from
inline std::string ReplaceString(std::string subject, const std::string& search,
const std::string& replace) {
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
return subject;
}
#define myRaise(fn, obj, err, msg) \
{ \
std::string fns(fn); \
std::string objs(PyString_AsString(PyObject_Repr(obj))); \
std::string msgs(std::string("when calling ")+ReplaceString(fns, std::string("SwigDirector_"), std::string(""))+std::string(" in ")+objs+std::string(" ")+std::string(msg)); \
Swig::DirectorTypeMismatchException::raise(err, msgs.c_str()); \
}
结果诊断为:
$ python useit.py
myA::fnA
catch
Traceback (most recent call last):
File "useit.py", line 12, in <module>
print("%d"% b.fnB(a) )
File "/home/schuttek/tmp/swigmodule.py", line 109, in fnB
def fnB(self, *args): return _swigmodule.B_fnB(self, *args)
TypeError: SWIG director type mismatch when calling virtual int A::fnA() in <__main__.myA; proxy of <Swig Object of type 'A *' at 0x7f3628d6b540> > in output value of type 'int'
这里可以看到__main__.myA
class没有提供virtual int A::fnA()
函数需要的int
return值
我正在封装以下 C++ 代码:
// content of cpp.h
class A
{
public:
virtual int fnA() = 0;
virtual ~A() { }
};
class B
{
public:
int fnB(A &a)
{
return a.fnA();
}
};
SWIG 使用 SWIG 包装器:
// content of swigmodule.i
%module(directors="1") swigmodule
%feature("director");
%feature("director:except") {
if ($error != NULL) {
fprintf(stderr, "throw\n");
throw Swig::DirectorMethodException();
}
}
%exception {
try { $action }
catch (Swig::DirectorException &e) { fprintf(stderr, "catch\n"); SWIG_fail; }
}
%{
#include "cpp.h"
%}
%include "cpp.h"
其中异常处理是从 SWIG 手册中复制的。 使用它我生成了 SWIG 包装器: “痛饮-c++-pythonswigmodule.i; g++ -shared -fPIC -I/usr/include/python2.7 swigmodule_wrap.cxx -o _swigmodule.so"
当在 Python 中使用 "void fnA()" 错误地重载 "int fnA()" 函数时,问题就来了。
# content of useit.py
from swigmodule import A, B
class myA(A):
def fnA(self):
print("myA::fnA")
b = B();
a = myA();
print("%d"% b.fnB(a) )
生成的 SWIG 包装器在运行时正确地将其标记为错误; fnA(self) returns None 这不是一个整数。但是,控制台的输出是:
$ python useit.py
myA::fnA
catch
Traceback (most recent call last):
File "useit.py", line 12, in <module>
print("%d"% b.fnB(a) )
File "/home/schuttek/tmp/swigmodule.py", line 109, in fnB
def fnB(self, *args): return _swigmodule.B_fnB(self, *args)
TypeError: SWIG director type mismatch in output value of type 'int'
这是误导性的,因为它表明错误在 B::fnB 中,而实际错误是在重载 A::fnA.
中如何让 SWIG 对发生错误的位置提供有意义的诊断?在我的真实代码(这是一个简化版本)中,我不得不使用 GDB 来捕获 Swig::DirectorException class 的构造函数。这是不需要的,因为实际错误在 Python 域中(执行了不正确的覆盖)并且我想保护未来的 Python 用户免受 GDB 及其使用以及 SWIG 内部这样的作为 DirectorException。
您可以使用 %typemap(directorout)
在一定程度上改善错误,例如:
// content of swigmodule.i
%module(directors="1") swigmodule
%feature("director");
%feature("director:except") {
if ($error != NULL) {
fprintf(stderr, "throw\n");
throw Swig::DirectorMethodException();
}
}
%exception {
// $parentclassname
try { $action }
catch (Swig::DirectorException &e) { fprintf(stderr, "catch\n"); SWIG_fail; }
}
%{
#include "cpp.h"
%}
%typemap(directorout,noblock=1,fragment=SWIG_AsVal_frag(int)) int (int swig_val) {
int swig_res = SWIG_AsVal(int)($input, &swig_val);
if (!SWIG_IsOK(swig_res)) {
//%dirout_fail(swig_res, "$type"); // This line expands into the default call
Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_res)), "in output of $symname value of type '""$type""'");
}
$result = %static_cast(swig_val,$type);
}
%include "cpp.h"
它替换了 int
的 directorout 类型映射。似乎 directorout 没有得到一些 special variables from %exception
的应用,所以我们从 SWIG 本身得到的最好的是 $symname,它不是完全合格的,但确实让你将 fnA
滑入错误消息.
您可以使用 __PRETTY_FUNCTION__
来更明确地了解导致它的原因,但现在类型中将包括 "SwigDirector_" - 可能没什么大不了的:
%typemap(directorout,noblock=1,fragment=SWIG_AsVal_frag(int)) int (int swig_val) {
int swig_res = SWIG_AsVal(int)($input, &swig_val);
if (!SWIG_IsOK(swig_res)) {
//%dirout_fail(swig_res, "$type");
const std::string msg = std::string("in output of ") + __PRETTY_FUNCTION__ + " ($symname) value of type '""$type""'";
Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_res)), msg.c_str());
}
$result = %static_cast(swig_val,$type);
}
不过,如果您想对所有可能的 return 类型执行相同的操作,那将变得棘手 - directorout 是由 SWIG 库中的一系列宏生成的,因此有一个 return按值(包括 const/reference/etc. 变体),一种用于字符串,一种用于非原始类型,因此在任何地方都需要大量工作。 (您可能需要修补核心 SWIG 库才能在任何地方正确执行此操作)。
为所有 return 类型修复此问题的一种方法是破解 SWIG 生成的包装文件。我通过以下方式完成了此操作:
cp swigmodule_wrap.cxx x
cp myRaise.h swigmodule_wrap.cxx
sed 's/Swig::DirectorTypeMismatchException::raise(/myRaise(__PRETTY_FUNCTION__,swig_get_self(),/' < x >> swigmodule_wrap.cxx
即通过调用单独的函数替换所有引发 DirectorTypeMismatchException 的地方,并提供 __PRETTY_FUNCTION__
和 swig_get_self()
额外信息以生成所需的额外信息。
myRaise.h 文件中提供了单独的函数(作为宏):
#include <string>
#include <Python.h>
// ReplaceString taken from
inline std::string ReplaceString(std::string subject, const std::string& search,
const std::string& replace) {
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
return subject;
}
#define myRaise(fn, obj, err, msg) \
{ \
std::string fns(fn); \
std::string objs(PyString_AsString(PyObject_Repr(obj))); \
std::string msgs(std::string("when calling ")+ReplaceString(fns, std::string("SwigDirector_"), std::string(""))+std::string(" in ")+objs+std::string(" ")+std::string(msg)); \
Swig::DirectorTypeMismatchException::raise(err, msgs.c_str()); \
}
结果诊断为:
$ python useit.py
myA::fnA
catch
Traceback (most recent call last):
File "useit.py", line 12, in <module>
print("%d"% b.fnB(a) )
File "/home/schuttek/tmp/swigmodule.py", line 109, in fnB
def fnB(self, *args): return _swigmodule.B_fnB(self, *args)
TypeError: SWIG director type mismatch when calling virtual int A::fnA() in <__main__.myA; proxy of <Swig Object of type 'A *' at 0x7f3628d6b540> > in output value of type 'int'
这里可以看到__main__.myA
class没有提供virtual int A::fnA()
函数需要的int
return值