如何从 SWIG 中获取正确的错误,并在 Python 中不正确地重载 C++ 虚函数

Posted

技术标签:

【中文标题】如何从 SWIG 中获取正确的错误,并在 Python 中不正确地重载 C++ 虚函数【英文标题】:How to obtain correct error from SWIG with incorrect overloading of C++ virtual function in Python 【发布时间】:2016-07-01 13:52:55 【问题描述】:

我正在包装以下 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++ -python swigmodule.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) 返回不是 int 的 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 类的构造函数。这是不需要的,因为实际错误在 Python 域中(执行了不正确的重载),我想保护未来的 Python 用户免受 GDB 及其使用以及 SWIG 内部(如 DirectorException)的影响。

【问题讨论】:

我不确定这是否真的是 A 中的错误,它是在应用 fnB 期间检测到类型不匹配并且正确的。如果你真的想要的话,你可能可以对元类做一些聪明的事情来更早地发现它,但我不确定我是否将它作为“一件大事”来购买。 我希望的是,TypeError 中的文本在其当前诊断中添加了“在匹配 A::fnA 期间”之类的内容。 要全局更改吗?我可以告诉你哪个类型映射控制%typemap(directorout),使用宏%dirout_fail 来实际执行它,但是全局更改很棘手,因为已经有很多类型映射用于导演,所以这不仅仅是调整一个的情况适用于任何地方。申请所有int 或任何其他已知的返回类型应该足够简单。 我想我希望在全球范围内进行更改。非常感谢您提供示例类型图! 【参考方案1】:

您可以使用%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);

如果你想对所有可能的返回类型都做同样的事情,那就有点棘手了——directorout 是由 SWIG 库中的一系列宏生成的,所以有一个按值返回的原语(包括 const/reference/etc . 变体),一个用于字符串,一个用于非原始类型,因此在任何地方都需要做很多工作。 (您可能需要修补核心 SWIG 库才能在任何地方正确执行此操作)。

【讨论】:

【参考方案2】:

为所有返回类型解决此问题的一种方法是破解 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 http://***.com/questions/3418231/replace-part-of-a-string-with-another-string
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 类没有提供virtual int A::fnA() 函数所需的int 返回值。

【讨论】:

以上是关于如何从 SWIG 中获取正确的错误,并在 Python 中不正确地重载 C++ 虚函数的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 SWIG 在 Python 中实例化 C++ 类(获取属性错误)

SWIG:如何从 SwigPyobject 获取包装的 std::shared_ptr 的值

如何从不同的表中获取 customer_id 并在 laravel 中使用正确的方法避免重复?

如何将复数从 python numpy 传递给 c(目前正在尝试使用 SWIG)

如何在 Windows 中为 GRIDdb python 客户端设置 swig 安装?

如何使用 swig 将 const 字符串参数从 perl 传递到 c++