【发布时间】:2018-05-09 17:39:17
【问题描述】:
我有一个 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
这是源代码(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]; }
观察对象在向量中存储为RAW POINTERS。类就是这样设计的,因此容器负责释放其析构函数中的对象,就像在析构函数 for 循环中所做的那样。
在 C++ 代码中,如下所示,将对象 o1 添加到容器 c 中,然后返回给客户端代码
MyContainer* getAContainerWithSomeObjects()
{
MyContainer* c = new MyContainer();
MyObject* o1 = new MyObject();
c.add(o1);
return c;
}
返回的容器拥有它的对象,并负责在完成后释放这些对象。在 C++ 中,在上面的函数退出后访问容器对象就可以了。
使用 Swig 将上述类暴露给 python,需要一个接口文件。这个接口文件是这样的
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
//Expose to Python
%include "freemenot.h"
为了使用 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}
)
使用 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())
此 Python 代码可能看起来不错,但 无法正常工作。问题是,当函数 getContainer() 返回时,对象 o1 的内存被释放,如果 thisown 标志未设置为零。在此行之后访问对象,使用返回的容器将导致灾难。请注意,这本身并没有什么问题,因为这就是 python 垃圾收集的工作方式。
对于上述用例,如果能够在 addObject 函数中设置 python 对象 thisown 标志 inside,将使 C++ 对象在 Python 中可用。 让用户设置这个标志不是好的解决方案。 还可以使用“addObject”函数扩展 python 类,并修改该函数内部的 thisown 标志,从而对用户隐藏这个记忆技巧。
问题是,如何在不扩展类的情况下让 Swig 做到这一点? 我正在寻找使用 typemap,或者可能是 %pythoncode,但我似乎无法找到一个好的工作示例。
上述代码将由调用 Python 解释器的 C++ 程序使用并传递给该程序。 C++ 程序负责管理在 python 函数中分配的内存,即使在 PyFinalize() 之后也是如此。
以上代码可从github下载https://github.com/TotteKarlsson/miniprojects
【问题讨论】:
-
这不是真正的 Python 代码。有一个 minimal reproducible example 会有所帮助,但在这种情况下,请查看 SWIG 的 %newobject 指令。
-
非常好的 MCVE!但是我不确定解决方案。
-
所以你想让
addObject自动窃取Python创建的MyObject?您是否要求MyContainer对象可以由 C++ 和 Python 代码创建,还是只能由 Python 代码创建?如果您使用 Python 添加一个从 C++ 开始的实例,您希望发生什么? -
是的,确切地说,addObject 函数应该将对象的 thisown 标志设置为零,对用户隐藏。当容器被销毁或决定销毁其对象时,该对象将被销毁。由于 MyObject 和 MyContainer 都起源于 C++,python 是从 C++ 传递一个 MyContainer 还是创建它都无关紧要。
-
好吧,我要去的地方是 a) 我可以自动复制传递给
addObject的所有内容吗? b)我可以让 MyContainer 在添加到它的所有内容后面持有对 PyObject 的引用。 (除了调整thisown之外,两者都是巧妙的技术,所以我可能会尝试将所有 3 个选项都写出来)。