源码下载:JniDemo

  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并运行

1.编写Java代码并编译

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

public class Sample2 {
public String name; public static String sayHello(String name) {
return "Hello, " + name + "!";
} public String sayHello() {
return "Hello, " + name + "!";
}
}

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

运行下面的命令编译

>javac Sample2.java

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

2.查看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++本地库.

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

先贴代码吧

#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[];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args; long status;
jclass cls;
jmethodID mid;
jfieldID fid;
jobject obj; options[].optionString = "-Djava.class.path=.";
memset(&vm_args, , sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = ;
vm_args.options = options; // 启动虚拟机
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (status != JNI_ERR)
{
// 先获得class对象
cls = (*env)->FindClass(env, "Sample2");
if (cls != )
{
// 获取方法ID, 通过方法名和签名, 调用静态方法
mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");
if (mid != )
{
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, );
printf("Result of sayHello: %s\n", str);
(*env)->ReleaseStringUTFChars(env, result, );
} /*** 新建一个对象 ***/
// 调用默认构造函数
//obj = (*env)->AllocObjdect(env, cls); // 调用指定的构造函数, 构造函数的名字叫做<init>
mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
obj = (*env)->NewObject(env, cls, mid);
if (obj == )
{
printf("Create object failed!\n");
}
/*** 新建一个对象 ***/ // 获取属性ID, 通过属性名和签名
fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
if (fid != )
{
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 != )
{
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
const char* str = (*env)->GetStringUTFChars(env, result, );
printf("Result of sayHello: %s\n", str);
(*env)->ReleaseStringUTFChars(env, result, );
}
} (*jvm)->DestroyJavaVM(jvm);
return ;
}
else
{
printf("JVM Created failed!\n");
return -;
}
}

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

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

1.创建虚拟机

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

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

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

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

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

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

下面就是创建虚拟机

options[].optionString = "-Djava.class.path=.";
memset(&vm_args, , sizeof(vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = ;
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 */

2.寻找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);

3.调用方法和修改属性

关于方法和属性是有两个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, );

 4.编译和运行

编译需要头文件, 头文件在这两个目录中%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两个地方.

参考文献:

  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

转载来自:http://www.cnblogs.com/icejoywoo/archive/2012/02/24/2367116.html

Android与JNI(三) ---- c++调用java(转载)的更多相关文章

  1. Android JNI c/c++调用java 无需新建虚拟机

    近期通过研究SDL源码 得出android JNI  c/c++调用java 无需新建虚拟机: 具体步骤如下 第一步获得:两个参数 JNIEnv和jclass void Java_com_Test_A ...

  2. Android For JNI(三)——C的指针,指针变量,指针常见错误,值传递,引用传递,返回多个值

    Android For JNI(三)--C的指针,指针变量,指针常见错误,值传递,引用传递,返回多个值 C中比较难的这一块,大概就是指针了,所以大家还是多翻阅一下资料,当然,如果只是想了解一下,看本篇 ...

  3. uni-app&H5&Android混合开发三 || uni-app调用Android原生方法的三种方式

    前言: 关于H5的调用Android原生方法的方式有很多,在该片文章中我主要简单介绍三种与Android原生方法交互的方式. 一.H5+方法调用android原生方法 H5+ Android开发规范官 ...

  4. Android JNI中C调用Java方法

    背景需求 我们需要在JNI的C代码调用Java代码.实现原理:使用JNI提供的反射借口来反射得到Java方法,进行调用. JNI关键方法讲解. 1. 在同一个类中,调用其他方法 JNIEXPORT v ...

  5. cocos2d-x笔记5: 通过jni实现C++调用Java

    Cocos2d-x的跨平台性很强大,但是偶尔也需要平台的原生API结合. C++在Win32平台下简单的很,C++可以直接用MFC或者调用Win32API. Ios在XCode下直接就能C++和OC混 ...

  6. Android NDK开发之C调用Java及原生代码断点调试(二)

    上一篇中,我们主要学习了Java调用本地方法,并列举了两大特殊实例来例证我们的论据,还没学习的伙伴必须先去阅读下,本次的学习是直接在上一篇的基础上进行了.点击:Android NDK开发之从Java与 ...

  7. Android中JNI的使用方法(转载)

    Android中JNI的使用方法 首先看一下Android平台的框架图:(网上盗用) 可以看到Android上层的Application和ApplicationFramework都是使用Java编写, ...

  8. Delphi XE6 for Android 让手机震动(调用Java的函数)

    震动,是调用了 安卓api  JNI 里面的 函数  ,这些都是 调用java的 ,如下面的引用, uses  FMX.Helpers.Android,  Androidapi.JNI.App,  A ...

  9. JNI C反射调用java方法

    前面记录了调用C的学习笔记,现在来记录一下C反射调用Java的笔记.JNI开发学习之调用C方法 Android开发中调用一个类中没有公开的方法,可以进行反射调用,而JNI开发中C调用java的方法也是 ...

随机推荐

  1. Day05_JAVAEE系列:XML

    XML概述 1)什么是xml? xml, eXtend Markup Language, 可扩展标记语言 2) html vs xml 都由w3c组织制定的. html语法特征:语法比较松散      ...

  2. SGU_390_Tickets(另类数位DP)

    Tickets Time Limit : 1000/500ms (Java/Other)   Memory Limit : 524288/262144K (Java/Other) Total Subm ...

  3. HDFS在Linux下的命令

    1.对hdfs操作的命令格式是 1.1hadoop fs  -ls <path> 表示对hdfs下一级目录的查看 1.2 hadoop fs -lsr <path> 表示对hd ...

  4. input text设置字体

    控件里设置: style="font-family:Arial" html里设置 <font face="Arial">

  5. JSON数据传递

    Servlet端代码 try { while (rs.next()) { for(int i=0;i<60;i++){ Day[i]+=rs.getInt("Day"+(i+ ...

  6. codeforces div2 677 D

    http://codeforces.com/problemset/problem/677/D 题目大意: 给你一个n*m的图,上面有p种钥匙(p<=n*m),每种钥匙至少有一个,期初所有为1的钥 ...

  7. 关于日期条件查询的sql 代码

    daysqhql = "select sum(c.casenum) from domain.Case c where" + " convert(varchar(10),c ...

  8. 使用JAVA NIO实现的UDP client和server

    //////////////////////////////////////////////////////////////////////////////////////////////////// ...

  9. char*赋值在常量区,不可以修改

    char*赋值在常量区,不可以修改,要想修改,用数组. char* = "abc";*(pCh+1) = 'k';//编译正常,运行报错. char pCh[] = "a ...

  10. cc2530串口通信流程

    //串口发送接收流程 main: //主函数 ->osal_init_system(); //操作系统初始化 ->osalInitTasks(); //任务初始化 -->ZDApp_ ...