上文介绍了封装c++接口,本文将详细介绍在Windows环境下封装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,把定义好的结构体,回调类,抽象类,工厂类全部绑定。
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
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。
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")