C++和python使用boost库混合编程

Thursday, March 9, 2023
本文共1759字
4分钟阅读时长

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/posts/c++%E5%92%8Cpython%E4%BD%BF%E7%94%A8boost%E5%BA%93%E6%B7%B7%E5%90%88%E7%BC%96%E7%A8%8B/。商业转载请联系作者获得授权,非商业转载请注明出处!

Wherever a man may happen to turn, whatever a man may undertake, he will always end up by returning to the path which nature has marked out for him. — Johann Wolfgang von Goethe

注意,请保证你的机器上下载了boost库,如果没有,Ubuntu系统下使用:

apt-get install libboost-all-dev

Windows请前往Boost Downloads下载

01_HelloWorld——简单示例

参考自Boost(2):boost.python库介绍及简单示例_boost python_翔底的博客-CSDN博客

假设工作目录如下:

.
├── 01_HelloWorld
│   ├── CMakeLists.txt
│   ├── hello.py
│   └── HelloWorld.cpp
└── CMakeLists.txt

在主目录的CMakeLists.txt:

# 设置项目名称为 Boost_Test
project(Boost_Test)

# 指定 CMake 最低版本号
cmake_minimum_required(VERSION 2.8.3)
set(CMAKE_VERBOSE_MAKEFILE ON)

# 查找 Python 解释器和 Python 库
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)

# 查找 Boost 库的 Python 组件
# 根据不同的 Boost 版本,Python 组件的名称可能是 python、python2、python27、python3、python36、python37 等
list(
  APPEND _components
    python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
    python${PYTHON_VERSION_MAJOR}
    python
  )

# 输出 BOOST_ROOT 变量的值
message(BOOST_ROOT " ${BOOST_ROOT}")

# 设置变量
set(_boost_python_found "")
set(Boost_NAMESPACE "libboost")
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)

# 循环查找 Boost 库的 Python 组件
foreach(_component IN ITEMS ${_components})
  find_package(Boost COMPONENTS ${_component})
  if(Boost_FOUND)
    set(_boost_python_found ${_component})
    break()
  endif()
endforeach()

# 如果没有找到符合要求的 Boost.Python 组件,则输出错误信息
#if(_boost_python_found STREQUAL "")
#  message(FATAL_ERROR "No matching Boost.Python component found")
#endif()

# 添加头文件搜索路径
include_directories("${PYTHON_INCLUDE_DIRS}")
include_directories("${Boost_INCLUDE_DIRS}")

# 输出变量的值
message(PYTHON_INCLUDE_DIRS " ${PYTHON_INCLUDE_DIRS}")
message(PYTHON_LIBRARIES " ${PYTHON_LIBRARIES}")
message(Boost_INCLUDE_DIRS " ${Boost_INCLUDE_DIRS}")
message(Boost_LIBRARIES " ${Boost_LIBRARIES}")

# 添加子目录 01_HelloWorld
ADD_SUBDIRECTORY(01_HelloWorld)

然后在01_HelloWorld文件夹下的CMakeLists.txt中控制动态共享库的生成:

# 设置 MODULE_NAME 变量的值为 hello
set(MODULE_NAME hello)

set(source_code_dir ${CMAKE_SOURCE_DIR}/01_HelloWorld)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${source_code_dir}/cpplib)
# 添加头文件搜索路径为 CMAKE_SOURCE_DIR
include_directories(${CMAKE_SOURCE_DIR})

# 添加动态库,名称为 MODULE_NAME 变量的值
add_library(${MODULE_NAME} SHARED
	HelloWorld.cpp
	)

# 根据操作系统设置动态库属性
if (UNIX)
  set_target_properties(${MODULE_NAME}
    PROPERTIES
    PREFIX ""
  )
elseif (WIN32)
  set_target_properties(${MODULE_NAME}
  PROPERTIES
  SUFFIX ".pyd"
  )
endif()

# 链接 Boost 和 Python 库
target_link_libraries(${MODULE_NAME}
  ${Boost_LIBRARIES}
  ${PYTHON_LIBRARIES}
)

然后我们在HelloWorld.cpp中写入如下代码:

#include <iostream>
#include <string>

// 原始C++函数
std::string greet()
{
    std::string str;
    std::cout << "Please input your name: ";
    std::getline(std::cin,str);
    return str + ",hello!";
}

#include <boost/python.hpp>
// hello是python模块的名字,请务必和导入的python模块名一致
BOOST_PYTHON_MODULE(hello)
{
    using namespace boost::python;
    // 将greet函数定义为python中的greet函数
    def("greet", greet);
}

然后我们使用cmake构建:

mkdir build
cd build
cmake ..
make

这时会在01_HelloWorld生成cpplib\hello.so,然后由于这个动态文件已经为python定义了接口,所以python可以直接将其当成普通模块使用:

#!/usr/bin/env python
import cpplib.hello as hello

print (hello.greet())

输出如下:

文内图片

show01——更复杂一些的例子

假设我们想要编写一个类,当其初始化的时候会读入二进制文件,然后将其转化为十六进制或二进制输出

这一次我们将boost.python模块的代码放到单独的一个C++文件中,其他的代码按照头文件-源文件的组织方式编写,然后我们使用一个函数来返回C++对象,让它能够被python的GC机制托管

生成模块的代码show01py.cpp

#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/manage_new_object.hpp>
#include <boost/python/return_value_policy.hpp>

