C++调用JAVA方法详解

来源:互联网 发布:淘宝瑜伽服模特是谁 编辑:程序博客网 时间:2024/05/17 23:41

C++调用JAVA主要用到了SUN公司的JNI技术, JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。相关资料见http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/jniTOC.html

 

开发环境安装及配置

 

1.1  安装JDK 

        到SUN公司网站可以下载到最新版的JDK。下载下来后开始安装,一路选择默认配置即可,本文档中假定安装的是JDK1.4,安装目录为C:\j2sdk1.4.2_15。



  

1.2  配置VC6.0 

         通过Visual C++ 6的菜单Tools→Options打开选项对话框。在Directories标签页下添加JDK的相关目录到Include和目录下。 
             

 

 


 开发测试用到的JAVA类


2.1  开发JAVA类

        在硬盘的任意地方新建一个名叫test的文件夹,本文档示例中将test文件夹建立在C盘根目录,然后在里面新建一个名称叫Demo.java的JAVA文件,将下面测试用的代码粘贴到该文件中。

 

 

Java代码  收藏代码
  1. package test;  
  2. /** 
  3. * 该类是为了演示JNI如何访问各种对象属性等 
  4. */  
  5. public class Demo   
  6. {  
  7.     //用于演示如何访问静态的基本类型属性  
  8.     public static int COUNT = 8;  
  9.     //演示对象型属性  
  10.     private String msg;  
  11.     private int[] counts;  
  12.       
  13.     public Demo()   
  14.     {  
  15.         this("缺省构造函数");  
  16.     }  
  17.     /** 
  18.      * 演示如何访问构造器 
  19.      */  
  20.     public Demo(String msg)   
  21.     {  
  22.         this.msg = msg;  
  23.         this.counts = null;  
  24.     }  
  25.     public String getMessage()  
  26.     {  
  27.         return msg;  
  28.     }  
  29.     /** 
  30.      * 该方法演示如何访问一个静态方法 
  31.      */  
  32.     public static String getHelloWorld()  
  33.     {  
  34.         return "Hello world!";  
  35.     }  
  36.   
  37.     /** 
  38.      * 该方法演示参数的传入传出及中文字符的处理 
  39.      */  
  40.     public String append(String str, int i)  
  41.     {  
  42.         return str + i;  
  43.     }  
  44.     /** 
  45.      * 演示数组对象的访问 
  46.      */  
  47.     public int[] getCounts()  
  48.     {  
  49.      return counts;  
  50.     }  
  51.     /** 
  52.      * 演示如何构造一个数组对象 
  53.     */  
  54.     public void setCounts(int[] counts)  
  55.     {  
  56.      this.counts = counts;  
  57.     }  
  58.     /** 
  59.      * 演示异常的捕捉 
  60.     */  
  61.     public void throwExcp()throws IllegalAccessException  
  62.     {  
  63.         throw new IllegalAccessException("exception occur.");  
  64.     }  
  65. }  

 

2.2 编译JAVA类

      运行CMD控制台程序进入命令行模式,输入命令javac -classpath c:\ c:\test\Demo.java,-classpath参数指定classpath的路径,这里就是test目录所在的路径。(注意:如果你没有将JDK的环境变量设置好,就需要先进入JDK的bin目录下,如下图所示。)


 

2.3 查看方法的签名

      我们知道Java中允许方法的多态,仅仅是通过方法名并没有办法定位到一个具体的方法,因此需要一个字符串来唯一表示一个方法。但是怎么利用一个字 符串来表示方法的具体定义呢?JDK中已经准备好一个反编译工具javap,通过这个工具就可以得到类中每个属性、方法的签名。在CMD下运行javap -s -p -classpath c:\ test.Demo即可看到属性和方法的签名。如下图红色矩形框起来的字符串为方法String append(String str, int i)的签名。

 

 

在VC中调用JAVA类

 

