核心思路:
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()