惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
U
Unit 42
The Register - Security
The Register - Security
S
Security Affairs
博客园 - 【当耐特】
Latest news
Latest news
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
F
Full Disclosure
C
Cisco Blogs
宝玉的分享
宝玉的分享
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LangChain Blog
P
Privacy & Cybersecurity Law Blog
腾讯CDC
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
Vulnerabilities – Threatpost
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
WordPress大学
WordPress大学
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
Recorded Future
Recorded Future
Scott Helme
Scott Helme
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
AWS News Blog
AWS News Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tor Project blog
F
Fortinet All Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Hacker News: Front Page
J
Java Code Geeks
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
博客园 - 聂微东
Last Week in AI
Last Week in AI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
V2EX - 技术
V2EX - 技术
T
Troy Hunt's Blog
Attack and Defense Labs
Attack and Defense Labs

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12) Snack JSONPath 项目架构分析 Claude Code Buddy 小析:一个非核心功能,如何体现产品的细节完成度 AI新时代下的图床管理方案-Cloudflare图床+MCP+Skills方案指南 化繁为简:顺丰速运App如何通过 HarmonyOS SDK实现专业级空间测量 从零实现富文本编辑器#13-React非编辑节点的内容渲染 AI开发-python-langchain框架(3-23-OpenAI Functions风格Tool Calling智能助手) .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut PbootCMS 网站内容数量多导致访问慢?这些实用优化方案帮你提速! - 家兴网络技术工作室 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 网站漏洞怎么发现并修复?一篇实用指南(附完整流程) - 家兴网络技术工作室 开了 TUN 模式还是直连?90% 的人都踩过这个坑 Github日报|2026年04月12日 - AI一族 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术 Etsy 把 1000 个 MySQL 分片迁进 Vitess:425TB 数据背后的真正问题不是性能,而是运维规模 MicroPython LVGL基础知识和概念:底层渲染与性能优化 - FreakStudio 数据库草图算法 Python 潮流周刊#146:CPython 引入 Rust 的进展 - 豌豆花下猫 最小生成树 - mofei1116 红日靶场七:从外网入口、容器逃逸到 AD 接管的完整利用链复盘 - YouDiscovered1t 分享四款开源且实用的 Kafka 管理工具 - 追逐时光者 vLLM 权重加载机制全解析:从挑战到理想架构 LCT 学习笔记 - ACehomoxue Avalonia UI 12.0.0 正式发布:架构演进和性能飞跃 - 张善友 当 AI Agent 把调用链拉长,延迟开始成为一门生意 conhost.exe 无法显示 U+2717 - 145a 太秀了,我把自己蒸馏成了 Skill!已开源 - 程序员鱼皮 ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑 基于 Ghostty 带有分割标签页和为 Claude 编程设计的通知终端 - BugShare AI 焊死入口:教育的“操作系统级”重塑 - 郝hai 初级Java开发工程师使用sql脚本编写代码的过程是简单而且不糊涂 - CoderOilStation Claude Code通关手册(六):MCP协议完全指南 - 暮色之狐 边框灯光环绕动画特效实现指南 - Newbe36524 开源:子木蒸馏版的 SEO 审计工具 seo-audit-skill v1.0 我所理解的Python元模型 【从0到1构建一个ClaudeAgent】规划与协调-TodoWrite - 程序员Seven Claude 和 Codex 在审计 Skill 上性能差异探究 - ACai_sec AScript如何实现中文脚本引擎 - rockey627 【渗透测试】HTB Season10 Garfield 全过程wp - dynasty_chenzi Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革 树状数组正确性证明 - AC-wyr 你的 AI 焦虑,可能比 AI 本身更危险——ATM 机没有消灭银行柜员,但恐慌消灭了你的判断力 - 我没有三颗心脏 一个拉胯的分库分表方案有多绝望?整个部门都在救火! - 冰河团队 动态规划入门必学之走方格问题 - Ofnoname PostgREST 与 PostgreSQL 角色权限配置全解析(生产级实践) - SheepDog1998 使用 UEFI 图形输出协议 GOP 在屏幕上显示图像的方法 - 阿源- Claude Code通关手册(五):组建你的AI专家团队,子代理系统 - 暮色之狐 一个程序员到架构师的催婚路之感悟(整整10年后的催婚相亲感悟) - MisterLip 用 Agent Skill 自动生成工作周报 - 赵康
pybind11函数指针入门到放弃
SkyOnSky · 2026-05-17 · via 博客园_首页

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  无

