在python里调用C++带参数的类方法

核心思路:

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()  

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章