1、 JNI的使用
对JNI了解不多,网上有说将java程序生成头文件并制作动态链接库的,过程看起来比较复杂,而且害怕动态库调用出问题,所以就用了Java提供的JNI接口。
a、包含Java提供的同文件,#include "include/jni.h",该文件在Java的jdk目录下,(发现include文件夹就能找到);然后加入以下语句
//设置环境变量,windows版和Linux版
#ifdef_WIN32
#definePATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR':'
#endif
//放置库搜索目录
#pragma comment(lib, "jvm")
b、初始化时首先要把Java虚拟机的动态链接库加载进去:
HMODULE JVM_DLL = LoadLibraryA("C://ProgramFiles//Java//jdk1.7.0_04//jre//bin//client//jvm.dll");//这个路径不是固定的,而且拷贝到项目下不行,可能是jvm这个动态库需要有环境支持或者与其他文件有关联吧,具体不太清楚
c、创建虚拟机,这个比较关键,看代码:
JNIEnv *env; //java运行时环境
JavaVM *jvm;//java虚拟机
static char Compiler[] ="-Djava.compiler=NONE"; //JVM编译器设定,none为使用默认编译器。
static char ClassPath[] ="-Djava.class.path=.//实验室//bin;.//实验室//lib//commons-codec-1.3.jar;.//实验室//lib//commons-httpclient-3.1.jar;.//实验室//lib//commons-logging-1.1.jar;.//实验室//lib//mysql-connector-java-5.1.19-bin.jar;.//实验室//lib//nekohtml.jar;.//实验室//lib//poi-3.8-20120326.jar;.//实验室//lib//xalan.jar;.//实验室//lib//xercesImpl.jar";
static char LibraryPath[] ="-Djava.library.path=.//实验室//lib";
typedef jint (WINAPI*JNICreateJavaVM)(JavaVM**, JNIEnv**, void *);
//JVM内部函数JNI_CreateJavaVM读取
JNICreateJavaVMcreateJavaVM = (JNICreateJavaVM)GetProcAddress(JVM_DLL,"JNI_CreateJavaVM");
if(createJavaVM == NULL)
{
::MessageBoxA(NULL,"JNI_CreateJavaVM函数读取失败!", "", MB_OK);
}
//设定JVM启动参数
JavaVMInitArgsvm_args;
JavaVMOptionoptions[4];//这个数组容量根据设置内容不同而不同,有些代码还设置了最大最小内存,需要的空间就多了
options[0].optionString= Compiler;
//类地址
options[1].optionString= ClassPath;
//lib地址
options[2].optionString= LibraryPath;
options[3].optionString="-verbose:jni";
//使用的jni版本,目前最高为JNI_VERSION_1_6。
vm_args.version = JNI_VERSION_1_6;
vm_args.options = options;
vm_args.nOptions= 4;
vm_args.ignoreUnrecognized= JNI_TRUE;
//启动JVM,并生成运行时环境
intres = createJavaVM(&jvm, &env, &vm_args);
if(res < 0) //为0表示成功
{
::MessageBoxA(NULL,"JVM启动失败!", "", MB_OK);
}
关键是对ClassPath的设置,开始认为只要设置当前类所在路径就可以了,因为所依赖的jar包在LibraryPath中设置,但是不行运行时总是找不到类ID和函数ID。所以设置的时候一定要将所有需要的jar包给写一遍,写的时候”.//”代表当前路径。生成虚拟机时会生成运行时环境,刚开始认为这个运行时环境很重要,总是设法保存这个变量,并做传递,后来因为做多线程,调试时总是出错,出现指针异常的情况,就想到这个运行环境应该是每个线程一个,有需要就创建。
d、找到类并生成对象
jclass clazz;
jmethodID mid;
jobject obj;
clazz = env->FindClass("file/ContentExe");//查找类
if(clazz == 0)
{
::MessageBoxA(NULL,"没有找到类", "", MB_OK);
jvm->DestroyJavaVM();
return;
}
//新建一个对象
obj= env->AllocObject(clazz);
if(obj == 0)
{
::MessageBoxA(NULL,"创建失败!", "", MB_OK);
jvm->DestroyJavaVM();
return;
}
//查找方法id
myid= env->GetMethodID(clazz, "getProcessString","()Ljava/lang/String;");
if(myid == 0)
{
::MessageBoxA(NULL,"没有找到getProcessString方法", "", MB_OK);
jvm->DestroyJavaVM();
return;
}
注意的是,查找类的时候因为已经设置类的路径了,所以用”包名/类名”查找即可。再有就是查找Java类的方法Id,使用的是GetMethodID方法,如果是静态类的话使用GetStaticMethodID方法,参数为类、方法名、方法标识符;方法标识符的获取需要用到javap命令,javap –s –p 类名查看,结果看”Signature: ()Ljava/lang/String;”这句其中括号里面的是这个方法的参数,没有时为空,后面是返回值,如果返回值为空的话是V,当然记不住也行直接使用命令查看就好。
e、Java方法的调用,这个比较简单,通过对象调用方法即可,只是参数需要注意
jstring result= (jstring) env->CallObjectMethod(obj, myid);
其中result是返回的String类型的值,调用CallObjectMethod方法时返回一个Object对象,需要强制类型转换,如果这个myid指向的Java函数需要参数,可在myid后面直接加,需要注意的是类型转换,需要吧参数转换成Java能够识别的类型(如jint、jstring…),还有就是参数按Java函数声明的顺序。
2、 关于多线程
对多线程了解不多,尤其感觉多线程的同步很难控制。网上的资料将工作者线程和UI线程,最开始也不知道说的什么意思,下载了些实例,程序能跑但是还不是不了解区别。于是看了Windows核心编程里关于进程与多线程的章节,晚上1点多时终于想明白他们的区别了,其实网上的资料已经说的很明白了,工作者线程主要是负责处理复杂的计算的,就像是后台运行那样,是界面还可以有其他操作;而UI线程主要是处理与界面有关的内容,我下载了一个例子就是打开两个以上的线程,每个线程管理一个界面实现滚动条的滚动,例子很简单,但是很能说明问题。
另外,主进程本身也有一个线程,这个需要注意,我生成的工作者线程开始执行后,总是跳转到主进程继续执行,工作者进程由于执行一条较费时间的语句到后台执行,查看线程可以明显的看到。
代码如下:
CWinThread*pGetThread; //采集线程
CWinThread*pShowThread; //显示结果线程
struct PARAM
{
int m_nID;
};//用于构造线程参数,根据具体情况添加(很有用的变量!)
PARAMm_pParam0;
PARAMm_pParam1;
pGetThread =AfxBeginThread(GetThread,&m_pParam0);//需要有GetThread方法
pShowThread =AfxBeginThread(ShowThread,&m_pParam1);//需要有ShowThread方法
UINTGetThread(LPVOID pParam);//需要具体实现
UINTShowThread(LPVOID pParam);
3、 CString类型与jstring类型的相互转换
//CString与jstring类型的转换
jstringretjstring; //用于保存转换后的jstring类型变量
jchar mystring[2000];
jsizeiLoop;
jsize jlength= convert.GetLength();
for (iLoop =0; iLoop < jlength; iLoop++)
mystring[iLoop] = (jchar) convert.GetAt(iLoop); //convert为所要转换的CString类型变量
retjstring = m_penv->NewString(mystring, jlength); //生成jstring类型变量
//jstring 转换到CString
CString myCString;//用于保存转换后的CString类型变量
jsizeistringlength;
jbooleanisCopy = JNI_TRUE;
constjchar *pChar = m_penv->GetStringChars(jnistr,&isCopy);
istringlength= m_penv->GetStringLength(jnistr);
myCString= (BSTR) pChar; //强制类型转换得到CString类型变量
m_penv->ReleaseStringChars(jnistr,pChar);
4、 关于字符集
Java使用的字符集是Unicode,VS2010默认的字符集也是Unicode的,这些本身没有问题,但是在读取Java保存的文本文件时,总是出现乱码的情况,个人认为是txt文档使用的字符集的问题,通过网上查资料,可以通过配置地域的信息的函数setlocale(需要头文件<locale.h>),将当前字符集转换为’’chs’’,需要注意的是要保存原字符集,读取文件后重新设置回去。
char*old_locale = _strdup(setlocale(LC_CTYPE,NULL));
setlocale(LC_CTYPE, "chs" );//设置编码格式
//读文件操作
setlocale( LC_CTYPE, old_locale );
5、 其他
a、关于获取当前路径
需要使用到的函数为GetModuleFileName,具体代码如下:
TCHARszPath[MAX_PATH];
GetModuleFileName(NULL,szPath, MAX_PATH); //获取当前路径
CStringPathName(szPath); //将其转换为CString类型,方便操作
b、关于CString与LPCSTR类型转换
网上好多人说这两个类型只用强制类型转换就可以实现相互转换了,但是在Unicode字符集下做此强制类型转换出现如下错误提示:
error C2440: “类型转换”: 无法从“CString”转换为“LPCSTR”
没有可用于执行该转换的用户定义的转换运算符,或者无法调用该运算符
如果将当前字符集改为“多字节字符集”就没有问题。最终的解决方法是:
int len =WideCharToMultiByte(CP_ACP, 0, strCong, strCong.GetLength(), NULL,0, NULL,NULL); //strCong为CString类型变量
char *str = new char[len+1]; //生成的char *变量可以直接转换为LPCSTR类型
WideCharToMultiByte(CP_ACP, 0, strCong,strCong.GetLength(), str, len, NULL,NULL);
str[len+1] = '\0'; //添加LPCSTR所需的结尾符
WideCharToMultiByte该函数映射一个unicode字符串到一个多字节字符串,具体参数意义可以参考百科。
c、CFileFind类
查找文件时使用,很方便的一个类。跟一个前辈说第一次用这个类,被鄙视了一把。
这个类需要注意的就是很多函数都要在使用FindNextFile函数后才能使用,但没有影响查找的开头,具体原因没有深究,太懒了!!