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
- #include
- #include
- #include
- 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_
(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_
(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_
(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_
>(m, "MLayout") - .def(py::init<>())
- .def("addWidget", &MLayout
::addWidget); - py::class_
>(m, "MHLayout") - .def(py::init<>());
注意看MLayout
模板函数也差不多
- py::class_
(m, "Signal") - .def(py::init<>())
- .def("Bind", &Signal::Bind
) - .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还是非常智能的