JNI中枚举类型作为参数

℡╲_俬逩灬. 提交于 2020-01-11 02:27:09

参考资料:使用JNI进行Java与C/C++语言混合编程(1)--在Java中调用C/C++本地库

     java native方法及JNI实例

JNI是Java Native Interface的英文缩写,中文翻译为本地调用, 自从Java 1.1开始就成为了Java标准的一部分.

C/C++是系统级的编程语言, 可以用来开发任何和系统相关的程序和类库, 但是Java本身编写底层的应用比较难实现, 使用JNI可以调用现有的本地库, 极大地灵活了Java的开发.

C/C++的效率是目前最好的语言, 可以使用C/C++来实现一些实时性非常高的部分. C/C++和Java本身都是非常流行的编程语言, 一些大型软件中经常使用语言之间的混合编程.

鉴于目前网络上JNI的文章不是特别多, 我将自己的一些总结写在这里. 如有错漏, 欢迎指正!

Java调用C/C++大概有这样几个步骤

  1. 编写带有native方法的Java类, 使用javac工具编译Java类
  2. 使用javah来生成与native方法对应的头文件
  3. 实现相应的头文件, 并编译为动态链接库(windows下是.dll, linux下是.so)

1.简单的JNI-HelloWorld

首先我们看一下:Linux下JNI技术使用的一个简单实例

首先,实现的是Java本地接口Hello.java,代码如下所示:

class HelloWorld {

    public native void sayHello();

    static {
        System.loadLibrary("HelloWorld");
    }

    public static void main(String[] args) {
        (new HelloWorld()).sayHello();
    }
}

 

其中,方法声明为native,其实HelloWorld类就相当于一个接口,是为其他编程语言声明的接口。System.loadLibrary("HelloWorld");语句是一个static块,也就是在该HelloWorld类加载的时候进行执行。其中,该语句实现了加载本地的动态连接库(DLL),在Linux平台下,动态连接库文件是以.so作为扩展名的,也就是标准对象(Standard Object)。

对该本地接口类进行编译:

[root@localhost jni]# javac HelloWorld.java

 

接着,通过编译的HelloWorld.class文件,生成C语言的头文件,执行命令:

[root@localhost jni]# javah -jni HelloWorld

 

可以看到,在当前目录下生成一个HelloWorld.h文件,该文件就是C的接口文件,为使用C实现Java接口中定义的方法,可以发现在HelloWorld.h中有一个方法声明:

/* DO NOT EDIT THIS FILE - it is machine generated */

#ifndef __HelloWorld__
#define __HelloWorld__

#include <jni.h>

#ifdef __cplusplus
extern "C"
{
#endif

JNIEXPORT void JNICALL Java_HelloWorld_sayHello (JNIEnv *env, jobject);

#ifdef __cplusplus
}
#endif

#endif /* __HelloWorld__ */

 


然后,用C实现该方法,在HelloWorld.c文件中,代码如下:

#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloWorld_sayHello (JNIEnv *env, jobject obj) {
    printf("Hello,the World!!!");
}

 

这里,方法签名为Java_HelloWorld_sayHello (JNIEnv *env, jobject obj),添加了形参obj,否则无法通过编译。

接下来,生成动态连接库libHelloWorld.so,执行命令:

[root@localhost jni]# gcc -fPIC -shared -o libHelloWorld.so HelloWorld.c

 

(这里可能会遇到jni.h找不到的情况,再你配置的JAVA_HOME 文件夹目录下加上如下路径选项即可:

-I $JAVA_HOME)/include -I $JAVA_HOME/include/linux


可以在当前目录下看到libHelloWorld.so,动态连接库文件名称以lib开头。将该文件拷贝到usr/lib目录下面,就可以测试了。

现在执行如下命令进行测试:

[root@localhost jni]# java HelloWorld

 

输出如下:

Hello,the World!!!

这只是一个非常简单的例子,主要是了解JNI在Linux下该如何用。在实际应用中,可能会非常复杂,并且要记住,一旦使用了JNI技术,系统的可移植性被破坏了。有些应用中,正是基于这种特性实现,比如限制软件的传播使用,保护开发商权益,等等。

2.枚举型作为本地函数的参数

主要参考了这篇文章:http://blog.csdn.net/hemowolf/article/details/6925243

JAVA 的标准JNI中并没有enum类型,但是有jobject对象,那么类和枚举类型都可以通过jobject进行传递。

  

2.1 创建枚举类型

每个枚举对象都有一个对应的索引值,枚举类中有一个返回值函数。

package test;
 
public class enumclass {
 
    public enum envelopeType {
        NOT_SPECIFIED(-1), NONE(0), IMAGE(1), BITMAP(2);
 
        private int value;
 
        private envelopeType(int value) {
            this.value = value;
        }
 
        public int getValue() {
            return value;
        }
    }
 
}

2.2 JAVA  本地函数

package test;
 
import test.enumclass.envelopeType;
 
public class enumtest {
 
    private envelopeType ent;
 
    public native static void printValue(envelopeType entype);
 
    public static void main(String[] args) {
 
        System.load("/home/sk/workspace/testenumjni2/jni/test.so");
 
        enumtest t1 = new enumtest();
        envelopeType e1 = null;
        int a = e1.IMAGE.getValue();
        System.out.println("------ the envelopeType value is :" + a);
        envelopeType e2 = envelopeType.NOT_SPECIFIED;
        // e2.IMAGE;
        printValue(e2);
 
    }
 
}

在命令行中(linux环境下)切换到工程的bin目录下执行如下命令:编译生成.h文件。

javah  -jni test.enumtest