3.1 快速调用JAVA中的函

      在VC中新建一个控制台程序,然后新建一个CPP文件,将下面的代码添加到该文件中。运行该文件,即可得到Demo类中String append(String str, int i)函数返回的字符串。

Cpp代码  收藏代码
  1. #include "windows.h"  
  2. #include "jni.h"  
  3. #include <string>  
  4. #include <iostream>  
  5. using namespace std;  
  6.   
  7. jstring NewJString(JNIEnv *env, LPCTSTR str);  
  8. string  JStringToCString (JNIEnv *env, jstring str);  
  9.   
  10. int main()  
  11. {  
  12.     //定义一个函数指针,下面用来指向JVM中的JNI_CreateJavaVM函数  
  13.     typedef jint (WINAPI *PFunCreateJavaVM)(JavaVM **, void **, void *);  
  14.       
  15.     int res;  
  16.     JavaVMInitArgs vm_args;  
  17.     JavaVMOption options[3];  
  18.     JavaVM *jvm;  
  19.     JNIEnv *env;  
  20.       
  21.     /*设置初始化参数*/  
  22.     //disable JIT,这是JNI文档中的解释,具体意义不是很清楚 ,能取哪些值也不清楚。  
  23.     //从JNI文档里给的示例代码中搬过来的  
  24.     options[0].optionString = "-Djava.compiler=NONE";  
  25.     //设置classpath,如果程序用到了第三方的JAR包,也可以在这里面包含进来  
  26.     options[1].optionString = "-Djava.class.path=.;c:\\";  
  27.     //设置显示消息的类型,取值有gc、class和jni,如果一次取多个的话值之间用逗号格开,如-verbose:gc,class  
  28.     //该参数可以用来观察C++调用JAVA的过程,设置该参数后,程序会在标准输出设备上打印调用的相关信息  
  29.     options[2].optionString = "-verbose:NONE";  
  30.           
  31.     //设置版本号,版本号有JNI_VERSION_1_1,JNI_VERSION_1_2和JNI_VERSION_1_4  
  32.     //选择一个根你安装的JRE版本最近的版本号即可,不过你的JRE版本一定要等于或者高于指定的版本号  
  33.     vm_args.version = JNI_VERSION_1_4;  
  34.     vm_args.nOptions = 3;  
  35.     vm_args.options = options;  
  36.     //该参数指定是否忽略非标准的参数,如果填JNI_FLASE,当遇到非标准参数时,JNI_CreateJavaVM会返回JNI_ERR  
  37.     vm_args.ignoreUnrecognized = JNI_TRUE;  
  38.     //加载JVM.DLL动态库  
  39.     HINSTANCE hInstance = ::LoadLibrary("C:\\j2sdk1.4.2_15\\jre\\bin\\client\\jvm.dll");  
  40.     if (hInstance == NULL)  
  41.     {  
  42.         return false;  
  43.     }  
  44.     //取得里面的JNI_CreateJavaVM函数指针  
  45.     PFunCreateJavaVM funCreateJavaVM = (PFunCreateJavaVM)::GetProcAddress(hInstance, "JNI_CreateJavaVM");  
  46.     //调用JNI_CreateJavaVM创建虚拟机  
  47.     res = (*funCreateJavaVM)(&jvm, (void**)&env, &vm_args);  
  48.     if (res < 0)  
  49.     {  
  50.         return -1;  
  51.     }  
  52.     //查找test.Demo类,返回JAVA类的CLASS对象  
  53.     jclass cls = env->FindClass("test/Demo");  
  54.     //根据类的CLASS对象获取该类的实例  
  55.     jobject obj = env->AllocObject(cls);  
  56.       
  57.     //获取类中的方法,最后一个参数是方法的签名,通过javap -s -p 文件名可以获得  
  58.     jmethodID mid = env->GetMethodID(cls, "append","(Ljava/lang/String;I)Ljava/lang/String;");  
  59.     //构造参数并调用对象的方法  
  60.     const char szTest[] = "电信";  
  61.     jstring arg = NewJString(env, szTest);  
  62.     jstring msg = (jstring) env->CallObjectMethod(obj, mid, arg, 12);  
  63.     cout<<JStringToCString(env, msg);  
  64.           
  65.     //销毁虚拟机并释放动态库  
  66.     jvm->DestroyJavaVM();  
  67.     ::FreeLibrary(hInstance);  
  68.     return 0;  
  69. }  
  70.   
  71. string  JStringToCString (JNIEnv *env, jstring str)// (jstring str, LPTSTR desc, int desc_len)  
  72. {  
  73.     if(str==NULL)  
  74.     {  
  75.         return "";  
  76.     }  
  77.     //在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型  
  78.     int len = env->GetStringLength(str);  
  79.     wchar_t *w_buffer = new wchar_t[len+1];  
  80.     char *c_buffer = new char[2*len+1];  
  81.     ZeroMemory(w_buffer,(len+1)*sizeof(wchar_t));  
  82.     //使用GetStringChars而不是GetStringUTFChars  
  83.     const jchar * jcharString = env->GetStringChars(str, 0);  
  84.     wcscpy(w_buffer,jcharString);     
  85.     env->ReleaseStringChars(str,jcharString);  
  86.     ZeroMemory(c_buffer,(2*len+1)*sizeof(char));  
  87.     /调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串  
  88.     len = WideCharToMultiByte(CP_ACP,0,w_buffer,len,c_buffer,2*len,NULL,NULL);  
  89.     string cstr = c_buffer;  
  90.     delete[] w_buffer;  
  91.     delete[] c_buffer;  
  92.       
  93.     return cstr;  
  94. }  
  95.   
  96. jstring NewJString(JNIEnv *env, LPCTSTR str)  
  97. {  
  98.     if(!env || !str)  
  99.     {  
  100.         return 0;  
  101.     }  
  102.     int slen = strlen(str);  
  103.     jchar* buffer = new jchar[slen];  
  104.     int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen);  
  105.     if(len>0 && len < slen)  
  106.     {  
  107.         buffer[len]=0;  
  108.     }  
  109.     jstring js = env->NewString(buffer,len);  
  110.     delete [] buffer;  
  111.     return js;  
  112. }  

 

