PyBind11可以让C++快速和Python进行绑定,有如下情况可以使用
1、我需要使用c++给python写库(打包pyd)
2、我的c++程序需要内嵌python(打包exe)
首先需要明确依赖项,以下是CMake脚本
include_directories("C:/Users/yanyan/AppData/Local/Programs/Python/Python310/include")
include_directories("C:/Users/yanyan/AppData/Local/Programs/Python/Python310/Lib/site-packages/pybind11/include")
link_directories("C:/Users/yanyan/AppData/Local/Programs/Python/Python310/libs")
LINK_LIBRARIES(python3 python310)
解释一下,pybind可以通过pip来安装,并且依赖python的头文件
另外还需要手动把相关的dll放到exe目录python310.dll python3.dll
如果你想把dll文件放到Bin目录里或其他目录,需要设置系统的环境变量PATH
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <pybind11/functional.h>
#include <pybind11/stl.h>
namespace py = pybind11;
不一定都用,但是都导入总没错的,之后就可以愉快的用c++对python进行绑定了
//启动解释器并保持其活动状态
py::module_ sys = py::module_::import("sys");
sys.attr("path")
.attr("append")("./Content/");
try {
mengyaengine = py::module_::import("MengYaEngine");
}
catch (py::error_already_set& e) {
py::print(e.type());
py::print(e.what());
return ;
}
首先sys可以把环境变量添加我们的目录,方便后面找到我们的py脚本
然后导入我们自己的py模块文件,如果出错就打印错误。
PYBIND11_EMBEDDED_MODULE(MengYa, m) {
py::class_<MRect>(m, "MRect")
.def(py::init<int, int, int, int>());
m.def("addPaintWidget", &addPaintWidget);
这是一个很大的宏,我在CLion可以直接看到展开后的样子,有点多我就不发出来了,想了解的可以自己看看。
MengYa是我的import模块的名字,可以自定义
m表示模块,可以绑定普通函数(使用def)
py::class_模板里填需要导出的c++类
py::init<>如果你的类构造函数没东西就不需要加这四个int,我这个类初始化四个整数才要加
有时候我们的c++类需要子类重写,所以要加virtual关键词
这里绑定的时候也跟上面差不多
py::class_<MWidget, PyMWidget>(m, "MWidget")
.def(py::init<>())
.def("setColor", &MWidget::setColor)
.def("setSize", &MWidget::setSize)
.def("setPos", &MWidget::setPos)
.def("paint", &MWidget::paint)
可以看到我们的类是MWidget,但旁边多了个新写的PyMWidget类
原来是pybind11绑定带有虚函数的类时只能自己写个工具类,如下
class PyMWidget : public MWidget {
public:
/* Inherit the constructors */
using MWidget::MWidget;
/* Trampoline (need one for each virtual function) */
void paint() override {
PYBIND11_OVERRIDE(
void, /* Return type */
MWidget, /* Parent class */
paint, /* Name of function in C++ (must match Python name) */
);
}
void mousePressEvent(py::args args) override {
PYBIND11_OVERRIDE(
void,
MWidget,
mousePressEvent,
args
);
}
稍微有点麻烦,不过熟了以后就好了,具体可以看pybind11文档,
override关键词表示重写这个成员函数
如果你的C++类是子类也需要重新抄一遍上述的工具类
py::class_<MButton, MLabel, PyMButton>(m, "MButton")
.def(py::init<>())
.def("setButtonIamge", &MButton::setButtonIamge)
.def("setButtonImageColor", &MButton::setButtonImageColor)
.def("setButtonNormalColor", &MButton::setButtonNormalColor)
.def("setButtonFocusColor", &MButton::setButtonFocusColor)
.def("setButtonPressedColor", &MButton::setButtonPressedColor);
重点看尖括号里面的三个类,MLabel是父类MButton是子类,
PyMButton是继承自MButton的工具类
py::class_<MLayout<MWidget>>(m, "MLayout")
.def(py::init<>())
.def("addWidget", &MLayout<MWidget>::addWidget);
py::class_<MHLayout, MLayout<MWidget>>(m, "MHLayout")
.def(py::init<>());
注意看MLayout<MWidget>,其实模板类也跟上述差不多,详情也看pybind11文档
模板函数也差不多
py::class_<Signal>(m, "Signal")
.def(py::init<>())
.def("Bind", &Signal::Bind<py::function>)
.def("emit", &Signal::emit);
.def_readwrite("signal_click", &MWidget::signal_click)
也跟def差不多
1、可以也把参数的类型绑定上,可以只绑用到的成员函数就行
2、如果出现错误可以先把参数变成void*再手动进行类型转换
补充:pybind11会把常见的类型自动转换,c++类到python相当于把原始指针包装一层
python的函数指针类型是py::function
这是一个类,它的实例可以直接当函数来用,因为operator()()把实例的括号改变了意义。
比如
py::function a;
a();
当然我们上面这个a函数啥都没有,实际上需要python那边传过来的才行,我们自己在c++定义的这个没卵用。上面只是说了a为啥可以当函数来用,但它不是函数指针。
补充std::function可以了解一下,这里我们用不上
py::cpp_function类是py::function的子类
可以在c++里面定义一个这种函数指针,比如下面我的拉姆达表达式
py::cpp_function func([this,widget](){this->paintlist.erase(remove(this->paintlist.begin(), this->paintlist.end(), widget), this->paintlist.end());});
widget->signal_close.Bind((py::function)func);
可以看到还需要转换一下,当然这是因为的Bind模板函数的类型就是py::function
这个地方卡了我很久
另外这个类的参数是py::args args,我们可以给它传递python列表,一个参数顶n个参数
所以调用函数指针的话就方便了,不需要研究c++的可变参数,那玩意有点难搞
MWidget::~MWidget()
{
py::list e;
e.append(this);
signal_close.emit(e);
std::cout << this << "控件被删除" << std::endl;
}
上面这个e就当做py::args类型传递进参数里了,我们的this是c++指针,但是由于之前我们以及绑定了,所以直接这样append不会有问题,pybind11还是非常智能的