科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件如何在C/C++中调用Java (3)

如何在C/C++中调用Java (3)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

如何在C/C++中调用Java (3)

作者:刘冬 来源:开放系统世界——赛迪网 2007年11月13日

关键字: java 调用 C++ Linux

  • 评论
  • 分享微博
  • 分享邮件
 

中文处理

中文字符的处理往往是让人比较头疼的事情,特别是使用Java语言开发的软件,在JNI这个问题更加突出。由于Java中所有的字符都是Unicode编码,但是在本地方法中,例如用VC编写的程序,如果没有特殊的定义一般都没有使用Unicode的编码方式。为了让本地方法能够访问Java中定义的中文字符及Java访问本地方法产生的中文字符串,我定义了两个方法用来做相互转换。

· 方法一,将Java中文字符串转为本地字符串

/**
第一个参数是虚拟机的环境指针
第二个参数为待转换的Java字符串定义
第三个参数是本地存储转换后字符串的内存块
第三个参数是内存块的大小
*/
int JStringToChar(JNIEnv *env, jstring str, LPTSTR desc, int desc_len)
{
 int len = 0;
 if(desc==NULL||str==NULL)
 return -1;
 //在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型
 wchar_t *w_buffer = new wchar_t[1024];
 ZeroMemory(w_buffer,1024*sizeof(wchar_t));
 //使用GetStringChars而不是GetStringUTFChars
 wcscpy(w_buffer,env->GetStringChars(str,0));
 env->ReleaseStringChars(str,w_buffer);
 ZeroMemory(desc,desc_len);
 //调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串
 //关于函数WideCharToMultiByte的使用请参考MSDN
 len = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL);
 //len = wcslen(w_buffer);
 if(len>0 && len<desc_len)
  desc[len]=0;
 delete[] w_buffer;
 return strlen(desc);
}

· 方法二,将C的字符串转为Java能识别的Unicode字符串

jstring NewJString(JNIEnv* env,LPCTSTR str)
{
 if(!env || !str)
  return 0;
 int slen = strlen(str);
 jchar* buffer = new jchar[slen];
 int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen);
 if(len>0 && len < slen)
  buffer[len]=0;
 jstring js = env->NewString(buffer,len);
 delete [] buffer;
 return js;
}

异常

由于调用了Java的方法,因此难免产生操作的异常信息。这些异常没有办法通过C++本身的异常处理机制来捕捉到,但JNI可以通过一些函数来获取Java中抛出的异常信息。之前我们在Demo类中定义了一个方法throwExcp,下面将访问该方法并捕捉其抛出来的异常信息,代码如下:

/**
假设我们已经构造了一个Demo的实例obj,其类定义为cls
*/
jthrowable excp = 0;		/* 异常信息定义 */
jmethodID mid=(*env)->GetMethodID(env,cls,"throwExcp","()V");
/*如果mid为0表示获取方法定义失败*/
jstring msg = (*env)-> CallVoidMethod(env, obj, mid);
/* 在调用该方法后会有一个IllegalAccessException的异常抛出 */
excp = (*env)->ExceptionOccurred(env);
if(excp){ 
 (*env)->ExceptionClear(env);
 //通过访问excp来获取具体异常信息
 /*
在Java中,大部分的异常信息都是扩展类java.lang.Exception,因此可以访问excp的toString
或者getMessage来获取异常信息的内容。访问这两个方法同前面讲到的如何访问类的方法是相同的。
 */
}

线程和同步访问

有些时候需要使用多线程的方式来访问Java的方法。我们知道一个Java虚拟机是非常消耗系统的内存资源,差不多每个虚拟机需要内存大约在20MB左右。为了节省资源要求每个线程使用的是同一个虚拟机,这样在整个的JNI程序中只需要初始化一个虚拟机就可以了。所有人都是这样想的,但是一旦子线程访问主线程创建的虚拟机环境变量,系统就会出现错误对话框,然后整个程序终止。

其实这里面涉及到两个概念,它们分别是虚拟机(JavaVM *jvm)和虚拟机环境(JNIEnv *env)。真正消耗大量系统资源的是jvm而不是env,jvm是允许多个线程访问的,但是env只能被创建它本身的线程所访问,而且每个线程必须创建自己的虚拟机环境env。这时候会有人提出疑问,主线程在初始化虚拟机的时候就创建了虚拟机环境env。为了让子线程能够创建自己的env,JNI提供了两个函数:AttachCurrentThread和DetachCurrentThread。下面代码就是子线程访问Java方法的框架:

DWORD WINAPI ThreadProc(PVOID dwParam)
{
 JavaVM jvm = (JavaVM*)dwParam;	/* 将虚拟机通过参数传入 */
 JNIEnv* env;
 (*jvm)-> AttachCurrentThread(jvm, (void**)&env, NULL);
 .........
 (*jvm)-> DetachCurrentThread(jvm);
}

时间

关于时间的话题是我在实际开发中遇到的一个问题。当要发布使用了JNI的程序时,并不一定要求客户要安装一个Java运行环境,因为可以在安装程序中打包这个运行环境。为了让打包程序利于下载,这个包要比较小,因此要去除JRE(Java运行环境)中一些不必要的文件。但是如果程序中用到Java中的日历类型,例如java.util.Calendar等,那么有个文件一定不能去掉,这个文件就是[JRE]\lib\tzmappings。它是一个时区映射文件,一旦没有该文件就会发现时间操作上经常出现与正确时间相差几个小时的情况。下面是打包JRE中必不可少的文件列表(以Windows环境为例),其中[JRE]为运行环境的目录,同时这些文件之间的相对路径不能变。

文件名 目录
hpi.dll [JRE]\bin
ioser12.dll [JRE]\bin
java.dll [JRE]\bin
net.dll [JRE]\bin
verify.dll [JRE]\bin
zip.dll [JRE]\bin
jvm.dll [JRE]\bin\classic
rt.jar [JRE]\lib
tzmappings [JRE]\lib

由于rt.jar有差不多10MB,但是其中有很大一部分文件并不需要,可以根据实际的应用情况进行删除。例如程序如果没有用到Java Swing,就可以把涉及到Swing的文件都删除后重新打包。

查看本文来源
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章