3.2 调用步骤分析及注意事项

 

     a、加载jvm.dll动态库,然后获取里面的JNI_CreateJavaVM函数。这个步骤也可以通过在VC工程的LINK标签页里添加对jvm.lib的连接,然后在环境变量里把jvm.dll所在的路径加上去来实现。但后面这种方法在部署的时候会比前一个方法麻烦。


     b、利用构造好的参数,调用JNI_CreateJavaVM函数创建JVM。JNI_CreateJavaVM函数内部会自动根据jvm.dll的路径来获取JRE的环境,所以千万不要把jvm.dll文件拷贝到别的地方,然后再通过LoadLibrary函数导入。


     c、JVM创建成功后,JNI_CreateJavaVM函数会传出一个JNI上下文环境对象(JNIEnv),利用该对象的相关函数就可以调用JAVA类的属性和方法了。


     d、以上面的代码为例:先调用JNIEnv的FindClass方法,该函数传入一个参数,该参数就是java类的全局带包名的名称,如上面示例中的test/Demo表示test包中的Demo类。这个方法会在你创建JVM时设置的classpath路径下找相应的类,找到后就会返回该类的class对象。 Class是JAVA中的一个类,每个JAVA类都有唯一的一个静态的Class对象,Class对象包含类的相关信息。为了使FindClass方法能找到你的类,请确保创建JVM时-Djava.class.path=参数设置正确。注意:系统环境变量中的CLASSPATH对这里创建JVM没有影响,所以不要以为系统CLASSPATH设置好了相关路径后这里就不用设置了。


     e、利用FindClass返回的class对象,调用GetMethodID函数可以获得里面方法的ID,在这里GetMethodID函数传入了三个参数:第一个参数是class对象,因为方法属于某个具体的类;第二个参数是方法的名称;第三个参数是方法的签名,这个签名可以在前面3.3中介绍的方法获得。


     f、利用class对象,可以通过调用AllocObject函数获得该class对象对应类的一个实例,即Demo类的对象。


     g、利用上面获取的函数ID和Demo类的对象,就可以通过CallObjectMethod函数调用相应的方法,该函数的参数跟printf函数的参数一样,个数是不定的。第一个参数是类的对象;第二个参数是要调用的方法的ID;后面的参数就是需要传给调用的JAVA类方法的参数,如果调用的JAVA类方法没有参数,则调用CallObjectMethod时传前两个参数就可以了。


     h、从上面的示例中可以看到,在调用JAVA的方法前,构造传入的字符串时,用到了NewJString函数;在调用该方法后,对传出的字符串调用了JstringToCString函数。这是由于Java中所有的字符都是Unicode编码,但是在本地方法中,例如用VC编写的程序,如果没有特殊的定义一般都没有使用Unicode的编码方式。为了让本地方法能够访问Java中定义的中文字符及Java访问本地方法产生的中文字符串,定义了两个方法用来做相互转换。


     i、避免在被调用的JAVA类中使用静态final成员变量,因为在C++中生成一个JAVA类的对象时,静态final成员变量不会像JAVA中new对象时那样先赋值。如果出现这种情况,在C++中调用该对象的方法时会发现该对象的静态final成员变量值全为0或者null(根据成员变量的类型而定)。

 

3.3 调用JAVA中的静态方法

 

Cpp代码  收藏代码
  1. //调用静态方法  
  2. jclass cls = env->FindClass("test/Demo");  
  3. jmethodID mid = env->GetStaticMethodID(cls, "getHelloWorld","()Ljava/lang/String;");  
  4. jstring msg = (jstring)env->CallStaticObjectMethod(cls, mid);      
  5. cout<<JStringToCString(env, msg);  

 

3.4 调用JAVA中的静态属性

 

C代码  收藏代码
  1. //调用静态方法  
  2. jclass cls = env->FindClass("test/Demo");  
  3. jfieldID fid = env->GetStaticFieldID(cls, "COUNT","I");  
  4. int count = (int)env->GetStaticIntField(cls, fid);     
  5. cout<<count<<endl;  
 

3.5 调用JAVA中的带参数构造函数

 

Cpp代码  收藏代码
  1. //调用构造函数  
  2. jclass cls = env->FindClass("test/Demo");  
  3. jmethodID mid = env->GetMethodID(cls,"<init>","(Ljava/lang/String;)V");  
  4. const char szTest[] = "电信";  
  5. jstring arg = NewJString(env, szTest);  
  6. jobject demo = env->NewObject(cls,mid,arg);  
  7. //验证是否构造成功  
  8. mid = env->GetMethodID(cls, "getMessage","()Ljava/lang/String;");  
  9. jstring msg = (jstring)env->CallObjectMethod(demo, mid);   
  10. cout<<JStringToCString(env, msg);  

 

3.6 传入传出数组

 

Cpp代码  收藏代码
  1. //传入传出数组  
  2. //构造数组  
  3. long        arrayCpp[] = {1,3,5,7,9};  
  4. jintArray array = env->NewIntArray(5);  
  5. env->SetIntArrayRegion(array, 0, 5, arrayCpp);  
  6. //传入数组  
  7. jclass cls = env->FindClass("test/Demo");  
  8. jobject obj = env->AllocObject(cls);  
  9. jmethodID mid = env->GetMethodID(cls,"setCounts","([I)V");  
  10. env->CallVoidMethod(obj, mid, array);  
  11. //获取数组  
  12. mid = env->GetMethodID(cls,"getCounts","()[I");  
  13. jintArray msg = (jintArray)env->CallObjectMethod(obj, mid, array);  
  14. int len =env->GetArrayLength(msg);  
  15. jint* elems =env-> GetIntArrayElements(msg, 0);  
  16. for(int i=0; i< len; i++)  
  17. {  
  18.     cout<<"ELEMENT "<<i<<" IS "<<elems[i]<<endl;  
  19. }  
  20. env->ReleaseIntArrayElements(msg, elems, 0);  

 

