1. 程式人生 > 其它 >pybind11使用記錄---使用cmake編譯c++工程為python庫

pybind11使用記錄---使用cmake編譯c++工程為python庫

技術標籤:程式設計c++python


前言:

因為最近c++下的工程需要在python下呼叫,所以需要把c++編譯成可供python呼叫的庫,記錄一下具體做法:

編譯c++有多種方法,因為我的是cmake構建的工程,所以直接在cmake的基礎上編譯成python庫(c++中用了第三方庫opencv和boost)


下載編譯pybind11

下載pybind11:git clone https://github.com/pybind/pybind11.git

安裝pytest: pip install pytest

編譯:

cd pybind11
mkdir build
cd build
cmake ..
cmake --build . --config Release --target check

操作C++程式碼

我的做法是將編譯好的pybind11資料夾拷貝到了c++工程目錄下(這樣是方便在編譯c++的時候能找到pybind11,當然你也可以通過其他方式,只要能找到pybind11就行)

(我只封裝了所需要的函式介面)

將你需要的函式介面定義在一個.cpp檔案中,比如說,我在pcc.cpp檔案中定義了兩個函式(該包含的.h檔案就正常包含):

int pcc_encoder() 和 int pcc_decoder()

然後在此檔案中加入如下程式碼:

#include <pybind11/pybind11.h>

namespace py = pybind11;
PYBIND11_PLUGIN(Pypcc) {
    py::module m("Pypcc", "pcc python module");

    m.def("pcc_encoder", &pcc_encoder, "Encoder the pointcloud data");
    m.def("pcc_decoder", &pcc_decoder, "Decoder the pointcloud data");

    return m.ptr();
}

其中比較重要的是這兩句:

m.def("pcc_encoder", &pcc_encoder, "Encoder the pointcloud data");

m.def("pcc_decoder", &pcc_decoder, "Decoder the pointcloud data");

這兩句程式碼表明瞭可供python呼叫的函式介面,沒有在此操作的函式python是找不到的

修改CMakeLists.txt

(只需改動幾處即可,第三方庫的include和連結基本和c++編譯一樣)

PROJECT(compression)
CMAKE_MINIMUM_REQUIRED(VERSION 2.1.8)

add_definitions(-std=c++11)

find_package(Boost COMPONENTS system thread program_options) 
find_package(OpenCV QUIET)

set(LIBRARY_OUTPUT_PATH ../)

set(dso_SOURCE_FILES
    ${PROJECT_SOURCE_DIR}/src/decoder.cpp
    ${PROJECT_SOURCE_DIR}/src/encoder.cpp
    ${PROJECT_SOURCE_DIR}/src/utils.cpp
    ${PROJECT_SOURCE_DIR}/src/io.cpp
    ${PROJECT_SOURCE_DIR}/src/pcc_module.cpp
)

#add_library(pcc SHARED ${dso_SOURCE_FILES})

include_directories(
  ${PROJECT_SOURCE_DIR}/include
  ${OpenCV_INCLUDE_DIRS} 
  ${Boost_INCLUDE_DIRS}
)

add_subdirectory(pybind11)
pybind11_add_module(Pypcc ${PROJECT_SOURCE_DIR}/src/pcc.cpp ${dso_SOURCE_FILES})
#add_executable(pcc_test ${PROJECT_SOURCE_DIR}/src/pcc_test.cpp ${dso_SOURCE_FILES})
#add_executable(pcc_test ${PROJECT_SOURCE_DIR}/src/pcc_test.cpp)
target_link_libraries(Pypcc PRIVATE
  ${Boost_LIBRARIES}
  ${OpenCV_LIBS}
)

可以看到只需要修改原本檔案的幾個地方就可以:

set(LIBRARY_OUTPUT_PATH ../)

這個的意思是設定生成的python庫的位置

add_subdirectory(pybind11)

這個是用來增加pybind11的目錄,因為之前我把pybind11放在了c++的工程目錄下,所以可以直接順利找到

pybind11_add_module(Pypcc ${PROJECT_SOURCE_DIR}/src/pcc.cpp ${dso_SOURCE_FILES})

這個的意思對應於編譯c++的add_executable命令,就是說把哪些檔案編譯成python庫,Pypcc是取的庫的名字

target_link_libraries(Pypcc PRIVATE

${Boost_LIBRARIES}

${OpenCV_LIBS}

)

這裡是連結第三方庫,注意要加上PRIVATE

然後按照一般的c++編譯步驟就可以了,編譯完成之後我們可以看到在指定目錄下生成.so檔案

編寫python呼叫檔案

import Pypcc        #匯入.so模組
Pypcc.XXX()         #呼叫模組的函式

這裡說明一下:因為我的.so檔案放在了和.py檔案同一目錄下,所以可以直接匯入

遇到的問題:

  • 因為我的.so檔案是在python3.6的環境下編譯的,所以python2.7環境下匯入報出沒有這個模組的錯誤,原因到底是編譯c++環境的原因,還是一開始編譯pybind11預設的python環境的原因?後續可以驗證一下
  • 因為我需要傳二維陣列給這個引數,考慮到python和c++之間用純c語言“溝通”可能會好一點,所以打算以這樣的方式傳參:

float points[][4]

int nums

但是在編譯的時候報出錯誤:

cannot convert ‘pybind11::detail::type_caster<float, void>::cast_op_type<float (*&&)[4]> {aka float*}’ to ‘float (*)[4]’ in argument passing

看到有人說pybind11不支援這種指標型別???這裡還沒弄清楚

後面我的解決方法是使用STL vector進行傳參的,c++中的STL與python是有型別對應的:

前提是要在之前的.cpp檔案中包含一個頭檔案

#include <pybind11/stl.h>

  • 在c++中,一般在.h中宣告一個函式的時候,可以賦給引數預設值,這樣在呼叫的時候如果預設值的引數沒有傳進來,那麼函式就是用其預設值,但是如果某個函式作為python介面供python呼叫,即使在c++中聲明瞭預設值,python呼叫的時候也要將全部引數傳參,否則會出現引數不匹配的錯誤