我有一个 swigged C++ 类容器MyContainer,保存MyObject类型的对象,也是一个 C++ 类。
以下是C++头代码(freemenot.h)
#ifndef freemenotH
#define freemenotH
#include <vector>
#include <string>
using std::string;
class MyObject
{
public:
MyObject(const string& lbl);
~MyObject();
string getLabel();
private:
string label;
};
class MyContainer
{
public:
MyContainer();
~MyContainer();
void addObject(MyObject* o);
MyObject* getObject(unsigned int t);
int getNrOfObjects();
private:
std::vector<MyObject*> mObjects;
};
#endif
Run Code Online (Sandbox Code Playgroud)
这是源代码(freemenot.cpp)
#include "freemenot.h"
#include <iostream>
using namespace std;
/* MyObject source */
MyObject::MyObject(const string& lbl)
:
label(lbl)
{ cout<<"In object ctor"<<endl; }
MyObject::~MyObject() { cout<<"In object dtor"<<endl; }
string MyObject::getLabel() { return label; }
/* MyContainer source */
MyContainer::MyContainer() { cout<<"In container ctor"<<endl; }
MyContainer::~MyContainer()
{
cout<<"In container dtor"<<endl;
for(unsigned int i = 0; i < mObjects.size(); i++)
{
delete mObjects[i];
}
}
int MyContainer::getNrOfObjects() { return mObjects.size(); }
void MyContainer::addObject(MyObject* o) { mObjects.push_back(o); }
MyObject* MyContainer::getObject(unsigned int i) { return mObjects[i]; }
Run Code Online (Sandbox Code Playgroud)
观察对象作为原始指针存储在向量中。该类就是这样设计的,因此容器负责释放其析构函数中的对象,就像在析构函数 for 循环中完成的那样。
在C++代码中,如下所示,对象o1被添加到容器c中,该对象返回给客户端代码
MyContainer* getAContainerWithSomeObjects()
{
MyContainer* c = new MyContainer();
MyObject* o1 = new MyObject();
c.add(o1);
return c;
}
Run Code Online (Sandbox Code Playgroud)
返回的容器拥有其对象,并负责在完成后取消分配这些对象。在 C++ 中,在上面的函数退出后访问容器对象就可以了。
使用 Swig 将上述类公开给 python,将需要一个接口文件。这个接口文件看起来像这样
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
//Expose to Python
%include "freemenot.h"
Run Code Online (Sandbox Code Playgroud)
为了使用 CMake 生成 Python 模块,使用了以下 CMake 脚本。
cmake_minimum_required(VERSION 2.8)
project(freemenot)
find_package(SWIG REQUIRED)
include(UseSWIG)
find_package(PythonInterp)
find_package(PythonLibs)
get_filename_component(PYTHON_LIB_FOLDER ${PYTHON_LIBRARIES} DIRECTORY CACHE)
message("Python lib folder: " ${PYTHON_LIB_FOLDER})
message("Python include folder: " ${PYTHON_INCLUDE_DIRS})
message("Python libraries: " ${PYTHON_LIBRARIES})
set(PyModule "freemenot")
include_directories(
${PYTHON_INCLUDE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}
)
link_directories( ${PYTHON_LIB_FOLDER})
set(CMAKE_MODULE_LINKER_FLAGS ${CMAKE_CURRENT_SOURCE_DIR}/${PyModule}.def)
set_source_files_properties(${PyModule}.i PROPERTIES CPLUSPLUS ON)
set_source_files_properties(${PyModule}.i PROPERTIES SWIG_FLAGS "-threads")
SWIG_ADD_LIBRARY(${PyModule}
MODULE LANGUAGE python
SOURCES ${PyModule}.i freemenot.cpp)
SWIG_LINK_LIBRARIES (${PyModule} ${PYTHON_LIB_FOLDER}/Python37_CG.lib )
# INSTALL PYTHON BINDINGS
# Get the python site packages directory by invoking python
execute_process(COMMAND python -c "import site; print(site.getsitepackages()[0])" OUTPUT_VARIABLE PYTHON_SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE)
message("PYTHON_SITE_PACKAGES = ${PYTHON_SITE_PACKAGES}")
install(
TARGETS _${PyModule}
DESTINATION ${PYTHON_SITE_PACKAGES})
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PyModule}.py
DESTINATION ${PYTHON_SITE_PACKAGES}
)
Run Code Online (Sandbox Code Playgroud)
使用 CMake 生成 make 文件,并使用 borlands bcc32 编译器进行编译,生成Python 模块 ( freemenot ) 并将其安装到 python3 有效的 sitepackages 文件夹中。
然后,在Python中,可以使用以下脚本来说明问题
import freemenot as fmn
def getContainer():
c = fmn.MyContainer()
o1 = fmn.MyObject("This is a label")
o1.thisown = 0
c.addObject(o1)
return c
c = getContainer()
print (c.getNrOfObjects())
#if the thisown flag for objects in the getContainer function
#is equal to 1, the following call return an undefined object
#If the flag is equal to 0, the following call will return a valid object
a = c.getObject(0)
print (a.getLabel())
Run Code Online (Sandbox Code Playgroud)
此 Python 代码可能看起来不错,但无法按预期工作。问题是,当函数 getContainer() 返回时,如果thisown标志未设置为零,则对象 o1 的内存将被释放。使用返回的容器访问此行之后的对象将导致灾难。观察一下,这本身并没有什么问题,因为这就是 python 垃圾收集的工作原理。
对于上述用例,能够在 addObject 函数内设置 python 对象thisown标志,将使 C++ 对象在 Python 中可用。让用户设置此标志并不是一个好的解决方案。人们还可以使用“addObject”函数扩展 python 类,并修改该函数内的 thisown 标志,从而对用户隐藏此内存技巧。
问题是,如何让 Swig 在不扩展课程的情况下做到这一点?我正在寻找使用typemap,或者可能是%pythoncode,但我似乎无法找到一个好的工作示例。
上面的代码将由调用 Python 解释器的 C++ 程序使用并传递给该程序。C++ 程序负责管理 python 函数中分配的内存,即使在 PyFinalize() 之后也是如此。
上述代码可以从github下载https://github.com/TotteKarlsson/miniprojects
有许多不同的方法可以解决这个问题,所以我将尝试依次解释它们,并在此过程中建立一些内容。希望这对于了解 SWIG 的选项和内部结构很有用,即使您只需要第一个示例。
thisown添加Python代码直接修改最类似于您提出的解决方案依赖于使用 SWIG 的%pythonprepend指令来添加一些额外的 Python 代码。您可以根据您关心的重载的 C++ 声明来定位它,例如:
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%pythonprepend MyContainer::addObject(MyObject*) %{
# mess with thisown
print('thisown was: %d' % args[0].thisown)
args[0].thisown = 0
%}
//Expose to Python
%include "freemenot.h"
Run Code Online (Sandbox Code Playgroud)
唯一值得注意的怪癖来自于参数是使用*args而不是命名参数传递的,因此我们必须通过位置号来访问它。
SWIG Python 文档中还有其他几个地方/方法可以注入额外的 Python 代码(前提是您没有使用-builtin),并且猴子修补也始终是一种选择。
thisown下一个可能的选择是使用类型映射调用 Python C API 来执行等效功能。在本例中,我匹配了参数类型和参数名称,但这确实意味着此处的类型映射将应用于接收命名的所有MyObject *函数o。(这里最简单的解决方案是让名称描述标题中的预期语义(如果当前会过度匹配),这具有使 IDE 和文档更清晰的副作用)。
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%typemap(in) MyObject *o {
PyObject_SetAttrString($input, "thisown", PyInt_FromLong(0)); // As above, but C API
$typemap(in,MyObject*); // use the default typemap
}
//Expose to Python
%include "freemenot.h"
Run Code Online (Sandbox Code Playgroud)
除了类型映射匹配之外,此示例中最值得注意的一点是使用$typemap此处“粘贴”另一个类型映射,特别是默认的类型映射MyObject*到我们自己的类型映射中。值得一看生成的包装文件内部的前后示例,了解最终的结果。
SwigPyObject结构体的own成员由于我们已经在编写 C++,而不是通过setattrPython 代码中的,我们可以调整此类型映射以使用更多 SWIG 的内部结构,并跳过从 C 到 Python 并再次返回 C 的往返过程。
SWIG 内部有一个结构体,其中包含每个实例的详细信息,包括所有权、类型等。
我们可以直接从我们自己进行转换PyObject*,SwigPyObject*但这需要我们自己编写错误处理/类型检查(这个 PyObject 甚至是 SWIG 吗?),并且依赖于 SWIG 生成 Python 接口的各种不同方式的细节。相反,我们可以调用一个函数来为我们处理所有这些事情,所以我们现在可以像这样编写类型映射:
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%typemap(in) MyObject *o {
// TODO: handle NULL pointer still
SWIG_Python_GetSwigThis($input)->own = 0; // Safely cast $input from PyObject* to SwigPyObject*
$typemap(in,MyObject*); // use the default typemap
}
//Expose to Python
%include "freemenot.h"
Run Code Online (Sandbox Code Playgroud)
这实际上只是之前答案的演变,但纯粹在 SWIG C 运行时中实现。
还有其他方法可以解决此类所有权问题。首先,在这个特定的实例中,您MyContainer假设它始终可以调用delete它存储的每个实例(因此拥有这些语义)。
如果我们也包装这样的函数,则最有启发性的例子是:
MyObject *getInstanceOfThing() {
static MyObject a;
return &a;
}
Run Code Online (Sandbox Code Playgroud)
这给我们之前的解决方案带来了一个问题——我们设置thisown为 0,但这里它已经是 0,所以delete当容器被释放时我们仍然无法合法地调用指针。
有一种简单的方法可以处理这个问题,不需要了解 SWIG 代理内部结构 - 假设MyObject是可复制构造的,那么您可以简单地创建一个新实例,并确保无论它来自哪里,对于容器来说都是合法的删除它。我们可以通过稍微调整我们的类型映射来做到这一点:
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%typemap(in) MyObject *o {
$typemap(in,MyObject*); // use the default typemap as before
$1 = new $*1_type(*$1); // but afterwards call copy-ctor
}
//Expose to Python
%include "freemenot.h"
Run Code Online (Sandbox Code Playgroud)
这里需要注意的一点是使用了更多 SWIG 功能,这些功能让我们知道类型映射输入的类型 -$*1_type是取消引用一次的类型映射参数的类型。我们可以直接写MyObject在这里,因为这就是它解析的内容,但是如果您的容器确实是模板,那么这可以让您处理模板之类的事情,或者在其他类似容器中使用%apply.
现在要注意的是,如果您有一个 C++ 函数,您故意允许该函数返回一个实例,而没有thisown假设容器将取得现在不持有的所有权,那么现在要注意的就是泄漏。
最后,我经常使用的其他技术之一在当前提出的情况下不能直接实现,但值得为后代提及。如果您有机会在容器中的每个实例旁边存储一些附加数据,您可以调用Py_INCREF并保留对底层的引用,PyObject*无论它来自何处。如果您在销毁时获得回调,您还可以调用Py_DECREF并强制 Python 运行时使对象与容器一样保持活动状态。
即使无法保持 1-1 MyObject*/PyObject*配对,您也可以通过在某处保持影子容器处于活动状态来做到这一点。这可能很难做到,除非您愿意将另一个对象添加到容器中,对其进行子类化,或者非常确定容器的初始 Python 实例将始终存在足够长的时间。
| 归档时间: |
|
| 查看次数: |
1532 次 |
| 最近记录: |