3.7 异常处理 
     由于调用了Java的方法,因此难免产生操作的异常信息,如JAVA函数返回的异常,或者调用JNI方法(如GetMethodID)时抛出的异常。这些异常没有办法通过C++本身的异常处理机制来捕捉到,但JNI可以通过一些函数来获取Java中抛出的异常信息。

Cpp代码  收藏代码
  1. //异常处理  
  2. jclass cls = env->FindClass("test/Demo");  
  3. jobject obj = env->AllocObject(cls);  
  4. jmethodID mid = env->GetMethodID(cls,"throwExcp","()V");  
  5. env->CallVoidMethod(obj, mid);  
  6. //获取异常信息  
  7. string exceptionInfo = "";  
  8. jthrowable excp = 0;  
  9. excp = env->ExceptionOccurred();   
  10. if(excp)  
  11. {  
  12.     jclass cls = env->GetObjectClass(excp);  
  13.     env->ExceptionClear();  
  14.     jmethodID mid = env->GetMethodID(cls, "toString","()Ljava/lang/String;");  
  15.     jstring msg = (jstring) env->CallObjectMethod(excp, mid);  
  16.     out<<JStringToCString(env, msg)<<endl;    
  17.     env->ExceptionClear();  
  18. }  
 

 

 

多线程

 

4.1 多线程中注意事项

 


    JNIEnv和jobject对象都不能跨线程使用

 

    对于jobject,解决办法是

    a、m_obj = m_env->NewGlobalRef(obj);//创建一个全局变量  

    b、jobject obj = m_env->AllocObject(m_cls);//在每个线程中都生成一个对象

 

    对于JNIEnv,解决办法是在每个线程中都重新生成一个env

 

    JNIEnv *env;  

    m_jvm->AttachCurrentThread((void **)&env, NULL);  

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 考上特岗教师后和老公异地怎么办 档案在无档案保管权限的公司怎么办 辞职了户口迁回农原籍不接收怎么办 当正职直接指挥下级副职领导怎么办 单位申报个税身份证号码填错怎么办 申报个税身份证号码填错了怎么办 办了人事代理联系函商调函怎么办 劳务公司与分包工头算账成怎么办 客户退保业务员己得保险佣金怎么办 投了简历公司打来电话没接到怎么办 跟老板要工资她说过几天怎么办 公路车弯把手小抓不到刹车怎么办 新手骑电动车上路车子一直晃怎么办 不知情收赃东西已经卖了怎么办 档案和派遣证不在同一个地方怎么办 档案缺招工表和派遣证怎么办退休 开发商对接的银行利息比较高怎么办 工作未满一年离职转正定级怎么办 被网络骗贷翻到几十万怎么办 面试通过后迟迟不通知入职怎么办 厂里捡到饭卡花了里面的钱怎么办 上班用自己手机打卡没电怎么办 逸尚考勤机显示超出考勤记录怎么办 下雨穿套裙工装上班好冷怎么办 蚂蚁借呗放款中不到账怎么办 护士电子化注册没有激活码怎么办? 地铁站务员入职体检身高差点怎么办 车子钥匙锁在车里了怎么办 打错账户了怎么办5天了 下岗失业人员在就业后档案怎么办 皮秒祛斑后没有敷面膜怎么办 硕士毕业工资好低不想工作怎么办 车卖了etc忘拿了怎么办 成熟卵泡打破卵针也不破怎么办? 深圳孩子户口挂别人名下上学怎么办 襄阳东风合运花园房贷怎么办下来 猫在外面躲起来找不到了怎么办 本科三批取消以后三本学校怎么办 机票名字多了个字母安检怎么办 出隧道口限速40超速了怎么办 社保交了五年后断交以后该怎么办