Python使用ctypes实现与C++互相调用的实践教程

Written by

in

文章目录
  • 小H上次使用了pybind11来调用C++的方法,这次同样是在项目中遇到了需要在py层调用C++方法的情况,现在对于性能的需求更加敏感,所以需要使用C++的底层方法来获得结果(当然也可能只是因为C++有这个方法,直接拿来用比较方便),选择ctypes应该也是因为这个比较方便吧。
  • 在C++侧,我们需要实现一个函数,然后导出一个动态库方法,这里楼主从py层传入了一个回调函数,返回了对应的字符结果。 在py侧,首先需要使用ctypes.CDLL加载刚才编译出的动态库,声明对应的方法,之后实现一个回调函数进行传入。
  • ctypes:纯 Python 侧绑定 不需要在 C++ 里写任何“绑定代码”,只要提供 C ABI 的动态库。 Python 侧通过 ctypes.CDLL、CFUNCTYPE 等描述函数签名。 只要函数满足“C 风格接口”(例如 extern "C" + 基本类型/指针),不在意具体是由 C 还是 C++ 实现。 非常适合: 已经有现成 C 库 / C 接口的 C++ 库 简单函数调用、回调、不复杂的数据结构 pybind11:在 C++ 侧写绑定,生成 Python 模块 是一个头文件库,写法大致像如之前提供的示例一致 编译后得到的是一个 Python 扩展模块(.so),import mymodule 就像普通 Python 包一样使用。 可以非常自然地暴露: C++ 类、方法、构造函数 STL 容器(std::vector, std::map 等) 智能指针、异常、枚举等 对复杂 C++ API 的封装能力远强于 ctypes。
  • 经过这两次学习,让小H更加能够体会到在代码的世界中,语言并不是孤立的,弱化了对语言的重视程度,学习编程更应该将编程语言看作一种工具,什么时候什么情况该换就换,大家各自完成各自擅长的部分,更有利于找到性能与效率的均衡点。 到此这篇关于Python使用ctypes实现与C++互相调用的实践教程的文章就介绍到这了,更多相关Python与C++互调内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Python使用grpc服务并与C++互相调用 Python中调用C++代码的方法总结 python调用C/C++动态库的实践案例 Python调用C++动态库详细步骤(附源码) C++如何调用python并取返回值 在python中调用C/C++的三种方法 Python中使用ctypes调用C++的方法 python 与c++相互调用实现
  • 目录
    • 背景
    • 示例
      • C++动态库实现
      • python加载动态库并调用
    • ctypes与pybind11区别
      • 结语

        小H上次使用了pybind11来调用C++的方法,这次同样是在项目中遇到了需要在py层调用C++方法的情况,现在对于性能的需求更加敏感,所以需要使用C++的底层方法来获得结果(当然也可能只是因为C++有这个方法,直接拿来用比较方便),选择ctypes应该也是因为这个比较方便吧。

        在C++侧,我们需要实现一个函数,然后导出一个动态库方法,这里楼主从py层传入了一个回调函数,返回了对应的字符结果。

        在py侧,首先需要使用ctypes.CDLL加载刚才编译出的动态库,声明对应的方法,之后实现一个回调函数进行传入。

        #include <iostream>
        #include <string>
        
        // 回调类型:void callback(int, const char*)
        using CallbackType = void(*)(int, const char*);
        
        extern "C" {
        
        // 导出函数:循环调用回调
        void run_with_callback(CallbackType cb, int count) {
            if (!cb) {
                std::cerr << "[C++] callback is null" << std::endl;
                return;
            }
        
            std::cout << "[C++] run_with_callback start, count = " << count << std::endl;
        
            for (int i = 0; i < count; ++i) {
                std::cout << "[C++] before calling callback, i = " << i << std::endl;
        
                std::string message = "msg from C++ index = " + std::to_string(i);
                cb(i, message.c_str());  // 回调到 Python
        
                std::cout << "[C++] after calling callback, i = " << i << std::endl;
            }
        
            std::cout << "[C++] run_with_callback end" << std::endl;
        }
        
        } // extern "C"

        ps:C++ 默认会对函数名做 name mangling(名字改编),导致动态库中的符号名变复杂;ctypes按 C ABI 去找函数名,如果不显式用 extern "C",Python 侧很难直接按名字找到

        用CMake编译成动态库

        cmake_minimum_required(VERSION 3.10)
        project(pyc_callback_demo LANGUAGES CXX)
        
        add_library(mycallback SHARED callback_lib.cpp)
        
        set_target_properties(mycallback PROPERTIES
            OUTPUT_NAME "mycallback"
            CXX_STANDARD 17
            CXX_STANDARD_REQUIRED YES
        )

        构建

        mkdir -p build
        cd build
        cmake ..
        cmake --build .

        这样就得到了一个.so文件,供py侧加载。

        • 用 @CALLBACK_CTYPE 装饰 Python 函数,ctypes 会把它包装成 C 函数指针。
        • 字符串参数在 Python 中收到的是 bytes,需要手动解码
        import ctypes
        import pathlib
        import os
        
        # 当前文件所在目录
        BASE_DIR = pathlib.Path(__file__).resolve().parent
        
        LIB_PATH = BASE_DIR / "cpp_lib" / "build" / "libmycallback.so"
        
        if not LIB_PATH.exists():
            raise FileNotFoundError(f"找不到动态库: {LIB_PATH}. 请先在 cpp_lib 目录下用 CMake 编译生成 libmycallback.so")
        
        # 加载动态库
        lib = ctypes.CDLL(str(LIB_PATH))
        
        # 定义 C 端回调类型
        # 第二个参数用 ctypes.c_char_p(C 端是 const char*)
        CALLBACK_CTYPE = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
        
        # 声明 C 函数的签名:void run_with_callback(CallbackType cb, int count)
        lib.run_with_callback.argtypes = [CALLBACK_CTYPE, ctypes.c_int]
        lib.run_with_callback.restype = None
        
        
        # Python 侧的回调实现
        @CALLBACK_CTYPE
        def py_callback(i: int, msg: bytes) -> None:
            # msg 是 bytes,需要按合适编码解码,这里假设 UTF-8
            text = msg.decode("utf-8") if msg is not None else "<NULL>"
            print(f"[Python] in callback, i = {i}, msg = {text}")
        
        
        def main() -> None:
            print("[Python] before calling C function")
        
            # 调用 C++ 库函数,并把 Python 回调传进去
            lib.run_with_callback(py_callback, 3)
        
            print("[Python] after calling C function")
        
        
        if __name__ == "__main__":
            main()
        

        ctypes:纯 Python 侧绑定

        不需要在 C++ 里写任何“绑定代码”,只要提供 C ABI 的动态库。

        Python 侧通过 ctypes.CDLL、CFUNCTYPE 等描述函数签名。

        只要函数满足“C 风格接口”(例如 extern "C" + 基本类型/指针),不在意具体是由 C 还是 C++ 实现。

        非常适合:

        • 已经有现成 C 库 / C 接口的 C++ 库
        • 简单函数调用、回调、不复杂的数据结构

        pybind11:在 C++ 侧写绑定,生成 Python 模块

        是一个头文件库,写法大致像如之前提供的示例一致

        编译后得到的是一个 Python 扩展模块(.so),import mymodule 就像普通 Python 包一样使用。

        可以非常自然地暴露:

        • C++ 类、方法、构造函数
        • STL 容器(std::vectorstd::map 等)
        • 智能指针、异常、枚举等

        对复杂 C++ API 的封装能力远强于 ctypes。

        经过这两次学习,让小H更加能够体会到在代码的世界中,语言并不是孤立的,弱化了对语言的重视程度,学习编程更应该将编程语言看作一种工具,什么时候什么情况该换就换,大家各自完成各自擅长的部分,更有利于找到性能与效率的均衡点。

        到此这篇关于Python使用ctypes实现与C++互相调用的实践教程的文章就介绍到这了,更多相关Python与C++互调内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

        您可能感兴趣的文章:

        • Python使用grpc服务并与C++互相调用
        • Python中调用C++代码的方法总结
        • python调用C/C++动态库的实践案例
        • Python调用C++动态库详细步骤(附源码)
        • C++如何调用python并取返回值
        • 在python中调用C/C++的三种方法
        • Python中使用ctypes调用C++的方法
        • python 与c++相互调用实现

        站内搜索