2.3 JNI 接口部分

根据上步的.h文件,编写对应的c/cpp文件。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_enumtest */
 
#ifndef _Included_test_enumtest
#define _Included_test_enumtest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_enumtest
 * Method:    printValue
 * Signature: (Ltest/enumclass/envelopeType;)V
 */
JNIEXPORT void JNICALL Java_test_enumtest_printValue
(JNIEnv *env, jobject obj1, jobject obj2) {
 
    jclass enumclass= env->GetObjectClass(obj2);
    if(enumclass == NULL) {
        printf(" get enumclass failed\r\n");
        return;
    }
 
    jmethodID getVal = env->GetMethodID(enumclass, "getValue", "()I");
    jint value = env->CallIntMethod(obj2, getVal);
        printf("------ the envelopeType value is :%d\r\n", value);
 
}
 
#ifdef __cplusplus
}
#endif
#endif

env->GetMethodID 有三个参数,第一个表示获取的类的名字,第二个参数是函数名,最后一个参数应该这样获得:

这个参数应该是JAVA的类的方法的签名,它可以通过jdk的工具 javap 得到。如上面的 test.enumtest.envelopeType 类中的 getValue 方法,可以在命令行里执行:

javap -s test.enumtest.envelopeType

 

得到 test.enumtest.envelopeType  的所有方法的签名。

 

2.4 下面是另一个没有索引值的枚举类型列子

考虑本地函数参数中含有枚举类型的对象,在JNI部分要获取的是枚举对象,而非对象的一个函数。

package test;
 
public class enumclass {
 
    public enum DATE {
 
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATUDAY, SUNDAY;
    }
 
     
}
import test.enumclass.DATE;
 
public class enumtest {
 
    private DATE date;
    public native static void callobj(DATE date);
     
    public static void main(String[] args){
         
        System.load("/home/sk/workspace/testenumjni3/jni/test.so");
        enumtest e1 = new enumtest();
        e1.date = DATE.SATUDAY;
        System.out.println(e1.date);
        //Now we call the native method
         
        DATE today = DATE.TUESDAY;
        callobj(today);
    }
     
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include<string.h>
/* Header for class test_enumtest */
 
#ifndef _Included_test_enumtest
#define _Included_test_enumtest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_enumtest
 * Method:    callobj
 * Signature: (Ltest/enumclass/DATE;)V
 */
JNIEXPORT void JNICALL Java_test_enumtest_callobj
  (JNIEnv *env, jobject obj1, jobject obj2){
 
    jclass enumclass= env->GetObjectClass(obj2);
        if(enumclass == NULL) {
            printf(" get enumclass failed\r\n");
            return;
        }
 
        jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;");
        jstring value = (jstring)env->CallObjectMethod(obj2, getVal);
        const char * valueNative = env->GetStringUTFChars(value, 0);
 
        if (strcmp(valueNative, "MONDAY") == 0) {
        printf("TODAY IS MONDAY!");
        }
        else
            printf("TODAY IS  NOT MONDAY!");
 
 
 
}
 
#ifdef __cplusplus
}
#endif
#endif

jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;");

这里name()函数是enum类型自带的函数,()Ljava/lang/String;是对应的签名。具体的ENUM类型可以参考官方文档:

http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Enum.html

另外就是这里换成了(jstring)env->CallObjectMethod(obj2, getVal);方法。

 

3.涉及C/C++与JAVA类型转换的问题

 下面的一个例子,是要在JNI中完成由C++写的代码函数的调用,函数的参数包括枚举类型的。需要在JNI中对原来C/C++定义的枚举型重新做一次映射(在数据量小的时候这样操作是可行的)。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include<string.h>
/*emun类型定义*/
enum DATE {
   
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATUDAY, SUNDAY;
    }
 void returndate(DATE date);
 void returndate(DATE date){
    printf("%d/n","TODAY IS :");
    switch(date){
    case MONDAY:
     printf("%d/n","MONDAY");
    break;
    case TUESDAY:
         printf("%d/n","TUESDAY");
         break;
    case WEDNESDAY:
         printf("%d/n","WEDNESDAY");
         break;
    case THURSDAY:
         printf("%d/n","THURSDAY");
         break;
    case FRIDAY:
         printf("%d/n","FRIDAY");
         break;
    case SATUDAY:
         printf("%d/n","SATUDAY");
         break;
     case SUNDAY:
         printf("%d/n","SUNDAY");
}
}
/* Header for class test_enumtest */
   
#ifndef _Included_test_enumtest
#define _Included_test_enumtest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_enumtest
 * Method:    callobj
 * Signature: (Ltest/enumclass/DATE;)V
 */
JNIEXPORT void JNICALL Java_test_enumtest_callobj
  (JNIEnv *env, jobject obj1, jobject obj2){
jclass enumclass= env->GetObjectClass(obj2);
        if(enumclass == NULL) {
            printf(" get enumclass failed\r\n");
            return;
        }
   
        jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;");
        jstring value = (jstring)env->CallObjectMethod(obj2, getVal);
        const char * valueNative = env->GetStringUTFChars(value, 0);
         DATE date;
        //这里完成一个转换
        if (strcmp(valueNative, "MONDAY") == 0) {
         date = MONDAY;
        }
        if.....(这里接着判断)
 returndate(date);
   
   
}
   
#ifdef __cplusplus
}
#endif
#endif

如果在JNI函数中调用C++的函数中带有枚举型参数,用jstring类型或者 char*类型的做参数都会报错,可以做的就是再做一次名字的映射,将上述类型映射成enmu类型,再当作参数去传递,就不会有问题了。

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!