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

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

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

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


目录

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

text 复制代码
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

优先搜:directorshared_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%extendHandle,追踪一个带观察者/回调味道的类型从声明到 Java 代理,不要通读金融域全量接口。

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

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

text 复制代码
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.jarlibgdalalljni 同源构建,避免「Java API 新、native 旧」
回调单独文件 callback.i 提示:回调不是随手塞进主 .i 的边角料

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

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

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

text 复制代码
director
%feature("director")
shared_ptr
%shared_ptr
swigReleaseOwnership
swigTakeOwnership
%extend
callback

并回答四个是非题:

  1. 回调是 Director ,还是 Java 接口 + C 函数指针适配
  2. cancel / dispose 谁 delete
  3. CI 是否强制重跑 SWIG
  4. 异步路径有没有进 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)的归宿

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

text 复制代码
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 版本、各项目维护状态与 typemap 行为会随发行版变化;落地前用目标 SWIG 版本 跑一小段 director + shared_ptr 冒烟,再放大到业务回调面。


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


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

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

text 复制代码
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 最重要的判断

  1. Director 能反调 Java,不等于 C++ 已经拥有回调。

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

  2. shared_ptr 管到哪一层必须说清楚。

    %shared_ptr(Proxy) 可能只保证 C++ adapter 存活;如果 adapter 内部仍保存 Delegate*,Java Director 仍可能先被释放。

  3. GlobalRef 只解决 GC 保活,不解决并发销毁。

    还必须有"停止新派发、注销、等待在途回调、删除引用"的关闭协议。

  4. JNIEnv* 不能缓存后跨线程使用。

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

  5. 没有项目给出完整的异步 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++ 下行链

典型调用链:

text 复制代码
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.swgswigCPtrswigCMemOwndelete()、输入输出 typemap;
  • Lib/java/director.swg:Java 对象引用、JavaVM*、线程 Attach 和 Director 异常。

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

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

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

11.2 C++ → Director → Java 上行链

启用:

swig 复制代码
%module(directors="1") sdk
%feature("director") Listener;

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

text 复制代码
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.swgJObjectWrapper 是理解 ownership 的核心:

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

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

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

cpp 复制代码
// 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 切换时的强弱引用互换:

cpp 复制代码
// 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 复制代码
// 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

禁止状态:

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

11.4 原生线程如何 Attach

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

cpp 复制代码
// 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
}

要点:

  1. Director 构造时保存 JavaVM*(不是 JNIEnv*);
  2. 上行时用 JavaVM::GetEnv 获取当前线程环境;
  3. 必要时调用 AttachCurrentThread
  4. SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON 改为 daemon attach;
  5. SWIG_JAVA_DETACH_ON_THREAD_END 通过 pthread TLS 在线程结束时 detach;
  6. 默认情况下,本次新 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++ 入口,应显式配置:

swig 复制代码
%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# 启用:

swig 复制代码
%module(directors="1") QuantLib

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

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

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

如何互相调用

UnaryFunction 为例:

text 复制代码
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 暴露的声明是三段式:

cpp 复制代码
// 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,但通常是:

text 复制代码
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

cpp 复制代码
// 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 方法返回即失效:

swig 复制代码
// 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; }
}

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

text 复制代码
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 函数。

DISOWNparentReference 的真实实现(同一段 javacode typemap):

java 复制代码
// 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.SetGeometryDirectlyGeometry.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,并为 SBMLValidatorSBMLConverterElementFilterCallback 等类型开启 Director;comp package 还为 SBMLResolver 开启 Director。

模式一:CallbackRegistry 借用裸指针

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

cpp 复制代码
// 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 的策略不同:

text 复制代码
传入多态对象
  → 调虚拟 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 宏集中声明 MatchDeciderMatchSpyKeyMakerPostingSource 等可由 Java 继承的类型。

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

Enquire 如何持有 MatchSpy

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

cpp 复制代码
// 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 WeightPostingSourceMatchSpy。但 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,而是它主动忽略了会转移裸指针所有权的一组接口:

swig 复制代码
// 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.javaNative.cpp。它仍然非常值得研究,因为它真实实现了 C++ 长期保存 Java callback。

callback state

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

cpp 复制代码
// 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:

cpp 复制代码
// 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 侧没有对等的"注销回调":

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

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

text 复制代码
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。

安全顺序只能是:

text 复制代码
停止/不再使用 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++ 目标代码):

cpp 复制代码
// 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,通常更合适:

text 复制代码
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"。每个跨边界参数都应标记为:

text 复制代码
borrowed
owned-by-caller
owned-by-callee
shared
cloned

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

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

text 复制代码
RUNNING
  register listener
  dispatch: inflight++

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

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

派发算法:

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

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


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

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

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

text 复制代码
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 多于普通同步方法;
  • 发布周期要求各语言绑定独立演进。

可采用:

c 复制代码
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 至少包含:

text 复制代码
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 的正确位置

推荐分成两层思考:

text 复制代码
业务对象寿命:
  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 构建、发布和测试门禁

最低门禁:

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

重点竞态用例:

text 复制代码
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。

相关推荐
爱笑的源码基地4 分钟前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
遇乐的果园1 小时前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
遇乐的果园2 小时前
前端学习笔记-vue状态管理优化
前端·笔记·学习
莫逸风3 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
从零开始的代码生活_3 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
可乐奶茶sky3 小时前
AI Agent 学习
人工智能·学习
z123456789863 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
茯苓gao3 小时前
嵌入式开发笔记:EtherCAT协议从硬件到软件完整配置指南——从零搭建一套EtherCAT通信系统
笔记·嵌入式硬件·学习
yaoxin5211234 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
自律懒人4 小时前
阿里 Qwen3.8-Max 预览版从零上手指南:2.4T 参数旗舰模型的 5 种接入方式与边界实测
开源