赞
踩
环境和工具:Windows10、Visual Studio 2019、Python3.10、PowerShell、pip
内容:分别展示了使用Python/C API和pybind11,在Python中调用C++的类、方法、变量的过程。
注意:过程就是把C++文件封装成Python包,在Python中导入包并使用。也就是说,该方法不支持C++类、方法、变量的动态修改,每次对包进行修改,都需要重新编译C++代码。
阅前提醒:本文不包含对于代码写法的解释,只提供了能跑通的代码、使用步骤。如果对代码写法感兴趣,建议搜索词:“Python 扩展 C++”。
目录
在Visual Studio中新建空项目BindingTest。
配置CPython环境。配置方法见博客:C++使用Python/C API_Eliza_Her的博客-CSDN博客
编写myclass.cpp,这里是要被打包的代码,包括下面三个内容:
- #include <iostream>
- #include <string>
- #include <Python.h>
-
- // C++代码
- class MyClass {
- public:
- void printMessage(const std::string& message) {
- std::cout << "Message: " << message << std::endl;
- }
- };
-
- int my_variable = 42;
- void update_variable(int new_value) {
- my_variable = new_value;
- }
编写myClassPy.cpp,这是打包的工具代码,该代码为包定义了四个方法:
- #include <Python.h>
- #include "myclass.cpp"
- #include "valueTest.cpp"
-
- static PyObject* create_instance(PyObject* self, PyObject* args) {
- MyClass* myclass = new MyClass();
- return PyCapsule_New(myclass, "myclass", NULL);
- }
-
- static PyObject* print_message(PyObject* self, PyObject* args) {
- PyObject* capsule;
- const char* message;
- if (!PyArg_ParseTuple(args, "Os", &capsule, &message)) {
- return nullptr;
- }
-
- const char* name = PyCapsule_GetName(capsule);
- if (name == nullptr) {
- return nullptr;
- }
- MyClass* instance = (MyClass*)PyCapsule_GetPointer(capsule, name);
- if (instance == nullptr) {
- return nullptr;
- }
-
- instance->printMessage(message);
- Py_RETURN_NONE;
- }
-
- static PyObject* get_variable(PyObject* self, PyObject* args) {
- return PyLong_FromLong(my_variable);
- }
-
- static PyObject* set_variable(PyObject* self, PyObject* args) {
- int new_value;
- if (!PyArg_ParseTuple(args, "i", &new_value)) {
- return NULL;
- }
-
- update_variable(new_value);
-
- Py_RETURN_NONE;
- }
-
- static PyMethodDef module_methods[] = {
- {"create_instance", create_instance, METH_NOARGS, "Create an instance of MyClass."},
- {"print_message", print_message, METH_VARARGS, "Print a message using MyClass."},
- {"get_variable", get_variable, METH_NOARGS, "Get the value of the variable."},
- {"set_variable", set_variable, METH_VARARGS, "Set the value of the variable."},
- {NULL, NULL, 0, NULL}
- };
-
- static struct PyModuleDef module_definition = {
- PyModuleDef_HEAD_INIT,
- "myclass",
- "A module that exposes a C++ class.",
- -1,
- module_methods
- };
-
- PyMODINIT_FUNC PyInit_myclass(void) {
- return PyModule_Create(&module_definition);
- }
新建setup.py,输入以下内容,其中version和author可省略:
- from distutils.core import setup,Extension
-
- MOD = 'myclass'
- setup(name=MOD,
- version = '1.0',
- author = 'Eliza',
- ext_modules=[Extension(MOD,sources=['myClassPy.cpp'])])
在管理员模式下运行PowerShell,运行以下语句:
- python setup.py build
- python setup.py install
如果没有报错,则可以看到,一开始的代码已经打包成了Python包,包名是myclass。
在python文件中输入以下代码并运行,以测试包的功能:
- import myclass
-
- value = myclass.get_variable()
- print("first use value: %d" %value)
-
- myclass.set_variable(233)
- value = myclass.get_variable()
- print("second use value: %d" %value)
-
- instance = myclass.create_instance()
- myclass.print_message(instance, "hello")
输出:
first use value: 42
second use value: 233
Message: hello
官方文档对于基础用法给出了详细的示例,比本文列出的更广泛:
First steps - pybind11 documentation
Object-oriented code - pybind11 documentation
与Linux环境不同,Windows环境下的pybind11的环境配置方法见该文章:【pybind11入门】Windows下为Python创建C++扩展|安装、CMake编译、调用 - 知乎 (zhihu.com)
官方给出的配置方法:Build systems - pybind11 documentation
我采用了CMake配置法,Windows环境下,使用pybind11为Python扩展C++分为以下步骤(具体操作参考链接):
pip install pytest
- git clone https://github.com/pybind/pybind11
- cd pybind11
- mkdir build
- cd build
- cmake ..
- cmake --build . --config Release --target check
- mkdir build
- cd build
- cmake ..
注意:修改C++代码之后,从第6步开始执行,以获取新的pyd文件 。
新建项目pybindTest,在项目根目录下新建CMakeLists.txt,内容如下:
- cmake_minimum_required(VERSION 3.4)
- project(pybindTest LANGUAGES CXX)
-
- add_subdirectory(pybind11)
- pybind11_add_module(pybindTest my_module.cpp)
my_module.cpp,涉及如下操作:
- #include <pybind11/pybind11.h>
- #include <iostream>
- #include <string>
-
- namespace py = pybind11;
-
- int my_variable = 42;
-
- int add(int x, int y) {
- return x + y;
- }
-
- class MyClass {
- public:
- MyClass(int x, int y) :m_x(x), m_y(y) {}
- void printMessage(const std::string& message) {
- std::cout << "Message: " << message << std::endl;
- }
- void printLocation() {
- std::cout << "Location: (" << m_x << ", " << m_y << ")" << std::endl;
- }
- std::string className = "Eliza";
- private:
- int m_x, m_y;
- };
-
- PYBIND11_MODULE(pybindTest, m) {
- m.doc() = "test pybind11";
- //定义变量
- m.attr("name") = "Eliza";
- m.attr("age") = 108;
-
- //临时定义函数内容
- m.def("get_variable", []() {
- return my_variable;
- }, "get variable");
-
- m.def("set_variable", [](int value) {
- my_variable = value;
- }, "set variable");
-
- //事先定义函数内容,通过py::arg设置参数的默认值
- //只有前两个参数必填,后3个参数可不填
- m.def("add", &add, "A function that adds two numbers",
- py::arg("i") = 1, py::arg("j") = 2);
-
- //类,及其带参方法init和printMessage、动态参数name
- py::class_<MyClass>(m, "MyClass", py::dynamic_attr())
- .def(py::init<int, int>())
- .def("printMessage", &MyClass::printMessage)
- .def("printLocation", &MyClass::printLocation)
- .def_readwrite("name", &MyClass::className);//另一种写法:def_property()
- }
- import pybindTest
-
- print("name: ", pybindTest.name)
- print("age: ", pybindTest.age)
-
- value = pybindTest.get_variable()
- print("Variable value: ", value)
-
- pybindTest.set_variable(100)
- new_value = pybindTest.get_variable()
- print("New variable value: ", new_value)
-
- addNum = pybindTest.add()
- print("Default add result: ", addNum)
- addNum = pybindTest.add(3, 4)
- print("New add result: ", addNum)
-
- myclass = pybindTest.MyClass(1, 3)
- myclass.printLocation()
- print("myclass name: ", myclass.name)
- myclass.name = "Sera"
- print("new myclass name: ", myclass.name)
- myclass.printMessage("Hello")
输出:
name: Eliza
age: 108
Variable value: 42
New variable value: 100
Default add result: 3
New add result: 7
Location: (1, 3)
myclass name: Eliza
new myclass name: Sera
Message: Hello
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。