通过简单例子学会在C/C++中调用JAVA类

    国庆长假,基本都窝在家里,呵呵,有时间正好把前一段时间做的东西整理出来,省得以后又忘记再去查资料。

    做过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。便于子线程创建自己的虚拟机环境。

 

  1. /** 
  2.  * 类MyTest为了测试JNI使用C/C++调用JAVA
  3.  * @author liudong
  4.  */
  5. public class MyTest {
  6.  /*
  7.   * 测试如何访问静态的基本类型属性
  8.   */
  9.  /*
  10.   *演示对象型属性
  11.   */
  12.  public String helloword;
  13.  public MyTest() {
  14.    this("JNI演示类");
  15.  }
  16.  /**
  17.   * 构造函数
  18.   */
  19.  public MyTest(String msg) {
  20.   System.out.println("构造函数:" + msg);
  21.   this.helloword = msg;
  22.  }
  23.  /**
  24.   * 该方法演示如何调用动态方
  25.   */
  26.  public String HelloWord() {
  27.   System.out.println("JAVA-CLASS:MyTest method:HelloWord:" + helloword);
  28.   return helloword; 
  29.  }
  30.  /** 
  31.   * 演示异常的捕捉 
  32.  */
  33.  public void throwExcp() throws IllegalAccessException {
  34.   throw new IllegalAccessException("exception occur.");
  35.  }
  36. }

那么在命令行运行: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:

  1. #include  
  2. #include 
  3. /*C字符串转JNI字符串*/
  4. jstring stoJstring(JNIEnv* env, const char* pat)
  5. {
  6.   jclass strClass = (*env)->FindClass(env,"Ljava/lang/String;");
  7.   jmethodID ctorID = (*env)->GetMethodID(env,strClass, """([BLjava/lang/String;)V");
  8.   jbyteArray bytes = (*env)->NewByteArray(env,strlen(pat));
  9.   (*env)->SetByteArrayRegion(env,bytes, 0, strlen(pat), (jbyte*)pat);
  10.   jstring encoding = (*env)->NewStringUTF(env,"utf-8");
  11.   return (jstring)(*env)->NewObject(env,strClass, ctorID, bytes, encoding);
  12. }
  13. /*JNI字符串转C字符串*/
  14. char* jstringTostring(JNIEnv* env, jstring jstr)
  15. {
  16.   char* rtn = NULL;
  17.   jclass clsstring = (*env)->FindClass(env,"java/lang/String");
  18.   jstring strencode = (*env)->NewStringUTF(env,"utf-8");
  19.   jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes""(Ljava/lang/String;)[B");
  20.   jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr, mid, strencode);
  21.   jsize alen = (*env)->GetArrayLength(env,barr);
  22.   jbyte* ba = (*env)->GetByteArrayElements(env,barr, JNI_FALSE);
  23.   if (alen > 0)
  24.   {
  25.     rtn = (char*)malloc(alen + 1);
  26.     memcpy(rtn, ba, alen);
  27.     rtn[alen] = 0;
  28.   }
  29.   (*env)->ReleaseByteArrayElements(env,barr, ba, 0);
  30.   return rtn;
  31. }
  32. int main() { 
  33.  int res; 
  34.  JavaVM *jvm; 
  35.  JNIEnv *env; 
  36.  JavaVMInitArgs vm_args; 
  37.  JavaVMOption options[3]; 
  38.  /*设置初始化参数*/ 
  39.  options[0].optionString = "-Djava.compiler=NONE";  
  40.  options[1].optionString = "-Djava.class.path=.";  
  41.  options[2].optionString = "-verbose:jni"//用于跟踪运行时的信息 
  42.  /*版本号设置不能漏*/ 
  43.  vm_args.version=JNI_VERSION_1_2;//jdk版本1.2
  44.  vm_args.nOptions = 3; 
  45.  vm_args.options = options; 
  46.  vm_args.ignoreUnrecognized = JNI_TRUE; 
  47.  res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); 
  48.  if (res < 0) { 
  49.   fprintf(stderr, "Can't create Java VM/n"); 
  50.   exit(1); 
  51.  }
  52.  /*获取实例的类定义*/ 
  53.  jmethodID mid;
  54.  jclass cls = (*env)->FindClass(env, "MyTest"); 
  55.  if (cls == 0)
  56.  {
  57.     fprintf(stderr, "FindClass failed/n");
  58.     (*jvm)->DestroyJavaVM(jvm);
  59.     fprintf(stdout, "Java VM destory./n");
  60.     return;
  61.  }
  62.  /*获取构造函数,用于创建对象*/
  63.  /***1.1可用""作为构造函数, 1.2用""参数中不能有空格"(Ljava/lang/String;)V"*/
  64.  mid = (*env)->GetMethodID(env,cls,"","(Ljava/lang/String;)V");
  65.  assert (0 != mid);
  66.  fprintf(stderr, "GetMethodID /n");
  67.  if (mid == 0)
  68.  {
  69.     fprintf(stderr, "GetMethodID failed/n");
  70.     (*jvm)->DestroyJavaVM(jvm);
  71.     fprintf(stdout, "Java VM destory./n");
  72.     return;
  73.  }
  74.  fprintf(stderr, "GetMethodID OK/n");
  75.  /*创建对象*/
  76.  const char str[]="this is a test for c call java";
  77.  jobject obj = (*env)->NewObject (env, cls, mid, stoJstring(env, str));
  78.  //jobject obj = (*env)->NewObject(env, cls, mid, 0);
  79.  /*如果mid为0表示获取方法定义失败*/
  80.  fprintf(stderr, "NewObject OK/n");
  81.  /*获取方法ID*/
  82.  mid=(*env)->GetMethodID(env,cls,"HelloWord","()Ljava/lang/String;");
  83.  if (mid == 0)
  84.  {
  85.     fprintf(stderr, "GetMethodID 'HelloWord' failed/n");
  86.     (*jvm)->DestroyJavaVM(jvm);
  87.     fprintf(stdout, "Java VM destory./n");
  88.     return;
  89.  }
  90.  fprintf(stderr, "GetMethodID 'HelloWord' OK/n");
  91.  /*调用动态方法*/
  92.  jstring msg = (*env)-> CallObjectMethod(env, obj, mid);
  93.  /* 
  94.   如果该方法是静态的方法那只需要将最后一句代码改为以下写法即可: 
  95.   jstring msg = (*env)-> CallStaticObjectMethod(env, cls, mid); 
  96.  */
  97.   /*捕捉异常*/
  98.   if ((*env)->ExceptionOccurred(env)) 
  99.   {
  100.     (*env)->ExceptionDescribe(env);
  101.     return ;
  102.   }
  103.   
  104.  /*销毁JAVA虚拟机*/
  105.  (*jvm)->DestroyJavaVM(jvm);
  106.  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里一样调用它的类、方法、访问它的属性。怎么样,很简单吧?

:)

 

你可能感兴趣的:(java,jni,虚拟机,jvm,反编译工具,jdk)