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

PyBind11暴露C++20协程为Python迭代器调用iter崩溃排查

问题原因

调用iter()直接崩溃的核心原因有3个:

  1. pybind11迭代器适配不支持异构哨兵的不对称比较:你用std::default_sentinel_t作为生成器end迭代器,只实现了Iterator == default_sentinel_t的成员比较,没有实现反向的default_sentinel_t == Iterator,pybind11内部做迭代器合法性校验时会触发非法内存访问,生成野指针直接段错误,这是调用iter()立刻崩溃的直接诱因。
  2. 自定义迭代器不符合C++ LegacyInputIterator规范:pybind11的make_iterator要求包装的迭代器满足传统输入迭代器要求:迭代器拷贝后和原实例完全等价、独立有效。但你的Iterator内部只存了协程句柄的裸指针,所有拷贝的迭代器都共享同一个协程状态,任意一个迭代器执行++操作resume协程,其他所有迭代器持有的帧引用会立刻失效,触发未定义行为。
  3. 多余的opaque类型声明阻塞数据转换:你加了PYBIND11_MAKE_OPAQUE(std::vector<unsigned char>),会让pybind11完全不识别字节vector的内容,就算迭代器不崩,Python侧也拿不到帧数据,更转不成numpy数组。
修复方案

不要硬套pybind11::make_iterator,C20协程生成器本身是一次性遍历的,和Python迭代器协议完全匹配,直接实现__iter__和__next__即可,绕开C传统迭代器适配的所有坑。

第一步:修改Generator类,加简单的拉取帧方法

在你的Generator类里加启动标记位,防止重复启动协程:

// 私有成员加启动标记
private:
    Handle m_coroutine;
    bool m_started = false; // 新增:标记协程是否已经启动

// 公共成员加拉取帧的方法
public:
    bool pull_next(T& out) {
        if (!m_coroutine) return false;
        if (!m_started) {
            m_coroutine.resume();
            m_started = true;
        } else {
            m_coroutine.resume();
        }
        if (m_coroutine.done()) return false;
        out = *m_coroutine.promise().m_value;
        return true;
    }
  • 把原来begin()里的首次resume逻辑移到这里,避免重复调用启动协程。

第二步:删除多余的opaque声明

把PYBIND11_MAKE_OPAQUE(std::vector<unsigned char>);这行直接删掉,你不需要把C++ vector暴露给Python,直接转numpy数组即可。

第三步:重写pybind11绑定逻辑

替换原来的__iter__实现,直接实现Python迭代器协议,顺便完成numpy数组转换:

PYBIND11_MODULE (test, m)
{
    m.doc () = "Camera utils";
        
    pybind11::class_<Generator<stFrame>>(m, "FrameReader")
        .def(pybind11::init([](std::string filename, std::array<size_t, 2> resolution)
        {
            return frame(filename, resolution);
        }))
        .def("__iter__", [](Generator<stFrame>& self) -> Generator<stFrame>& {
            // 迭代器返回自身即可,pull_next内部已经做了启动防重
            return self;
        }, py::keep_alive<0, 1>()) // 保证迭代器存活时Generator不被销毁
        .def("__next__", [](Generator<stFrame>& self) {
            stFrame frame;
            if (!self.pull_next(frame)) {
                throw py::stop_iteration(); // 遍历结束抛Python标准异常
            }
            // 直接构造numpy数组,拷贝帧数据避免野指针
            // 单通道灰度图用shape (height, width),RGB就加第三个通道维度
            py::array_t<unsigned char> np_frame(
                {942, 1824}, // 传入的分辨率[1824,942]是width*height,numpy是行优先,高在前
                {1824 * sizeof(unsigned char), sizeof(unsigned char)}
            );
            std::memcpy(np_frame.mutable_data(), frame.data.data(), frame.data.size());
            
            // 返回帧序号和numpy数组,不需要暴露stFrame类型给Python
            return py::make_tuple(frame.frameIndex, np_frame);
        });
        
    // 不需要再绑定stFrame和vector,Python侧直接拿到(index, ndarray)元组即可
}

额外说明

  • 这里直接拷贝帧数据到numpy数组是最稳妥的方案,协程resume后上一帧的buffer会被释放,numpy自己持有内存不会出现野指针,性能开销也极小。
  • 如果需要多通道(比如BGR/RGB),只需要修改numpy的shape参数为{height, width, 3},stride对应调整即可。
  • 协程内部如果抛出异常,会在pull_next调用resume()的时候直接抛出,pybind11会自动转成对应的Python异常,不需要额外处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:46:03