pybind11封装c++库(二)

        上文介绍了封装c++接口,本文将详细介绍在Windows环境下封装c++库的步骤。

编写c++代码       

#include 
#include 
#include "suber_plus.h"

namespace py = pybind11;

// sub_tick是c++库已定义的结构
// levelmax=10

// 由于pybind11 默认不支持直接绑定固定大小的原始数组类型,尤其是字符数组。所以字符串要转string,数组转std::array
struct stock_tick
{
    std::string symbol;                             // 合约号
    uint64_t data_time;                             // 时间类,格式为YYYYMMDDHHMMSSsss
    double last_price;                              // 最新价
    double pre_close_price;                         // 昨收盘
    double open_price;                              // 今开盘
    double high_price;                              // 最高价
    double low_price;                               // 最低价
    double close_price;                             // 今收盘
    double upper_limit_price;                       // 涨停价
    double lower_limit_price;                       // 跌停价
    uint64_t qty;                                   // 成交量
    double turnover;                                // 成交金额
    uint64_t tick_volume;                           // 成交量(分量)
    double amount;                                  // 成交金额(分量)
    std::array bid;               // 十档申买价
    std::array ask;               // 十档申卖价
};


// 定义 Python 回调类
class PySuberPlusSpi : public SuberPlusSpi {
public:
    using SuberPlusSpi::SuberPlusSpi;  // 继承构造函数

    // 回调方法
    void onConnected() override {
        PYBIND11_OVERLOAD_PURE(void, SuberPlusSpi, onConnected);
    }

    void onDisconnect() override {
        PYBIND11_OVERLOAD_PURE(void, SuberPlusSpi, onDisconnect);
    }

    void onError(int errorType, const char* errMsg) override {
        PYBIND11_OVERLOAD_PURE(void, SuberPlusSpi, onError, errorType, errMsg);
    }
    
    void onAfterSub(int bRet, const char* retMsg) override {
        PYBIND11_OVERLOAD_PURE(void, NcSuberPlusSpi, onAfterSub, bRet, retMsg);
    }    

    void onDataRecv(void* data, int len, nc_senddatatype msgType) override {
        sub_tick* ps = (sub_tick*)data;
        stock_tick tick{};
        tick.symbol=ps->symbol;
        tick.data_time=ps->data_time;
        tick.last_price=ps->last_price;
        tick.pre_close_price=ps->pre_close_price;
        tick.open_price=ps->open_price;
        tick.high_price=ps->high_price;
        tick.low_price=ps->low_price;
        tick.close_price=ps->close_price;
        tick.upper_limit_price=ps->upper_limit_price;
        tick.lower_limit_price=ps->lower_limit_price;
        tick.qty=ps->qty;
        tick.turnover=ps->turnover;
        tick.tick_volume=ps->tick_volume;
        tick.amount=ps->amount;
        tick.bid[0]=ps->bid[0];
        tick.ask[0]=ps->ask[0];

        PYBIND11_OVERLOAD_PURE(void, SuberPlusSpi, onDataRecv, tick);
    }
};

// 绑定到 Python
PYBIND11_MODULE(stock_quote, m) {

    // 绑定 StockTickData 结构体
    py::class_(m, "StockTickData")
        .def(py::init<>())
        .def_readwrite("symbol", &stock_tick::symbol)
        .def_readwrite("data_time", &stock_tick::data_time)
        .def_readwrite("last_price", &stock_tick::last_price)
        .def_readwrite("pre_close_price", &stock_tick::pre_close_price)
        .def_readwrite("open_price", &stock_tick::open_price)
        .def_readwrite("high_price", &stock_tick::high_price)
        .def_readwrite("low_price", &stock_tick::low_price)
        .def_readwrite("close_price", &stock_tick::close_price)
        .def_readwrite("upper_limit_price", &stock_tick::upper_limit_price)
        .def_readwrite("lower_limit_price", &stock_tick::lower_limit_price)
        .def_readwrite("qty", &stock_tick::qty)
        .def_readwrite("turnover", &stock_tick::turnover)
        .def_readwrite("tick_volume", &stock_tick::tick_volume)
        .def_readwrite("amount", &stock_tick::amount)
        .def_readwrite("bid", &stock_tick::bid)
        .def_readwrite("ask", &stock_tick::ask);

    // 绑定 回调类
    py::class_(m, "SuberPlusSpi")
        .def(py::init<>())
        .def("onConnected", &SuberPlusSpi::onConnected)
        .def("onDisconnect", &SuberPlusSpi::onDisconnect)
        .def("onError", &SuberPlusSpi::onError)
        .def("onAfterSub", &NcSuberPlusSpi::onAfterSub)
        .def("onDataRecv", &SuberPlusSpi::onDataRecv);

    // 绑定 SuberPlus 抽象类
    py::class_> suber_plus(m, "SuberPlus");
    suber_plus.def("initSuber", &SuberPlus::initSuber)
        .def("connectServer", &SuberPlus::connectServer)
        .def("disconnect", &SuberPlus::disconnect)
        .def("subData", &SuberPlus::subData);

    // 绑定 SuberPlusFactory 类
    py::class_(m, "SuberPlusFactory")
        .def_static("instance", &SuberPlusFactory::instance)
        .def("createSuber", &SuberPlusFactory::createSuber);
}

