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

 

 

 

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