前言


  在我们想把底层C++/C的接口暴露给python的时候,我们一般采用的是pybind11框架,可以很方便的让我们暴露接口。对于普通的接口参数类型来说,pybind11也提供了适合的转换类型(例如:std::vector/uint64_t 等等)。但是年前的一个项目中,遇到了一类特别的参数,函数指针,让我一度很头疼,导致我遇到了如下几个问题:

  • 问题1:python的函数怎么转换为pybind11中的std::function(底层原理)
  • 问题2:pybind11中的std::function 怎么转换为c++层的裸函数
  • 问题3:pybind11中的std::function对象复制的注意事项 (问题3出现的原因和我解决问题2有关系,如果不注意,会导致出现python解释器报错)

  下面来看看我遇到上面的几个问题的实际例子。

  事情是这样的,我有一个c++接口,接收一个类似void(*CB)(Data&)的函数指针,当我尝试将此接口暴露给python的时候,AI一步到位给我写好了转换代码如下:

#include <cstdio>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
namespace py = pybind11;

typedef void(*CB)(int&);

class Test{
    public:
    void test(CB cb)
    {
        printf("printf");
    }
};

PYBIND11_MODULE(testpy, m) {
    m.doc() = "pybind11 wrapper for testpy class";
    py::class_<Test>(m, "Test")
    .def(py::init<>(), "Constructor for the Test class")
    .def("test", [](Test& self, std::function<void(int&)> callback) {
        //... ...
        auto cb = [callback](int&d){
            callback(d);
        };
        // 下面这行报错,错误信息类似: error: cannot convert ‘pybind11_init_testpy(pybind11::module_&)::<lambda(Test&, std::function<void(int&)>)>::<lambda(int&)>’ to ‘CB’ {aka ‘void (*)(int&)’}
        return self.test(cb);
    }

}            
import testpy
def test_cb(i):
    print(f'test_cb i = {i}')
t = testpy.Test()
t.test()

  首先,这里有个结论,AI给出的代码是错误的(带捕获参数的lambda不能直接转换为裸指针,可看注释部分),但是形式上面给出了转换代码的大致框架,这里精简后,整个问题的核心就是:pybind11的std::function怎么转换为函数指针。

  下面我们看看怎么解决上面3个问题,并解决最终问题。

问题1:python的函数怎么转换为pybind11中的std::function(底层原理)


  要解决这个问题,我们得了解python的函数传递过来,这里的callback到底是什么东西?由于pybind11是大量的宏来生成代码,为了快速得到我们要的内容,我们使用gdb来对堆栈进行分析。

#0  test_cb (i=@0x7fffffffd714: 9999) at testpy.cpp:19
#1  0x00007ffff73bfded in Test::test (this=0xc49820, cb=0x7ffff73ab120 <test_cb(int&)>) at testpy.cpp:13
#2  0x00007ffff73ab3f7 in operator()(Test &, std::function<void(int&)>) const (__closure=0xc49678, self=..., callback=...) at testpy.cpp:33
#3  0x00007ffff73ac08c in pybind11::detail::argument_loader<Test&, std::function<void(int&)> >::call_impl<void, pybind11_init_testpy(pybind11::module_&)::<lambda(Test&, std::function<void(int&)>)>&, 0, 1, pybind11::detail::void_type>(struct {...} &, std::index_sequence, pybind11::detail::void_type &&) (this=0x7fffffffd8a0, f=...) at /usr/include/pybind11/cast.h:1480
#4  0x00007ffff73abd00 in pybind11::detail::argument_loader<Test&, std::function<void(int&)> >::call<void, pybind11::detail::void_type, pybind11_init_testpy(pybind11::module_&)::<lambda(Test&, std::function<void(int&)>)>&>(struct {...} &) (this=0x7fffffffd8a0, f=...) at /usr/include/pybind11/cast.h:1454
#5  0x00007ffff73ab9b7 in operator() (__closure=0x0, call=...) at /usr/include/pybind11/pybind11.h:254
#6  0x00007ffff73aba6c in _FUN () at /usr/include/pybind11/pybind11.h:224
#7  0x00007ffff73bd52c in pybind11::cpp_function::dispatcher (self=0x7ffff75fedc0, args_in=0x7ffff7421f80, kwargs_in=0x0) at /usr/include/pybind11/pybind11.h:946
#8  0x0000000000581ecf in cfunction_call (func=0x7ffff740ba10, args=<optimized out>, kwargs=<optimized out>) at ../Objects/methodobject.c:537
#9  0x0000000000549205 in _PyObject_MakeTpCall (tstate=0xba5748 <_PyRuntime+459656>, callable=0x7ffff740ba10, args=<optimized out>, nargs=2, keywords=0x0) at ../Objects/call.c:240
#10 0x0000000000549c3d in _PyObject_VectorcallTstate (kwnames=<optimized out>, nargsf=<optimized out>, args=<optimized out>, callable=<optimized out>, tstate=<optimized out>) at ../Include/internal/pycore_call.h:90
#11 0x00000000005d7109 in _PyEval_EvalFrameDefault (tstate=tstate@entry=0xba5748 <_PyRuntime+459656>, frame=<optimized out>, frame@entry=0x7ffff7fb2020, throwflag=throwflag@entry=0) at Python/bytecodes.c:2706
#12 0x00000000005d564b in _PyEval_EvalFrame (throwflag=0, frame=0x7ffff7fb2020, tstate=0xba5748 <_PyRuntime+459656>) at ../Include/internal/pycore_ceval.h:89
#13 _PyEval_Vector (kwnames=0x0, argcount=0, args=0x0, locals=0x7ffff75f9a80, func=0x7ffff75da160, tstate=0xba5748 <_PyRuntime+459656>) at ../Python/ceval.c:1683
#14 PyEval_EvalCode (co=co@entry=0x7ffff75604b0, globals=globals@entry=0x7ffff75f9a80, locals=locals@entry=0x7ffff75f9a80) at ../Python/ceval.c:578
#15 0x00000000006087b2 in run_eval_code_obj (locals=0x7ffff75f9a80, globals=0x7ffff75f9a80, co=0x7ffff75604b0, tstate=0xba5748 <_PyRuntime+459656>) at ../Python/pythonrun.c:1722
#16 run_mod (mod=<optimized out>, filename=<optimized out>, globals=0x7ffff75f9a80, locals=0x7ffff75f9a80, flags=<optimized out>, arena=<optimized out>) at ../Python/pythonrun.c:1743
#17 0x00000000006b4853 in pyrun_file (fp=fp@entry=0xbf6480, filename=filename@entry=0x7ffff7409ca0, start=start@entry=257, globals=globals@entry=0x7ffff75f9a80, locals=locals@entry=0x7ffff75f9a80, closeit=closeit@entry=1, flags=0x7fffffffe0a8) at ../Python/pythonrun.c:1643
#18 0x00000000006b45ba in _PyRun_SimpleFileObject (fp=fp@entry=0xbf6480, filename=filename@entry=0x7ffff7409ca0, closeit=closeit@entry=1, flags=flags@entry=0x7fffffffe0a8) at ../Python/pythonrun.c:433
#19 0x00000000006b43ef in _PyRun_AnyFileObject (fp=0xbf6480, filename=filename@entry=0x7ffff7409ca0, closeit=closeit@entry=1, flags=flags@entry=0x7fffffffe0a8) at ../Python/pythonrun.c:78
#20 0x00000000006bc455 in pymain_run_file_obj (skip_source_first_line=0, filename=0x7ffff7409ca0, program_name=0x7ffff75f9bf0) at ../Modules/main.c:360
#21 pymain_run_file (config=0xb48328 <_PyRuntime+77672>) at ../Modules/main.c:379
#22 pymain_run_python (exitcode=0x7fffffffe09c) at ../Modules/main.c:629
#23 Py_RunMain () at ../Modules/main.c:709
#24 0x00000000006bbf3d in Py_BytesMain (argc=<optimized out>, argv=<optimized out>) at ../Modules/main.c:763
#25 0x00007ffff7c2a1ca in __libc_start_call_main (main=main@entry=0x518ac0 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffe2e8) at ../sysdeps/nptl/libc_start_call_main.h:58
#26 0x00007ffff7c2a28b in __libc_start_main_impl (main=0x518ac0 <main>, argc=2, argv=0x7fffffffe2e8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe2d8) at ../csu/libc-start.c:360
#27 0x00000000006574f5 in _start ()

  从上面看,核心就是从python解释器,到了pybind11::cpp_function::dispatcher,然后到了我们的test函数。如果我们对pybind11不熟悉的话,我们还是需要深入去看pybind11才能回答我们上面的问题,但是我这里想到了另外一个办法。

  我们都知道,pybind11底层是由cpython实现的,因此我们通过cpython来实现上面的同样的功能是什么样子的呢?直接让AI生成示例如下:


 #define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <cstdio>
#include <functional>

// --- C++ 逻辑模拟部分 ---
typedef void(*CB)(int&);

class Test{
    public:
    void test(CB cb)
    {
        int i = 9999;
        cb(i);
    }
};

void test_cb(int& i)
{
    printf("test_cb from cxx i = %d\n", i);
}

// --- CPython 包装部分 ---

// 定义 Python 中的 Test 对象结构
typedef struct {
    PyObject_HEAD
    Test* cpp_obj; // 指向实际的 C++ 对象
} PyTestObject;

void pybind11_like_func(Test& self, std::function<void(int&)> callback)
{
    //... ...
    auto cb = [callback](int&d){
        callback(d);
    };
    int i = 8888;
    cb(i);
    // return self.test(cb);
    return self.test(test_cb);
}

// Test.test(callback) 的实现
static PyObject* PyTest_test(PyTestObject* self, PyObject* args) {
    PyObject* pycallback = NULL;

    // 1. 解析参数,期望得到一个可调用对象
    if (!PyArg_ParseTuple(args, "O", &pycallback)) {
        return NULL;
    }

    if (!PyCallable_Check(pycallback)) {
        PyErr_SetString(PyExc_TypeError, "Parameter must be callable");
        return NULL;
    }

    // 2. 核心:模拟 std::function<void(int&)> 的构造
    // 我们在这里捕获 py_callback 指针。注意:实际生产中需要处理引用计数
    std::function<void(int&)> cpp_callback = [pycallback](int& d) {
        // A. 必须获取 GIL,因为回调可能由 C++ 触发
        PyGILState_STATE gstate = PyGILState_Ensure();

        // B. 参数转换:C++ int& -> Python Long
        PyObject* arg = PyLong_FromLong((long)d);
        PyObject* arg_tuple = PyTuple_Pack(1, arg);

        // C. 调用 Python 函数
        PyObject* result = PyObject_CallObject(pycallback, arg_tuple);

        // D. 错误处理与清理
        if (!result) {
            PyErr_Print(); 
        }

        Py_XDECREF(result);
        Py_DECREF(arg_tuple);
        Py_DECREF(arg);

        // E. 释放 GIL
        PyGILState_Release(gstate);
    };

    pybind11_like_func(*self->cpp_obj, cpp_callback);
    Py_RETURN_NONE;
}

// --- 类型与模块定义 ---

static PyMethodDef PyTest_methods[] = {
    {"test", (PyCFunction)PyTest_test, METH_VARARGS, "Execute test with callback"},
    {NULL, NULL, 0, NULL}
};


static PyTypeObject PyTestType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "testcpy.Test",
    .tp_basicsize = sizeof(PyTestObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_methods = PyTest_methods,
    .tp_new = PyType_GenericNew,
};


static struct PyModuleDef testcpymodule = {
    PyModuleDef_HEAD_INIT,
    "testcpy",
    "CPython version of testcpy",
    -1,
    NULL
};


PyMODINIT_FUNC PyInit_testcpy(void) {
    PyObject* m;
    if (PyType_Ready(&PyTestType) < 0) return NULL;
    m = PyModule_Create(&testcpymodule);
    if (m == NULL) return NULL;

    Py_INCREF(&PyTestType);
    PyModule_AddObject(m, "Test", (PyObject*)&PyTestType);
    return m;
}

  其实我们已经看到了,我们用cpython来实现的话,调用的std::function一定是一个带状态的callable obj。至此,我们已经解决了问题1。

问题2:pybind11中的std::function 怎么转换为c++层的裸函数


  实际的方法就是在pybind11代码层,添加一个全局静态变量进行转换,参考如下代码(重点查看PyCBWrapper相关的内容):

#include <cstdio>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>

#include <functional>
namespace py = pybind11;
typedef void(*CB)(int&);

class Test{
    public:
    void test(CB cb)
    {
        int i = 9999;
        cb(i);
    }
};

struct PyCBWrapper {

    static std::function<void(int&)> py_cb;

    static void trampoline(int& i) {
        
        if (nullptr != py_cb)
            PyCBWrapper::py_cb(i);
        
    }
};


std::function<void(int&)> PyCBWrapper::py_cb = nullptr;


void test_cb(int& i)
{
    printf("test_cb from cxx i = %d\n", i);
}

PYBIND11_MODULE(testpy, m) {
    m.doc() = "pybind11 wrapper for testpy class";
    py::class_<Test>(m, "Test")
    .def(py::init<>(), "Constructor for the Test class")
    .def("test", [](Test& self, std::function<void(int&)> callback) {
        //... ...
        auto cb = [callback](int&d){
            callback(d);
        };
        int i = 8888;
        cb(i);
        PyCBWrapper::py_cb = callback;
        // return self.test(cb);
        return self.test(PyCBWrapper::trampoline);
    });

}    

问题3:pybind11中的std::function对象复制的注意事项


  问题2的这段代码会直接运行报错,堆栈如下

#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007ffff7c4527e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7c288ff in __GI_abort () at ./stdlib/abort.c:79
#5  0x00000000004b1252 in ?? ()
#6  0x00000000004b2908 in _Py_FatalErrorFunc ()
#7  0x00000000004b2cd2 in ?? ()
#8  0x000000000060861e in ?? ()
#9  0x00000000006a6e93 in PyEval_AcquireThread ()
#10 0x00007ffff73b9d20 in pybind11::gil_scoped_acquire::gil_scoped_acquire (this=0x7fffffffd760) at /usr/include/pybind11/gil.h:82
#11 0x00007ffff73dd1b1 in pybind11::detail::type_caster<std::function<void (int&)>, void>::load(pybind11::handle, bool)::func_handle::~func_handle() (this=0xc49870, __in_chrg=<optimized out>)
    at /usr/include/pybind11/functional.h:97
#12 0x00007ffff73dd324 in pybind11::detail::type_caster<std::function<void (int&)>, void>::load(pybind11::handle, bool)::func_wrapper::~func_wrapper() (this=0xc49870, __in_chrg=<optimized out>)
    at /usr/include/pybind11/functional.h:103
#13 0x00007ffff73e0ff2 in std::_Function_base::_Base_manager<pybind11::detail::type_caster<std::function<void(int&)>, void>::load(pybind11::handle, bool)::func_wrapper>::_M_destroy (__victim=...)
    at /usr/include/c++/13/bits/std_function.h:175
--Type <RET> for more, q to quit, c to continue without paging--
#14 0x00007ffff73e0ce4 in std::_Function_base::_Base_manager<pybind11::detail::type_caster<std::function<void(int&)>, void>::load(pybind11::handle, bool)::func_wrapper>::_M_manager (__dest=..., __source=..., 
    __op=std::__destroy_functor) at /usr/include/c++/13/bits/std_function.h:203
