29

我正在尝试编写自己的 jni 资源。查看一些 ndk 示例,我发现他们经常使用这些宏 JNIEXPORT 和 JNICALL 后跟 java 包的名称,就像这样

JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)

我用谷歌搜索了它,但我不明白何时以及如何使用这些宏

4

4 回答 4

40

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 答案中提供了更多信息。

于 2014-01-30T09:22:42.757 回答
9

您可以在 JNI 包含的机器相关部分(通常在$JAVA_HOME/include/<arch>/jni-md.h)中找到这些宏的定义。

简而言之,JNIEXPORT包含确保正确导出给定函数所需的任何编译器指令。在 android(和其他基于 linux 的系统)上,这将是空的。

JNICALL包含确保使用正确的调用约定处理给定函数所需的任何编译器指令。在 android 上也可能是空的(它是 w32 上的 __stdcall)。

一般来说,你应该把它们留在里面,即使它们是空#define的。

于 2013-10-17T11:33:27.117 回答
5

简单来说:

  • JNIEXPORT如果你应该使用registerNatives函数族,那么你不应该使用 JNIEXPORT。否则你必须使用它。
  • JNICALL必须始终使用。

JNIEXPORT 确保函数在符号表中可见。JNICALL 确保函数使用正确的调用约定。在 Android 上,JNICALL 具有基于架构的不同值。ARM 是空的,这可能会欺骗您不包含它。但是你必须使用 JNICALL。

registerNatives也允许您以编程方式在JNI_onLoad未来或将来的某个时间链接该功能。registerNatives允许更早地捕获错误的函数名称,我推荐这条路线。

于 2017-02-05T07:32:52.067 回答
3

只需在您的本机类上运行“javah”并使用它生成的任何内容。当您拥有可以 100% 可靠生产的工具时,您无需了解其中的来龙去脉。

于 2013-10-17T09:31:59.750 回答