#include "show01.hpp"

BinaryFile* get_binary_file_obj(std::string filename){
    return new BinaryFile(filename);
}

BOOST_PYTHON_MODULE(show01)
{
    using namespace boost::python;
    def("get_binary_file_obj", get_binary_file_obj, return_value_policy<manage_new_object>());
    class_<BinaryFile>("BinaryFile", init<std::string>())
        .def("get_hex", &BinaryFile::get_hex)
        .def("get_binary", &BinaryFile::get_binary)
        ;
}

show01.hpp & show01.cpp

头文件:

#ifndef __SHOW01_SHOW01_H__
#define __SHOW01_SHOW01_H__
#include <fstream>
#include <string>

class BinaryFile
{
public:
    BinaryFile() = delete;
    BinaryFile(std::string filename);
    ~BinaryFile();

    // 定义其他函数
    std::string get_hex(){ return hex_result_; }
    std::string get_binary(){ return binary_result_; }

private:
    std::ifstream&  fin;
    std::string hex_result_;
    std::string binary_result_;
    void read_content();
    std::string to_hex(char ch);
    std::string to_bin(char ch);
    bool check_exist();
};

#endif /* __SHOW01_SHOW01_H__ */

源文件:

#include <iostream>
#include <sstream>
#include <ctype.h>

#include "show01.hpp"

BinaryFile::BinaryFile(std::string filename)
    :fin(*(new std::ifstream(filename))), hex_result_(""), binary_result_("")
{
    if (!check_exist())
    {
        hex_result_ = "cannot open the file!";
        binary_result_ = "cannot open the file!";
        return;
    }else{
        read_content();
    }
}

BinaryFile::~BinaryFile()
{
    fin.close();
    delete &fin;
    hex_result_ = "Deleted!";
    binary_result_ = "Deleted!";
}

void BinaryFile::read_content()
{
    char hex_char[17] = {0};
    std::stringstream hss;
    hss << "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | Decoded Text   \n";
    hss << "------------------------------------------------|----------------\n";
    std::stringstream bss;
    char buf = 0;
    char cnt = 0;
    char hexcnt = 0;
    while (fin.get(buf)) { // 一个字节一个字节地读取文件
        // 处理读取到的字节
        hss << to_hex(buf) << ' ';
        if (isprint(buf))
        {
            hex_char[hexcnt++] = buf;
        }else
        {
            hex_char[hexcnt++] = '.';
        }
        bss << to_bin(buf) << ' ';
        ++cnt;
        if (cnt >= 0x10)
        {
            hss << "| " << hex_char << '\n';
            hexcnt = 0;
            bss << '\n';
            cnt = 0;
        }
    }
    hex_result_ = hss.str();
    binary_result_ = bss.str();
}

std::string BinaryFile::to_hex(char ch)
{
    const static char hex_dict[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    char ret[3] = {0};
    for(int i = 0; i < 2; i++)
    {
        ret[1-i] = hex_dict[(ch >> (i*4)) & 0xf];
    }
    return ret;
}

std::string BinaryFile::to_bin(char ch)
{
    char ret[9] = {0};
    for(int i = 0; i < 8; i++)
    {
        ret[7-i] = ((ch >> i) & 0x1) + '0';
    }
    return ret;
}

bool BinaryFile::check_exist()
{
    if(!this->fin.is_open())
    {
        std::cerr << "cannot open the file!" << std::endl;
        return false;
    }
    return true;
}

子目录的CMakeLists.txt

# 设置 MODULE_NAME 变量的值为 hello
set(MODULE_NAME show01)
set(CMAKE_CXX_STANDARD 20)
set(source_code_dir ${CMAKE_SOURCE_DIR}/show01)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${source_code_dir}/cpplib)
# 添加头文件搜索路径为 CMAKE_SOURCE_DIR
include_directories(${CMAKE_SOURCE_DIR})

set(sourcefiles 
show01.cpp
show01py.cpp)
# 添加动态库,名称为 MODULE_NAME 变量的值
add_library(${MODULE_NAME} SHARED
  ${sourcefiles}
	)

# 根据操作系统设置动态库属性
if (UNIX)
  set_target_properties(${MODULE_NAME}
    PROPERTIES
    PREFIX ""
  )
elseif (WIN32)
  set_target_properties(${MODULE_NAME}
  PROPERTIES
  SUFFIX ".pyd"
  )
endif()

# 链接 Boost 和 Python 库
target_link_libraries(${MODULE_NAME}
  ${Boost_LIBRARIES}
  ${PYTHON_LIBRARIES}
)

show01.cpp

import cpplib.show01 as show01

obj = show01.get_binary_file_obj("/cpp/FileAnalyst/show01/test")
print(obj.get_hex())
# print(obj.get_binary())

用于分析的二进制文件test.cpp->test

#include <iostream>

int main(int argc, char const *argv[])
{
    std::cout << "Hello World!" << std::endl;
    return 0;
}

编译完成后生成ELF文件test

如何跑起来

首先我们主目录的CMakeLists.txt和01_HelloWorld中的基本一样,只用在末尾加一句:

ADD_SUBDIRECTORY(show01)

因此我们的组织形式如下:

文内图片

然后和之前一样的方法cmake就行,或者使用vscode的cmake插件

最后使用命令运行python文件:

python show01.py > hex_output.txt

结果:

文内图片

头部的ELF魔数说明对这个ELF文件的输出是正确的