news 2026/5/11 2:06:35

C语言对象模型系列(三)JNIEnv 为什么是二级指针?本质就是函数表—— 一篇讲透 JNI 的底层设计思想

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言对象模型系列(三)JNIEnv 为什么是二级指针?本质就是函数表—— 一篇讲透 JNI 的底层设计思想

一、引言

第一次看到 JNI 时,我是懵的。

很多 Android 开发第一次看到 JNI 代码:

(*env)->CallVoidMethod(env, obj, method);

基本都会产生几个疑问:


第一反应:

env 是什么?

第二反应:

为什么是 (*env)->

第三反应:

这到底是函数调用? 还是对象调用?

甚至很多人学 JNI:

  • 会写
  • 会复制
  • 会调用

但:根本不知道 JNI 为什么这样设计。


而当我真正理解:“C语言对象模型”

之后,我突然意识到:JNI 本质根本不是“普通 C API”

而是:“C语言版虚函数对象”


二、先看 JNI 的经典代码

比如:

(*env)->FindClass(env, "java/lang/String");

或者:

(*env)->CallVoidMethod(env, obj, method);

你会发现:


第一:

函数不是:

FindClass(env)

而是:

(*env)->FindClass(...)

像对象调用。


第二:

函数参数里:

env

居然又传了一次自己。


第三:

整个写法:

(*env)->

特别像:

obj->

其实:这根本不是巧合。


三、JNIEnv 到底是什么?

很多人以为:

JNIEnv

是一个普通结构体。

实际上:它本质是:“函数表指针”。


先看 JNI 源码(简化版):

typedef const struct JNINativeInterface_* JNIEnv;

JNIEnv 本质:

JNIEnv = JNINativeInterface_*

也就是说:env 是一个指针


而:

JNINativeInterface_

里面是什么?


四、真正核心来了:JNI函数表

简化版:

struct JNINativeInterface_ { jclass (*FindClass)(JNIEnv*, const char*); void (*CallVoidMethod)( JNIEnv*, jobject, jmethodID, ... ); };

看到这里是不是突然熟悉了?


这就是:

struct + 函数指针


还记得上一篇:

typedef struct { void (*open)(void); } DeviceOps;

JNI 本质一样:

JNIEnv ↓ 函数表 ↓ 具体JNI函数

五、所以(*env)->xxx()到底是什么?

现在拆开:


第一步

env

是:

函数表指针

第二步

(*env)

解引用。

得到:

真正函数表

第三步

(*env)->FindClass

取出函数指针。


第四步

(*env)->FindClass(...)

调用函数。


所以:

(*env)->CallVoidMethod()

本质其实就是:

从函数表中找到函数地址 ↓ 再调用函数

六、这其实就是 C 的“虚函数表”

现在再回头看上一篇:

device->ops->open();

是不是和 JNI 一模一样?


Linux 驱动

device ↓ ops ↓ open()

JNI

env ↓ 函数表 ↓ CallVoidMethod()

本质完全一致。

所以:

JNIEnv 本质就是:

“C语言版虚函数对象”。


七、为什么 JNI 不直接设计成普通函数?

很多人会问:

为什么不这样?

CallVoidMethod(env, obj, method);

非得:

(*env)->CallVoidMethod(...)

这么复杂?


因为:

JNI 从设计上,

就是“可替换函数表”。


也就是说:

JVM 可以替换整套 JNI 实现。

比如:

  • HotSpot
  • ART
  • Dalvik

都可以:

提供不同函数实现。

但:

接口统一。

这就是:

多态。


八、为什么 env 还要再传一次自己?

经典代码:

(*env)->FindClass(env, xxx);

很多人会问:

不是已经有 env 了吗? 为什么参数里还传 env?

原因很简单:

因为:

JNI 本质还是 C。

而:

C 没有 this 指针。


Java:

obj.run();

实际上:

编译器偷偷传 this

而 C:

必须手动传。

所以:

(*env)->FindClass(env, xxx);

本质其实类似:

env->FindClass(this, xxx);

九、为什么 JNI 喜欢二级指针?

再看定义:

JNIEnv*

很多人又懵了:

怎么又是指针套指针?

其实:

因为 env 本身就是指针。


也就是说:

JNIEnv

本质:

JNINativeInterface_*

于是:

JNIEnv*

就变成:

函数表指针的指针

也就是:

二级指针。


十、为什么 JNI 必须这样设计?

因为 JNI 有一个非常重要目标:

“跨平台 ABI 稳定”


Java:

Windows Linux Mac Android

都要支持 JNI。

所以:

JNI 必须:

  • 不依赖 C++ ABI
  • 不依赖编译器对象模型
  • 保持稳定接口

于是:

最终选择:

struct + function pointer

这套经典系统级方案。


十一、现在你会发现

JNI 根本不是:

“奇怪的C语法”

而是:

完整的系统层对象模型。


甚至:

(*env)->CallVoidMethod()

本质其实等价于:


Java

obj.call();

C++

obj->virtual_func();

Linux

device->ops->open();

只是:

JNI 用 C 手写了整个对象模型。


十二、一句话总结

JNIEnv 本质不是“对象”

而是:

“函数表指针”


而:

(*env)->CallVoidMethod()

本质其实是:

“从函数表中取出函数并调用”

这就是:

C语言版虚函数表思想。


十三、最后

现在再回头看 JNI:

你会发现:


JNI 不是孤立知识。

它其实连接着:

C ↓ 函数指针 ↓ 虚函数表 ↓ Linux对象模型 ↓ Android Runtime

所以:

学懂 JNI 的关键,

从来不是背 API。

而是:

真正理解 C 的对象模型。


下一篇预告

《Linux 内核里的 container_of 到底是什么黑魔法?》

下一篇正式进入 Linux Kernel。

深入:

  • offsetof
  • container_of
  • struct embedding
  • intrusive list
  • Linux对象模型

真正理解:

Linux 内核为什么能用 C 写出“超级面向对象系统”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 1:58:32

第五部分-DockerCompose——29. Swarm 网络

29. Swarm 网络 1. Swarm 网络概述 Swarm 模式提供了多种网络驱动,用于解决跨主机容器通信问题。Overlay 网络是 Swarm 中最常用的网络驱动,它创建一个跨所有 Swarm 节点的虚拟网络。 ┌───────────────────────────────…

作者头像 李华
网站建设 2026/5/11 1:54:33

Dify插件集成Mem0 AI:为LLM应用构建长期记忆系统的实践指南

1. 项目概述与核心价值最近在折腾AI应用开发,特别是想给聊天机器人或者智能助手加上一个“长期记忆”的能力,让它们能记住和用户之前的对话历史、用户偏好,甚至是一些个性化的上下文信息。这听起来像是给AI装上一个“大脑皮层”,让…

作者头像 李华
网站建设 2026/5/11 1:40:43

基于WebRTC与Socket.IO构建本地化低延迟摄像头流媒体应用

1. 项目概述:一个本地化的WebRTC摄像头流媒体方案最近在折腾一个挺有意思的小项目,起因很简单:我需要在一个内部工作坊里,把一台笔记本电脑的摄像头画面,实时、低延迟地分享给会议室里的另一台设备,比如一台…

作者头像 李华
网站建设 2026/5/11 1:40:31

量子神经网络在噪声环境下的逼近理论与优化实践

1. 量子神经网络与噪声环境下的UAT理论概述量子神经网络(QNN)作为量子计算与经典机器学习交叉领域的前沿研究方向,其核心价值在于利用量子态的叠加性和纠缠特性,实现对高维函数的高效逼近。在理想情况下,量子神经网络遵…

作者头像 李华