registerNatives() 方法是做什么用的?

78
在Java中,Object类的私有静态方法registerNatives()是做什么用的?

1
啊,当然,我没有考虑到JNI方面......谢谢大家! - Hubris
3个回答

124
其他答案在技术上是正确的,但对于没有JNI经验的人来说并不是很有用。:-)
通常情况下,为了让JVM找到你的本地函数,它们必须按特定的方式命名。例如,对于java.lang.Object.registerNatives,相应的C函数的名称为Java_java_lang_Object_registerNatives。通过使用registerNatives(或者更准确地说是JNI函数RegisterNatives),您可以将C函数命名为您想要的任何名称。
这是相关的C代码(来自OpenJDK 6):
static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

(请注意,Object.getClass不在列表中;它仍将被“标准”名称Java_java_lang_Object_getClass所调用。) 对于列出的函数,相关的C函数在该表中如上所述,这比编写一堆转发函数更方便。

如果您将Java嵌入到C程序中并希望链接到应用程序本身内部的函数(而不是链接到共享库中的函数),或者正在使用的功能没有被“导出”,则注册本机功能也很有用,因为这些通常无法通过标准方法查找机制找到。注册本机函数还可以用于将本机方法“重绑定”到另一个C函数(例如,如果您的程序支持动态加载和卸载模块,则非常有用)。

我鼓励每个人阅读JNI书籍,其中讨论了这个问题以及更多内容。 :-)


2
你写了一个完全装饰的JNI函数名,这让人感到困惑:Java_java_lang_Object_registerNatives是由VM自动调用的,还是c/c++代码必须调用该函数,而且没有必要使用所有那些java术语。 - Pavel P
默认情况下,类fully.qualified.ClassName中的本地方法methodName将在C名称Java_fully_qualified_ClassName_methodName下搜索(您的C端代码必须导出)。您可以调用JNIEnvRegisterNatives方法来覆盖此链接。换句话说,如果您不先使用RegisterNatives,则需要“所有那些Java胡言乱语”[sic]。 - C. K. Young
啊,好的,所以只导出一个函数 Java_java_lang_Object_registerNatives,我实际上可以通过检查 cls 的值来链接所有我的 JNI 东西。我错过了 registerNatives 实际上是 Java 自身的一部分的那部分。那么...当某个类即将被使用时,JVM 使用哪些操作顺序来链接本地方法?似乎如果本地方法尚未注册(来自 c/c++),则 JVM 将检查所有这些导出名称,如果它们不存在,则尝试使用 Object.registerNatives(或者反之亦然?)。顺便说一下,jni 书籍链接已经失效了。 - Pavel P
如果您没有调用RegisterNatives,并且导出的名称不可用,则在尝试调用该方法时会出现运行时错误。 Object.registerNatives是OpenJDK对java.lang.Object的实现的内部方法,从其类初始化程序中调用,其唯一目的是注册与java.lang.Object相关的本机方法。 如果您正在编写自己的本机库,则需要自己注册库的本机方法(如果您希望这样做)。 - C. K. Young
我见过的最佳答案 - choxsword

14
可能会有些令人混淆的是,在先前答案中显示的java.lang.Object.registerNatives的代码只是注册本地函数的示例。 这是(在OpenJDK的实现中)为Object类注册本地函数的代码。 要为您自己的类注册本机函数,必须从您自己库中的本机代码调用JNI函数RegisterNatives。 这听起来可能有点循环,但有几种方法可以打破这个循环。
  1. 按照Object类的实现示例进行操作:

    a. 在您的Java类中,声明一个名为registerNatives(或其他任何名称,不要紧)的本机方法(最好是静态的)。

    b. 在您的本地代码中,定义一个名为Java_<your fully qualified class name>_registerNatives的函数,其中包含对JNI函数RegisterNatives的调用。

    c. 确保在您的Java代码中,在调用其他本机方法之前调用了您的Java registerNatives方法。

或者

  1. 使用JNI_OnLoad

    a. 在您的本机库中定义一个函数jint JNI_OnLoad(JavaVM * vm,void * reserved)。 在此函数的主体中,调用JNI函数RegisterNatives

    b. 当通过System.loadLibrary加载您的本机库时,Java VM将自动寻找并调用JNI_OnLoad,您应该已经在调用它,可能是在类的静态初始化器中。(您可以通过在vm指针指向的表中调用GetEnv函数来获取所需的env指针。)


0

它被用于以下场景:

C或C++作为主机,Java JVM作为客户端。 (C加载jvm.dll并使用JNI_CreateJavaVM创建JVM)

当Java代码需要调用主机的C函数时, 如果仍然使用jni dll (System.loadLibrary("foo.dll");)绑定本地java方法,则dll的内存空间与C主机不同。 (如果调用的函数是主机状态无关的,则仍然可以工作,但是您无法以此方式访问主机的状态值)

在这里,您需要在主机上使用env->RegisterNatives()将主机的C函数注入(绑定、公开、导出)到JVM中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接