代码解释:

        由于pybind11默认不支持固定大小的原始数组类型,当封装的库里包含了字符串,基本类型数组时,需要把字符串转成string,数组转成array。

        定义Python回调类,继承自c++库的回调类。在这个类里进行数据的转换。

        绑定Python,把定义好的结构体,回调类,抽象类,工厂类全部绑定。

编写CMakeLists.txt

cmake_minimum_required(VERSION 3.4)
project(your_module_name)

# 找到 pybind11  (D:/Program Files/python3/Lib/site-packages/pybind11 替换成环境下的实际pybind11包路径)
set(pybind11_DIR "D:/Program Files/python3/Lib/site-packages/pybind11/share/cmake/pybind11")
find_package(pybind11 REQUIRED)

# 设置 Python 库和头文件路径  (D:/Program Files/python3 替换成环境下的实际Python安装路径)
set(PYTHON_LIBRARY "D:/Program Files/python3/lib")
set(PYTHON_INCLUDE_DIR "D:/Program Files/python3/include")

# 设置头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置库文件路径
link_directories(${PROJECT_SOURCE_DIR}/lib)

# 添加模块
pybind11_add_module(your_module_name *.cpp)

# 设置输出目录和文件名
set_target_properties(your_module_name PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/python_modules
    SUFFIX ".pyd"
    OUTPUT_NAME "your_module_name"
)

if(WIN32)
    target_link_libraries(stock_quote PRIVATE suber)  # 添加依赖库
    target_link_libraries(your_module_name PRIVATE pybind11::module)
endif()

        设置目标库的头文件和库文件路径

# 设置头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 设置库文件路径
link_directories(${PROJECT_SOURCE_DIR}/lib)

        设置库的链接。

target_link_libraries(stock_quote PRIVATE suber)  # 添加依赖库

项目结构:

project/
├── CMakeLists.txt
├── stock_quote.cpp
├── include/
│   └── *.h
└── lib/
    └── *.lib

生成Python module  

mkdir build
cd build
cmake ..
make
# 没有装make执行下面命令
# cmake --build . --config Release

把生成的pyd文件拷贝到Python安装包的路径下,即python3/Lib/site-packages/stock_quote下。

把目标库的dll文件以及它依赖的其他dll全部拷贝到相同路径下。

如果拷贝不全,则会报错:ImportError: DLL load failed while importing stock_quote: 找不到指定的模块。可以利用工具depends来检查dll的依赖项,并补全依赖dll。

在Python中使用

import sys
import time
sys.path.append('D:/Program Files/python3/Lib/site-packages/stock_quote')
import stock_quote  

# 获取工厂实例
factory = stock_quote.SuberPlusFactory.instance()

# 创建 SuberPlus 实例
suber = factory.createSuber()

# 创建回调类实例
class MyCallback(stock_quote.SuberPlusSpi):
    def onConnected(self):
        print("Connected")
        suber.subData("300687SZ", 1)

    def onDisconnect(self):
        print("Disconnected")

    def onError(self, errorType, errMsg):
        print(f"Error: {errorType}, {errMsg}")

    def onAfterSub(self, bRet, retMsg):
        print(f"Error: {bRet}, {retMsg}")

    def onDataRecv(self, data):
        print(f"onDataRecv: {data.symbol}, {data.last_price}")

callback = MyCallback()
suber.initSuber("10.10.*.*", 1258, False)
suber.registerSpi(callback)
suber.connectServer()
time.sleep(100)

print("end")

你可能感兴趣的:(c++,python)