You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让pybind11正确适配Python ctypes类型与C++函数参数?

问题

我从设备采集的数据在Python中直接以ctypes.c_uint16的ctypes.Array类型存储,为加速迭代后处理任务,用C++实现处理逻辑并通过pybind11生成绑定,最终需返回torch::Tensor。

C++函数签名:

torch::Tensor foo(uint16_t* a, int b, int c, int d, uint64_t e, uint64_t* f);

pybind11绑定代码:

namespace py = pybind11;

PYBIND11_MODULE(foo_lib, m){
    m.def("foo", &foo,
        py::arg("a"),
        py::arg("b"),
        py::arg("c"),
        py::arg("d"),
        py::arg("e"),
        py::arg("f")
    );
}

Python调用代码:

import torch
import foo_lib
from ctypes import Array, c_int, c_uint16, c_uint64

a: Array[c_uint16] = ... # 输入数据数组
b: c_int = c_int(0)
c: c_int = c_int(13)
d: c_int = c_int(16)
e: c_uint64 = c_uint64(1000)
f: Array[c_uint64] = ... # 所需元数据数组

output: torch.Tensor = foo_lib.foo(a, b, c, d, e, f)

运行时出现类型错误:

Traceback (most recent call last):
  File "~/test.py", line 14, in <module>
    output: torch.Tensor = foo_lib.foo(a, b, c, d, e, f)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: foo(): incompatible function arguments. The following argument types are supported:
    1. (a: int, b: int, c: int, d: int, e: int, f: int) -> torch.Tensor

Invoked with: <__main__.c_ushort_Array_1000 object at 0x7ad66ff620d0>, c_int(0), c_int(13), c_int(16), c_ulong(1000), <__main__.c_ulong_Array_10 object at 0x7ad66ff62250>

需求:不想将ctypes数组转为numpy.ndarray(会降低速度),也不能用纯ctypes绑定(需要返回torch.Tensor),希望pybind11能正确识别ctypes类型并转换为对应C++类型。

环境:Python 3.12.3、Ubuntu 24.04.4、torch 2.7.1、pybind11 2.11.2

解决方案

pybind11默认不支持直接转换ctypes类型,最直接且易维护的方式是写一个C++包装函数,在包装中手动提取ctypes对象的底层数据和数值,再调用原处理函数。

实现步骤

1. 编写包装函数

在绑定代码中添加一个包装函数,负责转换Python传入的ctypes参数到C++类型:

#include <pybind11/pybind11.h>
#include <torch/torch.h>

namespace py = pybind11;

// 原处理函数(已存在)
torch::Tensor foo(uint16_t* a, int b, int c, int d, uint64_t e, uint64_t* f);

// 包装函数
torch::Tensor foo_wrapper(py::object a_obj, py::object b_obj, py::object c_obj, py::object d_obj, py::object e_obj, py::object f_obj) {
    // 导入ctypes模块,用于获取指针和数值
    py::object ctypes = py::module::import("ctypes");
    py::object addressof = ctypes.attr("addressof");

    // 转换ctypes Array为C++指针(零拷贝)
    uintptr_t a_addr = py::cast<uintptr_t>(addressof(a_obj));
    uint16_t* a = reinterpret_cast<uint16_t*>(a_addr);

    uintptr_t f_addr = py::cast<uintptr_t>(addressof(f_obj));
    uint64_t* f = reinterpret_cast<uint64_t*>(f_addr);

    // 转换ctypes数值类型为C++基本类型
    int b = py::cast<int>(b_obj.attr("value"));
    int c = py::cast<int>(c_obj.attr("value"));
    int d = py::cast<int>(d_obj.attr("value"));
    uint64_t e = py::cast<uint64_t>(e_obj.attr("value"));

    // 调用原函数
    return foo(a, b, c, d, e, f);
}

// 绑定模块
PYBIND11_MODULE(foo_lib, m){
    m.def("foo", &foo_wrapper,
        py::arg("a"),
        py::arg("b"),
        py::arg("c"),
        py::arg("d"),
        py::arg("e"),
        py::arg("f")
    );
}

2. 编译并测试

用原有的编译流程重新编译pybind11模块,之后Python端的调用代码无需修改,直接运行即可。

原理说明

  • ctypes的Array对象可以通过ctypes.addressof()获取底层内存地址,再通过reinterpret_cast转换为对应C++类型的指针,完全零拷贝,不会影响性能。
  • ctypes的数值类型(如c_int、c_uint64)都有value属性,直接提取即可得到对应的Python数值,再转换为C++基本类型。
  • 包装函数相当于在pybind11和原函数之间做了一层类型适配,既保留了原C++函数的性能,又兼容了Python端的ctypes类型输入。

内容的提问来源于stack exchange,提问作者inventi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 19:23:10