• 【转】使用JNI进行混合编程:在C/C++中调用Java代码


    原文链接:http://www.cnblogs.com/icejoywoo/archive/2012/02/24/2367116.html

    JNI就是Java Native Interface, 即可以实现Java调用本地库, 也可以实现C/C++调用Java代码, 从而实现了两种语言的互通, 可以让我们更加灵活的使用。

    通过使用JNI可以从一个侧面了解Java内部的一些实现。

    本文使用的环境是:

    1. 64位的win7系统
    2. JDK 1.6.0u30 (32位)
    3. C/C++编译器是 Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86 (VC 6.0的, 其他版本的也可以编译通过, 测试过vs2010)

    本文使用到的一些功能:

    1. 创建虚拟机
    2. 寻找class对象, 创建对象
    3. 调用静态方法和成员方法
    4. 获取成员属性, 修改成员属性

    C/C++调用Java代码的一般步骤:

    1. 编写Java代码, 并编译
    2. 编写C/C++代码
    3. 配置lib进行编译, 配置PATH添加相应的dll或so并运行

    编写Java代码并编译

    这段代码非常简单, 有个静态方法和成员方法, 一个public的成员变量

    1. public class Sample2 {  
    2.     public String name;  
    3.        
    4.     public static String sayHello(String name) {  
    5.         return "Hello, " + name + "!";  
    6.     }  
    7.        
    8.     public String sayHello() {  
    9.         return "Hello, " + name + "!";  
    10.     }  

    由于没有定义构造函数, 所以会有一个默认的构造函数.

    运行下面的命令编译

    >javac Sample2.java

    可以在当前目录下看到Sample2.class文件, 编译成功, 第一步完成了, So easy!

    可以查看Sample2类中的签名

    >javap -s -private Sample2

    结果如下

    1. Compiled from "Sample2.java" 
    2. public class Sample2 extends java.lang.Object{  
    3. public java.lang.String name;  
    4.   Signature: Ljava/lang/String;  
    5. public Sample2();  
    6.   Signature: ()V  
    7. public static java.lang.String sayHello(java.lang.String);  
    8.   Signature: (Ljava/lang/String;)Ljava/lang/String;  
    9. public java.lang.String sayHello();  
    10.   Signature: ()Ljava/lang/String;  

    关于签名的含义, 请参看使用JNI进行Java与C/C++语言混合编程(1)--在Java中调用C/C++本地库.

    编写C/C++代码调用Java代码

    先贴代码吧

    1. #include <jni.h>  
    2. #include <string.h>  
    3. #include <stdio.h>  
    4.    
    5. // 环境变量PATH在windows下和linux下的分割符定义  
    6. #ifdef _WIN32  
    7. #define PATH_SEPARATOR ';' 
    8. #else 
    9. #define PATH_SEPARATOR ':' 
    10. #endif  
    11.    
    12.    
    13. int main(void)  
    14. {  
    15.     JavaVMOption options[1];  
    16.     JNIEnv *env;  
    17.     JavaVM *jvm;  
    18.     JavaVMInitArgs vm_args;  
    19.        
    20.     long status;  
    21.     jclass cls;  
    22.     jmethodID mid;  
    23.     jfieldID fid;  
    24.     jobject obj;  
    25.        
    26.     options[0].optionString = "-Djava.class.path=.";  
    27.     memset(&vm_args, 0, sizeof(vm_args));  
    28.     vm_args.version = JNI_VERSION_1_4;  
    29.     vm_args.nOptions = 1;  
    30.     vm_args.options = options;  
    31.        
    32.     // 启动虚拟机  
    33.     status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);  
    34.        
    35.     if (status != JNI_ERR)  
    36.     {  
    37.         // 先获得class对象  
    38.         cls = (*env)->FindClass(env, "Sample2");  
    39.         if (cls != 0)  
    40.         {  
    41.             // 获取方法ID, 通过方法名和签名, 调用静态方法  
    42.             mid = (*env)->GetStaticMethodID(env, cls, "sayHello""(Ljava/lang/String;)Ljava/lang/String;");  
    43.             if (mid != 0)  
    44.             {  
    45.                 const char* name = "World";  
    46.                 jstring arg = (*env)->NewStringUTF(env, name);  
    47.                 jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);  
    48.                 const char* str = (*env)->GetStringUTFChars(env, result, 0);  
    49.                 printf("Result of sayHello: %s ", str);  
    50.                 (*env)->ReleaseStringUTFChars(env, result, 0);  
    51.             }  
    52.                
    53.             /*** 新建一个对象 ***/ 
    54.             // 调用默认构造函数  
    55.             //obj = (*env)->AllocObjdect(env, cls);   
    56.                
    57.             // 调用指定的构造函数, 构造函数的名字叫做<init>  
    58.             mid = (*env)->GetMethodID(env, cls, "<init>""()V");  
    59.             obj = (*env)->NewObject(env, cls, mid);  
    60.             if (obj == 0)  
    61.             {  
    62.                 printf("Create object failed! ");  
    63.             }  
    64.             /*** 新建一个对象 ***/ 
    65.                
    66.             // 获取属性ID, 通过属性名和签名  
    67.             fid = (*env)->GetFieldID(env, cls, "name""Ljava/lang/String;");  
    68.             if (fid != 0)  
    69.             {  
    70.                 const char* name = "icejoywoo";  
    71.                 jstring arg = (*env)->NewStringUTF(env, name);  
    72.                 (*env)->SetObjectField(env, obj, fid, arg); // 修改属性  
    73.             }  
    74.                
    75.             // 调用成员方法  
    76.             mid = (*env)->GetMethodID(env, cls, "sayHello""()Ljava/lang/String;");  
    77.             if (mid != 0)  
    78.             {  
    79.                 jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);  
    80.                 const char* str = (*env)->GetStringUTFChars(env, result, 0);  
    81.                 printf("Result of sayHello: %s ", str);  
    82.                 (*env)->ReleaseStringUTFChars(env, result, 0);  
    83.             }  
    84.         }  
    85.            
    86.         (*jvm)->DestroyJavaVM(jvm);  
    87.         return 0;  
    88.     }  
    89.     else 
    90.     {  
    91.         printf("JVM Created failed! ");  
    92.         return -1;  
    93.     }  

    这段代码大概做了这几件事:

    1. 创建虚拟机JVM, 在程序结束的时候销毁虚拟机JVM
    2. 寻找class对象
    3. 创建class对象的实例
    4. 调用方法和修改属性

    虚拟的创建

    与之相关的有这样几个变量

    1. JavaVMOption options[1];  
    2. JNIEnv *env;  
    3. JavaVM *jvm;  
    4. JavaVMInitArgs vm_args; 

    JavaVM就是我们需要创建的虚拟机实例

    JavaVMOption相当于在命令行里传入的参数

    JNIEnv在Java调用C/C++中每个方法都会有的一个参数, 拥有一个JNI的环境

    JavaVMInitArgs就是虚拟机创建的初始化参数, 这个参数里面会包含JavaVMOption

    下面就是创建虚拟机

    1. options[0].optionString = "-Djava.class.path=.";  
    2. memset(&vm_args, 0, sizeof(vm_args));  
    3. vm_args.version = JNI_VERSION_1_4;  
    4. vm_args.nOptions = 1;  
    5. vm_args.options = options;  
    6.    
    7. // 启动虚拟机  
    8. status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); 

    "-Djava.class.path=."看着眼熟吧, 这个就是传入当前路径, 作为JVM寻找class的用户自定义路径, 我们的Sample2.class就在当前路径(当然也可以不在当前路径, 你可以随便修改).

    vm_args.version是Java的版本, 这个应该是为了兼容以前的JDK, 可以使用旧版的JDK, 这个宏定义是在jni.h中,  有以下四种

    1. #define JNI_VERSION_1_1 0x00010001 
    2. #define JNI_VERSION_1_2 0x00010002 
    3. #define JNI_VERSION_1_4 0x00010004 
    4. #define JNI_VERSION_1_6 0x00010006   

    vm_args.nOptions的含义是, 你传入的options有多长, 我们这里就一个, 所以是1。

    vm_args.options = options把JavaVMOption传给JavaVMInitArgs里面去。

    然后就是启动虚拟机了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)。

    可以通过这个返回值status , 知道虚拟机是否启动成功

    1. #define JNI_OK           0                 /* success */ 
    2. #define JNI_ERR          (-1)              /* unknown error */ 
    3. #define JNI_EDETACHED    (-2)              /* thread detached from the VM */ 
    4. #define JNI_EVERSION     (-3)              /* JNI version error */ 
    5. #define JNI_ENOMEM       (-4)              /* not enough memory */ 
    6. #define JNI_EEXIST       (-5)              /* VM already created */ 
    7. #define JNI_EINVAL       (-6)              /* invalid arguments */   

    寻找class对象, 并实例化

    JVM在Java中都是自己启动的, 在C/C++中只能自己来启动了, 启动完之后的事情就和在Java中一样了, 不过要使用C/C++的语法.

    获取class对象比较简单, FindClass(env, className).

    1. cls = (*env)->FindClass(env, "Sample2");  

    在Java中的类名格式是java.lang.String, 但是className的格式有点不同, 不是使用'.'作为分割, 而是'/', 即java/lang/String.

    我们知道Java中构造函数有两种, 一种是默认的没有参数的, 一种是自定义的带有参数的. 对应的在C/C++中, 有两种调用构造函数的方法.

    调用默认构造函数

    1. // 调用默认构造函数  
    2. obj = (*env)->AllocObjdect(env, cls);   

    构造函数也是方法, 类似调用方法的方式.

    1. // 调用指定的构造函数, 构造函数的名字叫做<init>  
    2. mid = (*env)->GetMethodID(env, cls, "<init>""()V");  
    3. obj = (*env)->NewObject(env, cls, mid);  

    调用方法和修改属性

    关于方法和属性是有两个ID与之对应, 这两个ID用来标识方法和属性.

    1. jmethodID mid;  
    2. jfieldID fid; 

    方法分为静态和非静态的, 所以对应的有

    1. mid = (*env)->GetStaticMethodID(env, cls, "sayHello""(Ljava/lang/String;)Ljava/lang/String;");  
    2.    
    3. mid = (*env)->GetMethodID(env, cls, "sayHello""()Ljava/lang/String;");   

    上面两个方法是同名的, 都叫sayHello, 但是签名不同, 所以可以区分两个方法.

    JNI的函数都是有一定规律的, Static就表示是静态, 没有表示非静态.

    方法的调用如下

    1. jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);  
    2.    
    3. jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);   

    我们可以看到静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象.

    属性也有静态和非静态, 示例中只有非静态的.

    获取属性ID

    1. fid = (*env)->GetFieldID(env, cls, "name""Ljava/lang/String;");   

    改属性的值

    1. (*env)->SetObjectField(env, obj, fid, arg); // 修改属性  

    关于jstring的说明

    java的String都是使用了unicode, 是双字节的字符, 而C/C++中使用的单字节的字符。

    从C转换为java的字符, 使用NewStringUTF方法

    1. jstring arg = (*env)->NewStringUTF(env, name);  

    从java转换为C的字符, 使用GetStringUTFChars

    1. const char* str = (*env)->GetStringUTFChars(env, result, 0); 
    const char* str = (*env)->GetStringUTFChars(env, result, 0);

     编译和运行

    编译需要头文件, 头文件在这两个目录中%JAVA_HOME%include和%JAVA_HOME%includewin32, 第一个是与平台无关的, 第二个是与平台有关的, 由于笔者的系统是windows, 所以是win32.

    编译的时候还要一个lib文件, 是对虚拟机的支持, 保证编译通过.

    1. cl -I%JAVA_HOME%include -I%JAVA_HOME%includewin32 Sample2.c %JAVA_HOME%libjvm.lib  
    我们可以看到在当前目录下Sample2.exe, 运行的时候需要jvm.dll(不要将其复制到当前目录下, 这样不可以运行, 会导致jvm创建失败)
    1. set PATH=%JAVA_HOME%jreinclient;%PATH%  
    2. Sample2  
    jvm.dll在%JAVA_HOME%jreinclient目录下, 所以我把这个目录加入到PATH中, 然后就可以运行
    1. Result of sayHello: Hello, World!  
    2. Result of sayHello: Hello, icejoywoo! 
    关于C++的说明

    本示例的C++版本, 请自行下载后面的源代码来查看, 区别不是很大.

    主要是JNIEnv和JavaVM两个对象, 在C中是结构体, 是函数指针的集合, 在C++中结构体拥有类的能力, 使用起来更为简便, 与Java之间的差异更小一些.

    结语

    本文介绍了一个简单的例子, 分析了其中的一些代码, 笔者希望通过这篇文章让大家对JNI的了解更加深入一些.

    水平有限, 错漏在所难免, 欢迎指正!
     

    源代码下载: c调用java.zip

    使用方法: 参照里面的build&run.bat, 使用了%JAVA_HOME%环境变量.

    注意:

    1. 动态链接库和JDK都有32位和64位的区别, 使用64位系统的朋友, 要注意这个问题, 可能导致运行或编译错误.
    2. 还要注意区分C和C++代码, 在JNI中两种代码有一定的区别, 主要是env和jvm两个地方.

    参考文献:

    1. public0821, C++调用JAVA方法详解, http://public0821.iteye.com/blog/423941
    2. Scott Stricker, 用 JNI 进行 Java 编程, http://www.ibm.com/developerworks/cn/education/java/j-jni/section3.html
    3. JDK 6u30 docs, Java Native Interface Specification
    阅读(339) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~
    评论热议
  • 相关阅读:
    ios程序中传值的几种方式
    iOS系统、设备信息获取方式
    关于如何使自定义的Button和系统的UIBarButtonItem保持一致的两种方法
    关于iOS中音视频播放的几种方式介绍
    关于IOS数据操作方式详解(三)— Sqlite数据解析
    关于iOS数据操作方式详解(二)— Jason数据解析
    关于IOS数据操作方式详解(一)— XML数据解析
    git常用命令
    pip及npm换源
    win10安装Docker并换国内源
  • 原文地址:https://www.cnblogs.com/black/p/5171798.html
Copyright © 2020-2023  润新知