当前位置: 首页 > 工具软件 > pybind11 > 使用案例 >

PyBind11踩坑笔记

商品
2023-12-01

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脚本

//启动解释器并保持其活动状态
	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模块文件,如果出错就打印错误。

绑定C++类

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++类

有时候我们的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的工具类

绑定C++模板类

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函数指针给c++调用

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还是非常智能的

 类似资料: