这里要转一篇文章,写的简单易懂,这篇文章更像是C++混合Java代码混合编程;
但是要注意,这个方法不是用JNI方法,网上有很多Java调用C++的方法,一般是用JNI方法,Java应用编译好的动态链接库,JNI的本质就是Java应用本地动态链接库的过程方法;
JNI允许您从本机代码内调用 Java 类方法。
要做到这一点,通常必须使用 Invocation API 在本机代码内创建和初始化一个 JVM。
下列是您可能决定从 C/C++ 代码调用Java 代码的典型情况:
1.希望实现的这部分代码是平台无关的,它将用于跨多种平台使用的功能。
2.需要在本机应用程序中访问用 Java 语言编写的代码或代码库。
3.希望从本机代码利用标准 Java 类库。
从C/C++ 程序调用 Java 代码的四个步骤:1.编写 Java 代码。
这个步骤包含编写一个或多个 Java 类,这些类实现(或调用其它方法实现)您想要访问的功能。
2.编译 Java 代码。
在能够使用这些 Java 类之前,必须成功地将它们编译成字节码。
3.编写 C/C++ 代码。
这个代码将创建和实例化 JVM,并调用正确的 Java 方法。
4.运行本机 C/C++ 应用程序。
将运行应用程序以查看它是否正常工作。我们还将讨论一些用于处理常见错误的技巧。步骤 1:编写Java 代码
我们从编写一个或多个 Java 源代码文件开始,这些文件将实现我们想要本机 C/C++ 代码使用的功能。
下面显示了一个 Java 代码示例JNI_cCalljava_test.java:
- package test;
- public class JNI_cCalljava_test {
- public static int intMethod(int n) {
- return n*n;
- }
- public static boolean booleanMethod(boolean bool) {
- return !bool;
- }
- }
注:JNI_cCalljava_test.java 实现了两个 static Java 方法:intMethod(intn) 和 booleanMethod(boolean bool)(分别在第 3 行和第 7 行)。static方法是一种不需要与对象实例关联的类方法。调用 static方法要更容易些,因为不必实例化对象来调用它们。
步骤 2:编译Java 代码
接下来,我们将 Java 代码编译成字节码。
完成这一步的方法之一是使用随SDK 一起提供的Java 编译器 javac。使用的命令是:
JNI_cCalljava_test.java
或者直接在eclipose中编写保存即可
步骤 3:编写 C/C++ 代码
即使是在本机应用程序中运行,所有 Java 字节码也必须在 JVM 中执行。
因此 C/C++ 应用程序必须包含用来创建和初始化 JVM 的调用。
为了方便我们,SDK 包含了作为共享库文件(jvm.dll 或 jvm.so)的 JVM,这个库文件可以嵌入到本机应用程序中。
带有嵌入式 JVM的 C 应用程序:
- #include <jni.h>
- //jni.h文件包含在 C 代码中所需要的 JNI 的所有类型和函数定义
- #ifdef _WIN32
- #define PATH_SEPARATOR ';'
- #else
- #define PATH_SEPARATOR ':'
- #endif
- //1.包括准备本机应用程序以处理 Java 代码
- //2.将 JVM 嵌入本机应用程序
- //3.然后从该应用程序内找到并调用 Java 方法。
- int main()
- {
- /*
- 接下来,声明所有希望在程序中使用的变量。
- JavaVMOption options[] 具有用于 JVM 的各种选项设置。
- 当声明变量时,确保所声明的JavaVMOption options[] 数组足够大,以便能容纳您希望使用的所有选项。
- 在本例中,我们使用的唯一选项就是类路径选项。
- 因为在本示例中,我们所有的文件都在同一目录中,所以将类路径设置成当前目录。
- 可以设置类路径,使它指向任何您希望使用的目录结构。*/
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
- /*JNIEnv *env 表示 JNI 执行环境。
- JavaVM jvm 是指向 JVM 的指针,我们主要使用这个指针来创建、初始化和销毁 JVM。
- JavaVMInitArgs vm_args 表示可以用来初始化 JVM 的各种 JVM 参数。*/
- long status;
- jclass cls;
- jmethodID mid;
- jint square;
- jboolean not;
- /*avaVMInitArgs 结构表示用于 JVM 的初始化参数。
- 在执行 Java 代码之前,可以使用这些参数来定制运行时环境。
- 正如您所见,这些选项是一个参数,而 Java 版本是另一个参数。
- 按如下所示设置了这些参数:*/
- /*为 JVM 设置类路径,以使它能找到所需要的 Java 类。
- 在这个特定示例中,因为 Sample2.class 和Sample2.exe 都位于同一目录中,所以将类路径设置成当前目录。
- 我们用来为 Sample2.c 设置类路径的代码如下所示:*/
- options[0].optionString = "-Djava.class.path=.";
- memset(&vm_args, 0, sizeof(vm_args));
- vm_args.version = JNI_VERSION_1_2;
- vm_args.nOptions = 1;
- vm_args.options = options;
- /*创建 JVM
- 处理完所有设置之后,现在就准备创建 JVM 了。先从调用方法开始
- 如果成功,则这个方法返回零,否则,如果无法创建 JVM,则返回JNI_ERR。*/
- status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
- if (status != JNI_ERR)
- {
- /*
- 查找并装入 Java 类
- 一旦创建了 JVM 之后,就可以准备开始在本机应用程序中运行 Java 代码。
- 首先,需要使用FindClass() 函数查找并装入 Java 类,如下所示:
- cls 变量存储执行FindClass() 函数后的结果,如果找到该类,则 cls 变量表示该Java 类的句柄,
- 如果不能找到该类,则 cls 将为零。
- */
- cls = (*env)->FindClass(env, "test/JNI_cCalljava_test");
- printf("test1,cls=%d...\n",cls);
- if(cls !=0)
- {
- /*
- 查找 Java 方法
- 接下来,我们希望用 GetStaticMethodID() 函数在该类中查找某个方法。
- 我们希望查找方法 intMethod,它接收一个 int 参数并返回一个 int。
- 以下是查找 intMethod 的代码:
- */
- mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");
- /*
- mid 变量存储执行 GetStaticMethodID() 函数后的结果。
- 如果找到了该方法,则 mid 变量表示该方法的句柄。
- 如果不能找到该方法,则mid 将为零。
- */
- if(mid !=0)
- {
- /*CallStaticIntMethod() 方法接受 cls(表示类)、mid(表示方法)以及用于该方法一个或多个参数。
- 在本例中参数是 int 5。*/
- square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
- printf("Result of intMethod: %d\n", square);
- }
- mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z");
- if(mid !=0)
- {
- not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1);
- printf("Result of booleanMethod: %d\n", not);
- }
- }
- (*jvm)->DestroyJavaVM(jvm);
- return 0;
- }
- else
- return -1;
- }
带有嵌入式 JVM的 C++ 应用程序
- #include <jni.h>
- #ifdef _WIN32
- #define PATH_SEPARATOR ';'
- #else
- #define PATH_SEPARATOR ':'
- #endif
- int main()
- {
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
- long status;
- jclass cls;
- jmethodID mid;
- jint square;
- jboolean not;
- options[0].optionString = "-Djava.class.path=.";
- memset(&vm_args, 0, sizeof(vm_args));
- vm_args.version = JNI_VERSION_1_2;
- vm_args.nOptions = 1;
- vm_args.options = options;
- status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
- if (status != JNI_ERR)
- {
- cls = env->FindClass("Sample2");
- if(cls !=0)
- {
- mid = env->GetStaticMethodID(cls, "intMethod", "(I)I");
- if(mid !=0)
- {
- square = env->CallStaticIntMethod(cls, mid, 5);
- printf("Result of intMethod: %d\n", square);
- }
- mid = env->GetStaticMethodID(cls, "booleanMethod", "(Z)Z")
- if(mid !=0)
- {
- not = env->CallStaticBooleanMethod(cls, mid, 1);
- printf("Result of booleanMethod: %d\n", not);
- }
- }
- jvm->DestroyJavaVM();
- return 0;
- }
- else
- return -1;
- }
C 和 C++ 实现的比较
C 和C++ 代码几乎相同;唯一的差异在于用来访问 JNI 函数的方法。
在 C 中,为了取出函数指针所引用的值,JNI 函数调用前要加一个(*env)-> 前缀。
在 C++ 中,JNIEnv类拥有处理函数指针查找的内联成员函数。
因此,虽然这两行代码访问同一函数,但每种语言都有各自的语法,如下所示。
C 语法:cls = (*env)->FindClass(env, "Sample2");
C++ 语法:
cls = env->FindClass("Sample2");
mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");
C++ 语法:
mid = env->GetStaticMethodID(cls, "intMethod", "(I)I");C 语法:
square = env->CallStaticIntMethod(cls, mid, 5);
C++ 语法:
square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
C 语法:
(*jvm)->DestroyJavaVM(jvm);
C++ 语法:
jvm->DestroyJavaVM();
步骤 4:运行应用程序
现在准备运行这个 C 应用程序,并确保代码正常工作。当运行 Sample2.exe 时,应该可以得到如下结果:
windows:使用vc6.0建一个普通的C语言工程
头文件路径设置同Java调用C语言里的设置
连接时需要jvm.lib支持
这里需要右击建立的工程,单击设置(Settings),link选项栏将数据库路径添加进来
C:"\Program Files"\Java\jdk1.6.0_10\lib\jvm.lib
在下面的project options中加入以上语句,用空格隔开,programe Files用双引号引起来
运行时需要jvm.dll动态库的支持,需要在系统环境变量中增加以下路径:
C:\Program Files\Java\jdk1.6.0_10\jre\bin\server
方法:右击 我的电脑-》属性-》高级-》环境变量-》PATH 编辑,在原有环境变量的基础上增加以上路径,注意用";"号隔开
将eclipose生成的java代码放在JNI_cCalljava_test.exe同目录下(注意按照把报名文件夹也拷过去)
E:\Sample2>JNI_cCalljava_test.exeResult of intMethod: 25
Result of booleanMethod: 0
做过JAVA开发的朋友都知道,java开发的applet在每次启动时都会弹出一个DOS窗口,这个控制窗口让你开发的非常出色的界面失色不少。那怎么出除这个启动窗口呢?其实很简单,大家可能都用过eclipse,它就是java开发的,而它启动时跟VC、DEPHI做的一样不会弹出那个可恶的DOS窗口, 这是因为它使用了JNI技术,可以通过本地GUI程序直接启动JAVA程序,这样让你的程序更加专业。
JNI(java native interface)它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。同时JNI提供的一整套的API,允许将Java虚拟机直接嵌入到本地的应用程序中。JNI大家用的比较多的就是C/C++按照JNI标准进行封闭成动态库,被JAVA程序调用。这里我就不多说了。
C/C++要调用JAVA程序,必须先加载JAVA虚拟机,由JAVA虚拟机解释执行class文件。为了初始化JAVA虚拟机,JNI提供了一系列的接口函数,通过这些函数方便地加载虚拟机到内存中。
1.加载虚拟机:
函数:jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void args);
参数说明:JavaVM **pvm JAVA虚拟机指针,第二个参数JNIEnv *env是贯穿整个调用过程的一个参数,因为后面的所有函数都需要这个参数,需注意的是第三个参数,在jdk1.1与1.2版本有些不同,在JDK 1.1中第三个参数总是指向一个结构JDK1_ 1InitArgs,这个结构无法完全在所有版本的虚拟机中进行无缝移植。所以为了保证可移植性,建议使用jdk1.2的方法加载虚拟机。
2.获取指定对象的类定义:
有两种方法可获得类定义,一是在已知类名的情况使用FindClass来获取;二是通过对象直接得到类定义GetObjectClass
3.获取要调用的方法:
获得非静态方法:
jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
获得静态方法:
jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass class, const char *name, const char *sig);
参数说明:JNIEnv *env初始化是得到的JNI环境;jclass class前面已获取到的类定义;const char *name方法名;const char *sig方法的定义,我们知道JAVA支持多态,同名方法通过第四个参数来定位得到具体的方法,那么这个参数怎么填呢?我们如何能知道应该用什么样的字符串来表示?在JDK已经准备好了一个反编译工具,通过这个工具我们就可察看类中的属性及方法的定义,如何做?很简单,假如我们在之前建立了一个MyTest.java,通过javac已经编译好此程序,MyTest.java如下:
4.调用JAVA类方法:
函数:CallObjectMethod(JNIEnv *env, jobject obj, jmethodID mid);
函数:CallStaticObjectMethod((JNIEnv *env, jobject obj, jmethodID mid);
5.获得类属性的定义:
jfieldID (JNICALL *GetFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
静态属性:
jfieldID (JNICALL *GetStaticFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
6.数组处理:
要创建数组首先要知道类型及长度,JNI提供了一系列的数组类型及操作的函数如:
NewIntArray、NewLongArray、NewShortArray、NewFloatArray、NewDoubleArray、NewBooleanArray、NewStringUTF、NewCharArray、NewByteArray、NewString,访问通过GetBooleanArrayElements、GetIntArrayElements等函数。
7.异常:
由于调用了Java的方法,会产生异常。这些异常在C/C++中无法通过本身的异常处理机制来捕捉到,但可以通过JNI一些函数来获取Java中抛出的异常信息。
8.多线程调用
我们知道JAVA是非常消耗内存的,我们希望在多线程中能共享一个JVM虚拟机,真正消耗大量系统资源的是JAVA虚拟机jvm而不是虚拟机环境env,jvm是允许多个线程访问的,但是虚拟机环境只能被创建它本身的线程所访问,而且每个线程必须创建自己的虚拟机环境env。JNI提供了两个函数:AttachCurrentThread和DetachCurrentThread。便于子线程创建自己的虚拟机环境。
- /**
- * 类MyTest为了测试JNI使用C/C++调用JAVA
- * @author liudong
- */
- public class MyTest {
- /*
- * 测试如何访问静态的基本类型属性
- */
- /*
- *演示对象型属性
- */
- public String helloword;
- public MyTest() {
- this("JNI演示类");
- }
- /**
- * 构造函数
- */
- public MyTest(String msg) {
- System.out.println("构造函数:" + msg);
- this.helloword = msg;
- }
- /**
- * 该方法演示如何调用动态方
- */
- public String HelloWord() {
- System.out.println("JAVA-CLASS:MyTest method:HelloWord:" + helloword);
- return helloword;
- }
- /**
- * 演示异常的捕捉
- */
- public void throwExcp() throws IllegalAccessException {
- throw new IllegalAccessException("exception occur.");
- }
- }
那么在命令行运行:javap -s -p MyTest ,你将看到如下输出:
Compiled from "MyTest.java"
public class MyTest extends java.lang.Object{
public java.lang.String helloword;
Signature: Ljava/lang/String;
public MyTest();
Signature: ()V
public MyTest(java.lang.String);
Signature: (Ljava/lang/String;)V
public java.lang.String HelloWord();
Signature: ()Ljava/lang/String;
public void throwExcp() throws java.lang.IllegalAccessException;
Signature: ()V
}
C代码testjava.c:
- #include <jni.h>
- #include <assert.h>
- /*C字符串转JNI字符串*/
- jstring stoJstring(JNIEnv* env, const char* pat)
- {
- jclass strClass = (*env)->FindClass(env,"Ljava/lang/String;");
- jmethodID ctorID = (*env)->GetMethodID(env,strClass, "<init>", "([BLjava/lang/String;)V");
- jbyteArray bytes = (*env)->NewByteArray(env,strlen(pat));
- (*env)->SetByteArrayRegion(env,bytes, 0, strlen(pat), (jbyte*)pat);
- jstring encoding = (*env)->NewStringUTF(env,"utf-8");
- return (jstring)(*env)->NewObject(env,strClass, ctorID, bytes, encoding);
- }
- /*JNI字符串转C字符串*/
- char* jstringTostring(JNIEnv* env, jstring jstr)
- {
- char* rtn = NULL;
- jclass clsstring = (*env)->FindClass(env,"java/lang/String");
- jstring strencode = (*env)->NewStringUTF(env,"utf-8");
- jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
- jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode);
- jsize alen = (*env)->GetArrayLength(env,barr);
- jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE);
- if (alen > 0)
- {
- rtn = (char*)malloc(alen + 1);
- memcpy(rtn, ba, alen);
- rtn[alen] = 0;
- }
- (*env)->ReleaseByteArrayElements(env,barr, ba, 0);
- return rtn;
- }
- int main() {
- int res;
- JavaVM *jvm;
- JNIEnv *env;
- JavaVMInitArgs vm_args;
- JavaVMOption options[3];
- /*设置初始化参数*/
- options[0].optionString = "-Djava.compiler=NONE";
- options[1].optionString = "-Djava.class.path=.";
- options[2].optionString = "-verbose:jni"; //用于跟踪运行时的信息
- /*版本号设置不能漏*/
- vm_args.version=JNI_VERSION_1_2;//jdk版本1.2
- vm_args.nOptions = 3;
- vm_args.options = options;
- vm_args.ignoreUnrecognized = JNI_TRUE;
- res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
- if (res < 0) {
- fprintf(stderr, "Can't create Java VM/n");
- exit(1);
- }
- /*获取实例的类定义*/
- jmethodID mid;
- jclass cls = (*env)->FindClass(env, "MyTest");
- if (cls == 0)
- {
- fprintf(stderr, "FindClass failed/n");
- (*jvm)->DestroyJavaVM(jvm);
- fprintf(stdout, "Java VM destory./n");
- return;
- }
- /*获取构造函数,用于创建对象*/
- /***1.1可用""作为构造函数, 1.2用"<init>"参数中不能有空格"(Ljava/lang/String;)V"*/
- mid = (*env)->GetMethodID(env,cls,"<init>","(Ljava/lang/String;)V");
- assert (0 != mid);
- fprintf(stderr, "GetMethodID /n");
- if (mid == 0)
- {
- fprintf(stderr, "GetMethodID failed/n");
- (*jvm)->DestroyJavaVM(jvm);
- fprintf(stdout, "Java VM destory./n");
- return;
- }
- fprintf(stderr, "GetMethodID OK/n");
- /*创建对象*/
- const char str[]="this is a test for c call java";
- jobject obj = (*env)->NewObject (env, cls, mid, stoJstring(env, str));
- //jobject obj = (*env)->NewObject(env, cls, mid, 0);
- /*如果mid为0表示获取方法定义失败*/
- fprintf(stderr, "NewObject OK/n");
- /*获取方法ID*/
- mid=(*env)->GetMethodID(env,cls,"HelloWord","()Ljava/lang/String;");
- if (mid == 0)
- {
- fprintf(stderr, "GetMethodID 'HelloWord' failed/n");
- (*jvm)->DestroyJavaVM(jvm);
- fprintf(stdout, "Java VM destory./n");
- return;
- }
- fprintf(stderr, "GetMethodID 'HelloWord' OK/n");
- /*调用动态方法*/
- jstring msg = (*env)-> CallObjectMethod(env, obj, mid);
- /*
- 如果该方法是静态的方法那只需要将最后一句代码改为以下写法即可:
- jstring msg = (*env)-> CallStaticObjectMethod(env, cls, mid);
- */
- /*捕捉异常*/
- if ((*env)->ExceptionOccurred(env))
- {
- (*env)->ExceptionDescribe(env);
- return ;
- }
- /*销毁JAVA虚拟机*/
- (*jvm)->DestroyJavaVM(jvm);
- fprintf(stdout, "Java VM destory./n");
- }
编译:
在linux下:cc -o testjava testjava.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -L${JAVA_HOME}/jre/lib/i386/client -ljvm
运行结果:
GetMethodID
GetMethodID OK
[Dynamic-linking native method java.io.FileOutputStream.writeBytes ... JNI]
MyTest:this is a test for c call java
NewObject OK
GetMethodID 'HelloWord' OK
JAVA-CLASS:MyTest method:HelloWord:this is a test for c call java
Java VM destory.
上面是一个非常简单的例子,你还可以访问类属性,访问静态方法。这样在C中就能跟JAVA里一样调用它的类、方法、访问它的属性。怎么样,很简单吧?
:)
C++调用Java类
JNI之C++调用Java类
——java.lang.String
为什么要用C++调用Java类?很难回答,写着文章只是觉得JNI很有意思。于是开始编写一段使用VC++在Windows系统里调用java的String类,在C++里调用String类内的一些方法。
JNI已经被开发了很多年,而在我2年多的Java编程时间里从来没有接触过。直到最近研究JVM实现原理才注意到JNI。 JNI既Java Native Interface,Native这个词我见过我认为最恰当的翻译就是原生。原生的意思就是来自系统自己的,原汁原味的东西,例如Win32 API。Java类需要在虚拟机上运行,也就不是原生的,同样.NET Framework也不是原生的。JNI也就是Java原生接口。关于JNI的规范,以及为什么要使用它,它能做些什么,都在http://java.sun.com/j2se/1.4.2/docs/guide/jni/spec/jniTOC.html里记述着。
JNI是规范,它规定了虚拟机的接口,而把具体的实现留给开发者。
JVM的实现不是唯一的,目前存在很多种Java虚拟机,Sun Hotspot,IBM JDK,还有HP的,Kaffe等等。最流行的就是Sun的Hotspot,最复杂的就是IBM JDK,这是IBM的一贯作风。本文不讨论JVM的实现,只关注JNI。如果您安装了Sun的JDK,您就能在[JAVA_HOME]\include目录下找到jni.h。这个头文件就是虚拟机的唯一接口,你可以调用它声明的函数创建一个JVM。
在说明C++调用Java类之前,我想先演示一下如果编写JavaNative Method。
1.编写带有Native方法的Java类
packageorg.colimas.jni.test;
public class JniTest{
static {System.loadLibrary("JniTestImpl"); } //JVM调用JniTestImpl.dll
public JniTest(){
}
//原生方法
public native void print(String str);
/**
*@param args
*/
public static void main(String[] args) {
JniTest test=new JniTest();
test.print("hello JVM");//调用原生方法
}
}
2.使用javah生成c语言头文件。
javah -jniorg.colimas.jni.test.JniTest
目录里多了一个org_colimas_jni_test_JniTest.h文件,打开文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_colimas_jni_test_JniTest */
#ifndef_Included_org_colimas_jni_test_JniTest
#define_Included_org_colimas_jni_test_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_colimas_jni_test_JniTest
* Method: print
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_org_colimas_jni_test_JniTest_print
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
其中的Java_org_colimas_jni_test_JniTest_print就是JniTest类里面的print原生方法的C语言声明。
3.编写C代码实现原生方法print
#include <jni.h>
#include "org_colimas_jni_test_JniTest.h" //javah生成的头文件
#include <stdio.h>
JNIEXPORT void JNICALL Java_org_colimas_jni_test_JniTest_print
(JNIEnv *env, jobjectobject,jstring str)
{
//获得字符串
const char *txt=(*env)->GetStringUTFChars(env,str,0);
printf("%s\n",txt); //打印到控制台
return;
}
参数JNIEnv *env,是JNI里最重要的变量。Java.exe创建JVM,之后JVM生成一个env,该env相当于JVM内的Session,可以完成创建Java对象,调用类方法,获得类的属性等等。
在这里env将方法的参数Str从JNI的jstring类型转换为常数char数组。
4.编译
cl /Ic:\j2sdk1.4.2_10\include/Ic:\j2sdk1.4.2_10\include\win32 /c JniTestImpl.c
5.连接为DLL
link /dllJniTestImpl.obj
6.设置PATH
setPATH=C:\MyProject\Colimas\CD\JNI\MyJNI;%PATH%
7.运行
javaorg.colimas.jni.test.JniTest
返回结果
hello JVM
结束
以上是实现Java原生方法的开发过程,下面进入正题,使用C++调用Java的java.lang.String类。
1. Object类出创建JVM。
使用Java类之前必须要创建JVM环境。JDK由java.exe来完成。本文有Object类的静态方法BeginJVM来创建,用EndJVM来关闭。
创建JVM之后会在创建2个变量,分别是JNIEnv* env和JavaVM* jvm,JNIEnv上文已经说明,JavaVM,顾名思义,代表Java虚拟机,用它来关闭JVM。
Object类的头文件
#include"jni.h"
class Object
{
public:
static bool BeginJVM();
static bool EndJVM();
Object();
virtual ~Object();
protected:
static JNIEnv* env;
static JavaVM* jvm;
};
object.cpp代码
#include"stdafx.h"
#include"JavaClasses.h"
#include"Object.h"
Object::Object()
{}
Object::~Object()
{}
JNIEnv*Object::env=NULL;
JavaVM*Object::jvm=NULL;
//创建JVM
boolObject::BeginJVM()
{
JavaVMOption options[3];
JavaVMInitArgs vm_args;
//各种参数
options[0].optionString="-Xmx128m";
options[1].optionString="-Verbose:gc";
options[2].optionString="-Djava.class.path=.";
vm_args.version=JNI_VERSION_1_2;
vm_args.options=options;
vm_args.nOptions=3;
//创建JVM,获得jvm和env
int res = JNI_CreateJavaVM(&jvm,(void**)&env, &vm_args);
return true;
}
bool Object::EndJVM()
{
//关闭JVM
jvm->DestroyJavaVM();
return true;
}
2. C++的String类调用java.lang.String类方法
编写C++版的String类,调用java String类方法。调用的方法如下:
String replaceAll(String regex, String replacement);
boolean endsWith(String str);
int indexOf(String str);
int compareTo(String anotherString);
char charAt(int i);
String的头文件:
class String :public Object
{
public:
//与要调用的Java方法名一致。
const char * replaceAll(char *regex,char*replacement);
bool endsWith(char * str);
int indexOf(char * str);
int compareTo(char *anotherString);
char charAt(int i);
String(char *str);
virtual ~String();
};
实现:
#include"stdafx.h"
#include"String.h"
#include"jni.h"
using namespace std;
jclass clazz; //全局变量,用来传递class
jobject object; //全局变量,用来传递object
String::String(char*str)
{
jstring jstr;
if (Object::env ==NULL)
{
cout << "JVM is notcreated" << endl;
exit(-1);
}
//获得java.lang.String类
clazz=Object::env->FindClass("java/lang/String");
if (clazz ==0 ){
cout << "Class is notfound" << endl;
exit(-1);
}
//获得String(String str)构造体
jmethodID mid=Object::env->GetMethodID(clazz,"<init>","(Ljava/lang/String;)V");
if (mid==0){
cerr<< "GetMethodIDError for class" << endl;
exit(-1);
}
//江字符串封装为jstring。
jstr = Object::env->NewStringUTF(str);
if (jstr == 0) {
cerr << "Out ofmemory" <<endl;
exit(-1);
}
cout << "invoking method"<< endl;
//创建一个java.lang.String对象。
object=Object::env->NewObject(clazz,mid,jstr);
}
String::~String()
{}
char String::charAt(inti)
{
if (Object::env ==NULL)
{
cout << "JVM is notcreated" << endl;
exit(-1);
}
if (clazz ==0 ){
cout << "Class is notfound" << endl;
exit(-1);
}
if (object ==0 ){
cout << "String objectis not created" << endl;
exit(-1);
}
jmethodID mid;
//获得charAt方法,(I)C表示 参数为int型,返回char型。详细参见JNI规范
mid=Object::env->GetMethodID(clazz,"charAt", "(I)C");
if (mid==0){
cerr<< "GetMethodIDError for class" << endl;
exit(-1);
}
jint ji=i;
cout << "invoking method"<< endl;
//调用charAt
jchar z=Object::env->CallCharMethod(object,mid,i);
//返回结果。
return z;
}
intString::compareTo(char *anotherString)
{
if (Object::env ==NULL)
{
cout << "JVM is notcreated" << endl;
exit(-1);
}
if (clazz ==0 ){
cout << "Class is notfound" << endl;
exit(-1);
}
if (object ==0 ){
cout << "String objectis not created" << endl;
exit(-1);
}
jmethodID mid;
//(Ljava/lang/String;)I表示参数为java.lang.String,返回int
mid=Object::env->GetMethodID(clazz,"compareTo","(Ljava/lang/String;)I");
if (mid==0){
cerr<< "GetMethodIDError for class" << endl;
exit(-1);
}
jstring jstr =Object::env->NewStringUTF(anotherString);
cout << "invoking method"<< endl;
//调用方法
jintz=Object::env->CallIntMethod(object,mid,jstr);
//返回结果
return z;
}
intString::indexOf(char *str)
{
if (Object::env ==NULL)
{
cout << "JVM is notcreated" << endl;
exit(-1);
}
if (clazz ==0 ){
cout << "Class is notfound" << endl;
exit(-1);
}
if (object ==0 ){
cout << "String objectis not created" << endl;
exit(-1);
}
jmethodID mid;
mid=Object::env->GetMethodID(clazz,"indexOf","(Ljava/lang/String;)I");
if (mid==0){
cerr<< "GetMethodIDError for class" << endl;
exit(-1);
}
jstring jstr =Object::env->NewStringUTF(str);
cout << "invoking method"<< endl;
jint z=Object::env->CallIntMethod(object,mid,jstr);
return z;
}
boolString::endsWith(char *str)
{
if (Object::env ==NULL)
{
cout << "JVM is notcreated" << endl;
exit(-1);
}
if (clazz ==0 ){
cout << "Class is notfound" << endl;
exit(-1);
}
if (object ==0 ){
cout << "String objectis not created" << endl;
exit(-1);
}
jmethodID mid;
mid=Object::env->GetMethodID(clazz,"endsWith","(Ljava/lang/String;)Z");
if (mid==0){
cerr<< "GetMethodIDError for class" << endl;
exit(-1);
}
jstring jstr =Object::env->NewStringUTF(str);
cout << "invoking method"<< endl;
boolz=Object::env->CallBooleanMethod(object,mid,jstr);
return z;
}
const char *String::replaceAll(char *regex, char *replacement)
{
if (Object::env ==NULL)
{
cout << "JVM is notcreated" << endl;
exit(-1);
}
if (clazz ==0 ){
cout << "Class is notfound" << endl;
exit(-1);
}
if (object ==0 ){
cout << "String objectis not created" << endl;
exit(-1);
}
jmethodID mid;
mid=Object::env->GetMethodID(clazz,"replaceAll","(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
if (mid==0){
cerr<< "GetMethodIDError for class" << endl;
exit(-1);
}
jvalue array[2];
jstring jreg =Object::env->NewStringUTF(regex);
jstring jstr = Object::env->NewStringUTF(replacement);
array[0].l=jreg;
array[1].l=jstr;
cout << "invoking method"<< endl;
//传入参数,调用replaceAll方法
jobjectz=Object::env->CallObjectMethodA(object,mid,array);
const char*result=Object::env->GetStringUTFChars((jstring)z, 0);
return (const char *)result;
}
3.测试
编写测试代码
using namespace std;
int _tmain(int argc,TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
if (!AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0))
{
cerr << _T("Fatal Error:MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
//创建JVM
Object::BeginJVM();
String test("hello");
//调用replaceAll
const char*result=test.replaceAll("l","z");
//返回结果
cout<< result <<endl;
//关闭JVM
Object::EndJVM();
}
return nRetCode;
}
4.运行
编译需要 jni.h和jvm.lib文件。
jni.h在[JAVA_HOME]\include
jvm.lib在[JAVA_HOME]\lib
运行需要jvm.dll
jvm.dll在[JAVA_HOME]\jre\bin\client
运行结果如下:
invoking method
invoking method
hezzo
Press any key tocontinue
尽管本文的代码很有意思,但我还没有想到有什么价值,以及应用到实际项目中的理由。
为什么要自己写C++程序加载Java虚拟机
通常情况下,我们完成了Java类的开发后,都是通过脚本调用Java的java.exe或者javaw.exe来启动Java虚拟机的,但在某些情况下,我们需要用C++程序来自行加载Java虚拟机运行我们的java程序,例如:
1、 在windows下面,你想把java程序做成一个后台服务,就像tomcat那样,这样就可以让windows操作系统帮你管理java程序的启动和停止了。
4<span style="font-size:12.0pt;line-height:150%;font-family:"Times New Roman" ''","serif";mso-ascii-font-family:="" "times="" new="" roman";mso-fareast-font-family:"times="" roman\0027\0027\0027";="" mso-hansi-font-family:"times="" roman";color:#666666;mso-font-kerning:0pt'="">、 你在开发象IE这样的程序,在必要的时候才启动虚拟机运行一段Java程序
本文就教你如何做到这一点。
C++加载虚拟机的两种方法比较
加载虚拟机可以采用以下两种方法:
1、 用创建子进程的方法调用java.exe来启动,在windows下面可以用CreateProcess方法,在Linux下面可以用execvp方法,调用的方式和用脚本调用差不多,这种方法比较简单我就不详细介绍了,详细的方式可以参考开源项目 Java Service Wrapper。用创建子进程的方式用以下优点:
1) 开发简单,兼容性好,对于java虚拟机的不同版本调用方式都是一样的
2) 创建出来的java进程和C++程序的进程分离,不会相互影响,你不会因为C程序出现什么内存错误崩溃导致java进程也一起完蛋(当然分离进程也带来了其他问题,详见我后面的分析)。
但是我不太喜欢这种方法,因为有以下缺点:
1) 生成分离的java进程后,在系统里会看到另外一个java.exe的进程,当启动了很多这样的进程时,你会看到很多名称为java的进程,你无法区分哪个是哪个。
2) 生成了分离的Java 进程后,你就不好对它进行控制,比如说要关闭它,如果直接kill掉java进程,可能java进程里面一些关闭时需要做的清理工作还来不及做。如果想让java程序执行清理工作再退出,你就需要建立和java程序之间的通讯机制,以向java程序发出退出的消息,这通常还需要在java程序里面加载专门的类,以及需要java程序的开发人员进行相关的接口开发工作。
2、 加载java虚拟机的另外一种方法是加载jvm的动态库,并通过动态库的接口来在本进程内启动java虚拟机。我更加喜欢这种方法,因为这种方法可以带来很多好处:
1) 你可以更好地对java虚拟机进行有效地控制,java程序不需要加载专门的类库,你只需要调用不同的java类方法就可以控制java程序的启动、停止等操作
2) 不会产生额外的java进程,在系统进程中进程名称是你C++主程序的名字,你可以在系统进程列表中很清楚地区分每个进程
当然这种方法也有缺点,但我认为不是大问题:
1) 由于java虚拟机在进程内部加载,如果主程序写得不好而崩溃会导致java程序也一起终止
2) 动态库接口得开发要稍微难一点,需要你了解jvm动态库的接口,不过待会儿你就会看到,这也不是很困难的事情
3) jdk不同版本的接口稍微有一点区别,以后可能也会发生变化,你可能得对不同的jdk版本修改一下C程序,但相对来说jvm的兼容性应该是可以信任的
加载jvm动态库的方法介绍
下面就详细介绍一下用C++程序加载动态库的方法,我将分别针对windows平台和linux/unix平台分别进行介绍:
windows平台的加载方法
windows平台下jdk的java虚拟机动态库为jvm.dll,位于:
%JAVA_HOME%/jre/bin/client/jvm.dll
%JAVA_HOME%/jre/bin/server/jvm.dll
Jvm动态库有client和server两个版本,分别针对桌面应用和服务器应用做了相应的优化,client版本加载速度较快,server版本加载速度较慢但运行起来较快。
虚拟机加载可按照以下步骤进行:
1) 装载jvm动态库
2) 查找JNI_CreateJavaVM接口
3) 设置JVM启动参数,调用JNI_CreateJavaVM接口启动虚拟机
4) 查找启动类,设置命令行参数,设置窗口输出重导向文件
5) 调用启动类的启动方法启动java程序
6) 要停止Java程序运行时,调用java类的停止方法即可
下面的示例代码就说明了一个简单的加载过程
typedef jint(JNICALL *JNICREATEPROC)(JavaVM **, void **, void *);
bool setStream(JNIEnv *env, const char* pszFileName, const char*pszMethod);
//启动java虚拟机方法
bool startJvm()
{
//jvm动态库的路径
const char szJvmPath[] ="c:\\jdk1.5.0_01\\jre\\bin\\server\\jvm.dll";
//java 虚拟机的启动参数,每个参数写一项,不能合在一起写
int nOptionCount = 2;
JavaVMOption options[2];
options[1].optionString ="-Xmx256M";
//设置classpath
options[0].optionString ="-Djava.class.path=./Test.jar;./Test1.jar";
JavaVMInitArgs vm_args;
vm_args.version =JNI_VERSION_1_4;
vm_args.options = options;
vm_args.nOptions = nOptionCount;
vm_args.ignoreUnrecognized =JNI_TRUE;
//启动类,注意分割符是/,例如启动类test.JTest应该写成test/JTest
const char szStartClass[] ="test/JTest";
//启动方法,通常是main函数,你也可以设定成其他函数
const char szStartMethod[] ="main";
//重导向文件
const char szStdoutFileName ="stdout.txt";
const char szStderrFileName ="stderr.txt";
//java程序的命令行参数
int nParamCount = 2;
const char *szParams[2] ={"arg1","arg2"};
//加载JVM。
HINSTANCE jvmDll =LoadLibrary(szJvmPath);
if (jvmDll == NULL)
{
printf("加载JVM动态库错误。%l",::GetLastError());
returnfalse;
}
//查找JNI_CreateJavaVM过程。
JNICREATEPROC jvmCreateProc =(JNICREATEPROC)GetProcAddress(jvmDll, "JNI_CreateJavaVM");
if (jvmCreateProc == NULL)
{
FreeLibrary(jvmDll);
printf("查找JNI_CreateJavaVM过程错误。%l",::GetLastError());
returnfalse;
}
//创建JVM。
JNIEnv *env;
jint r =(jvmCreateProc)(&jvm, (void **)&env, &vm_args);
if (r < 0 || jvm == NULL ||env == NULL)
{
FreeLibrary(jvmDll);
printf("创建JVM发生错误。");
returnfalse;
}
//重导向stdout, stderr到输出文件
if (!setStream(env,szStdoutFileName, "setOut"))
{
printf("设置stdout输出文件失败");
return false;
}
if (!setStream(env,szStderrFileName, "setErr"))
{
printf("设置stderr输出文件失败");
return false;
}
//加载启动类。
jclass serviceClass =env->FindClass(szStartClass);
if (env->ExceptionCheck() == JNI_TRUE ||serviceClass == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
FreeLibrary(jvmDll);
printf("加载启动类失败。");
returnfalse;
}
//启动方法
jmethodID mid =env->GetStaticMethodID(serviceClass, szStartMethod ,"([Ljava/lang/String;)V");
if (env->ExceptionCheck() ==JNI_TRUE || mid == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
FreeLibrary(jvmDll);
printf("查找启动方法失败。");
returnfalse;
}
//查找String类。
jclass stringClass =env->FindClass("java/lang/String");
if (env->ExceptionCheck() ==JNI_TRUE || stringClass == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
FreeLibrary(jvmDll);
printf("查找String类失败。");
returnfalse;
}
jstring jstr;
for (int i=0; i<nParamCount;i++)
{
jstr =env->NewStringUTF(szParams[i]);
if (jstr == 0) {
printf("分配String失败\n");
if (env->ExceptionOccurred()){
env->ExceptionDescribe();
env->ExceptionClear();
}
return false;
}
env->SetObjectArrayElement(args,i, jstr);
if(env->ExceptionCheck() == JNI_TRUE)
{
printf("设置参数失败\n");
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
return false;
}
}
//调用启动类的启动方法启动Java程序
env->CallStaticVoidMethod(serviceClass, mid, parameterArray);
if (env->ExceptionCheck() ==JNI_TRUE)
{
env->ExceptionDescribe();
env->ExceptionClear();
FreeLibrary(jvmDll);
return false;
}
return true;
}
//设置输出流的方法
bool setStream(JNIEnv *env, const char* pszFileName, const char*pszMethod)
{
int pBufferSize = 1024;
char* pBuffer = newchar[pBufferSize];
//创建字符串对象。
jstring pathString =env->NewStringUTF(pszFileName);
if (env->ExceptionCheck()== JNI_TRUE || pathString == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("创建字符串失败。");
return false;
}
//查找FileOutputStream类。
jclass fileOutputStreamClass =env->FindClass("java/io/FileOutputStream");
if (env->ExceptionCheck()== JNI_TRUE || fileOutputStreamClass == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("查找FileOutputStream类失败。");
return false;
}
//查找FileOutputStream类构造方法。
jmethodIDfileOutputStreamConstructor = env->GetMethodID(fileOutputStreamClass,"<init>", "(Ljava/lang/String;)V");
if (env->ExceptionCheck()== JNI_TRUE || fileOutputStreamConstructor == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("查找FileOutputStream类构造方法失败。");
return false;
}
//创建FileOutputStream类的对象。
jobject fileOutputStream = env->NewObject(fileOutputStreamClass,fileOutputStreamConstructor, pathString);
if (env->ExceptionCheck()== JNI_TRUE || fileOutputStream == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("创建FileOutputStream类的对象失败。");
return false;
}
//查找PrintStream类。
jclass printStreamClass =env->FindClass("java/io/PrintStream");
if (env->ExceptionCheck()== JNI_TRUE || printStreamClass == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("查找PrintStream类失败。");
return false;
}
//查找PrintStream类构造方法。
jmethodID printStreamConstructor= env->GetMethodID(printStreamClass, "<init>","(Ljava/io/OutputStream;)V");
if (env->ExceptionCheck()== JNI_TRUE || printStreamConstructor == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("查找PrintStream类构造方法失败。");
return false;
}
//创建PrintStream类的对象。
jobject printStream =env->NewObject(printStreamClass, printStreamConstructor, fileOutputStream);
if (env->ExceptionCheck()== JNI_TRUE || printStream == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("创建PrintStream类的对象失败。");
return false;
}
//查找System类。
jclass systemClass =env->FindClass("java/lang/System");
if (env->ExceptionCheck()== JNI_TRUE || systemClass == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf( "查找System类失败。");
return false;
}
//查找System类设置方法。
jmethodID setStreamMethod =env->GetStaticMethodID(systemClass, pszMethod,"(Ljava/io/PrintStream;)V");
if (env->ExceptionCheck()== JNI_TRUE || setStreamMethod == NULL)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("查找System类设置方法失败。");
return false;
}
//设置System类的流。
env->CallStaticVoidMethod(systemClass,setStreamMethod, printStream);
if (env->ExceptionCheck()== JNI_TRUE)
{
env->ExceptionDescribe();
env->ExceptionClear();
printf("设置System类的流失败。");
return false;
}
}
Linux平台的加载方法
Linux平台下的虚拟机的加载方法跟windows平台基本一致,不同的地方仅在于加载动态库的方法不同,在linux平台的JDK中,jvm动态库所在的路径为
$JAVA_HOME/jre/lib/i386/client/libjvm.so
$JAVA_HOME/jre/lib/i386/server/libjvm.so
在Linux下装载动态库的示例代码如下:
#include <jni.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef void* (*JNICREATEPROC)(JavaVM **, JNIEnv **, void *);
bool startJVM() {
JNIEnv *env;
JavaVM *jvm;
jclass cls;
jmethodID mid;
jobjectArray args;
//jvm动态库的路径
const char szJvmPath[] ="/usr/jdk1.5.0_01/jre/lib/i386/server/libjvm.so";
//启动类
const char szStartClass[] ="test/JTest";
//启动方法
const char szStartMethod[] ="main";
//Java启动参数
int nOptionCount = 2;
JavaVMOption options[2];
options[1].optionString ="-Xmx256M";
options[0].optionString ="-Djava.class.path=./Test.jar;./Test1.jar";
JavaVMInitArgs vm_args;
vm_args.version =JNI_VERSION_1_4;
vm_args.options = options;
vm_args.nOptions = nOptionCount;
vm_args.ignoreUnrecognized =JNI_TRUE;
//命令行参数
int nParamCount = 2;
const char *szParams[2] ={"arg1","arg2"};
//装载动态库
void* lib_handle = 0;
lib_handle = dlopen(szJvmPath,RTLD_NOW);
if (!lib_handle)
{
fprintf(stderr, "dlopenfailed\n");
return false;
}
JNICREATEPROC lib_func = 0;
//查找JNI_CreateJavaVM过程
lib_func =(JNICREATEPROC)dlsym(lib_handle, "JNI_CreateJavaVM");
lib_func(&jvm,&env,&vm_args);
//后面与windows平台处理流程完全一样
cls =env->FindClass(szStartClass);
if (cls == 0) {
fprintf(stderr,"Can''''t find class:%s\n",szStartClass);
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
return false;
}
mid =env->GetStaticMethodID(cls,szStartMethod,"([Ljava/lang/String;)V");
if (mid == 0) {
fprintf(stderr,"Can''''t find Method:%s\n",szStartMethod);
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
return false;
}
jclass stringClass =env->FindClass("java/lang/String");
if (env->ExceptionCheck()== JNI_TRUE || stringClass == NULL)
{
fprintf(stderr,"find String class error");
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
return false;
}
args =env->NewObjectArray(nParamCount, stringClass, NULL);
if (args == 0) {
fprintf(stderr, "Out ofmemory\n");
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
return false;
}
jstring jstr;
for (int i=0; i<nParamCount;i++)
{
jstr =env->NewStringUTF(szParams[i]);
if (jstr == 0) {
fprintf(stderr,"Out of memory\n");
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
jvm->DestroyJavaVM();
return false;
}
env->SetObjectArrayElement(args,i, jstr);
if(env->ExceptionCheck() == JNI_TRUE)
{
fprintf(stderr,"set param error\n");
if(env->ExceptionOccurred()) {
env->ExceptionDescribe();
}
jvm->DestroyJavaVM();
return -1;
}
}
env->CallStaticVoidMethod(cls,mid, args);
if (env->ExceptionOccurred()){
env->ExceptionDescribe();
return false;
}
return true;
}
参考资料
1、 用创建子进程方式启动Java虚拟机的具体方法,可参考开源项目 Java Service Wrapper,http://wrapper.tanukisoftware.org
2、 关于jvm动态库JNI方法的具体说明,参加SUN网站关于JNI的说明http://java.sun.com/j2se/1.4.2/docs/guide/jni/
版权声明
本文为fita个人原创,未经本人允许不得转载、摘抄
转一个代码示例:
下面这代码是在Android系统的源代码中的dalvik\dalvikvm目录下Main.cpp 和 响应的Android.mk文件;
Main.cpp:
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Command-line invocation of the Dalvik VM.
*/
#include "jni.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
/*
* We want failed write() calls to just return with an error.
*/
static void blockSigpipe()
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGPIPE);
if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
fprintf(stderr, "WARNING: SIGPIPE not blocked\n");
}
/*
* Create a String[] and populate it with the contents of argv.
*/
static jobjectArray createStringArray(JNIEnv* env, char* const argv[], int argc)
{
jclass stringClass = NULL;
jobjectArray strArray = NULL;
jobjectArray result = NULL;
int i;
stringClass = env->FindClass("java/lang/String");
if (env->ExceptionCheck()) {
fprintf(stderr, "Got exception while finding class String\n");
goto bail;
}
assert(stringClass != NULL);
strArray = env->NewObjectArray(argc, stringClass, NULL);
if (env->ExceptionCheck()) {
fprintf(stderr, "Got exception while creating String array\n");
goto bail;
}
assert(strArray != NULL);
for (i = 0; i < argc; i++) {
jstring argStr;
argStr = env->NewStringUTF(argv[i]);
if (env->ExceptionCheck()) {
fprintf(stderr, "Got exception while allocating Strings\n");
goto bail;
}
assert(argStr != NULL);
env->SetObjectArrayElement(strArray, i, argStr);
env->DeleteLocalRef(argStr);
}
/* return the array, and ensure we don't delete the local ref to it */
result = strArray;
strArray = NULL;
bail:
env->DeleteLocalRef(stringClass);
env->DeleteLocalRef(strArray);
return result;
}
/*
* Determine whether or not the specified method is public.
*
* Returns JNI_TRUE on success, JNI_FALSE on failure.
*/
static int methodIsPublic(JNIEnv* env, jclass clazz, jmethodID methodId)
{
static const int PUBLIC = 0x0001; // java.lang.reflect.Modifiers.PUBLIC
jobject refMethod = NULL;
jclass methodClass = NULL;
jmethodID getModifiersId;
int modifiers;
int result = JNI_FALSE;
refMethod = env->ToReflectedMethod(clazz, methodId, JNI_FALSE);
if (refMethod == NULL) {
fprintf(stderr, "Dalvik VM unable to get reflected method\n");
goto bail;
}
/*
* We now have a Method instance. We need to call
* its getModifiers() method.
*/
methodClass = env->FindClass("java/lang/reflect/Method");
if (methodClass == NULL) {
fprintf(stderr, "Dalvik VM unable to find class Method\n");
goto bail;
}
getModifiersId = env->GetMethodID(methodClass,
"getModifiers", "()I");
if (getModifiersId == NULL) {
fprintf(stderr, "Dalvik VM unable to find reflect.Method.getModifiers\n");
goto bail;
}
modifiers = env->CallIntMethod(refMethod, getModifiersId);
if ((modifiers & PUBLIC) == 0) {
fprintf(stderr, "Dalvik VM: main() is not public\n");
goto bail;
}
result = JNI_TRUE;
bail:
env->DeleteLocalRef(refMethod);
env->DeleteLocalRef(methodClass);
return result;
}
/*
* Parse arguments. Most of it just gets passed through to the VM. The
* JNI spec defines a handful of standard arguments.
*/
int main(int argc, char* const argv[])
{
JavaVM* vm = NULL;
JNIEnv* env = NULL;
JavaVMInitArgs initArgs;
JavaVMOption* options = NULL;
char* slashClass = NULL;
int optionCount, curOpt, i, argIdx;
int needExtra = JNI_FALSE;
int result = 1;
setvbuf(stdout, NULL, _IONBF, 0);
/* ignore argv[0] */
argv++;
argc--;
/*
* If we're adding any additional stuff, e.g. function hook specifiers,
* add them to the count here.
*
* We're over-allocating, because this includes the options to the VM
* plus the options to the program.
*/
optionCount = argc;
options = (JavaVMOption*) malloc(sizeof(JavaVMOption) * optionCount);
memset(options, 0, sizeof(JavaVMOption) * optionCount);
/*
* Copy options over. Everything up to the name of the class starts
* with a '-' (the function hook stuff is strictly internal).
*
* [Do we need to catch & handle "-jar" here?]
*/
for (curOpt = argIdx = 0; argIdx < argc; argIdx++) {
if (argv[argIdx][0] != '-' && !needExtra)
break;
options[curOpt++].optionString = strdup(argv[argIdx]);
/* some options require an additional arg */
needExtra = JNI_FALSE;
if (strcmp(argv[argIdx], "-classpath") == 0 ||
strcmp(argv[argIdx], "-cp") == 0)
/* others? */
{
needExtra = JNI_TRUE;
}
}
if (needExtra) {
fprintf(stderr, "Dalvik VM requires value after last option flag\n");
goto bail;
}
/* insert additional internal options here */
assert(curOpt <= optionCount);
initArgs.version = JNI_VERSION_1_4;
initArgs.options = options;
initArgs.nOptions = curOpt;
initArgs.ignoreUnrecognized = JNI_FALSE;
//printf("nOptions = %d\n", initArgs.nOptions);
blockSigpipe();
/*
* Start VM. The current thread becomes the main thread of the VM.
*/
if (JNI_CreateJavaVM(&vm, &env, &initArgs) < 0) {
fprintf(stderr, "Dalvik VM init failed (check log file)\n");
goto bail;
}
/*
* Make sure they provided a class name. We do this after VM init
* so that things like "-Xrunjdwp:help" have the opportunity to emit
* a usage statement.
*/
if (argIdx == argc) {
fprintf(stderr, "Dalvik VM requires a class name\n");
goto bail;
}
/*
* We want to call main() with a String array with our arguments in it.
* Create an array and populate it. Note argv[0] is not included.
*/
jobjectArray strArray;
strArray = createStringArray(env, &argv[argIdx+1], argc-argIdx-1);
if (strArray == NULL)
goto bail;
/*
* Find [class].main(String[]).
*/
jclass startClass;
jmethodID startMeth;
char* cp;
/* convert "com.android.Blah" to "com/android/Blah" */
slashClass = strdup(argv[argIdx]);
for (cp = slashClass; *cp != '\0'; cp++)
if (*cp == '.')
*cp = '/';
startClass = env->FindClass(slashClass);
if (startClass == NULL) {
fprintf(stderr, "Dalvik VM unable to locate class '%s'\n", slashClass);
goto bail;
}
startMeth = env->GetStaticMethodID(startClass,
"main", "([Ljava/lang/String;)V");
if (startMeth == NULL) {
fprintf(stderr, "Dalvik VM unable to find static main(String[]) in '%s'\n",
slashClass);
goto bail;
}
/*
* Make sure the method is public. JNI doesn't prevent us from calling
* a private method, so we have to check it explicitly.
*/
if (!methodIsPublic(env, startClass, startMeth))
goto bail;
/*
* Invoke main().
*/
env->CallStaticVoidMethod(startClass, startMeth, strArray);
if (!env->ExceptionCheck())
result = 0;
bail:
/*printf("Shutting down Dalvik VM\n");*/
if (vm != NULL) {
/*
* This allows join() and isAlive() on the main thread to work
* correctly, and also provides uncaught exception handling.
*/
if (vm->DetachCurrentThread() != JNI_OK) {
fprintf(stderr, "Warning: unable to detach main thread\n");
result = 1;
}
if (vm->DestroyJavaVM() != 0)
fprintf(stderr, "Warning: Dalvik VM did not shut down cleanly\n");
/*printf("\nDalvik VM has exited\n");*/
}
for (i = 0; i < optionCount; i++)
free((char*) options[i].optionString);
free(options);
free(slashClass);
/*printf("--- VM is down, process exiting\n");*/
return result;
}
Android.mk
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH:= $(call my-dir)
#
# Common definitions.
#
dalvikvm_src_files := \
Main.cpp
dalvikvm_c_includes := \
$(JNI_H_INCLUDE) \
dalvik/include
#
# Build for the target (device).
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(dalvikvm_src_files)
LOCAL_C_INCLUDES := $(dalvikvm_c_includes)
LOCAL_SHARED_LIBRARIES := \
libdvm \
libssl \
libz
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := dalvikvm
include $(BUILD_EXECUTABLE)
#
# Build for the host.
#
ifeq ($(WITH_HOST_DALVIK),true)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(dalvikvm_src_files)
LOCAL_C_INCLUDES := $(dalvikvm_c_includes)
ifeq ($(HOST_OS)-$(HOST_ARCH),darwin-x86)
# OS X comes with all these libraries, so there is no need
# to build any of them. Note: OpenSSL consists of libssl
# and libcrypto.
LOCAL_LDLIBS := -lffi -lssl -lcrypto -lz
else
LOCAL_LDLIBS += -ldl -lpthread
LOCAL_SHARED_LIBRARIES += libdvm libcrypto libicuuc libicui18n libssl
endif
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := dalvikvm
include $(BUILD_HOST_EXECUTABLE)
endif
上述讲了很多常用的方法,Qt中提供了更简单的方法,当然上述方法也是可以的;
QAndroidJniObject
Detailed Description: Provides APIs to call Java code from C++.