在c/c++中调用Java方法

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

经过使用JNI能够从一个侧面了解Java内部的一些实现.java

本文使用的环境是linux

  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代码的通常步骤:windows

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

编写Java代码并编译jvm

这段代码很是简单, 有个静态方法和成员方法, 一个public的成员变量函数

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

 因为没有定义构造函数, 因此会有一个默认的构造函数.测试

运行下面的命令编译ui

?
>javac Sample2.java

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

能够查看Sample2类中的签名

?
>javap -s - private Sample2

 结果以下

?
Compiled from "Sample2.java"
public class Sample2 extends java.lang.Object{
public java.lang.String name;
   Signature: Ljava/lang/String;
public Sample2();
   Signature: ()V
public static java.lang.String sayHello(java.lang.String);
   Signature: (Ljava/lang/String;)Ljava/lang/String;
public java.lang.String sayHello();
   Signature: ()Ljava/lang/String;
}

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

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

先贴代码吧

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

 这段代码大概作了这几件事

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

虚拟的建立

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

    JavaVMOption options[1];     JNIEnv *env;     JavaVM *jvm;     JavaVMInitArgs vm_args;

JavaVM就是咱们须要建立的虚拟机实例

JavaVMOption至关于在命令行里传入的参数

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

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

下面就是建立虚拟机

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

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

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

?
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#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 , 知道虚拟机是否启动成功

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

 寻找class对象, 并实例化

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

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

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

 在Java中的类名格式是java.lang.String, 可是className的格式有点不一样, 不是使用'.'做为分割, 而是'/', 即java/lang/String.

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

调用默认构造函数

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

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

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

 调用方法和修改属性

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

?
jmethodID mid;
jfieldID fid;

 方法分为静态和非静态的, 因此对应的有

?
mid = (*env)->GetStaticMethodID(env, cls, "sayHello" , "(Ljava/lang/String;)Ljava/lang/String;" );
 
mid = (*env)->GetMethodID(env, cls, "sayHello" , "()Ljava/lang/String;" );

上面两个方法是同名的, 都叫sayHello, 可是签名不一样, 因此能够区分两个方法.

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

方法的调用以下

?
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
 
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);

 咱们能够看到静态方法是只须要class对象, 不须要实例的, 而非静态方法须要使用咱们以前实例化的对象.

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

获取属性ID

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

 修改属性的值

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

 关于jstring的说明

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

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

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

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

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

 编译和运行

编译须要头文件, 头文件在这两个目录中%JAVA_HOME%\include和%JAVA_HOME%\include\win32, 第一个是与平台无关的, 第二个是与平台有关的, 因为笔者的系统是windows, 因此是win32.

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

?
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 Sample2.c %JAVA_HOME%\lib\jvm.lib

咱们能够看到在当前目录下Sample2.exe, 运行的时候须要jvm.dll(不要将其复制到当前目录下, 这样不能够运行, 会致使jvm建立失败)

?
set PATH=%JAVA_HOME%\jre\bin\client\;%PATH%
Sample2

 jvm.dll在%JAVA_HOME%\jre\bin\client\目录下, 因此我把这个目录加入到PATH中, 而后就能够运行

?
Result of sayHello: Hello, World!
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两个地方.

 

  
做者:icejoywoo
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利. 短网址: http://goo.gl/ZiZCi
相关文章
相关标签/搜索