#15 0x00007ffff73e02aa in std::_Function_handler<void(int&), pybind11::detail::type_caster<std::function<void(int&)>, void>::load(pybind11::handle, bool)::func_wrapper>::_M_manager (__dest=..., __source=..., 
    __op=std::__destroy_functor) at /usr/include/c++/13/bits/std_function.h:282
#16 0x00007ffff73b62c1 in std::_Function_base::~_Function_base (this=0x7ffff73ff440 <PyCBWrapper::py_cb>, __in_chrg=<optimized out>) at /usr/include/c++/13/bits/std_function.h:244
#17 0x00007ffff73bff78 in std::function<void(int&)>::~function (this=0x7ffff73ff440 <PyCBWrapper::py_cb>, __in_chrg=<optimized out>) at /usr/include/c++/13/bits/std_function.h:334
#18 0x00007ffff7c47a76 in __run_exit_handlers (status=0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=true, run_dtors=run_dtors@entry=true) at ./stdlib/exit.c:108
#19 0x00007ffff7c47bbe in __GI_exit (status=<optimized out>) at ./stdlib/exit.c:138
#20 0x00007ffff7c2a1d1 in __libc_start_call_main (main=main@entry=0x518c60, argc=argc@entry=2, argv=argv@entry=0x7fffffffda28) at ../sysdeps/nptl/libc_start_call_main.h:74
#21 0x00007ffff7c2a28b in __libc_start_main_impl (main=0x518c60, argc=2, argv=0x7fffffffda28, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffda18)
    at ../csu/libc-start.c:360
