欢迎光临
我们一直在努力

SWIG Java胶水层开源项目学习路径

SWIG Java胶水层开源项目学习路径

C++ 核心库要通过 SWIG 暴露给 Java,并带上 回调 / 异步派发 / shared_ptr 生命周期,几乎找不到「长得一模一样」的开源范本。更现实的学法是:挑胶水层纪律好的项目,带着固定问题去拆 所有权、Director、shared_ptr、取消与工程门禁,再裁剪到自己的 API 模型。

本文给出 1~2 周可读完的路径:聚焦怎么读开源绑定、抄什么不抄什么;工具选型本身不在本文展开。


目录

  • 先定预期:没有完美克隆
  • 官方与机制:标准答案在哪
    • 2.1 必读:SWIG Java 文档
    • 2.2 Ownership 与 Director 的物理含义
    • 2.3 shared_ptr + Director:有限支持,不是歪门
    • 2.4 SWIG 自带 Java test-suite
  • 值得翻的开源工程
    • 3.1 QuantLib:学 shared_ptr 不泄漏到宿主语言
    • 3.2 GDAL:学模块拆分与生成流水线
    • 3.3 怎么读才有用(不要通读)
  • 带着问题读:四个对照检查点
    • 4.1 所有权与 shared_ptr 边界
    • 4.2 Director“纯度”与异步陷阱
    • 4.3 胶水层工程纪律
    • 4.4 异步取消(Cancel / Dispose)的归宿
  • 对照学:不一定用 SWIG 的胶水层
  • 工业实践里的常见终局
  • 1~2 周最小闭环
  • 速查表
    • 8.1 关键词 → 去哪看
    • 8.2 反模式
  • 延伸阅读
  • 源码调研补充:范围与结论
    • 10.1 能力矩阵
    • 10.2 最重要的判断
  • SWIG 4.3.1:Java JNI 双向调用基线
    • 11.1 Java → JNI → C++ 下行链
    • 11.2 C++ → Director → Java 上行链
    • 11.3 WeakGlobalRef 与 GlobalRef 的切换
    • 11.4 原生线程如何 Attach
    • 11.5 Director 异常
    • 11.6 shared_ptr 与 Director 的边界
  • 逐项目源码分析
    • 12.1 QuantLib-SWIG:Director API 形态好,长期回调所有权不足
      • 绑定结构
      • 如何互相调用
      • 生命周期问题
      • 线程与异步
      • 可学与不可照搬
    • 12.2 GDAL:不使用 Director,擅长普通对象 ownership
      • 绑定结构
      • progress 回调如何工作
      • 普通对象生命周期做得更好
      • 可学与不可照搬
    • 12.3 libSBML:裸指针 registry 与 clone-based ownership 并存
      • Director 范围
      • 模式一:CallbackRegistry 借用裸指针
      • 模式二:虚拟 clone 后由 C++ 拥有
      • 所有权工具
      • 线程与异步
      • 可学与不可照搬
    • 12.4 Xapian:C++ intrusive ownership 清楚,Java 暴露不完整
      • Director 范围
      • Enquire 如何持有 MatchSpy
      • Java 路径的缺口
      • clone registry 的限制
      • 线程与异步
      • 可学与不可照搬
    • 12.5 Open Babel:拒绝包装危险接口
    • 12.6 Z3 Java:GlobalRef 长期 callback 对照
      • callback state
      • 线程限制
      • 关闭缺口
      • 普通对象
    • 12.7 JavaCPP:线程 Attach 完整,ownership 握手弱于 SWIG
      • 线程处理
      • callback 保活
  • 横向比较:哪些模式真正可复用
    • 13.1 三种上行模型
      • A. SWIG Director
      • B. 手写 JNI callback bridge
      • C. 固定句柄上行
    • 13.2 四种 ownership 模式
    • 13.3 一份可靠的异步关闭协议
  • 对多语言 C++ SDK 的设计启发
    • 14.1 不要直接把全部 C++ 类暴露给多语言
    • 14.2 C ABI 句柄层是否必须
    • 14.3 Java 生命周期规范
    • 14.4 回调 API 分类
    • 14.5 Java 线程运行时
    • 14.6 shared_ptr 的正确位置
    • 14.7 构建、发布和测试门禁
    • 14.8 推荐的落地终局

  • 1. 先定预期:没有完美克隆

    若你的场景同时具备:

    • 海量 listen* / 观察者式回调;
    • SWIG Director(Java 子类回调进 C++);
    • 异步线程上锁外派发;
    • C++ 侧长期持有 shared_ptr;

    那么开源库最多提供局部模式,不会提供整包复制品。目标不是「找到一个项目照搬」,而是抽出可产品化的规则:

    学什么不学什么
    所有权谁 delete、何时 swigReleaseOwnership 把对方业务 API 整包抄进自家 .i
    Director 是否允许跨线程 指望 %shared_ptr + director 开箱即完美
    .i 如何拆模块、CI 如何强制重生 通读几千行接口文件
    cancel/dispose 如何耗尽回调 把对方目录结构原样复制

    #mermaid-svg-T9mVWwXUhFfoYmbN{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-T9mVWwXUhFfoYmbN .error-icon{fill:#552222;}#mermaid-svg-T9mVWwXUhFfoYmbN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-T9mVWwXUhFfoYmbN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-T9mVWwXUhFfoYmbN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-T9mVWwXUhFfoYmbN .marker.cross{stroke:#333333;}#mermaid-svg-T9mVWwXUhFfoYmbN svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-T9mVWwXUhFfoYmbN p{margin:0;}#mermaid-svg-T9mVWwXUhFfoYmbN .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN .cluster-label text{fill:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN .cluster-label span{color:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN .cluster-label span p{background-color:transparent;}#mermaid-svg-T9mVWwXUhFfoYmbN .label text,#mermaid-svg-T9mVWwXUhFfoYmbN span{fill:#333;color:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN .node rect,#mermaid-svg-T9mVWwXUhFfoYmbN .node circle,#mermaid-svg-T9mVWwXUhFfoYmbN .node ellipse,#mermaid-svg-T9mVWwXUhFfoYmbN .node polygon,#mermaid-svg-T9mVWwXUhFfoYmbN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-T9mVWwXUhFfoYmbN .rough-node .label text,#mermaid-svg-T9mVWwXUhFfoYmbN .node .label text,#mermaid-svg-T9mVWwXUhFfoYmbN .image-shape .label,#mermaid-svg-T9mVWwXUhFfoYmbN .icon-shape .label{text-anchor:middle;}#mermaid-svg-T9mVWwXUhFfoYmbN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-T9mVWwXUhFfoYmbN .rough-node .label,#mermaid-svg-T9mVWwXUhFfoYmbN .node .label,#mermaid-svg-T9mVWwXUhFfoYmbN .image-shape .label,#mermaid-svg-T9mVWwXUhFfoYmbN .icon-shape .label{text-align:center;}#mermaid-svg-T9mVWwXUhFfoYmbN .node.clickable{cursor:pointer;}#mermaid-svg-T9mVWwXUhFfoYmbN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-T9mVWwXUhFfoYmbN .arrowheadPath{fill:#333333;}#mermaid-svg-T9mVWwXUhFfoYmbN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-T9mVWwXUhFfoYmbN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-T9mVWwXUhFfoYmbN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-T9mVWwXUhFfoYmbN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-T9mVWwXUhFfoYmbN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-T9mVWwXUhFfoYmbN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-T9mVWwXUhFfoYmbN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-T9mVWwXUhFfoYmbN .cluster text{fill:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN .cluster span{color:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-T9mVWwXUhFfoYmbN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-T9mVWwXUhFfoYmbN rect.text{fill:none;stroke-width:0;}#mermaid-svg-T9mVWwXUhFfoYmbN .icon-shape,#mermaid-svg-T9mVWwXUhFfoYmbN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-T9mVWwXUhFfoYmbN .icon-shape p,#mermaid-svg-T9mVWwXUhFfoYmbN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-T9mVWwXUhFfoYmbN .icon-shape .label rect,#mermaid-svg-T9mVWwXUhFfoYmbN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-T9mVWwXUhFfoYmbN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-T9mVWwXUhFfoYmbN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-T9mVWwXUhFfoYmbN :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    自家痛点模型

    官方标准行为

    QuantLib / GDAL 等纪律

    裁剪:同步面 / 句柄上行 / 手写补丁

    门禁:生成物 + ASAN 竞态


    2. 官方与机制:标准答案在哪

    2.1 必读:SWIG Java 文档

    入口:SWIG 4.4 Java。优先章节:

    主题要建立的「标准行为」
    Directors C++ 虚接口 → Java 子类;上行调用路径与 director:except
    Memory ownership swigCMemOwn、swigReleaseOwnership / swigTakeOwnership
    shared_ptr 库层 %shared_ptr 与代理类如何持有引用
    线程 Director 回调进 JVM 时的 Attach;SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON 等宏

    官方对线程的提示很直接:Director 可能从非 Java 线程回调用 JVM,需要正确 Attach;部分环境还要考虑进程退出时 Detach 导致的挂起(文档建议尝试 AttachCurrentThreadAsDaemon)。

    2.2 Ownership 与 Director 的物理含义

    SWIG Java Director 在 C++ 侧持有对 Java 代理的引用,并在「谁拥有 C++ 对象」变化时切换 WeakGlobalRef / GlobalRef:

    Java 持有 C++ 生命周期 → WeakGlobalRef(允许 Java 代理被 GC)
    C++ 持有 C++ 生命周期 → GlobalRef(钉住 Java 代理,避免上行时空指针)
    切换手段 → swigReleaseOwnership / swigTakeOwnership

    这解释了为何「C++ 长期持有回调对象」时,必须显式处理 ownership:否则 Java 侧 GC 掉代理,Director 上行会拿到空引用。

    2.3 %shared_ptr + Director:有限支持,不是歪门

    社区与历史文档反复说明:%shared_ptr 与 director 的组合支持有限,语言之间成熟度不一。典型症状:

    • Director 方法里出现 SWIGTYPE_p_std__shared_ptrT_… 而不是正常代理类型;
    • 需要补 directorin / directorout / javadirectorin / javadirectorout typemap;
    • 额外 shared_ptr 引用可能导致「C++ 已释放业务引用,但包装层仍占一票」的泄漏,或反向过早回收。

    相关讨论见 Using shared_ptr with SWIG Directors for Java 与 SWIG issue 对 ownership 的讨论。结论应记成:

    手写 typemap / 手写接管 shared_ptr 是常态,不是歪门。

    2.4 SWIG 自带 Java test-suite

    比随便一个业务库更干净的对照源:

    • Examples/
    • Examples/test-suite/java

    优先搜:director、shared_ptr、ownership、异常映射。先看「官方怎么测」,再看「业务库怎么绕」。


    3. 值得翻的开源工程

    按「和异步 Director 场景的相似度 / 工程纪律」排序,而不是按 star。

    项目为什么看重点看什么核对日备注
    QuantLib-SWIG 多年多语言 SWIG;大量 %shared_ptr / %extend SWIG/*.i 如何把 C++ shared_ptr 藏成 idiomatic API;Java 目录如何构建 common.i 使用 boost_shared_ptr.i + SWIG_SHARED_PTR_NAMESPACE
    GDAL Java 大体量 C++→Java,工业发布 swig/include + swig/include/java/* 模块拆分;CMake/Ant 生成 gdal.jar + gdalalljni 官方强调 jar 与 native 必须同源同版本
    libSBML 经典「一核多语言」 src/bindings/{swig,java,…} 目录策略、生成物与版本 bindings 按语言分子目录
    Xapian Java 搜索引擎,有遍历/回调类 API Java 侧 close、异常、ownership 约定 xapian-bindings/java
    OpenBabel 大 API 面 SWIG 大规模 .i 如何组织、减复制 学结构多于学化学域
    Z3 Java SWIG API 面大 复杂对象图边界 先确认仓库仍维护 Java SWIG 路径,再投入时间

    3.1 QuantLib:学「shared_ptr 不泄漏到宿主语言」

    QuantLib 绑定的核心动机之一,是 C++ 库大量使用智能指针,但不希望 Python/Java 用户手写 shared_ptr。常见手法:

    • %include boost_shared_ptr.i(或等价)+ %shared_ptr(T);
    • 用 %extend 把构造/工厂接到「看起来像普通对象」的代理上;
    • 内部仍是 shared_ptr,对外是语言惯用对象。

    读法:在 SWIG/*.i 里搜 %shared_ptr、%extend、Handle,追踪一个带观察者/回调味道的类型从声明到 Java 代理,不要通读金融域全量接口。

    3.2 GDAL:学「模块拆分 + 生成流水线」

    GDAL Java 绑定的工程结构大致是:

    swig/
    include/ # 公共与各语言共享接口
    include/java/ # Java 专用:callback.i / typemaps_java.i / *_java.i / ogr_java_extend.i
    java/
    CMakeLists.txt # 调 SWIG 生成 *_wrap.cpp,再 Ant 打 jar
    build.xml

    可复用纪律:

    纪律含义
    接口按模块拆 gdal / ogr / osr / gnm 分文件,避免单文件几千行
    语言专用层 include/java 放 typemap、extend、异常,不和 C API 声明搅在一起
    生成与打包同批 gdal.jar 与 libgdalalljni 同源构建,避免「Java API 新、native 旧」
    回调单独文件 callback.i 提示:回调不是随手塞进主 .i 的边角料

    官方文档也写明:绑定产物是 jar + 本地 JNI 库,运行时库搜索路径(LD_LIBRARY_PATH 等)必须找得到配套 native。

    3.3 怎么读才有用(不要通读)

    在任意目标仓库里只搜这些关键词:

    director
    %feature("director")
    shared_ptr
    %shared_ptr
    swigReleaseOwnership
    swigTakeOwnership
    %extend
    callback

    并回答四个是非题:

  • 回调是 Director,还是 Java 接口 + C 函数指针适配?
  • cancel / dispose 谁 delete?
  • CI 是否强制重跑 SWIG?
  • 异步路径有没有进 Director?

  • 4. 带着问题读:四个对照检查点

    4.1 所有权与 shared_ptr 边界

    问题在 QuantLib / GDAL 里看什么
    默认谁管理? SWIG 默认 vs 手写接管
    Director 返回对象谁持引用计数? %shared_ptr + directorout typemap
    Java 何时切断? delete() / dispose() / swigReleaseOwnership
    异步时 C++ 寿命 > Java GC? GlobalRef、双层包装、weak_ptr 规避环

    自家痛点映射: 异步外派时若 C++ 仍持有回调,而 Java 代理已被 GC,属于 ownership 产品化失败,不是「再加一个 %shared_ptr」能糊弄过去。

    4.2 Director「纯度」与异步陷阱

    问题看什么
    Director 是否仅用于同步回调? libSBML / Xapian 的实际调用线程
    有没有在 Director 方法里直接跨线程 JNI Upcall? 搜索 Attach / 消息队列
    如何规避? 入口 AttachCurrentThread,或禁止 Director 跨线程,改为队列 / 句柄 ID

    Java 代理

    SWIG Director

    C++ 工作线程

    Java 代理

    SWIG Director

    C++ 工作线程

    #mermaid-svg-VsBUukhyLPTvJf7D{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VsBUukhyLPTvJf7D .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VsBUukhyLPTvJf7D .error-icon{fill:#552222;}#mermaid-svg-VsBUukhyLPTvJf7D .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VsBUukhyLPTvJf7D .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VsBUukhyLPTvJf7D .marker.cross{stroke:#333333;}#mermaid-svg-VsBUukhyLPTvJf7D svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VsBUukhyLPTvJf7D p{margin:0;}#mermaid-svg-VsBUukhyLPTvJf7D .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-VsBUukhyLPTvJf7D text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-VsBUukhyLPTvJf7D .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-VsBUukhyLPTvJf7D .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D .sequenceNumber{fill:white;}#mermaid-svg-VsBUukhyLPTvJf7D #sequencenumber{fill:#333;}#mermaid-svg-VsBUukhyLPTvJf7D #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D .messageText{fill:#333;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-VsBUukhyLPTvJf7D .labelText,#mermaid-svg-VsBUukhyLPTvJf7D .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .loopText,#mermaid-svg-VsBUukhyLPTvJf7D .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-VsBUukhyLPTvJf7D .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-VsBUukhyLPTvJf7D .noteText,#mermaid-svg-VsBUukhyLPTvJf7D .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-VsBUukhyLPTvJf7D .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-VsBUukhyLPTvJf7D .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-VsBUukhyLPTvJf7D .actorPopupMenu{position:absolute;}#mermaid-svg-VsBUukhyLPTvJf7D .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-VsBUukhyLPTvJf7D .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-VsBUukhyLPTvJf7D .actor-man circle,#mermaid-svg-VsBUukhyLPTvJf7D line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-VsBUukhyLPTvJf7D :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    alt

    [未 Attach / 已

    Detach]

    [已 Attach

    或收口到固定上行]

    异步回调

    崩溃或空上行

    安全 upcall

    4.3 胶水层工程纪律(比单行 .i 更重要)

    从 GDAL / QuantLib 抄流程,而不是抄某一行 typemap:

    纪律落地检查
    .i 按模块拆分 是否出现「单文件上帝接口」
    CI 强制 SWIG 重跑 生成物是否进仓;是否有 diff 门禁
    生成代码 vs 手写补丁 %extend / 额外 .cxx 是否目录清晰
    发布同批 Java 包与 .so/.dll 是否同一构建号

    4.4 异步取消(Cancel / Dispose)的归宿

    对照开源时问清关闭协议:

    Java close()
    → native shutdown()
    → 停止新回调注册
    → 等待在途回调耗尽(或 oneshot 截止)
    → 释放 Director / GlobalRef / shared_ptr

    重点看有没有:

    • oneshot:关闭后至多再投递一次终态;
    • 全量 persist 清单:哪些 listener 在关闭窗口仍必须送达;
    • 锁外派发:持锁登记、解锁后再调 Director,避免重入死锁——同时也要保证解锁后对象仍存活(refcount / 快照)。

    5. 对照学:不一定用 SWIG 的胶水层

    这些材料建立「好 JNI 层长什么样」的感觉,用于评估是否该缩小 Director 表面积:

    项目/技术价值
    Android NDK / AOSP JNI 规范 GlobalRef、线程 Attach、谁 Delete 写得很死
    gRPC Java / Netty native(手写 JNI) 异步回调生命周期极谨慎
    JavaCPP(OpenCV 等) 注解生成绑定;和大 API + SWIG 对比修改成本、调试友好度
    放弃 SWIG 改手写的 changelog 看被什么坑逼走(多为 director / 异步 / 所有权)

    JavaCPP 不是「SWIG 替代品万能药」,但能回答:同样大 API,另一条生成路线如何处理指针与生命周期。读 OpenCV 绑定时代入同一套检查点即可。


    6. 工业实践里的常见终局

    若目标是「胶水层 bug 负担趋近于零」,社区与生产里常见的不是「把 Director 用得更花」,而是缩小暴露面:

    路线做法代价
    A. SWIG 只包同步 API 异步收口到 C++ 线程,经句柄 ID / 结构体拷贝固定 native 上行回 Java 上行代码要手写或半生成
    B. 回调密集接口手写 JNI listen* 脱离 Director,GlobalRef + 明确 dispose 维护成本高,但生命周期最确定
    C. 门禁升级 ASAN、竞态用例、生成物 diff、同批发布 不减少设计复杂度,但降低回归

    #mermaid-svg-Cx5DttU0PaDCf5tU{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Cx5DttU0PaDCf5tU .error-icon{fill:#552222;}#mermaid-svg-Cx5DttU0PaDCf5tU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Cx5DttU0PaDCf5tU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Cx5DttU0PaDCf5tU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Cx5DttU0PaDCf5tU .marker.cross{stroke:#333333;}#mermaid-svg-Cx5DttU0PaDCf5tU svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Cx5DttU0PaDCf5tU p{margin:0;}#mermaid-svg-Cx5DttU0PaDCf5tU .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU .cluster-label text{fill:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU .cluster-label span{color:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU .cluster-label span p{background-color:transparent;}#mermaid-svg-Cx5DttU0PaDCf5tU .label text,#mermaid-svg-Cx5DttU0PaDCf5tU span{fill:#333;color:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU .node rect,#mermaid-svg-Cx5DttU0PaDCf5tU .node circle,#mermaid-svg-Cx5DttU0PaDCf5tU .node ellipse,#mermaid-svg-Cx5DttU0PaDCf5tU .node polygon,#mermaid-svg-Cx5DttU0PaDCf5tU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Cx5DttU0PaDCf5tU .rough-node .label text,#mermaid-svg-Cx5DttU0PaDCf5tU .node .label text,#mermaid-svg-Cx5DttU0PaDCf5tU .image-shape .label,#mermaid-svg-Cx5DttU0PaDCf5tU .icon-shape .label{text-anchor:middle;}#mermaid-svg-Cx5DttU0PaDCf5tU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Cx5DttU0PaDCf5tU .rough-node .label,#mermaid-svg-Cx5DttU0PaDCf5tU .node .label,#mermaid-svg-Cx5DttU0PaDCf5tU .image-shape .label,#mermaid-svg-Cx5DttU0PaDCf5tU .icon-shape .label{text-align:center;}#mermaid-svg-Cx5DttU0PaDCf5tU .node.clickable{cursor:pointer;}#mermaid-svg-Cx5DttU0PaDCf5tU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Cx5DttU0PaDCf5tU .arrowheadPath{fill:#333333;}#mermaid-svg-Cx5DttU0PaDCf5tU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Cx5DttU0PaDCf5tU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Cx5DttU0PaDCf5tU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Cx5DttU0PaDCf5tU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Cx5DttU0PaDCf5tU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Cx5DttU0PaDCf5tU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Cx5DttU0PaDCf5tU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Cx5DttU0PaDCf5tU .cluster text{fill:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU .cluster span{color:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Cx5DttU0PaDCf5tU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Cx5DttU0PaDCf5tU rect.text{fill:none;stroke-width:0;}#mermaid-svg-Cx5DttU0PaDCf5tU .icon-shape,#mermaid-svg-Cx5DttU0PaDCf5tU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Cx5DttU0PaDCf5tU .icon-shape p,#mermaid-svg-Cx5DttU0PaDCf5tU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Cx5DttU0PaDCf5tU .icon-shape .label rect,#mermaid-svg-Cx5DttU0PaDCf5tU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Cx5DttU0PaDCf5tU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Cx5DttU0PaDCf5tU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Cx5DttU0PaDCf5tU :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    是且低频同步

    是且高频异步

    回调是否必须跨语言虚表?

    同步 SWIG + 句柄上行

    谨慎使用 Director

    手写 JNI / 固定上行

    Ownership 产品化 + 竞态门禁

    这不是否定 shared_ptr 或 Director,而是:表面积越小越接近「完美胶水层」。


    7. 1~2 周最小闭环

    天数动作产出
    D1–D3 精读 SWIG Java:director、ownership、shared_ptr、线程宏 「标准行为」笔记一页
    D4–D7 QuantLib-SWIG:挑一个带 callback/观察者的类型,走完 C++→.i→Java 一张「他们如何藏 shared_ptr」序列图
    D8–D10 GDAL:只看 swig/include/java + swig/java/CMakeLists.txt / build.xml 模块拆分与生成流水线清单
    D11–D12 JavaCPP OpenCV:对比大 API 下所有权与生成方式 「SWIG vs 注解生成」对照表
    D13–D14 回写自家模型:哪些 API 可同步化、哪些必须句柄上行、门禁缺哪几项 改造 backlog(可排期)

    完成标志:你能用一页纸写出自家所有权状态机(注册 / 派发 / 取消 / 释放),并指出每一边对应官方文档哪一节、开源哪一类项目。


    8. 速查表

    8.1 关键词 → 去哪看

    关键词首选材料
    Director 线程 Attach SWIG Java 文档 Directors 章节
    swigReleaseOwnership SWIG java.swg / director ownership typemap
    %shared_ptr + director SO 帖 + 自测 typemap;勿假设开箱
    模块拆分 / 打包 GDAL swig/include/java + CMake/Ant
    shared_ptr 对外隐藏 QuantLib-SWIG SWIG/*.i + %extend
    另一条绑定路 JavaCPP

    8.2 反模式

    反模式后果
    通读整个开源 .i 时间耗尽,模式记不住
    默认 %shared_ptr + director「能编过就行」 泄漏或空上行,难复现
    Director 直接跑在业务线程池 Attach/重入/锁顺序踩坑
    jar 与 .so 分开发布 「方法在、符号无」类故障
    关闭时不等待在途回调 use-after-free

    9. 延伸阅读

    官方与社区:

    • SWIG 4.4 Java
    • SWIG 源码仓库(Examples/、Lib/java/)
    • Using shared_ptr with SWIG Directors for Java

    开源绑定:

    • QuantLib-SWIG
    • GDAL Java bindings 文档 / 源码 swig/java
    • libSBML bindings
    • JavaCPP

    SWIG 版本、各项目维护状态与 typemap 行为会随发行版变化;落地前用目标 SWIG 版本跑一小段 director + shared_ptr 冒烟,再放大到业务回调面。


    一句话:没有现成的「异步 Director 完美范本」——精读 SWIG 官方 Java/director/ownership,精拆 QuantLib-SWIG 的 shared_ptr 纪律与 GDAL 的绑定工程结构,再把所有权与派发表面积产品化;胶水层越薄,bug 越少。


    10. 源码调研补充:范围与结论

    本节之后的内容基于本地拉取的源码逐项核对,而不是只参考项目文档。调研对象:

    swig-4.3.1/ SWIG 4.3.1 生成器、Java runtime 与 test-suite
    quantlib-swig/ QuantLib 的 SWIG 多语言绑定
    gdal/ GDAL Java SWIG 与手写 progress JNI
    libsbml/ libSBML SWIG Director 与 callback registry
    xapian/ Xapian Java Director 与 intrusive ownership
    openbabel/ Open Babel Java SWIG
    z3/ Z3 自研 Java JNI 生成器(非 SWIG,对照)
    javacpp/ JavaCPP 注解生成路线(非 SWIG,对照)

    10.1 能力矩阵

    项目Java → C++C++ → Java异步/跨线程上行C++ 主导 Java 回调寿命结论
    SWIG 4.3.1 内核 完整 Director 有 Attach 基础设施 swigReleaseOwnership + GlobalRef 标准机制来源
    QuantLib-SWIG 完整 Director 未发现真实异步用例 不完整,多为 Delegate* 裸指针 学 API 形态,不学长期回调所有权
    GDAL 完整 手写同步 progress proxy 不支持;上下文在 JNI 栈上 普通对象较成熟,回调不支持 学 DISOWN、父子保活和构建
    libSBML 完整 Director 未发现异步回调 部分:裸指针 registry 或 clone 学 clone-based 持有与显式转移
    Xapian 完整 Director 未发现异步回调 C++ 有 intrusive 模型,Java 未完整暴露 学存活契约,不照搬 Java ownership
    Open Babel 完整 无 Director 仅普通对象,危险接口直接忽略 学“少暴露”
    Z3 完整 手写 JNI callback 无 Attach,隐含同线程 GlobalRef + native context 最强长期持有对照,但不是异步范本
    JavaCPP 完整 FunctionPointer / @Virtual 自动 AttachAsDaemon 桌面默认弱引用,业务保活 学线程基础设施,注意强引用缺口

    10.2 最重要的判断

  • Director 能反调 Java,不等于 C++ 已经拥有回调。 QuantLib、libSBML、Xapian 都证明了 Director 的功能可用,但项目级 C++ 容器经常只保存裸指针,回调是否存活仍依赖调用者。

  • shared_ptr 管到哪一层必须说清楚。 %shared_ptr(Proxy) 可能只保证 C++ adapter 存活;如果 adapter 内部仍保存 Delegate*,Java Director 仍可能先被释放。

  • GlobalRef 只解决 GC 保活,不解决并发销毁。 还必须有“停止新派发、注销、等待在途回调、删除引用”的关闭协议。

  • JNIEnv* 不能缓存后跨线程使用。 GDAL progress 和 Z3 callback 都缓存当前调用的 JNIEnv*,因此只能视为同线程同步方案。跨线程必须缓存 JavaVM*,每次通过 GetEnv / Attach 获取当前线程的 JNIEnv*。

  • 没有项目给出完整的异步 Director 产品范本。 真正落地时,需要把 SWIG 的 ownership/Attach 机制与业务自己的取消、在途计数和锁外派发组合起来。


  • 11. SWIG 4.3.1:Java JNI 双向调用基线

    开源项目的 .i 文件只是配置;SWIG 生成器本身决定了 Proxy、JNI 和 Director 的物理行为。因此先建立 SWIG 4.3.1 的标准模型,再评估项目有没有补齐业务协议。

    11.1 Java → JNI → C++ 下行链

    典型调用链:

    Java Proxy.method(…)
    → ModuleJNI.method(swigCPtr, …)
    → JNIEXPORT Java_pkg_ModuleJNI_method(JNIEnv* jenv, …)
    → typemap 将 jlong 解为 T*
    → 调用真实 C++ 方法
    → typemap 将结果包装回 Java Proxy

    关键组件:

    • Source/Modules/java.cxx:生成 Java Proxy、JNI wrapper 和 Director;
    • Lib/java/java.swg:swigCPtr、swigCMemOwn、delete()、输入输出 typemap;
    • Lib/java/director.swg:Java 对象引用、JavaVM*、线程 Attach 和 Director 异常。

    Java Proxy 中的两个字段语义不同:

    • swigCPtr:指向 native 对象或智能指针包装壳;
    • swigCMemOwn:Java Proxy 是否负责触发 native 销毁。

    swigCMemOwn 不是 C++ 对象内部引用计数,也不能表示业务容器是否仍在使用对象。

    11.2 C++ → Director → Java 上行链

    启用:

    %module(directors="1") sdk
    %feature("director") Listener;

    后,SWIG 为 Listener 生成 SwigDirector_Listener。上行路径为:

    C++ 调 Listener::onEvent()
    → 实际虚表落到 SwigDirector_Listener::onEvent()
    → JNIEnvWrapper 获取当前线程 JNIEnv*
    → 取 Java proxy 的 local ref
    → JNI CallStatic*Method
    → intermediary 再调用 Java override

    下图把下行调用和 Director 上行放在同一张时序图里:

    C++核心

    SwigDirector_Listener

    ModuleJNI / JNI wrapper

    Java Proxy

    Java业务代码

    C++核心

    SwigDirector_Listener

    ModuleJNI / JNI wrapper

    Java Proxy

    Java业务代码

    #mermaid-svg-MotD0lpvPEcax7MT{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MotD0lpvPEcax7MT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MotD0lpvPEcax7MT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MotD0lpvPEcax7MT .error-icon{fill:#552222;}#mermaid-svg-MotD0lpvPEcax7MT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MotD0lpvPEcax7MT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MotD0lpvPEcax7MT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MotD0lpvPEcax7MT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MotD0lpvPEcax7MT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MotD0lpvPEcax7MT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MotD0lpvPEcax7MT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MotD0lpvPEcax7MT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MotD0lpvPEcax7MT .marker.cross{stroke:#333333;}#mermaid-svg-MotD0lpvPEcax7MT svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MotD0lpvPEcax7MT p{margin:0;}#mermaid-svg-MotD0lpvPEcax7MT .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MotD0lpvPEcax7MT text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-MotD0lpvPEcax7MT .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MotD0lpvPEcax7MT .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-MotD0lpvPEcax7MT .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-MotD0lpvPEcax7MT .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-MotD0lpvPEcax7MT #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-MotD0lpvPEcax7MT .sequenceNumber{fill:white;}#mermaid-svg-MotD0lpvPEcax7MT #sequencenumber{fill:#333;}#mermaid-svg-MotD0lpvPEcax7MT #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-MotD0lpvPEcax7MT .messageText{fill:#333;stroke:none;}#mermaid-svg-MotD0lpvPEcax7MT .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MotD0lpvPEcax7MT .labelText,#mermaid-svg-MotD0lpvPEcax7MT .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-MotD0lpvPEcax7MT .loopText,#mermaid-svg-MotD0lpvPEcax7MT .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-MotD0lpvPEcax7MT .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MotD0lpvPEcax7MT .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-MotD0lpvPEcax7MT .noteText,#mermaid-svg-MotD0lpvPEcax7MT .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-MotD0lpvPEcax7MT .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MotD0lpvPEcax7MT .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MotD0lpvPEcax7MT .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MotD0lpvPEcax7MT .actorPopupMenu{position:absolute;}#mermaid-svg-MotD0lpvPEcax7MT .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-MotD0lpvPEcax7MT .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MotD0lpvPEcax7MT .actor-man circle,#mermaid-svg-MotD0lpvPEcax7MT line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-MotD0lpvPEcax7MT :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    同步调用线程或native工作线程

    opt

    [当前native线程未附着]

    client.start(listener)

    1

    start(swigCPtr, listenerCPtr)

    2

    Client::start(Listener*)

    3

    return

    4

    return

    5

    return

    6

    listener->>onEvent(event)

    7

    JavaVM::GetEnv

    8

    AttachCurrentThread(AsDaemon)

    9

    CallStatic*Method(director method)

    10

    listener.onEvent(eventProxy)

    11

    Java override

    12

    return / throwable

    13

    return / DirectorException

    14

    这说明 Director 是一个 C++ 派生对象 + Java proxy 引用 的双层结构。只保住其中一层并不够:

    • C++ Director 被删:native 虚表对象失效;
    • Java proxy 被 GC:Director 无法找到上行目标;
    • 两边都强持有却没有关闭协议:可能形成跨语言环。

    11.3 WeakGlobalRef 与 GlobalRef 的切换

    Lib/java/director.swg 的 JObjectWrapper 是理解 ownership 的核心:

    Java 管 C++ 寿命
    swigCMemOwn = true
    Director 通常持 WeakGlobalRef
    Java proxy 不可达后允许 GC,并触发 native delete

    C++ 管 C++ 寿命
    Java 调 swigReleaseOwnership()
    swigCMemOwn = false
    Director 将 WeakGlobalRef 切为 GlobalRef
    即使 Java 局部变量消失,C++ 仍能安全上行

    核心逻辑就在 JObjectWrapper::set()。注意这一行决定了「未拥有即弱引用」:

    // swig-4.3.1/Lib/java/director.swg:71-86
    bool set(JNIEnv *jenv, jobject jobj, bool mem_own, bool weak_global) {
    if (!jthis_) {
    weak_global_ = weak_global || !mem_own; // 未拥有(!mem_own)则强制弱引用
    if (jobj)
    jthis_ = weak_global_ ? jenv->NewWeakGlobalRef(jobj) : jenv->NewGlobalRef(jobj);
    return true;
    }
    // …
    }

    ownership 切换时的强弱引用互换:

    // swig-4.3.1/Lib/java/director.swg:123-138
    void java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
    if (take_or_release) { // Java 接管 → 弱引用,允许 GC
    if (!weak_global_) {
    jenv->DeleteGlobalRef(jthis_);
    jthis_ = jenv->NewWeakGlobalRef(jself);
    weak_global_ = true;
    }
    } else { // Java 释放 → 强引用,钉住 proxy
    if (weak_global_) {
    jenv->DeleteWeakGlobalRef((jweak)jthis_);
    jthis_ = jenv->NewGlobalRef(jself);
    weak_global_ = false;
    }
    }
    }

    Java 侧暴露的入口只是薄薄一层 typemap,真正动作在上面的 C++:

    // swig-4.3.1/Lib/java/java.swg:1375-1387
    %typemap(directorowner_release, methodname="swigReleaseOwnership") SWIGTYPE %{
    public void $methodname() {
    swigCMemOwn = false; // Java 不再负责 delete
    $jnicall; // 触发 java_change_ownership(…, false) → GlobalRef
    }
    %}
    %typemap(directorowner_take, methodname="swigTakeOwnership") SWIGTYPE %{
    public void $methodname() {
    swigCMemOwn = true; // Java 重新负责 delete
    $jnicall; // 触发 java_change_ownership(…, true) → WeakGlobalRef
    }
    %}

    其他锚点:Examples/test-suite/director_ownership.i 是官方 ownership 用例。

    推荐的 C++ 主导状态机:

    #mermaid-svg-XEKUFnfOFTJ5NnjB{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XEKUFnfOFTJ5NnjB .error-icon{fill:#552222;}#mermaid-svg-XEKUFnfOFTJ5NnjB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XEKUFnfOFTJ5NnjB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XEKUFnfOFTJ5NnjB .marker.cross{stroke:#333333;}#mermaid-svg-XEKUFnfOFTJ5NnjB svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XEKUFnfOFTJ5NnjB p{margin:0;}#mermaid-svg-XEKUFnfOFTJ5NnjB defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-XEKUFnfOFTJ5NnjB g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-XEKUFnfOFTJ5NnjB g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-XEKUFnfOFTJ5NnjB g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-XEKUFnfOFTJ5NnjB g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-XEKUFnfOFTJ5NnjB g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-XEKUFnfOFTJ5NnjB .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-XEKUFnfOFTJ5NnjB .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XEKUFnfOFTJ5NnjB .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XEKUFnfOFTJ5NnjB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XEKUFnfOFTJ5NnjB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XEKUFnfOFTJ5NnjB .edgeLabel .label text{fill:#333;}#mermaid-svg-XEKUFnfOFTJ5NnjB .label div .edgeLabel{color:#333;}#mermaid-svg-XEKUFnfOFTJ5NnjB .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-XEKUFnfOFTJ5NnjB .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-XEKUFnfOFTJ5NnjB .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-XEKUFnfOFTJ5NnjB .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-XEKUFnfOFTJ5NnjB .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-XEKUFnfOFTJ5NnjB .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XEKUFnfOFTJ5NnjB #statediagram-barbEnd{fill:#333333;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .cluster-label,#mermaid-svg-XEKUFnfOFTJ5NnjB .nodeLabel{color:#131300;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-XEKUFnfOFTJ5NnjB .note-edge{stroke-dasharray:5;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-note text{fill:black;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram-note .nodeLabel{color:black;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagram .edgeLabel{color:red;}#mermaid-svg-XEKUFnfOFTJ5NnjB #dependencyStart,#mermaid-svg-XEKUFnfOFTJ5NnjB #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-XEKUFnfOFTJ5NnjB .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XEKUFnfOFTJ5NnjB :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Java new Director

    swigReleaseOwnership()

    C++ 保存并调用

    回调完成

    swigTakeOwnership() 可选交回

    停止注册/派发

    在途归零,C++ delete

    disconnect + DeleteGlobalRef

    JavaOwns

    CppOwns

    Dispatching

    Closing

    Destroyed

    禁止状态:

    C++ 长期保存 Director*
    + Java 仍 swigCMemOwn=true
    + Director 只持 WeakGlobalRef
    = Java GC 后空上行、悬空指针或双重释放风险

    11.4 原生线程如何 Attach

    上行的线程处理集中在 JNIEnvWrapper:构造时 GetEnv,未附着则 Attach;析构时按宏决定是否 detach。

    // swig-4.3.1/Lib/java/director.swg:196-243(节选)
    JNIEnvWrapper(const Director *director) : director_(director), ... {
    env_status = director_->swig_jvm_->GetEnv((void **)&jenv_, JNI_VERSION_1_2);
    JavaVMAttachArgs args;
    args.version = JNI_VERSION_1_2;
    // …
    #if defined(SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON)
    director_->swig_jvm_->AttachCurrentThreadAsDaemon(jenv, &args);
    #else
    director_->swig_jvm_->AttachCurrentThread(jenv, &args);
    #endif
    #if defined(SWIG_JAVA_DETACH_ON_THREAD_END)
    // Android:每次回调后 detach 会泄漏,改为注册线程析构键,线程结束才 detach
    pthread_once(&once, JObjectWrapper::make_detach_key);
    pthread_setspecific(JObjectWrapper::detach_key_, director->swig_jvm_);
    #endif
    }
    ~JNIEnvWrapper() {
    #if !defined(SWIG_JAVA_DETACH_ON_THREAD_END) && !defined(SWIG_JAVA_NO_DETACH_CURRENT_THREAD)
    if (env_status == JNI_EDETACHED) // 仅 detach 本次新 attach 的线程
    director_->swig_jvm_->DetachCurrentThread();
    #endif
    }

    要点:

  • Director 构造时保存 JavaVM*(不是 JNIEnv*);
  • 上行时用 JavaVM::GetEnv 获取当前线程环境;
  • 必要时调用 AttachCurrentThread;
  • SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON 改为 daemon attach;
  • SWIG_JAVA_DETACH_ON_THREAD_END 通过 pthread TLS 在线程结束时 detach;
  • 默认情况下,本次新 attach 的线程会在 wrapper 析构时 detach。
  • 需要按平台验证宏组合,而不是机械开启全部宏:

    • JVM 退出被 native 工作线程阻塞:考虑 daemon attach;
    • Android 每次回调后 detach 有额外问题:考虑在线程结束时 detach;
    • 使用长期线程池:不要每次回调反复 attach/detach;
    • JVM shutdown 后仍可能派发:Attach 机制也救不了,必须先停 native 线程。

    11.5 Director 异常

    Java override 抛异常时,SWIG Director 默认构造 Swig::DirectorException。但这不代表每个 JNI 下行入口都会自动将它还原为 Java 异常。

    对于可能触发 Director 的 C++ 入口,应显式配置:

    %catches(Swig::DirectorException) run;

    并为业务异常定义统一映射。否则 Java 异常穿过 C++ 栈后无人捕获,可能导致 std::terminate 或进程退出。

    11.6 %shared_ptr 与 Director 的边界

    Lib/java/boost_shared_ptr.i 对部分按值签名支持 Director,但对多种引用/裸指针 directorout 明确不支持。设计 API 时应:

    • 优先返回 shared_ptr<T> 值或普通值;
    • 避免 Director 方法返回 T&、shared_ptr<T>& 等复杂引用;
    • 分清 swigCMemOwn 管的是 shared_ptr<T> 包装壳,还是 T 本体;
    • 检查 Director 与 shared_ptr 是否形成跨语言环;
    • 用目标 SWIG 版本生成并检查 wrapper,不以“编译通过”代替生命周期测试。

    12. 逐项目源码分析

    12.1 QuantLib-SWIG:Director API 形态好,长期回调所有权不足

    绑定结构

    SWIG/quantlib.i:21-27 对 Java/C# 启用:

    %module(directors="1") QuantLib

    Java 路径实际启用了多个 Delegate Director,包括:

    • UnaryFunctionDelegate;
    • BinaryFunctionDelegate;
    • CostFunctionDelegate;
    • OdeFctDelegate;
    • 三类 FDM Delegate。

    Java/Makefile.am 用 swig -java -c++ 生成 wrapper,JNI 库为 QuantLibJNI,Java 类进入 QuantLib.jar。

    如何互相调用

    以 UnaryFunction 为例:

    Java 匿名类继承 UnaryFunctionDelegate
    → Java override value(double)
    → JNI 将 Director* 传给 UnaryFunction 构造
    → UnaryFunction 保存 UnaryFunctionDelegate* delegate_
    → C++ operator() 调 delegate_->value()
    → Director 上行到 Java

    完整证据在 SWIG/functions.i:106-145。可以看到 Director 接口、C++ adapter 与对 SWIG 暴露的声明是三段式:

    // quantlib-swig/SWIG/functions.i:108-145(节选)
    %{
    class UnaryFunctionDelegate { // Director 接口(Java 继承它)
    public:
    virtual ~UnaryFunctionDelegate() {}
    virtual Real value(Real x) const {
    QL_FAIL("implementation of UnaryFunctionDelegate.value is missing");
    }
    };

    class UnaryFunction { // C++ adapter:包成算法要的函数对象
    public:
    UnaryFunction(UnaryFunctionDelegate* delegate) : delegate_(delegate) { }
    Real operator()(Real x) const { return delegate_->value(x); }
    private:
    UnaryFunctionDelegate* delegate_; // 只保存裸指针
    };
    %}
    // …
    %feature("director") UnaryFunctionDelegate;

    adapter 很薄,能把 Java override 适配成 QuantLib 算法需要的函数对象,这是值得借鉴的 API 形态。

    QuantLib算法

    C++ UnaryFunction

    SwigDirector_UnaryFunctionDelegate

    QuantLibJNI

    UnaryFunctionDelegate Proxy

    Java FunctionDelegates

    QuantLib算法

    C++ UnaryFunction

    SwigDirector_UnaryFunctionDelegate

    QuantLibJNI

    UnaryFunctionDelegate Proxy

    Java FunctionDelegates

    #mermaid-svg-Oh62UdpbJyJEB7nS{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Oh62UdpbJyJEB7nS .error-icon{fill:#552222;}#mermaid-svg-Oh62UdpbJyJEB7nS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Oh62UdpbJyJEB7nS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Oh62UdpbJyJEB7nS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Oh62UdpbJyJEB7nS .marker.cross{stroke:#333333;}#mermaid-svg-Oh62UdpbJyJEB7nS svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Oh62UdpbJyJEB7nS p{margin:0;}#mermaid-svg-Oh62UdpbJyJEB7nS .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Oh62UdpbJyJEB7nS text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Oh62UdpbJyJEB7nS .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Oh62UdpbJyJEB7nS .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Oh62UdpbJyJEB7nS #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Oh62UdpbJyJEB7nS .sequenceNumber{fill:white;}#mermaid-svg-Oh62UdpbJyJEB7nS #sequencenumber{fill:#333;}#mermaid-svg-Oh62UdpbJyJEB7nS #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Oh62UdpbJyJEB7nS .messageText{fill:#333;stroke:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Oh62UdpbJyJEB7nS .labelText,#mermaid-svg-Oh62UdpbJyJEB7nS .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .loopText,#mermaid-svg-Oh62UdpbJyJEB7nS .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Oh62UdpbJyJEB7nS .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Oh62UdpbJyJEB7nS .noteText,#mermaid-svg-Oh62UdpbJyJEB7nS .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Oh62UdpbJyJEB7nS .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Oh62UdpbJyJEB7nS .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Oh62UdpbJyJEB7nS .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Oh62UdpbJyJEB7nS .actorPopupMenu{position:absolute;}#mermaid-svg-Oh62UdpbJyJEB7nS .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Oh62UdpbJyJEB7nS .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Oh62UdpbJyJEB7nS .actor-man circle,#mermaid-svg-Oh62UdpbJyJEB7nS line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Oh62UdpbJyJEB7nS :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    A只保存Delegate*裸指针

    new UnaryFunctionDelegate(){ value(x) }

    1

    director_connect(…)

    2

    new SwigDirector(proxy ref)

    3

    new UnaryFunction(delegateCPtr)

    4

    UnaryFunction(Delegate*)

    5

    调积分/求根/ODE API

    6

    QuantLib算法执行

    7

    function(x)

    8

    delegate_->>value(x)

    9

    JNI上行

    10

    Java override value(x)

    11

    Real结果

    12

    生命周期问题

    关键就在上面那一行 UnaryFunctionDelegate* delegate_;。adapter 不 delete delegate_,也不持 shared_ptr<UnaryFunctionDelegate>。因此:

    • C++ 可复制或长期保存 UnaryFunction;
    • 但它保存的仍是 Director 裸指针;
    • Java 若提前 delete() / close() Delegate,C++ 继续调用会悬空;
    • 项目没有注册 token、unregister 或在途耗尽协议。

    QuantLib 大量使用 %shared_ptr,但通常是:

    shared_ptr<业务对象或 Proxy>
    → Proxy 内部仍是 Delegate*

    所以“外层对象由 shared_ptr 管理”不能推出“回调 Director 也由 C++ 管理”。

    线程与异步

    源码中没有项目自定义 JavaVM / AttachCurrentThread 实现,也没有明确的 native worker thread Director 用例。生成的 SWIG runtime 具备通用 Attach 代码,但 QuantLib 项目没有建立跨线程回调的业务约束与测试。

    可学与不可照搬

    可学:

    • Java Delegate 接口与 C++ adapter 分离;
    • Java 匿名类实现回调;
    • adapter 把跨语言回调转成现有 C++ 函数对象;
    • 普通业务对象统一用 %shared_ptr 隐藏智能指针。

    不可照搬:

    • 长期保存 Delegate* 裸指针;
    • 认为 %shared_ptr(Proxy) 自动拥有 Delegate;
    • 在没有取消/在途屏障时让 Java close() 回调;
    • 把同步算法回调直接推广到异步线程池。

    12.2 GDAL:不使用 Director,擅长普通对象 ownership

    绑定结构

    GDAL 的 gdal/ogr/osr/gnm 模块都使用普通 %module,没有 directors="1"。CMake 为各模块生成 Java wrapper,最后统一链接到 gdalalljni。

    它的 C++ → Java progress 回调不是 Director,而是 swig/include/java/callback.i 中的手写 JNI bridge。

    progress 回调如何工作

    回调上下文与 proxy 全在 swig/include/java/callback.i:

    // gdal/swig/include/java/callback.i:6-55(节选)
    typedef struct {
    JNIEnv *jenv; // 缓存的是当前调用线程的 env
    jobject pJavaCallback; // 只是 local reference,没有 NewGlobalRef
    } JavaProgressData;

    static int CPL_STDCALL
    JavaProgressProxy( double dfComplete, const char *pszMessage, void *pData )
    {
    JavaProgressData* psProgressInfo = (JavaProgressData*)pData;
    JNIEnv *jenv = psProgressInfo->jenv;
    const jclass cls = jenv->FindClass("org/gdal/gdal/ProgressCallback");
    const jmethodID runMethod = jenv->GetMethodID(cls, "run", "(DLjava/lang/String;)I");
    jstring temp_string = jenv->NewStringUTF(pszMessage);
    int ret = jenv->CallIntMethod(psProgressInfo->pJavaCallback, runMethod, dfComplete, temp_string);
    jenv->DeleteLocalRef(temp_string);
    return ret;
    }

    而 JavaProgressData 是在 arginit typemap 里栈上创建的,随 JNI 方法返回即失效:

    // gdal/swig/include/java/callback.i:58-72
    %typemap(arginit, noblock=1) (GDALProgressFunc callback=NULL, void* callback_data=NULL) {
    JavaProgressData sProgressInfo; // 栈变量
    sProgressInfo.jenv = jenv;
    sProgressInfo.pJavaCallback = NULL;
    }
    %typemap(in) (GDALProgressFunc callback=NULL, void* callback_data=NULL) {
    if ( $input != 0 ) {
    sProgressInfo.pJavaCallback = $input;
    $1 = JavaProgressProxy;
    $2 = &sProgressInfo; // 指向栈变量的地址
    } else { $1 = NULL; $2 = NULL; }
    }

    这是一种严格的同步借用模型:

    Java 调 GDAL 方法
    → JNI 栈上创建 callback context
    → C++ 在本次调用内报告进度
    → proxy 用同一线程的 JNIEnv* 上行
    → native 方法返回,context 失效

    JavaProgressProxy

    GDAL C/C++函数

    栈上JavaProgressData

    GDAL Java Proxy/JNI

    Java应用

    JavaProgressProxy

    GDAL C/C++函数

    栈上JavaProgressData

    GDAL Java Proxy/JNI

    Java应用

    #mermaid-svg-ZznPBTt8b4RQh0ZG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZznPBTt8b4RQh0ZG .error-icon{fill:#552222;}#mermaid-svg-ZznPBTt8b4RQh0ZG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZznPBTt8b4RQh0ZG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZznPBTt8b4RQh0ZG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZznPBTt8b4RQh0ZG .marker.cross{stroke:#333333;}#mermaid-svg-ZznPBTt8b4RQh0ZG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZznPBTt8b4RQh0ZG p{margin:0;}#mermaid-svg-ZznPBTt8b4RQh0ZG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZznPBTt8b4RQh0ZG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZznPBTt8b4RQh0ZG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZznPBTt8b4RQh0ZG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZznPBTt8b4RQh0ZG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZznPBTt8b4RQh0ZG .sequenceNumber{fill:white;}#mermaid-svg-ZznPBTt8b4RQh0ZG #sequencenumber{fill:#333;}#mermaid-svg-ZznPBTt8b4RQh0ZG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZznPBTt8b4RQh0ZG .messageText{fill:#333;stroke:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZznPBTt8b4RQh0ZG .labelText,#mermaid-svg-ZznPBTt8b4RQh0ZG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .loopText,#mermaid-svg-ZznPBTt8b4RQh0ZG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZznPBTt8b4RQh0ZG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZznPBTt8b4RQh0ZG .noteText,#mermaid-svg-ZznPBTt8b4RQh0ZG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZznPBTt8b4RQh0ZG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZznPBTt8b4RQh0ZG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZznPBTt8b4RQh0ZG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZznPBTt8b4RQh0ZG .actorPopupMenu{position:absolute;}#mermaid-svg-ZznPBTt8b4RQh0ZG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZznPBTt8b4RQh0ZG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZznPBTt8b4RQh0ZG .actor-man circle,#mermaid-svg-ZznPBTt8b4RQh0ZG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZznPBTt8b4RQh0ZG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    loop

    [本次native调用尚未返回]

    JNI返回后栈对象、local ref均失效

    operation(progressCallback)

    1

    保存当前JNIEnv*和local jobject

    2

    func(JavaProgressProxy, &sProgressInfo)

    3

    progress(percent, msg, context)

    4

    读取JNIEnv*和callback

    5

    CallIntMethod(run)

    6

    continue/cancel

    7

    return

    8

    return

    9

    它不能异步化,因为 JNI 返回后:

    • sProgressInfo 已离开栈;
    • jobject 只是 local reference;
    • 缓存的 JNIEnv* 不能在另一线程使用。

    swig/include/Dataset.i 还明确对 SWIGJAVA 排除了 AsyncReader wrapper,进一步说明 Java binding 没有提供异步读取回调。

    普通对象生命周期做得更好

    GDAL 的价值主要在普通对象 ownership:

    • %newobject 标记新对象;
    • DISOWN 清除 Java ownership,适配 C++ 接管;
    • parentReference 让 borrowed child 在 Java 侧强持有 parent;
    • Feature/Geometry 使用 ReferenceQueue 类清理机制;
    • Dataset/DataSource/Geometry 使用各自正确的 native release 函数。

    DISOWN 与 parentReference 的真实实现(同一段 javacode typemap):

    // gdal/swig/include/java/typemaps_java.i:79-94
    private Object parentReference;

    protected static long getCPtrAndDisown($javaclassname obj) {
    if (obj != null) {
    obj.swigCMemOwn = false; // 交给 C++ 后 Java 不再 delete
    obj.parentReference = null;
    }
    return getCPtr(obj);
    }

    /* 防止 GC 回收 Java 侧父对象 */
    protected void addReference(Object reference) {
    parentReference = reference;
    }

    典型 C++ 接管:Feature.SetGeometryDirectly、Geometry.AddGeometryDirectly。这类接口不是简单地“把指针存进去”,而是绑定层同步更新 Java ownership,避免 Java 和 C++ 同时 delete。

    可学与不可照搬

    可学:

    • DISOWN 显式表达所有权转移;
    • borrowed child 强持有 parent,避免父对象先被 GC;
    • 对不同 native 类型调用正确的 Close/Release/Destroy;
    • Java jar 与 JNI wrapper 同一构建图生成;
    • 回调 typemap 独立成文件。

    不可照搬:

    • 将 JavaProgressData 保存到 JNI 调用之外;
    • 从另一个线程复用其中的 JNIEnv*;
    • 把 GDAL native 核心的 AsyncReader 误认为 Java binding 已支持;
    • 用 progress bridge 作为长期 listener 模板。

    12.3 libSBML:裸指针 registry 与 clone-based ownership 并存

    Director 范围

    src/bindings/swig/libsbml.i 启用 directors,并为 SBMLValidator、SBMLConverter、ElementFilter、Callback 等类型开启 Director;comp package 还为 SBMLResolver 开启 Director。

    模式一:CallbackRegistry 借用裸指针

    CallbackRegistry 单例保存 std::vector<Callback*>,遍历调用虚函数,但增删只动指针、从不 delete:

    // libsbml/src/sbml/util/CallbackRegistry.cpp:17-41,56-65(节选)
    int CallbackRegistry::invokeCallbacks(SBMLDocument* doc) {
    int result = LIBSBML_OPERATION_SUCCESS;
    std::vector<Callback*>& cbs = getInstance().mCallbacks;
    for (int i = 0; i < (int)cbs.size(); ++i)
    result += cbs[i]->process(doc); // 若是 Java 子类,这里上行进 JVM
    return result;
    }
    void CallbackRegistry::clearCallbacks() { getInstance().mCallbacks.clear(); } // 不 delete
    void CallbackRegistry::addCallback(Callback *cb) { getInstance().mCallbacks.push_back(cb); }
    void CallbackRegistry::removeCallback(Callback* cb) {
    std::vector<Callback*>& cbs = getInstance().mCallbacks;
    std::vector<Callback*>::iterator it = std::find(cbs.begin(), cbs.end(), cb);
    if (it != cbs.end()) cbs.erase(it); // 仅移除引用
    }

    如果 Callback 来自 Java 子类,vector 实际持有的是 SWIG Director 指针。这个模式意味着:

    • registry 只借用;
    • Java/调用者必须保证 callback 在 remove 前一直存活;
    • 无锁 vector 不支持注册、移除、派发并发;
    • 派发中 remove 还可能破坏迭代;
    • 没有 C++ 主导的销毁闭环。

    它能作为同步 callback registry 的最小示例,但不是产品级异步 listener 设计。

    模式二:虚拟 clone 后由 C++ 拥有

    Validator、Resolver、Converter registry 的策略不同:

    传入多态对象
    → 调虚拟 clone()
    → C++ registry 保存 clone
    → registry/document 析构时 delete clone

    libSBML 实际并存两条持有路线:

    #mermaid-svg-kSbZkwaAi5F62lNS{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kSbZkwaAi5F62lNS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kSbZkwaAi5F62lNS .error-icon{fill:#552222;}#mermaid-svg-kSbZkwaAi5F62lNS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kSbZkwaAi5F62lNS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kSbZkwaAi5F62lNS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kSbZkwaAi5F62lNS .marker.cross{stroke:#333333;}#mermaid-svg-kSbZkwaAi5F62lNS svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kSbZkwaAi5F62lNS p{margin:0;}#mermaid-svg-kSbZkwaAi5F62lNS .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-kSbZkwaAi5F62lNS .cluster-label text{fill:#333;}#mermaid-svg-kSbZkwaAi5F62lNS .cluster-label span{color:#333;}#mermaid-svg-kSbZkwaAi5F62lNS .cluster-label span p{background-color:transparent;}#mermaid-svg-kSbZkwaAi5F62lNS .label text,#mermaid-svg-kSbZkwaAi5F62lNS span{fill:#333;color:#333;}#mermaid-svg-kSbZkwaAi5F62lNS .node rect,#mermaid-svg-kSbZkwaAi5F62lNS .node circle,#mermaid-svg-kSbZkwaAi5F62lNS .node ellipse,#mermaid-svg-kSbZkwaAi5F62lNS .node polygon,#mermaid-svg-kSbZkwaAi5F62lNS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kSbZkwaAi5F62lNS .rough-node .label text,#mermaid-svg-kSbZkwaAi5F62lNS .node .label text,#mermaid-svg-kSbZkwaAi5F62lNS .image-shape .label,#mermaid-svg-kSbZkwaAi5F62lNS .icon-shape .label{text-anchor:middle;}#mermaid-svg-kSbZkwaAi5F62lNS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kSbZkwaAi5F62lNS .rough-node .label,#mermaid-svg-kSbZkwaAi5F62lNS .node .label,#mermaid-svg-kSbZkwaAi5F62lNS .image-shape .label,#mermaid-svg-kSbZkwaAi5F62lNS .icon-shape .label{text-align:center;}#mermaid-svg-kSbZkwaAi5F62lNS .node.clickable{cursor:pointer;}#mermaid-svg-kSbZkwaAi5F62lNS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kSbZkwaAi5F62lNS .arrowheadPath{fill:#333333;}#mermaid-svg-kSbZkwaAi5F62lNS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kSbZkwaAi5F62lNS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kSbZkwaAi5F62lNS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kSbZkwaAi5F62lNS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kSbZkwaAi5F62lNS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kSbZkwaAi5F62lNS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kSbZkwaAi5F62lNS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kSbZkwaAi5F62lNS .cluster text{fill:#333;}#mermaid-svg-kSbZkwaAi5F62lNS .cluster span{color:#333;}#mermaid-svg-kSbZkwaAi5F62lNS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kSbZkwaAi5F62lNS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kSbZkwaAi5F62lNS rect.text{fill:none;stroke-width:0;}#mermaid-svg-kSbZkwaAi5F62lNS .icon-shape,#mermaid-svg-kSbZkwaAi5F62lNS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kSbZkwaAi5F62lNS .icon-shape p,#mermaid-svg-kSbZkwaAi5F62lNS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kSbZkwaAi5F62lNS .icon-shape .label rect,#mermaid-svg-kSbZkwaAi5F62lNS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kSbZkwaAi5F62lNS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kSbZkwaAi5F62lNS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kSbZkwaAi5F62lNS :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    CallbackRegistry.add

    addValidator/addResolver

    Java Director子类

    SWIG JNI

    C++接收接口

    保存Callback*裸指针

    invokeCallbacks

    Director上行Java process

    remove/clear仅移除

    调用者负责对象寿命

    虚调用clone

    C++保存clone副本

    业务时虚调用

    Director上行Java

    owner析构时delete clone

    优点:

    • 长期对象与传入的临时 proxy 解耦;
    • C++ 明确拥有 clone;
    • owner 析构时释放路径明确。

    风险:

    • Java 子类必须正确实现 clone;
    • clone 返回的 Director ownership 仍需 typemap 配合;
    • clone 可能复制跨语言引用和业务状态;
    • 对高频 listener 来说,clone 语义未必自然。
    所有权工具

    libSBML 使用:

    • DISOWN;
    • %newobject;
    • Java 侧 getCPtrAndDisown()。

    这说明它更倾向于在 API 接口上显式标记“谁 delete”,而非完全依赖默认 SWIG 行为。

    线程与异步

    未发现这些 Director 由 native worker thread 异步调用,也未发现项目自定义 Attach。现有模式应按同步调用理解。

    可学与不可照搬

    可学:

    • clone-based C++ ownership;
    • 转移参数统一走 getCPtrAndDisown();
    • callback registry 的 add/remove 基本形态。

    不可照搬:

    • 无锁 vector<Callback*> 用于异步;
    • clear/remove 不等待在途回调;
    • 把 clone 当成所有 listener 的通用解法;
    • 未验证线程 Attach 就从 worker thread 调 Director。

    12.4 Xapian:C++ intrusive ownership 清楚,Java 暴露不完整

    Director 范围

    xapian-bindings/java/java.i 启用 Director;SUBCLASSABLE 宏集中声明 MatchDecider、MatchSpy、KeyMaker、PostingSource 等可由 Java 继承的类型。

    这种集中声明方式值得借鉴:Director 表面积可审计,不会在大量 .i 文件中零散扩张。

    Enquire 如何持有 MatchSpy

    Enquire::add_matchspy(MatchSpy*) 的头文件注释把存活契约写得很清楚——这正是值得学的地方:

    // xapian/xapian-core/include/xapian/enquire.h:314-336(节选)
    /** Add a matchspy.
    * @param spy The MatchSpy subclass to add. The caller must
    * ensure that this remains valid while the Enquire
    * object remains active, or until clear_matchspies()
    * is called, or else allocate the MatchSpy object with
    * new and then disown it by calling spy->release()
    * before passing it in.
    */

    void add_matchspy(MatchSpy* spy) XAPIAN_NONNULL();

    C++ 用 opt_intrusive_ptr 同时支持:

    • 未启用引用计数:借用对象;
    • 调用 release() 启用 ownership:intrusive pointer 接管。

    这比单纯裸指针更精细,因为 API 明确区分 borrowed 与 owned。

    #mermaid-svg-JPxcv0s9Usf0Ow8h{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JPxcv0s9Usf0Ow8h .error-icon{fill:#552222;}#mermaid-svg-JPxcv0s9Usf0Ow8h .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JPxcv0s9Usf0Ow8h .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .marker.cross{stroke:#333333;}#mermaid-svg-JPxcv0s9Usf0Ow8h svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JPxcv0s9Usf0Ow8h p{margin:0;}#mermaid-svg-JPxcv0s9Usf0Ow8h .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .cluster-label text{fill:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .cluster-label span{color:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .cluster-label span p{background-color:transparent;}#mermaid-svg-JPxcv0s9Usf0Ow8h .label text,#mermaid-svg-JPxcv0s9Usf0Ow8h span{fill:#333;color:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .node rect,#mermaid-svg-JPxcv0s9Usf0Ow8h .node circle,#mermaid-svg-JPxcv0s9Usf0Ow8h .node ellipse,#mermaid-svg-JPxcv0s9Usf0Ow8h .node polygon,#mermaid-svg-JPxcv0s9Usf0Ow8h .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .rough-node .label text,#mermaid-svg-JPxcv0s9Usf0Ow8h .node .label text,#mermaid-svg-JPxcv0s9Usf0Ow8h .image-shape .label,#mermaid-svg-JPxcv0s9Usf0Ow8h .icon-shape .label{text-anchor:middle;}#mermaid-svg-JPxcv0s9Usf0Ow8h .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .rough-node .label,#mermaid-svg-JPxcv0s9Usf0Ow8h .node .label,#mermaid-svg-JPxcv0s9Usf0Ow8h .image-shape .label,#mermaid-svg-JPxcv0s9Usf0Ow8h .icon-shape .label{text-align:center;}#mermaid-svg-JPxcv0s9Usf0Ow8h .node.clickable{cursor:pointer;}#mermaid-svg-JPxcv0s9Usf0Ow8h .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .arrowheadPath{fill:#333333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JPxcv0s9Usf0Ow8h .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JPxcv0s9Usf0Ow8h .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JPxcv0s9Usf0Ow8h .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JPxcv0s9Usf0Ow8h .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .cluster text{fill:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h .cluster span{color:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JPxcv0s9Usf0Ow8h .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JPxcv0s9Usf0Ow8h rect.text{fill:none;stroke-width:0;}#mermaid-svg-JPxcv0s9Usf0Ow8h .icon-shape,#mermaid-svg-JPxcv0s9Usf0Ow8h .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JPxcv0s9Usf0Ow8h .icon-shape p,#mermaid-svg-JPxcv0s9Usf0Ow8h .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JPxcv0s9Usf0Ow8h .icon-shape .label rect,#mermaid-svg-JPxcv0s9Usf0Ow8h .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JPxcv0s9Usf0Ow8h .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JPxcv0s9Usf0Ow8h .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JPxcv0s9Usf0Ow8h :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Java MatchSpy子类

    SWIG Director Proxy

    XapianJNI

    Enquire::add_matchspy

    opt_intrusive_ptr保存MatchSpy*

    get_mset执行匹配

    虚调用MatchSpy

    C++对象是否调用release?

    borrowed: 调用者必须保活

    owned: intrusive refcount接管

    Java绑定忽略release

    Java 路径的缺口

    Java binding 忽略了 release()。因此 C++ 头文件虽然支持 ownership transfer,Java 用户却不能完整使用这条路径。

    结果是:

    • Enquire 可以长期保存并回调 Java Director;
    • Java 侧仍要保存强引用;
    • 不能仅根据 C++ 文档判断 Java 绑定也支持 disown;
    • 绑定层必须重新审计每个被 %ignore 的生命周期 API。
    clone registry 的限制

    Xapian 某些 registry 会 clone Weight、PostingSource、MatchSpy。但 SUBCLASSABLE 宏对部分 clone/serialise 方法做了忽略,不能把 C++ clone registry 直接等同于 Java 自定义子类也能安全注册。

    线程与异步

    未找到 native worker thread 调 Java Director 的证据。源码中的异步 remote I/O 是网络状态机,不等于异步 Java callback。

    可学与不可照搬

    可学:

    • 在公共 API 文档中写明 callback 最小存活区间;
    • borrowed/owned 两种模式显式区分;
    • Director 类型集中维护;
    • intrusive reference counting 适合跨 API 长期对象。

    不可照搬:

    • 假设 C++ 的 release() 自动暴露到 Java;
    • 只保存 Java 临时变量后把 Director 交给 Enquire;
    • 把 remote async 概念误判为 JNI 跨线程回调。

    12.5 Open Babel:回调能力弱,但“拒绝包装危险接口”很重要

    Open Babel 的 scripts/openbabel-java.i 没有启用 Director,因此它主要是 Java → C++ 的大 API 包装。

    #mermaid-svg-vzyasxv6qwfKNdgP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vzyasxv6qwfKNdgP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vzyasxv6qwfKNdgP .error-icon{fill:#552222;}#mermaid-svg-vzyasxv6qwfKNdgP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vzyasxv6qwfKNdgP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP .marker.cross{stroke:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vzyasxv6qwfKNdgP p{margin:0;}#mermaid-svg-vzyasxv6qwfKNdgP .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster-label text{fill:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster-label span{color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster-label span p{background-color:transparent;}#mermaid-svg-vzyasxv6qwfKNdgP .label text,#mermaid-svg-vzyasxv6qwfKNdgP span{fill:#333;color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .node rect,#mermaid-svg-vzyasxv6qwfKNdgP .node circle,#mermaid-svg-vzyasxv6qwfKNdgP .node ellipse,#mermaid-svg-vzyasxv6qwfKNdgP .node polygon,#mermaid-svg-vzyasxv6qwfKNdgP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .rough-node .label text,#mermaid-svg-vzyasxv6qwfKNdgP .node .label text,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape .label,#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape .label{text-anchor:middle;}#mermaid-svg-vzyasxv6qwfKNdgP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .rough-node .label,#mermaid-svg-vzyasxv6qwfKNdgP .node .label,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape .label,#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape .label{text-align:center;}#mermaid-svg-vzyasxv6qwfKNdgP .node.clickable{cursor:pointer;}#mermaid-svg-vzyasxv6qwfKNdgP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP .arrowheadPath{fill:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vzyasxv6qwfKNdgP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vzyasxv6qwfKNdgP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vzyasxv6qwfKNdgP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vzyasxv6qwfKNdgP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vzyasxv6qwfKNdgP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vzyasxv6qwfKNdgP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster text{fill:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster span{color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vzyasxv6qwfKNdgP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vzyasxv6qwfKNdgP rect.text{fill:none;stroke-width:0;}#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape p,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape .label rect,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vzyasxv6qwfKNdgP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vzyasxv6qwfKNdgP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vzyasxv6qwfKNdgP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    无Director上行

    %ignore

    Java应用

    Open Babel Java Proxy

    SWIG JNI wrapper

    Open Babel C++对象

    Java override不可达

    SetData等危险ownership API

    不进入Java公共API

    CloneData安全替代

    最值得关注的不是 callback,而是它主动忽略了会转移裸指针所有权的一组接口:

    // openbabel/scripts/openbabel-java.i:236-239
    // CloneData should be used instead of the following method
    %ignore OpenBabel::OBBase::SetData;
    %ignore OpenBabel::OBBase::GetData(char const *);
    %ignore OpenBabel::OBBase::HasData(char const *);

    OBBase::SetData 会让 C++ 对象保存并在析构时删除传入裸指针。若直接暴露给 Java,而绑定层没有可靠 disown,很容易双重释放。Open Babel 选择不包装,要求改用 clone 语义。

    这给 SDK 一个重要启发:

    绑定层不是必须暴露全部 C++ API。无法稳定表达 ownership 的接口,应改造、复制或忽略。

    虽然 C++ reaction.h 使用 shared_ptr<OBMol>,Java .i 没有完整 %shared_ptr(OBMol) 映射。因此它不能作为 SWIG Java 智能指针范本。

    可学:

    • 用 %ignore 缩小危险表面积;
    • 以 clone API 替代隐式 ownership transfer;
    • 大 API 按领域拆分。

    不可照搬:

    • 把 C++ 内部用了 shared_ptr 视为 Java binding 已正确管理;
    • 从该项目学习 Director、异步回调或线程 Attach。

    12.6 Z3 Java:最清楚的 GlobalRef 长期 callback 对照

    Z3 当前 Java JNI 不使用 SWIG,而是由 scripts/update_api.py 生成 Native.java 和 Native.cpp。它仍然非常值得研究,因为它真实实现了 C++ 长期保存 Java callback。

    callback state

    src/api/java/NativeStatic.txt 的 JavaInfo 缓存 env、Java 对象和一批 method ID:

    // z3/src/api/java/NativeStatic.txt:83-98(节选)
    struct JavaInfo {
    JNIEnv *jenv = nullptr; // 注意:缓存的是 env,不是 JavaVM*
    jobject jobj = nullptr;
    jmethodID push = nullptr;
    jmethodID pop = nullptr;
    // … created / fixed / eq / final / decide / on_binding
    Z3_solver_callback cb = nullptr;
    };

    初始化把 jobj 升级为 GlobalRef,缓存 method ID,并把 JavaInfo* 作为 user context 注册进 solver:

    // z3/src/api/java/NativeStatic.txt:163-186(节选)
    Java_..._propagateInit(JNIEnv *jenv, jclass cls, jobject jobj, jlong ctx, jlong solver) {
    JavaInfo *info = new JavaInfo;
    info->jenv = jenv;
    info->jobj = jenv->NewGlobalRef(jobj); // 钉住 Java callback
    jclass jcls = jenv->GetObjectClass(info->jobj);
    info->push = jenv->GetMethodID(jcls, "pushWrapper", "()V");
    // … 其余 method ID
    Z3_solver_propagate_init((Z3_context)ctx, (Z3_solver)solver, info, push_eh, pop_eh, fresh_eh);
    return (jlong)info;
    }

    销毁时删除 GlobalRef 并释放 context——但注意 solver 侧没有对等的“注销回调”:

    // z3/src/api/java/NativeStatic.txt:188-192
    Java_..._propagateDestroy(..., jlong javainfo) {
    JavaInfo *info = (JavaInfo*)javainfo;
    info->jenv->DeleteGlobalRef(info->jobj); // 只删引用,未从 solver 注销
    delete info;
    }

    这是完整展示以下关系的样本:

    C++ owner
    → native callback context
    → GlobalRef(Java callback)
    → cached method IDs

    Z3 Solver

    JavaInfo context

    Native.cpp JNI

    Java UserPropagator

    Z3 Solver

    JavaInfo context

    Native.cpp JNI

    Java UserPropagator

    #mermaid-svg-B3f2BDlc7b6eu1T0{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-B3f2BDlc7b6eu1T0 .error-icon{fill:#552222;}#mermaid-svg-B3f2BDlc7b6eu1T0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B3f2BDlc7b6eu1T0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B3f2BDlc7b6eu1T0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B3f2BDlc7b6eu1T0 .marker.cross{stroke:#333333;}#mermaid-svg-B3f2BDlc7b6eu1T0 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B3f2BDlc7b6eu1T0 p{margin:0;}#mermaid-svg-B3f2BDlc7b6eu1T0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-B3f2BDlc7b6eu1T0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-B3f2BDlc7b6eu1T0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-B3f2BDlc7b6eu1T0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-B3f2BDlc7b6eu1T0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-B3f2BDlc7b6eu1T0 .sequenceNumber{fill:white;}#mermaid-svg-B3f2BDlc7b6eu1T0 #sequencenumber{fill:#333;}#mermaid-svg-B3f2BDlc7b6eu1T0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-B3f2BDlc7b6eu1T0 .messageText{fill:#333;stroke:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-B3f2BDlc7b6eu1T0 .labelText,#mermaid-svg-B3f2BDlc7b6eu1T0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .loopText,#mermaid-svg-B3f2BDlc7b6eu1T0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-B3f2BDlc7b6eu1T0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-B3f2BDlc7b6eu1T0 .noteText,#mermaid-svg-B3f2BDlc7b6eu1T0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-B3f2BDlc7b6eu1T0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-B3f2BDlc7b6eu1T0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-B3f2BDlc7b6eu1T0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-B3f2BDlc7b6eu1T0 .actorPopupMenu{position:absolute;}#mermaid-svg-B3f2BDlc7b6eu1T0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-B3f2BDlc7b6eu1T0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-B3f2BDlc7b6eu1T0 .actor-man circle,#mermaid-svg-B3f2BDlc7b6eu1T0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-B3f2BDlc7b6eu1T0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    求解期间,同一已附着线程

    solver没有对等unregister时,之后再回调将悬空

    propagateInit(this, ctx, solver)

    1

    new JavaInfo

    2

    NewGlobalRef(this)

    3

    缓存jmethodID

    4

    注册Info*与C回调函数

    5

    init返回

    6

    push_eh/info回调

    7

    cached JNIEnv*.CallVoidMethod

    8

    return

    9

    propagateDestroy(info)

    10

    DeleteGlobalRef + delete

    11

    线程限制

    它保存的是初始化线程的 JNIEnv*,没有缓存 JavaVM*,也没有 AttachCurrentThread。因此这套实现隐含:

    • callback 与注册发生在同一 Java 附着线程;
    • solver 同步调用 callback;
    • 不能搬到任意 native worker thread。

    若 SDK 要支持异步,应把 JNIEnv* 改为 JavaVM*,上行时获取当前线程的 env。

    关闭缺口

    propagateDestroy() 删除 GlobalRef 和 callback context,但 solver 侧没有对等的完全注销语义。若销毁 callback 后继续使用 solver 并触发回调,就可能访问已释放 context。

    安全顺序只能是:

    停止/不再使用 solver
    → 确认不会再触发 callback
    → destroy callback context / DeleteGlobalRef
    → 关闭 Context

    这再次说明:GlobalRef 不是关闭协议。

    普通对象

    Z3 Java 普通对象使用 native incRef/decRef,Java 侧用 PhantomReference 队列兜底,并支持 AutoCloseable 式显式关闭。这比依赖 finalize() 更可控。

    可学:

    • native context + GlobalRef + method ID cache;
    • C++ owner 明确持有 callback state;
    • native refcount + Java reference queue。

    不可照搬:

    • 长期缓存 JNIEnv*;
    • 只 delete callback context,不从 owner 解除注册;
    • callback close 后继续使用 owner。

    12.7 JavaCPP:线程 Attach 完整,ownership 握手弱于 SWIG

    JavaCPP 不使用 .i,而是从 Java native 声明和注解生成 JNI:

    • FunctionPointer 生成 C 函数指针 trampoline;
    • @Virtual 生成 C++ 派生类,将虚函数上行到 Java;
    • Pointer / Deallocator / PointerScope 管 native 清理。

    native worker thread

    C++ trampoline/派生类

    JavaCPP生成JNI

    Java FunctionPointer/@Virtual

    native worker thread

    C++ trampoline/派生类

    JavaCPP生成JNI

    Java FunctionPointer/@Virtual

    #mermaid-svg-pts5IGPWGYkw9QGX{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pts5IGPWGYkw9QGX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pts5IGPWGYkw9QGX .error-icon{fill:#552222;}#mermaid-svg-pts5IGPWGYkw9QGX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pts5IGPWGYkw9QGX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pts5IGPWGYkw9QGX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pts5IGPWGYkw9QGX .marker.cross{stroke:#333333;}#mermaid-svg-pts5IGPWGYkw9QGX svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pts5IGPWGYkw9QGX p{margin:0;}#mermaid-svg-pts5IGPWGYkw9QGX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pts5IGPWGYkw9QGX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-pts5IGPWGYkw9QGX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pts5IGPWGYkw9QGX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-pts5IGPWGYkw9QGX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-pts5IGPWGYkw9QGX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-pts5IGPWGYkw9QGX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-pts5IGPWGYkw9QGX .sequenceNumber{fill:white;}#mermaid-svg-pts5IGPWGYkw9QGX #sequencenumber{fill:#333;}#mermaid-svg-pts5IGPWGYkw9QGX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-pts5IGPWGYkw9QGX .messageText{fill:#333;stroke:none;}#mermaid-svg-pts5IGPWGYkw9QGX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pts5IGPWGYkw9QGX .labelText,#mermaid-svg-pts5IGPWGYkw9QGX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-pts5IGPWGYkw9QGX .loopText,#mermaid-svg-pts5IGPWGYkw9QGX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-pts5IGPWGYkw9QGX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pts5IGPWGYkw9QGX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pts5IGPWGYkw9QGX .noteText,#mermaid-svg-pts5IGPWGYkw9QGX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-pts5IGPWGYkw9QGX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pts5IGPWGYkw9QGX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pts5IGPWGYkw9QGX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pts5IGPWGYkw9QGX .actorPopupMenu{position:absolute;}#mermaid-svg-pts5IGPWGYkw9QGX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-pts5IGPWGYkw9QGX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pts5IGPWGYkw9QGX .actor-man circle,#mermaid-svg-pts5IGPWGYkw9QGX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-pts5IGPWGYkw9QGX :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    opt

    [worker尚未附着]

    线程退出时TLS析构执行Detach

    allocate / 传入callback

    1

    创建trampoline并保存weak peer

    2

    业务字段保存强引用

    3

    函数指针或虚函数调用

    4

    JavaCPP_getEnv()

    5

    AttachCurrentThreadAsDaemon

    6

    env写入TLS

    7

    CallMethod(Java override)

    8

    return

    9

    unregister/等待在途/deallocate

    10

    删除trampoline与weak ref

    11

    最后清除Java强引用

    12

    线程处理

    Generator.java 生成的 JavaCPP_getEnv() 是一份可直接参考的跨线程模板(下面是它 println 出来的 C++ 目标代码):

    // javacpp Generator.java:1557-1629 生成的 JavaCPP_getEnv(节选)
    static JavaCPP_noinline bool JavaCPP_getEnv(JNIEnv** env) {
    bool attached = false;
    JavaVM *vm = JavaCPP_vm; // 缓存的是 JavaVM*
    // … TLS 命中则直接复用 env …
    if (vm->GetEnv((void**)env, JNI_VERSION) != JNI_OK) {
    JavaVMAttachArgs args;
    args.version = JNI_VERSION;
    // … 设置线程名 …
    if (vm->AttachCurrentThreadAsDaemon(env2, &args) != JNI_OK) {
    *env = NULL; goto done; // daemon attach,JVM 退出不被阻塞
    }
    pthread_setspecific(JavaCPP_current_env, *env); // 存入 TLS
    attached = true;
    }
    done:
    return attached;
    }

    配合 pthread TLS(Linux/macOS)或 thread-local(Windows)的析构在线程退出时 Detach。这部分比多数业务项目完整,可作为手写 JNI 线程基础设施的参考。

    callback 保活

    桌面 JVM 上,FunctionPointer 与 @Virtual peer 默认使用 WeakGlobalRef。因此 C++ 长期保存 callback 时:

    • Java 必须在 owner/service 字段或注册表中保存强引用;
    • retainReference() 只影响 native deallocator 计数,不等价于 Java 强引用;
    • 注销后等待在途回调;
    • 再 deallocate();
    • 最后清除 Java 强引用。

    SWIG Director 的优势是存在 java_change_ownership(),可以随着 ownership 转移切换 weak/global reference;JavaCPP 没有完全等价的自动握手。

    可学:

    • callback trampoline 与 @Virtual 两种上行方式;
    • daemon Attach + TLS Detach;
    • PhantomReference / explicit deallocate 双轨清理。

    不可照搬:

    • 只调用 retainReference() 就认为 Java callback 不会 GC;
    • 未注销、未耗尽就 deallocate;
    • 把 Android/iOS 的强引用宏行为当成桌面 JVM 默认。

    13. 横向比较:哪些模式真正可复用

    13.1 三种上行模型

    A. SWIG Director

    代表:QuantLib、libSBML、Xapian。

    适合:

    • C++ 本来就有虚接口;
    • 回调低频;
    • 调用以同步为主;
    • Java 希望通过继承实现。

    成本:

    • ownership 状态复杂;
    • Java 异常需跨 C++ 栈处理;
    • shared_ptr typemap 组合有限;
    • 异步销毁竞态仍需业务层解决。
    B. 手写 JNI callback bridge

    代表:GDAL progress、Z3 user propagator。

    适合:

    • 回调面很小;
    • 需要完全控制 GlobalRef、method ID 和异常;
    • 生命周期协议比 API 数量更重要。

    成本:

    • 每种参数都要转换;
    • 容易错误缓存 JNIEnv*;
    • 注销、Attach、异常和 local ref 都由业务负责。
    C. 固定句柄上行

    调研项目没有给出完整范本,但对于高频异步 SDK,通常更合适:

    C++ worker
    → 生成 Event{listenerId, type, payload copy}
    → 放入线程安全队列
    → 固定 JNI dispatcher 线程
    → 根据 listenerId 调 Java

    优点:

    • Director 不进入任意业务线程;
    • Attach 点集中;
    • 可以统一背压、丢弃、终态和 shutdown;
    • Java callback 对象放在单一注册表中管理。

    13.2 四种 ownership 模式

    模式代表谁 delete风险
    Java owning proxy SWIG 默认 Java delete/close 或 GC 兜底 C++ 不得长期借用
    显式 disown SWIG/GDAL C++ 必须同步切 GlobalRef 或保活关系
    clone 后 C++ owning libSBML/Open Babel 建议 C++ owner clone 语义和 Director 返回 ownership
    native refcount Xapian/Z3 最后一方 release/decRef Java binding 必须完整暴露协议

    不存在“自动推断 ownership”。每个跨边界参数都应标记为:

    borrowed
    owned-by-caller
    owned-by-callee
    shared
    cloned

    13.3 一份可靠的异步关闭协议

    综合所有项目的缺口,建议 SDK 统一实现:

    RUNNING
    register listener
    dispatch: inflight++

    CLOSING
    原子设置 closing=true
    拒绝新注册和新派发
    从 native owner 注销
    等待 inflight==0

    CLOSED
    DeleteGlobalRef / delete Director / release handle
    清 Java 强引用

    派发算法:

    持锁:
    检查 closing
    获取 callback 强快照
    inflight++
    解锁:
    Attach / 调 Java
    finally:
    inflight–
    若 closing && inflight==0,唤醒 close()

    不要持业务锁调用 Java,因为 Java override 可能重入 C++,造成锁顺序反转或死锁。


    14. 对多语言 C++ SDK 的设计启发

    14.1 不要直接把全部 C++ 类暴露给多语言

    为 C++ SDK 增加稳定的绑定边界:

    C++ 核心实现

    语言中立 facade / handle 层

    SWIG 同步绑定
    + Java 专用 callback/runtime 层
    + Python/C#/其他语言专用策略

    facade 应避免:

    • STL 容器直接跨边界;
    • T&、shared_ptr<T>&;
    • 模板和复杂继承树;
    • 隐式 ownership transfer;
    • 在析构函数中跨语言回调;
    • 把 C++ 线程模型直接泄露给宿主语言。

    14.2 C ABI 句柄层是否必须

    不是所有 SDK 都必须先改成完整 C ABI,但以下情况强烈建议采用 opaque handle:

    • 需要长期 ABI 稳定;
    • 同时支持 Java、Python、C#、Rust 等;
    • 核心 C++ ABI 经常变化;
    • callback/async 多于普通同步方法;
    • 发布周期要求各语言绑定独立演进。

    可采用:

    typedef struct SdkClientHandle_* SdkClientHandle;
    SdkStatus sdk_client_create(const SdkClientOptions*, SdkClientHandle*);
    void sdk_client_retain(SdkClientHandle);
    void sdk_client_release(SdkClientHandle);

    SWIG 可以包装这层,也可以继续包装经过裁剪的 C++ facade。关键不是“必须 C API”,而是绑定边界必须比内部 C++ API 更稳定、更简单。

    14.3 Java 生命周期规范

    建议统一为:

    • 所有 owning proxy 实现 AutoCloseable;
    • Java 用户通过 try-with-resources 确定性关闭;
    • Cleaner / PhantomReference 只做泄漏兜底;
    • 禁止依赖 finalize();
    • borrowed child 在 Java 侧强持有 parent;
    • C++ 接管 Director 时显式 swigReleaseOwnership();
    • callback 注册返回独立 Registration / Subscription handle;
    • Registration.close() 负责注销并耗尽,而不是只删 Java 引用。

    14.4 回调 API 分类

    在设计阶段为每个回调标注:

    维度可选值
    调用线程 调用线程 / 固定 dispatcher / 任意 worker
    调用次数 one-shot / finite / persistent
    生命周期 owner Java / C++ / shared registry
    关闭保证 立即停止 / 允许一个终态 / 耗尽后返回
    重入 允许 / 禁止
    异常策略 传播 / 转状态码 / 记录并取消
    负载 借用只读 / 拷贝 / handle

    只有“低频 + 同步 + 生命周期短 + 虚接口天然存在”的回调适合直接用 Director。高频异步 listener 建议固定句柄上行或手写 JNI。

    14.5 Java 线程运行时

    如果 C++ 线程会进入 JVM,Java 专用 runtime 至少包含:

    JNI_OnLoad 缓存 JavaVM*
    GetEnv / AttachCurrentThreadAsDaemon
    线程退出 Detach
    GlobalRef/WeakGlobalRef RAII
    LocalRef RAII
    jmethodID/jclass 缓存
    Java exception 检查与转换
    JVM shutting-down 标志
    callback inflight 屏障

    不得把 JNIEnv* 存进跨线程对象。JNIEnv* 是线程局部接口,只能在所属线程使用。

    14.6 shared_ptr 的正确位置

    推荐分成两层思考:

    业务对象寿命:
    shared_ptr<CoreObject>

    跨语言 callback 寿命:
    CallbackRegistration
    ├─ native callback handle
    ├─ Java GlobalRef 或 SWIG Director ownership
    ├─ closing flag
    └─ inflight counter

    不要让 shared_ptr<CoreObject> 顺便承担 callback 注册的全部语义。二者关闭时机不同:

    • core object 可能仍被其他 API 使用;
    • callback 可以提前取消;
    • callback 可能正在上行;
    • Java proxy 可能已不可达;
    • 跨语言环需要由 registration 明确断开。

    14.7 构建、发布和测试门禁

    最低门禁:

  • 固定 SWIG 版本;
  • CI 强制重生成 wrapper 并检查 diff;
  • Java jar 与 native library 同一构建号;
  • 加载时做 Java/native ABI 版本握手;
  • 对生成代码和手写 JNI 分目录;
  • ASAN/LSAN 跑 native 生命周期测试;
  • Java 压测强制 System.gc(),验证回调不会被提前回收;
  • 并发执行 register / callback / close;
  • 覆盖 Java callback 抛异常;
  • 覆盖 JVM shutdown 前停止 native 线程。
  • 重点竞态用例:

    close 与 callback 同时发生
    callback 内重入 close
    Java 丢弃最后一个强引用后 C++ 再回调
    C++ owner 析构时仍有在途回调
    线程首次 Attach 时回调
    Java override 抛异常穿过 C++ 栈
    jar 与 native 版本不匹配

    14.8 推荐的落地终局

    #mermaid-svg-MSNKBQrBbb29Y29R{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MSNKBQrBbb29Y29R .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MSNKBQrBbb29Y29R .error-icon{fill:#552222;}#mermaid-svg-MSNKBQrBbb29Y29R .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MSNKBQrBbb29Y29R .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MSNKBQrBbb29Y29R .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MSNKBQrBbb29Y29R .marker.cross{stroke:#333333;}#mermaid-svg-MSNKBQrBbb29Y29R svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MSNKBQrBbb29Y29R p{margin:0;}#mermaid-svg-MSNKBQrBbb29Y29R .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-MSNKBQrBbb29Y29R .cluster-label text{fill:#333;}#mermaid-svg-MSNKBQrBbb29Y29R .cluster-label span{color:#333;}#mermaid-svg-MSNKBQrBbb29Y29R .cluster-label span p{background-color:transparent;}#mermaid-svg-MSNKBQrBbb29Y29R .label text,#mermaid-svg-MSNKBQrBbb29Y29R span{fill:#333;color:#333;}#mermaid-svg-MSNKBQrBbb29Y29R .node rect,#mermaid-svg-MSNKBQrBbb29Y29R .node circle,#mermaid-svg-MSNKBQrBbb29Y29R .node ellipse,#mermaid-svg-MSNKBQrBbb29Y29R .node polygon,#mermaid-svg-MSNKBQrBbb29Y29R .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MSNKBQrBbb29Y29R .rough-node .label text,#mermaid-svg-MSNKBQrBbb29Y29R .node .label text,#mermaid-svg-MSNKBQrBbb29Y29R .image-shape .label,#mermaid-svg-MSNKBQrBbb29Y29R .icon-shape .label{text-anchor:middle;}#mermaid-svg-MSNKBQrBbb29Y29R .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MSNKBQrBbb29Y29R .rough-node .label,#mermaid-svg-MSNKBQrBbb29Y29R .node .label,#mermaid-svg-MSNKBQrBbb29Y29R .image-shape .label,#mermaid-svg-MSNKBQrBbb29Y29R .icon-shape .label{text-align:center;}#mermaid-svg-MSNKBQrBbb29Y29R .node.clickable{cursor:pointer;}#mermaid-svg-MSNKBQrBbb29Y29R .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MSNKBQrBbb29Y29R .arrowheadPath{fill:#333333;}#mermaid-svg-MSNKBQrBbb29Y29R .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MSNKBQrBbb29Y29R .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MSNKBQrBbb29Y29R .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MSNKBQrBbb29Y29R .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MSNKBQrBbb29Y29R .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MSNKBQrBbb29Y29R .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MSNKBQrBbb29Y29R .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MSNKBQrBbb29Y29R .cluster text{fill:#333;}#mermaid-svg-MSNKBQrBbb29Y29R .cluster span{color:#333;}#mermaid-svg-MSNKBQrBbb29Y29R div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MSNKBQrBbb29Y29R .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MSNKBQrBbb29Y29R rect.text{fill:none;stroke-width:0;}#mermaid-svg-MSNKBQrBbb29Y29R .icon-shape,#mermaid-svg-MSNKBQrBbb29Y29R .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MSNKBQrBbb29Y29R .icon-shape p,#mermaid-svg-MSNKBQrBbb29Y29R .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MSNKBQrBbb29Y29R .icon-shape .label rect,#mermaid-svg-MSNKBQrBbb29Y29R .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MSNKBQrBbb29Y29R .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MSNKBQrBbb29Y29R .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MSNKBQrBbb29Y29R :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    低频同步虚接口

    高频/异步/跨线程

    C++ 核心 SDK

    稳定 facade / handle 层

    SWIG: 同步 API 与普通对象

    CallbackRegistration

    回调类型

    SWIG Director

    固定 JNI dispatcher / 手写 JNI

    ownership + exception 门禁

    JavaVM + GlobalRef + inflight 屏障

    Java AutoCloseable API

    最终原则:

    SWIG 负责生成重复性的类型胶水;业务层负责定义不可推断的 ownership、线程、取消和关闭协议。

    若一个接口无法清楚回答“谁持有、在哪个线程调用、close 返回后还能否回调、异常去哪里”,就不应直接进入多语言公共 API。

    赞(0)
    未经允许不得转载:171主机测评 » SWIG Java胶水层开源项目学习路径
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址