SWIG Java胶水层开源项目学习路径
C++ 核心库要通过 SWIG 暴露给 Java,并带上 回调 / 异步派发 / shared_ptr 生命周期 ,几乎找不到「长得一模一样」的开源范本。更现实的学法是:挑胶水层纪律好 的项目,带着固定问题去拆 所有权、Director、shared_ptr、取消与工程门禁,再裁剪到自己的 API 模型。
本文给出 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.1 QuantLib:学 shared_ptr 不泄漏到宿主语言](#3.1 QuantLib:学 shared_ptr 不泄漏到宿主语言)
- [3.2 GDAL:学模块拆分与生成流水线](#3.2 GDAL:学模块拆分与生成流水线)
- [3.3 怎么读才有用(不要通读)](#3.3 怎么读才有用(不要通读))
- 带着问题读:四个对照检查点
- [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)的归宿)
- [对照学:不一定用 SWIG 的胶水层](#对照学:不一定用 SWIG 的胶水层)
- 工业实践里的常见终局
- [1~2 周最小闭环](#1~2 周最小闭环)
- 速查表
- [8.1 关键词 → 去哪看](#8.1 关键词 → 去哪看)
- [8.2 反模式](#8.2 反模式)
- 延伸阅读
- 源码调研补充:范围与结论
- [10.1 能力矩阵](#10.1 能力矩阵)
- [10.2 最重要的判断](#10.2 最重要的判断)
- [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.1 QuantLib-SWIG:Director API 形态好,长期回调所有权不足](#12.1 QuantLib-SWIG:Director API 形态好,长期回调所有权不足)
- [12.2 GDAL:不使用 Director,擅长普通对象 ownership](#12.2 GDAL:不使用 Director,擅长普通对象 ownership)
- 绑定结构
- [progress 回调如何工作](#progress 回调如何工作)
- 普通对象生命周期做得更好
- 可学与不可照搬
- [12.3 libSBML:裸指针 registry 与 clone-based ownership 并存](#12.3 libSBML:裸指针 registry 与 clone-based ownership 并存)
- [12.4 Xapian:C++ intrusive ownership 清楚,Java 暴露不完整](#12.4 Xapian:C++ intrusive ownership 清楚,Java 暴露不完整)
- [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)
- 线程处理
- [callback 保活](#callback 保活)
- 横向比较:哪些模式真正可复用
- [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 一份可靠的异步关闭协议)
- [13.1 三种上行模型](#13.1 三种上行模型)
- [对多语言 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 | swigCMemOwn、swigReleaseOwnership / swigTakeOwnership |
| shared_ptr | 库层 %shared_ptr 与代理类如何持有引用 |
| 线程 | Director 回调进 JVM 时的 Attach;SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON 等宏 |
官方对线程的提示很直接:Director 可能从非 Java 线程回调用 JVM,需要正确 Attach;部分环境还要考虑进程退出时 Detach 导致的挂起(文档建议尝试 AttachCurrentThreadAsDaemon)。
2.2 Ownership 与 Director 的物理含义
SWIG Java Director 在 C++ 侧持有对 Java 代理的引用,并在「谁拥有 C++ 对象」变化时切换 WeakGlobalRef / GlobalRef:
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/javadirectorouttypemap; - 额外
shared_ptr引用可能导致「C++ 已释放业务引用,但包装层仍占一票」的泄漏,或反向过早回收。
相关讨论见 Using shared_ptr with SWIG Directors for Java 与 SWIG issue 对 ownership 的讨论。结论应记成:
手写 typemap / 手写接管
shared_ptr是常态,不是歪门。
2.4 SWIG 自带 Java test-suite
比随便一个业务库更干净的对照源:
Examples/Examples/test-suite/java
优先搜:director、shared_ptr、ownership、异常映射。先看「官方怎么测」,再看「业务库怎么绕」。
3. 值得翻的开源工程
按「和异步 Director 场景的相似度 / 工程纪律」排序,而不是按 star。
| 项目 | 为什么看 | 重点看什么 | 核对日备注 |
|---|---|---|---|
| QuantLib-SWIG | 多年多语言 SWIG;大量 %shared_ptr / %extend |
SWIG/*.i 如何把 C++ shared_ptr 藏成 idiomatic API;Java 目录如何构建 |
common.i 使用 boost_shared_ptr.i + SWIG_SHARED_PTR_NAMESPACE |
| GDAL Java | 大体量 C++→Java,工业发布 | swig/include + swig/include/java/* 模块拆分;CMake/Ant 生成 gdal.jar + gdalalljni |
官方强调 jar 与 native 必须同源同版本 |
| libSBML | 经典「一核多语言」 | src/bindings/{swig,java,...} 目录策略、生成物与版本 |
bindings 按语言分子目录 |
| Xapian Java | 搜索引擎,有遍历/回调类 API | Java 侧 close、异常、ownership 约定 | xapian-bindings/java |
| OpenBabel | 大 API 面 SWIG | 大规模 .i 如何组织、减复制 |
学结构多于学化学域 |
| Z3 Java SWIG | API 面大 | 复杂对象图边界 | 先确认仓库仍维护 Java SWIG 路径,再投入时间 |
3.1 QuantLib:学「shared_ptr 不泄漏到宿主语言」
QuantLib 绑定的核心动机之一,是 C++ 库大量使用智能指针,但不希望 Python/Java 用户手写 shared_ptr。常见手法:
%include boost_shared_ptr.i(或等价)+%shared_ptr(T);- 用
%extend把构造/工厂接到「看起来像普通对象」的代理上; - 内部仍是
shared_ptr,对外是语言惯用对象。
读法:在 SWIG/*.i 里搜 %shared_ptr、%extend、Handle,追踪一个带观察者/回调味道的类型从声明到 Java 代理,不要通读金融域全量接口。
3.2 GDAL:学「模块拆分 + 生成流水线」
GDAL Java 绑定的工程结构大致是:
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.jar 与 libgdalalljni 同源构建,避免「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
并回答四个是非题:
- 回调是 Director ,还是 Java 接口 + C 函数指针适配?
- cancel / dispose 谁 delete?
- CI 是否强制重跑 SWIG?
- 异步路径有没有进 Director?
4. 带着问题读:四个对照检查点
4.1 所有权与 shared_ptr 边界
| 问题 | 在 QuantLib / GDAL 里看什么 |
|---|---|
| 默认谁管理? | SWIG 默认 vs 手写接管 |
| Director 返回对象谁持引用计数? | %shared_ptr + directorout typemap |
| Java 何时切断? | delete() / dispose() / swigReleaseOwnership |
| 异步时 C++ 寿命 > Java GC? | GlobalRef、双层包装、weak_ptr 规避环 |
自家痛点映射: 异步外派时若 C++ 仍持有回调,而 Java 代理已被 GC,属于 ownership 产品化失败,不是「再加一个 %shared_ptr」能糊弄过去。
4.2 Director「纯度」与异步陷阱
| 问题 | 看什么 |
|---|---|
| Director 是否仅用于同步回调? | libSBML / Xapian 的实际调用线程 |
| 有没有在 Director 方法里直接跨线程 JNI Upcall? | 搜索 Attach / 消息队列 |
| 如何规避? | 入口 AttachCurrentThread,或禁止 Director 跨线程,改为队列 / 句柄 ID |
Java 代理 SWIG Director C++ 工作线程 Java 代理 SWIG Director C++ 工作线程 #mermaid-svg-VsBUukhyLPTvJf7D{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VsBUukhyLPTvJf7D .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VsBUukhyLPTvJf7D .error-icon{fill:#552222;}#mermaid-svg-VsBUukhyLPTvJf7D .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VsBUukhyLPTvJf7D .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VsBUukhyLPTvJf7D .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VsBUukhyLPTvJf7D .marker.cross{stroke:#333333;}#mermaid-svg-VsBUukhyLPTvJf7D svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VsBUukhyLPTvJf7D p{margin:0;}#mermaid-svg-VsBUukhyLPTvJf7D .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-VsBUukhyLPTvJf7D text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-VsBUukhyLPTvJf7D .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-VsBUukhyLPTvJf7D .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D .sequenceNumber{fill:white;}#mermaid-svg-VsBUukhyLPTvJf7D #sequencenumber{fill:#333;}#mermaid-svg-VsBUukhyLPTvJf7D #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-VsBUukhyLPTvJf7D .messageText{fill:#333;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-VsBUukhyLPTvJf7D .labelText,#mermaid-svg-VsBUukhyLPTvJf7D .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .loopText,#mermaid-svg-VsBUukhyLPTvJf7D .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-VsBUukhyLPTvJf7D .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-VsBUukhyLPTvJf7D .noteText,#mermaid-svg-VsBUukhyLPTvJf7D .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-VsBUukhyLPTvJf7D .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-VsBUukhyLPTvJf7D .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-VsBUukhyLPTvJf7D .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-VsBUukhyLPTvJf7D .actorPopupMenu{position:absolute;}#mermaid-svg-VsBUukhyLPTvJf7D .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-VsBUukhyLPTvJf7D .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-VsBUukhyLPTvJf7D .actor-man circle,#mermaid-svg-VsBUukhyLPTvJf7D line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-VsBUukhyLPTvJf7D :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 未 Attach / 已 Detach 已 Attach 或收口到固定上行 异步回调 崩溃或空上行 安全 upcall
4.3 胶水层工程纪律(比单行 .i 更重要)
从 GDAL / QuantLib 抄流程,而不是抄某一行 typemap:
| 纪律 | 落地检查 |
|---|---|
.i 按模块拆分 |
是否出现「单文件上帝接口」 |
| CI 强制 SWIG 重跑 | 生成物是否进仓;是否有 diff 门禁 |
| 生成代码 vs 手写补丁 | %extend / 额外 .cxx 是否目录清晰 |
| 发布同批 | Java 包与 .so/.dll 是否同一构建号 |
4.4 异步取消(Cancel / Dispose)的归宿
对照开源时问清关闭协议:
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 4.4 Java
- SWIG 源码仓库(
Examples/、Lib/java/) - Using shared_ptr with SWIG Directors for Java
开源绑定:
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 最重要的判断
-
Director 能反调 Java,不等于 C++ 已经拥有回调。
QuantLib、libSBML、Xapian 都证明了 Director 的功能可用,但项目级 C++ 容器经常只保存裸指针,回调是否存活仍依赖调用者。
-
shared_ptr管到哪一层必须说清楚。%shared_ptr(Proxy)可能只保证 C++ adapter 存活;如果 adapter 内部仍保存Delegate*,Java Director 仍可能先被释放。 -
GlobalRef只解决 GC 保活,不解决并发销毁。还必须有"停止新派发、注销、等待在途回调、删除引用"的关闭协议。
-
JNIEnv*不能缓存后跨线程使用。GDAL progress 和 Z3 callback 都缓存当前调用的
JNIEnv*,因此只能视为同线程同步方案。跨线程必须缓存JavaVM*,每次通过GetEnv/ Attach 获取当前线程的JNIEnv*。 -
没有项目给出完整的异步 Director 产品范本。
真正落地时,需要把 SWIG 的 ownership/Attach 机制与业务自己的取消、在途计数和锁外派发组合起来。
11. SWIG 4.3.1:Java JNI 双向调用基线
开源项目的 .i 文件只是配置;SWIG 生成器本身决定了 Proxy、JNI 和 Director 的物理行为。因此先建立 SWIG 4.3.1 的标准模型,再评估项目有没有补齐业务协议。
11.1 Java → JNI → C++ 下行链
典型调用链:
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.swg:swigCPtr、swigCMemOwn、delete()、输入输出 typemap;Lib/java/director.swg:Java 对象引用、JavaVM*、线程 Attach 和 Director 异常。
Java Proxy 中的两个字段语义不同:
swigCPtr:指向 native 对象或智能指针包装壳;swigCMemOwn:Java Proxy 是否负责触发 native 销毁。
swigCMemOwn 不是 C++ 对象内部引用计数,也不能表示业务容器是否仍在使用对象。
11.2 C++ → Director → Java 上行链
启用:
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.swg 的 JObjectWrapper 是理解 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
}
要点:
- Director 构造时保存
JavaVM*(不是JNIEnv*); - 上行时用
JavaVM::GetEnv获取当前线程环境; - 必要时调用
AttachCurrentThread; SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON改为 daemon attach;SWIG_JAVA_DETACH_ON_THREAD_END通过 pthread TLS 在线程结束时 detach;- 默认情况下,本次新 attach 的线程会在 wrapper 析构时 detach。
需要按平台验证宏组合,而不是机械开启全部宏:
- JVM 退出被 native 工作线程阻塞:考虑 daemon attach;
- Android 每次回调后 detach 有额外问题:考虑在线程结束时 detach;
- 使用长期线程池:不要每次回调反复 attach/detach;
- JVM shutdown 后仍可能派发:Attach 机制也救不了,必须先停 native 线程。
11.5 Director 异常
Java override 抛异常时,SWIG Director 默认构造 Swig::DirectorException。但这不代表每个 JNI 下行入口都会自动将它还原为 Java 异常。
对于可能触发 Director 的 C++ 入口,应显式配置:
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.am 用 swig -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 函数。
DISOWN 与 parentReference 的真实实现(同一段 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.SetGeometryDirectly、Geometry.AddGeometryDirectly。这类接口不是简单地"把指针存进去",而是绑定层同步更新 Java ownership,避免 Java 和 C++ 同时 delete。
可学与不可照搬
可学:
DISOWN显式表达所有权转移;- borrowed child 强持有 parent,避免父对象先被 GC;
- 对不同 native 类型调用正确的
Close/Release/Destroy; - Java jar 与 JNI wrapper 同一构建图生成;
- 回调 typemap 独立成文件。
不可照搬:
- 将
JavaProgressData保存到 JNI 调用之外; - 从另一个线程复用其中的
JNIEnv*; - 把 GDAL native 核心的 AsyncReader 误认为 Java binding 已支持;
- 用 progress bridge 作为长期 listener 模板。
12.3 libSBML:裸指针 registry 与 clone-based ownership 并存
Director 范围
src/bindings/swig/libsbml.i 启用 directors,并为 SBMLValidator、SBMLConverter、ElementFilter、Callback 等类型开启 Director;comp package 还为 SBMLResolver 开启 Director。
模式一:CallbackRegistry 借用裸指针
CallbackRegistry 单例保存 std::vector<Callback*>,遍历调用虚函数,但增删只动指针、从不 delete:
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 宏集中声明 MatchDecider、MatchSpy、KeyMaker、PostingSource 等可由 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 Weight、PostingSource、MatchSpy。但 SUBCLASSABLE 宏对部分 clone/serialise 方法做了忽略,不能把 C++ clone registry 直接等同于 Java 自定义子类也能安全注册。
线程与异步
未找到 native worker thread 调 Java Director 的证据。源码中的异步 remote I/O 是网络状态机,不等于异步 Java callback。
可学与不可照搬
可学:
- 在公共 API 文档中写明 callback 最小存活区间;
- borrowed/owned 两种模式显式区分;
- Director 类型集中维护;
- intrusive reference counting 适合跨 API 长期对象。
不可照搬:
- 假设 C++ 的
release()自动暴露到 Java; - 只保存 Java 临时变量后把 Director 交给 Enquire;
- 把 remote async 概念误判为 JNI 跨线程回调。
12.5 Open Babel:回调能力弱,但"拒绝包装危险接口"很重要
Open Babel 的 scripts/openbabel-java.i 没有启用 Director,因此它主要是 Java → C++ 的大 API 包装。
#mermaid-svg-vzyasxv6qwfKNdgP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vzyasxv6qwfKNdgP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vzyasxv6qwfKNdgP .error-icon{fill:#552222;}#mermaid-svg-vzyasxv6qwfKNdgP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vzyasxv6qwfKNdgP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vzyasxv6qwfKNdgP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP .marker.cross{stroke:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vzyasxv6qwfKNdgP p{margin:0;}#mermaid-svg-vzyasxv6qwfKNdgP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster-label text{fill:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster-label span{color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster-label span p{background-color:transparent;}#mermaid-svg-vzyasxv6qwfKNdgP .label text,#mermaid-svg-vzyasxv6qwfKNdgP span{fill:#333;color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .node rect,#mermaid-svg-vzyasxv6qwfKNdgP .node circle,#mermaid-svg-vzyasxv6qwfKNdgP .node ellipse,#mermaid-svg-vzyasxv6qwfKNdgP .node polygon,#mermaid-svg-vzyasxv6qwfKNdgP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .rough-node .label text,#mermaid-svg-vzyasxv6qwfKNdgP .node .label text,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape .label,#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape .label{text-anchor:middle;}#mermaid-svg-vzyasxv6qwfKNdgP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .rough-node .label,#mermaid-svg-vzyasxv6qwfKNdgP .node .label,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape .label,#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape .label{text-align:center;}#mermaid-svg-vzyasxv6qwfKNdgP .node.clickable{cursor:pointer;}#mermaid-svg-vzyasxv6qwfKNdgP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP .arrowheadPath{fill:#333333;}#mermaid-svg-vzyasxv6qwfKNdgP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vzyasxv6qwfKNdgP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vzyasxv6qwfKNdgP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vzyasxv6qwfKNdgP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vzyasxv6qwfKNdgP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vzyasxv6qwfKNdgP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vzyasxv6qwfKNdgP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster text{fill:#333;}#mermaid-svg-vzyasxv6qwfKNdgP .cluster span{color:#333;}#mermaid-svg-vzyasxv6qwfKNdgP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vzyasxv6qwfKNdgP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vzyasxv6qwfKNdgP rect.text{fill:none;stroke-width:0;}#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape p,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vzyasxv6qwfKNdgP .icon-shape .label rect,#mermaid-svg-vzyasxv6qwfKNdgP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vzyasxv6qwfKNdgP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vzyasxv6qwfKNdgP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vzyasxv6qwfKNdgP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 无Director上行
%ignore
Java应用
Open Babel Java Proxy
SWIG JNI wrapper
Open Babel C++对象
Java override不可达
SetData等危险ownership API
不进入Java公共API
CloneData安全替代
最值得关注的不是 callback,而是它主动忽略了会转移裸指针所有权的一组接口:
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.java 和 Native.cpp。它仍然非常值得研究,因为它真实实现了 C++ 长期保存 Java callback。
callback state
src/api/java/NativeStatic.txt 的 JavaInfo 缓存 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_ptrtypemap 组合有限;- 异步销毁竞态仍需业务层解决。
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/Subscriptionhandle; 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 构建、发布和测试门禁
最低门禁:
- 固定 SWIG 版本;
- CI 强制重生成 wrapper 并检查 diff;
- Java jar 与 native library 同一构建号;
- 加载时做 Java/native ABI 版本握手;
- 对生成代码和手写 JNI 分目录;
- ASAN/LSAN 跑 native 生命周期测试;
- Java 压测强制
System.gc(),验证回调不会被提前回收; - 并发执行 register / callback / close;
- 覆盖 Java callback 抛异常;
- 覆盖 JVM shutdown 前停止 native 线程。
重点竞态用例:
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。