#22 0x0000000000657b05 in _start ()

  从堆栈分析可知,问题出在程序退出的时候,PyCBWrapper::py_cb(全局静态变量)析构的时候,这个时候其实python解释器已经退出了,再使用python解释器相关的资源,就会报错。这个其实就是问题3。由于从上面的例子可以知道,这个时候的PyCBWrapper::py_cb是一个捕获了python函数的PyObject的std::function,那么解决方案也很简单,那就是在上面的pybind11代码层中添加如下核心代码:

PYBIND11_MODULE(testpy, m) {
    m.doc() = "pybind11 wrapper for testpy class";
    py::class_<Test>(m, "Test")
    .def(py::init<>(), "Constructor for the Test class")
    .def("test", [](Test& self, std::function<void(int&)> callback) {
        //... ...
        auto cb = [callback](int&d){
            callback(d);
        };
        int i = 8888;
        cb(i);
        PyCBWrapper::py_cb = callback;
        // return self.test(cb);
        self.test(PyCBWrapper::trampoline);
        // 核心代码
        PyCBWrapper::py_cb = nullptr;
        return;
    });

}    

  这里的核心就是将PyCBWrapper::py_cb = nullptr;置为空,保证pyobject变量在python解释器还正常的工作时候进行析构。问题3解决,完结散花。

后记


  其实在了解了cpython转换python函数为std::function的细节,解决这些奇怪的问题还是很简单的。

  在AI的帮助下,解决这些问题只需要有方向即可,验证时间及方法已经非常的快了。

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。