我正在尝试编写自己的 jni 资源。查看一些 ndk 示例,我发现他们经常使用这些宏 JNIEXPORT 和 JNICALL 后跟 java 包的名称,就像这样
JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)
我用谷歌搜索了它,但我不明白何时以及如何使用这些宏
我正在尝试编写自己的 jni 资源。查看一些 ndk 示例,我发现他们经常使用这些宏 JNIEXPORT 和 JNICALL 后跟 java 包的名称,就像这样
JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)
我用谷歌搜索了它,但我不明白何时以及如何使用这些宏
JNIEXPORT 和 JNICALL 定义在 NDK_ROOT/platforms/android-9/arch-arm/usr/include/jni.h 中。根据您的设置,此路径会有所不同,但大多相似。
#define JNIIMPORT
#define JNIEXPORT __attribute__ ((visibility ("default")))
#define JNICALL
JNIEXPORT 用于使本地函数出现在构建的二进制文件(*.so 文件)的动态表中。它们可以设置为“隐藏”或“默认”(更多信息在这里)。如果这些函数不在动态表中,JNI 将无法找到调用它们的函数,因此 RegisterNatives 调用将在运行时失败。
值得注意的是,默认情况下,所有函数最终都在动态表中,因此任何人都可以很容易地反编译您的本机代码。每个函数调用都内置在二进制文件中,以防 JNI 需要找到它。这可以使用编译器选项进行更改-fvisibility
。我建议每个人都设置它-fvisibility=hidden
以确保您的代码安全,然后使用 JNIEXPORT 将函数标记为具有外部可见性。
使用 strip 命令只是删除调试符号,动态表是分开的。玩 objdump 看看一个人可以从你的 .so 文件中得到多少。
我们最近被这个绊倒了,希望这对某人有所帮助。
编辑:我们使用自定义构建系统,因此默认情况下可以为其他构建设置设置可见性选项。此 SO 答案中提供了更多信息。
您可以在 JNI 包含的机器相关部分(通常在$JAVA_HOME/include/<arch>/jni-md.h
)中找到这些宏的定义。
简而言之,JNIEXPORT
包含确保正确导出给定函数所需的任何编译器指令。在 android(和其他基于 linux 的系统)上,这将是空的。
JNICALL
包含确保使用正确的调用约定处理给定函数所需的任何编译器指令。在 android 上也可能是空的(它是 w32 上的 __stdcall)。
一般来说,你应该把它们留在里面,即使它们是空#define
的。
简单来说:
JNIEXPORT
如果你应该使用registerNatives
函数族,那么你不应该使用 JNIEXPORT。否则你必须使用它。JNICALL
必须始终使用。JNIEXPORT 确保函数在符号表中可见。JNICALL 确保函数使用正确的调用约定。在 Android 上,JNICALL 具有基于架构的不同值。ARM 是空的,这可能会欺骗您不包含它。但是你必须使用 JNICALL。
registerNatives
也允许您以编程方式在JNI_onLoad
未来或将来的某个时间链接该功能。registerNatives
允许更早地捕获错误的函数名称,我推荐这条路线。
只需在您的本机类上运行“javah”并使用它生成的任何内容。当您拥有可以 100% 可靠生产的工具时,您无需了解其中的来龙去脉。