JNI转换通俗易懂的总结(Java调用C++篇)

本文详细介绍了如何通过JNI从Java调用C++代码,包括创建Java类、使用javah生成JNI头文件、编写C++源文件、编译动态库以及解决java.library.path问题。此外,还探讨了静态注册与动态注册的区别,提供了动态注册的流程示例。
摘要由CSDN通过智能技术生成

相对C++调用java而言,使用JNI实现Java调用C++相对比较简单点,因为不用自己启动和管理一个JVM。

最简单的流程:

1.      编写一份简单的HelloWorld.java

class HelloWorld {

public native void displayHelloWorld();

 

static {

System.loadLibrary("hello");

}

 

public static void main(String[] args) {

new HelloWorld().displayHelloWorld();

}

}

 

2. 执行javah –jni HelloWorld生成一份C++的头文件HelloWorld.h

/* DO NOT EDIT THIS FILE - it is machinegenerated */

#include <jni.h>

/* Header for class HelloWorld */

 

#ifndef _Included_HelloWorld

#define _Included_HelloWorld

#ifdef __cplusplus

extern "C" {

#endif

/*

 *Class:     HelloWorld

 *Method:    displayHelloWorld

 *Signature: ()V

 */

JNIEXPORT void JNICALLJava_HelloWorld_displayHelloWorld

 (JNIEnv *, jobject);

 

#ifdef __cplusplus

}

#endif

#endif

 

3.根据已经生成的头文件,编写一份.cpp文件HelloWorld.cpp(其中函数名跟.h文件中自动生成的保持一致),并且要include  jni.h文件,(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的JNIEnvjobject等类型都是在该头文件中定义的

#include <jni.h>

#include "HelloWorld.h"

#include <stdio.h>

JNIEXPORT void JNICALLJava_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj){

         printf("Helloworld!\n");

         return;

}

 

4. 对C++文件进行编译生成目标文件HelloWorld.o:g++ -c -fPICHelloWorld.cpp    生成HelloWorld.o

 

 

5.生成动态链接文件libhello.so: g++ -fpic -shared -oHelloWorld.so libhello.o 

--千万要注意.so文件的命名规则,一定要以lib+hello+.so的格式,其中hello是必须跟System.loadLibrary("hello");中的hello保持一致。尤其是lib作为前缀千万不能忘记。

 

6. 要把生成的libhello.so拷贝到java.library.path目录下,这个目录不知怎么在linux查询,有个笨办法就是写一个简单java程序,执行System.out.println(System.getProperty("java.library.path"));就可以得到目录路径。或者可以export  LD_LIBRARY_PATH=(libhello.so所在的目录)

 

7.执行javaHelloWorld,正常输出为:

Hello world!

--注意第6步,如果没有很好解决java.library.path路径问题的话,执行HelloWorld时就会报错:

Exception in thread "main"java.lang.UnsatisfiedLinkError: no hello in java.library.path

       at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)

       at java.lang.Runtime.loadLibrary0(Runtime.java:870)

       at java.lang.System.loadLibrary(System.java:1122)

       at HelloWorld.<clinit>(HelloWorld.java:5)

 

8. 静态注册和动态注册

在java调用C++的过程中,有一个很重要的步骤就是:java运行时如何去查找到某个native方法,这就涉及到native方法要注册到JVM中,这就分成了动态(手工)注册和静态(自动)注册的概念了,为什么上述流程没有看到这一步呢?因为我们偷懒,选择了静态(自动)注册这一方式。我们用javah xxxx去生成.h文件,这样native的函数名就按标准格式生成了,这样JVM在加载动态库后就可以根据标准格式去找到对应的native函数调用。至于动态注册的过程后续再补充。

补充:

动态注册的流程

动态注册和静态注册的区别就是动态注册不需要使用javah生成JNI标准规范的.h头文件,可以根据自己的需要去编写C++的头文件和源文件,函数名也可以自己定义,比如HelloWorld.c(这里为了简单,用C语言的文件,其实也可以使用C++)

#include <stdlib.h>

#include <string.h>

#include <stdio.h>

#include <jni.h>

#include <assert.h>

/*自己定义函数名,而非javah所生成的复杂的标准的函数名*/

JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)

{

    printf("hello in c native code./n");

    return (*env)->NewStringUTF(env, "hello world returned.");

}

 

// 指定要注册的类,把

#define JNIREG_CLASS "HelloWorld"

// 定义一个JNINativeMethod数组,其中的成员就是Java代码中对应的native方法,下面代码其实使用了构造函数初始化了JNINativeMethod数组中的第一个对象

static JNINativeMethodgMethods[] = {

    { "hello", "()Ljava/lang/String;", (void*)native_hello},

};

其中JNINativeMethod是一个struct结构体:

typedefstruct {

    constchar* name;

    constchar* signature;

    void*       fnPtr;

} JNINativeMethod;

 

//调用JNIEnvRegisterNatives来真正实现本地函数名和java方法名的映射关系注册到JVM

staticint registerNativeMethods(JNIEnv* env, constchar* className,

JNINativeMethod* gMethods, int numMethods) {

    jclass clazz;

    clazz =(*env)->FindClass(env, className);

    if (clazz == NULL) {

        returnJNI_FALSE;

    }

    if ((*env)->RegisterNatives(env, clazz,gMethods, numMethods) < 0) {

        returnJNI_FALSE;

    }

    returnJNI_TRUE;

}

 

//实现JNI_OnLoad函数

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {

    JNIEnv* env = NULL;

    jint result = -1;

 

    if ((*vm)-> GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        return -1;

    }

    assert(env != NULL);

 

    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0]))) { //注册

        return -1;

    }

    result = JNI_VERSION_1_4;

 

    return result;

 

}

 

--代码流程并不复杂,简单来说就是当java代码中调用System.loadLibraryhello.so)时,JVM会先去找到JNI_OnLoad函数,在该该函数中调用了registerNativeMethods(env,JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))---à(*env)->RegisterNatives(env, clazz, gMethods,numMethods),最终调到了RegisterNatives真正把本地函数名和java方法名的映射关系注册到JVM中,后续java调用这些本地方法时就能根据映射关系找到。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值