简体   繁体   English

什么是使用SWIG包装对象从C ++调用Python函数的最简洁方法

[英]What Is The Cleanest Way to Call A Python Function From C++ with a SWIG Wrapped Object

I have the following code, which implements a simple C++ class (ObjWithPyCallback) with a Python callback function. 我有以下代码,它实现了一个带有Python回调函数的简单C ++类(ObjWithPyCallback)。 The idea is to call the Python function with "this" as the single argument. 我们的想法是用“this”作为单个参数调用Python函数。

The problem is that since ObjWithPyCallback is a SWIG wrapped object I need the SWIG typeinfo in order to create a Python object. 问题是,由于ObjWithPyCallback是一个SWIG包装对象,我需要SWIG typeinfo才能创建一个Python对象。

The problem with this is that it's inside of the SWIG generated file "ObjWithPyCallback_wrap.cxx". 这个问题是它在SWIG生成的文件“ObjWithPyCallback_wrap.cxx”中。 Can SWIG generate a header file? SWIG可以生成头文件吗? I have thus far not been able to make this happen. 到目前为止,我还未能实现这一目标。

However, even with a header file there is a circular dependency between SWIG and my main implementation, which is annoying. 但是,即使使用头文件,SWIG和我的主要实现之间也存在循环依赖关系,这很烦人。 I'd like to find a way to avoid it if at all possible. 如果可能的话,我想找到一种方法来避免它。 Ultimately ObjWithPyCallback ends up in a different shared library than the Python bindings. 最终ObjWithPyCallback最终存在于与Python绑定不同的共享库中。

Is there a clean way to pull this off? 是否有一个干净的方法来解决这个问题? I'm aware of this post , but it only addresses the mechanics of SWIG_NewPointerObj. 我知道这篇文章 ,但它只涉及SWIG_NewPointerObj的机制。

Thanks in advance for any help! 在此先感谢您的帮助!

Here's the code: 这是代码:

File: example.py 文件:example.py

import cb

def foo(x=None):
    print("Hello from Foo!")
    # I'd like x to be a reference to a ObjWithPyCallback object.
    print(x)

o = cb.ObjWithPyCallback()
o.setCallback(foo)
o.call()

File: ObjWithPyCallback.h 文件:ObjWithPyCallback.h

#include <Python.h>

class ObjWithPyCallback 
{
   public:

      ObjWithPyCallback();
      void setCallback(PyObject *callback);
      void call();

      PyObject *callback_;
};

File: ObjWithCallback.cpp 文件:ObjWithCallback.cpp

#include "ObjWithPyCallback.h"

#include <iostream>

ObjWithPyCallback::ObjWithPyCallback() : callback_(NULL) {}

void ObjWithPyCallback::setCallback(PyObject* callback)
{
   if (!PyCallable_Check(callback))
   {
      std::cerr << "Object is not callable.\n";
   }
   else
   {
      if ( callback_ ) Py_XDECREF(callback_);
      callback_ = callback;
      Py_XINCREF(callback_);
   }
}

void ObjWithPyCallback::call()
{
   if ( ! callback_ )
   {
      std::cerr << "No callback is set.\n";
   }
   else
   {
      // I want to call "callback_(*this)", how to do this cleanly?
      PyObject *result = PyObject_CallFunction(callback_, "");
      if (result == NULL)
         std::cerr << "Callback call failed.\n";
      else
         Py_DECREF(result);
   }
}

File:: ObjWithPyCallback.i File :: ObjWithPyCallback.i

%module cb
%{
   #include "ObjWithPyCallback.h"
%}

%include "ObjWithPyCallback.h"

Below is my working solution for solving this problem. 以下是解决此问题的解决方案。 It uses the suggestions from both @omnifarious and @flexo above. 它使用了@omnifarious和@flexo上面的建议。

In particular we create a Callback class with a SWIG director and then derive from it in Python to get the required callback functionality without introducing a circular dependency. 特别是我们使用SWIG导向器创建一个Callback类,然后在Python中派生它以获得所需的回调功能,而不引入循环依赖。

In addition we provide an interface which allows any Python object that is callable to act as a callback. 此外,我们提供了一个接口,允许任何可调用的Python对象充当回调。 We achieve this by using the "pythonprend" directive in SWIG to prepend some code to the "setCallback" function. 我们通过在SWIG中使用“pythonprend”指令来实现这一点,以便将一些代码添加到“setCallback”函数中。 This code simply checks for a callable object and if it finds one, wraps it in an instance of a Callback. 此代码只检查可调用对象,如果找到可调用对象,则将其包装在Callback实例中。

Finally we deal with the memory issues related to having a C++ class (ObjWithPyCallback) reference a director object (ie a subclass of Callback). 最后,我们处理与C ++类(ObjWithPyCallback)引用一个director对象(即Callback的子类)相关的内存问题。

File example.py: 文件example.py:

import cb

class CB(cb.Callback):
    def __init__(self):
        super(CB, self).__init__()
    def call(self, x):
        print("Hello from CB!")
        print(x)

def foo(x):
    print("Hello from foo!")
    print(x)

class Bar:
    def __call__(self, x):
        print("Hello from Bar!")
        print(x)


o = cb.ObjWithPyCallback()
mycb=CB()
o.setCallback(mycb)
o.call()
o.setCallback(foo)
o.call()
o.setCallback(Bar())
o.call()

File ObjWithPyCallback.i: 文件ObjWithPyCallback.i:

%module(directors="1") cb
%{
   #include "Callback.h"
   #include "ObjWithPyCallback.h"
%}
%feature("director") Callback;
%feature("nodirector") ObjWithPyCallback;

%feature("pythonprepend") ObjWithPyCallback::setCallback(Callback&) %{
   if len(args) == 1 and (not isinstance(args[0], Callback) and callable(args[0])):
      class CallableWrapper(Callback):
         def __init__(self, f):
            super(CallableWrapper, self).__init__()
            self.f_ = f
         def call(self, obj):
            self.f_(obj)

      args = tuple([CallableWrapper(args[0])])
      args[0].__disown__()
   elif len(args) == 1 and isinstance(args[0], Callback):
      args[0].__disown__()


%}

%include "Callback.h"
%include "ObjWithPyCallback.h"

File Callback.h: 文件Callback.h:

#ifndef CALLBACK_H
#define CALLBACK_H

class ObjWithPyCallback;

class Callback
{
   public:
      Callback(){}

      virtual ~Callback(){}
      virtual void call(ObjWithPyCallback& object){} 
};

#endif

File ObjWithPyCallback.h: 文件ObjWithPyCallback.h:

#ifndef OBJWITHPYCALLBACK_H
#define OBJWITHPYCALLBACK_H

class Callback;

class ObjWithPyCallback 
{
   public:

      ObjWithPyCallback();
      ~ObjWithPyCallback();
      void setCallback(Callback &callback);
      void call();

   private:

      Callback* callback_;
};

#endif

File ObjWithPyCallback.cpp: 文件ObjWithPyCallback.cpp:

#include "ObjWithPyCallback.h"
#include "Callback.h"

#include <iostream>

ObjWithPyCallback::ObjWithPyCallback() : callback_(NULL) {}

ObjWithPyCallback::~ObjWithPyCallback()
{
}

void ObjWithPyCallback::setCallback(Callback &callback)
{
   callback_ = &callback;
}

void ObjWithPyCallback::call()
{
   if ( ! callback_ )
   {
      std::cerr << "No callback is set.\n";
   }
   else
   {
      callback_->call(*this);
   }
}

I would use SWIG's mechanisms for handling inheritance and have a callback class with a virtual function void call() . 我将使用SWIG的机制来处理继承,并有一个带有虚函数void call()的回调类。 Then you use SWIG to enable that class to be derived from in Python. 然后使用SWIG使该类从Python派生。

In Python, you simply make sure that where the callback is set, you wrap it in an instance of a Python class derived from the C++ callback class, and make it's call member function execute the callback. 在Python中,您只需确保设置回调的位置,将其包装在从C ++回调类派生的Python类的实例中,并使其call成员函数执行回调。 That's also where you'd do the test to see if it's callable. 这也是你进行测试以查看它是否可调用的地方。 Then you would call the setCallback function with this wrapper object. 然后,您将使用此包装器对象调用setCallback函数。

1. General idea of solve the problem: 1.解决问题的一般思路:

(1). (1)。 Define a C++ class named Callback, which has a method run(). 定义一个名为Callback的C ++类,它有一个方法run()。

(2). (2)。 Inherit Callback in Python code, and create a instance. 在Python代码中继承Callback,并创建一个实例。

(3). (3)。 Use C++ method to bind the instance to C++ pointor. 使用C ++方法将实例绑定到C ++ pointor。

(4). (4)。 Use the pointor to access run(), which is defined in python code. 使用pointor访问run(),它在python代码中定义。

2. Sample code 2.示例代码

(1). (1)。 example.h example.h文件

class Callback{
    public:
    virtual void run(int n);                                                                                                                                                      
    virtual ~Callback() {}; 
};   
extern Callback * callback;
extern void doSomeWithCallback();
extern void setCallback(Callback * cb);

(2). (2)。 example.cxx example.cxx

#include <iostream>
#include "example.h"

int n=0;
Callback * callback = NULL;

void Callback::run(int n){ 
    std::cout << "This print from C++: n = " << n << std::endl;
}    

void setCallback(Callback * cb){
    callback = cb; 
}    

void doSomeWithCallback(){
    if(callback == NULL){
        std::cout << "Must set callback first!" << std::endl;
    }else{
        callback->run(n++);
    }                                                                                                                                                                                         
}

(3). (3)。 example.i example.i

/* File : example.i */                                                                                                                                                                        
%module(directors="1") example
%{
#include "example.h"                                                                                                                                                                          
%}

/* turn on director wrapping Callback */
%feature("director") Callback;

%include "example.h"

3. Compile 3.编译

$ swig -c++ -python example.i
$ g++ -c -fPIC example.cxx example_wrap.cxx -I/usr/include/python2.7/
$ g++ -shared example.o example_wrap.o -o _example.so

4. Use in python shell 4.在python shell中使用

In [1]: import example

In [2]: example.doSomeWithCallback()
Must set callback first!

In [3]: callback = example.Callback()

In [4]: example.setCallback(callback)

In [5]: example.doSomeWithCallback()
This print from C++: n = 0

In [6]: class Callback(example.Callback):
   ...:     def run(self, n):
   ...:         print 'This print from Python: n =', n
   ...:         

In [7]: callback =  Callback()

In [8]: example.setCallback(callback)

In [9]: example.doSomeWithCallback()
This print from Python: n = 1

5. Other 5.其他

I think there's more thing you need. 我想你需要的更多东西。 Try: 尝试:

$ ls swig-x.x.x/Examples/python

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM