核心思路:
python的底层是用C实现的,所以理论上python可以调用C的代码。
如果想调用C++的代码,需要另外将C++代码通过extern "C"包含起来,使其命名修饰等符合C的规则
然后将C/C++代码打包成一个.so文件(这一步有个坑,我用bazel编译的.so被import到flask项目中时,会导致整个服务以exit code 245退出,但是直接在python脚本里调用是没问题的;后来换成用make编译,flask中的问题就没了)
通过python的ctypes加载这个.so文件(注意,如果C/C++代码生成的.so文件还要依赖其他的.so,需要一并在python中加载进来,先加载被依赖的.so。建议尽量将所有相关代码一次性编译在同一个.so中)
然后在python中指定每一个extern "C"中接口的输入输出类型
在python代码里,(据我所知,如果有误欢迎更正)是不能直接得到一个C++对应的对象,然后直接调用该对象的方法的。能做的只是加载.so后得到一个对象,通过这个对象调用extern "C"中封装的一个接口。
有两种做法可以达到调用C++类方法的目的:
1、在extern “C”中定义一个接口,返回想要调用的类实例指针,其他的接口接受类实例指针作为参数,再去调用类方法
2、在extern “C”中定义一个全局的类实例,其他接口通过该全局实例去执行类方法。
第二种可能有一些竞争条件存在,多线程访问容易出问题,第一种调用比较好控制访问情况。但是第一种情况需要每次都生成一个新的对象,如果生成对象的代价比较大,第二方法会更好些。
做法1:
想调用trajectory_generator.h中的类方法TrajectoryGeneratorAdapter::GetDetailedPathPoints()方法
C++部分:
// filename: trajectory_generator_wrapper.cc
#include "modules/simulation/common/trajectory_generator.h"
extern "C" {
TrajectoryGeneratorAdapter *GetTrajGenInstance() {
return new TrajectoryGeneratorAdapter();
}
int GetDetailedPathPoints(TrajectoryGeneratorAdapter *traj_gen,
const double *sample_xs, int sample_point_num) {
std::vector<PointState> sample_points;
for (int i = 0; i < sample_point_num; i++) {
sample_points.emplace_back(sample_xs[i], sample_ys[i], 0, sample_vels[i],
sample_timestamps[i]);
}
return traj_gen->GetDetailedPathPoints(sample_points);
}
double x(TrajectoryGeneratorAdapter *traj_gen, int detailed_point_index) {
return traj_gen->x(detailed_point_index);
}
} // end extern "C"
makefile
CFLAGS=-fPIC -W -shared -I ~/roadstar -I /usr/include/eigen3 --std=c++11
all: libtrajectory_generator_wrapper.so
libtrajectory_generator_wrapper.so: trajectory_generator.cc trajectory_generator_wrapper.cc
g++ ${CFLAGS} $^ -o bin/$@
clean:
rm -rf bin/*
python部分:
把用上述makefile编译得到的.so文件放到与下面的python同目录下(不同目录也行,只要ctypes.CDLL的参数能找到正确的地方)
import ctypes
import os
script_path = os.path.abspath(__file__)
script_dir, script_name = os.path.split(script_path)
ctrajgen = ctypes.CDLL(
"/".join((script_dir, "libtrajectory_generator_wrapper.so")))
class TrajectoryGenerator(object):
def __init__(self):
ctrajgen.GetTrajGenInstance.argtypes = None
ctrajgen.GetTrajGenInstance.restype = ctypes.c_voidp
self.__trajgen = ctrajgen.GetTrajGenInstance()
ctrajgen.x.argtypes = [ctypes.c_voidp, ctypes.c_int]
ctrajgen.x.restype = ctypes.c_double
def x(self, i):
return ctrajgen.x(self.__trajgen, i)
def fit(self, xs, point_num):
assert point_num == len(xs)
self.prepare(point_num)
c_xs = (ctypes.c_double * len(xs))(*xs)
return ctrajgen.GetDetailedPathPoints(self.__trajgen, c_xs, point_num)
def prepare(self, point_num):
DoubleArray = ctypes.c_double * point_num
#注意这里,GetDetailedPathPoints的第二个参数是一个变长数组,但我所知道的是argtypes只能制定固定长度的数组
#所以我每次调用这个方法之前,都重新设置一下argtypes,经测试这样是有效的
ctrajgen.GetDetailedPathPoints.argtypes = [ctypes.c_voidp, DoubleArray, ctypes.c_int]
ctrajgen.GetDetailedPathPoints.restype = ctypes.c_int
最终在需要使用的地方使用上述python代码中TrajectoryGenerator对象,调用其fit方法达到调用GetDetailedPathPoint的目的
做法2:
这里给出一个不带参数的简单例子,带参数需要的东西应该和做法1中的例子类似
C++部分
#include <iostream>
using namespace std;
class TestLib
{
public:
void func();
};
void TestLib::func() {
cout<<"call func"<<endl;
}
extern "C" {
TestLib obj;
void func() {
obj.func();
}
}
python部分:
import ctypes
so = ctypes.cdll.LoadLibrary
lib = so("./libpyapi.so")
lib.func()