深圳市网站建设_网站建设公司_前后端分离_seo优化
2026/1/9 4:47:43 网站建设 项目流程

关键词:JNI / JNIEnv / 二级指针 / 函数表 / 函数指针 / C 对象模型 /->语法糖 / 系统接口
适合人群:Android NDK / C / 系统层方向学习者

一、先给结论:JNI 不是函数库,是函数表

几乎所有 JNI 教程都会从这句开始:

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

很多人被这行代码吓住,是因为:

  • 二级指针
  • *
  • ->
  • 函数指针
  • 可变参数

全叠在了一起。

但如果从系统层视角看,这行代码非常“朴素”:

👉 JNI 不是函数集合
👉 JNI 是 JVM 给 native 世界的一张函数表(能力表)

而这行代码,本质就是:

👉从函数表中取一个函数地址,然后跳过去执行。

二、什么叫“结构体函数表”?

先不谈 JNI,看一个纯 C 结构体。

1️⃣ 普通结构体(存数据)

struct Person { int age; float height; };

这是“数据结构”。

2️⃣ 函数表结构体(存函数地址)

struct Ops { void (*open)(); int (*read)(int); void (*close)(); };

这个结构体:

  • 不存业务数据
  • 每个成员都是函数指针

内存形态是:

open -> 某个函数地址 read -> 某个函数地址 close -> 某个函数地址

👉 这种结构体,在系统层就叫:

函数表 / 操作表 / 虚函数表(vtable)

一句话定义:

👉结构体里几乎全是函数指针,它就是函数表

三、JNI 的函数表是什么?

jni.h中(简化):

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

这个结构体是什么?

👉 JVM 对外暴露的全部能力入口表

FindClass / CallVoidMethod / NewObject
都只是这张表里的“函数地址槽位”。

四、JNIEnv 到底是什么?

接着看这一句:

typedef const struct JNINativeInterface_* JNIEnv;

这句话极其关键:

👉JNIEnv本身就是一个指针类型

也就是:

JNIEnv == struct JNINativeInterface_*

所以:

👉 JNIEnv 不是对象
👉 是“指向函数表结构体的指针”

五、那JNIEnv* env又是什么?

native 方法参数是:

JNIEnv* env

把 JNIEnv 展开:

struct JNINativeInterface_** env;

所以真实关系是:

env ---> JNIEnv ---> JNINativeInterface_ 指针 指针 结构体(函数表)

结论非常清楚:

  • env:二级指针
  • *env:一级指针(函数表指针)
  • **env:函数表结构体本体

六、env 和 *env 分别是什么?

类比最普通的 C 指针:

int x = 10; int* p = &x; int** pp = &p;
表达式含义
pp指向 p 的指针
*pp
**px

JNI 中:

表达式含义
env指向函数表指针的指针
*env函数表指针
**env函数表结构体

所以这句话完全正确:

👉*env仍然是指针
👉 它是指向函数表结构体的指针

七、->到底在干嘛?(核心解惑点)

C 语言里有一个定义级别等价式

p->field ≡ (*p).field

也就是说:

👉->不是新机制
👉 它只是(*p).语法糖

作用只有一个:

先解引用指针,再访问结构体成员。

1️⃣ 普通例子

struct S { int x; }; struct S s; struct S* p = &s; p->x 等价于 (*p).x

p 是指针,
但它指向 struct,所以可以用->

2️⃣ 套回 JNI

你已经知道:

*env == struct JNINativeInterface_*

那:

(*env)->CallVoidMethod

严格展开就是:

(*(*env)).CallVoidMethod

也就是:

👉 解一次指针
👉 得到“指向结构体的指针”
👉 再解一次
👉 访问结构体里的字段

八、->CallVoidMethod到底是什么意思?

回到结构体定义:

struct JNINativeInterface_ { void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); };

所以:

table->CallVoidMethod

含义是:

👉 从结构体中,取出一个成员变量
👉 这个变量的类型是:函数指针

它不是在“调用函数”,
👉 是在取函数地址。

九、整句 JNI 拆成三行白话 C

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

等价于:

struct JNINativeInterface_* table = *env; // ① 取函数表指针 void (*func)(JNIEnv*, jobject, jmethodID, ...) = table->CallVoidMethod; // ② 取函数地址 func(env, obj, mid); // ③ 调用

这一刻你可以清楚看到:

👉 没有魔法
👉 全是指针 + 结构体 + 函数指针调用

十、为什么 JNI 一定要设计成“函数表 + 二级指针”?

因为 JNI 是系统级 ABI 边界

它必须:

  • 跨虚拟机实现(Dalvik / ART / 未来 JVM)
  • 跨 Android 版本
  • 跨 CPU 架构
  • 保证 so 的二进制兼容

系统世界最稳定的接口形式只有一种:

👉函数表(vtable / ops table)

Linux 内核、HAL、驱动、OpenGL、FFmpeg、COM,全是这套。

十一、把这条认知链彻底钉死

👉 函数表:结构体里全是函数指针
👉 JNIEnv:指向函数表的指针
👉 env:指向“函数表指针”的指针(二级指针)
👉*env:函数表指针
👉->(*p).的语法糖
👉->CallVoidMethod:从结构体中取函数指针
👉(...):真正调用

十二、你以后应该怎样“系统级读 JNI”

当你再看到:

(*env)->FindClass(env, ...); (*env)->CallObjectMethod(env, ...);

你脑中自动翻译成:

👉 通过函数表
👉 查到函数地址
👉 用 this(env)调用

而不是:

👉 JNI 的奇怪写法。

十三、一句话系统级总结

JNI 不是函数集合,
JNI 是 JVM 的能力函数表。
env 是这张表的对象句柄。
->只是解引用 + 取成员的语法糖。
一切 JNI 调用,本质都是“查表跳转”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询