本文使用的开发环境:
- vs2008
- idea 64位
- jdk 1.8 64位
JAVA 调用 C/C++ 动态库
前一篇文章 JNI学习笔记(一):环境配置及简单使用 java调用c++ dll库
中已经实现了java调用dll 。 但是,由于java和c++ 的数据类型是不同的,所以下一步需要实现java 和 c++
各种类型的转换。
1.基本类型的处理
jni.h中已经对基本类型有了定义,不需要进行额外处理
示例代码:
//JniTest.java 在java中的函数声明
//通过返回值 返回 int类型 a+b
public native int intAddTest(int a,int b);
//JniTest.h 在c++中的函数声明
JNIEXPORT jint JNICALL Java_JniTest_intAddTest(JNIEnv *, jobject, jint, jint);
//JniTest.cpp 在c++中的函数实现
//intAddTest
JNIEXPORT jint JNICALL Java_JniTest_intAddTest(JNIEnv *, jobject, jint a, jint b)
{
return a+b;
}
2.数组的处理
相关函数说明:
//获取byte数组内的数据
jbyte * GetByteArrayElements(
jbyteArray array, // 原数组
jboolean *isCopy //[出参]用于返回是否成功,如果不关心是否成功,可以直接设置为NULL(0)
)
//返回值是一个c/c++中的数组指针
jni.h中对于每一种基础数据类型,都对应的相关函数,例如GetIntArrayElements,GetDoubleArrayElements等。
示例代码:
//JniTest.java 在java中的函数声明
//通过返回值 返回 int类型 a+b
public native byte[] byteArrayTest(byte[] baA,byte[] baB);
//JniTest.h 在c++中的函数声明
JNIEXPORT jbyteArray JNICALL Java_JniTest_byteArrayTest
(JNIEnv *, jobject, jbyteArray, jbyteArray);
//JniTest.cpp 在c++中的函数实现
//byteArrayTest
JNIEXPORT jbyteArray JNICALL Java_JniTest_byteArrayTest
(JNIEnv *env, jobject, jbyteArray ba1, jbyteArray ba2)
{
//获取byte数组 数据
jbyte* buffer1 = env->GetByteArrayElements(ba1,0);
//获取byte数组长度
jsize ulb1Len = env->GetArrayLength(ba1);
jbyte* buffer2 = env->GetByteArrayElements(ba2,0);
jsize ulb2Len = env->GetArrayLength(ba2);
//创建新内存用于存储拼接到一起的数组
unsigned char* buffer3 = new unsigned char[ulb1Len + ulb2Len];
ZeroMemory(buffer3,ulb1Len+ulb2Len );
memcpy(buffer3,buffer1,ulb1Len);
memcpy(buffer3+ulb1Len ,buffer2,ulb2Len);
//创建新byte数组对象,用于返回
jbyteArray rByteArray = env->NewByteArray(ulb1Len + ulb2Len);
//用buffer3填充新键的byte数组对象
env->SetByteArrayRegion(rByteArray,0,ulb1Len + ulb2Len,(jbyte*)buffer3);
delete[] buffer3;
//释放由GetByteArrayElements创建的内存
env->ReleaseByteArrayElements(ba1,(jbyte*)buffer1,0);
env->ReleaseByteArrayElements(ba2,(jbyte*)buffer2,0);
return rByteArray;
}
注意事项: 函数返回前 要用ReleaseByteArrayElements 释放 GetByteArrayElements申请的内存。
3.String
GetStringChars的使用和 数组的GetxxArrayElements 类似。
需要关注的是 :
在JAVA中 字符串编码格式是UTF-8 ,而c/c++ 的编码格式 是 ANSI(中文系统下是GBK/GB18030)和 UNICODE (UTF-16),处理中文时需要转码。
jni提供了两组字符串相关的函数 : 一组接收/返回的是 UNICODE(UTF-16)字符 ,类似 GetStringChars,NewString 等, 还有一组接收/返回的是UTF-8字符,类似 GetStringUTFChars,NewStringUTF 等。 由于c/c++中没有提供UTF-8字符类型,需要手动转码,所以在使用中文字符时,使用UNICODE来处理更加方便。
示例代码中使用的全部是UNICODE,如果需要使用UTF-8,则要用WideCharToMultiByte和MultiByteToWideChar进行转码。
示例代码:
//JniTest.java 在java中的函数声明
//通过返回值返回 String类型的 a + b + c
public native String strAddTest(String a,String b,String c);
//JniTest.h 在c++中的函数声明
JNIEXPORT jstring JNICALL Java_JniTest_strAddTest
(JNIEnv *, jobject, jstring, jstring, jstring);
//JniTest.cpp 在c++中的函数实现
//strAddTest
JNIEXPORT jstring JNICALL Java_JniTest_strAddTest
(JNIEnv *env, jobject, jstring strA, jstring strB,jstring strC)
{
//由java String类对象 提取c字符串指针
jboolean jbIsCopy = JNI_FALSE;
const jchar* str1= env->GetStringChars(strA, &jbIsCopy);
const jchar* str2= env->GetStringChars(strB, &jbIsCopy);
const jchar* str3= env->GetStringChars(strC, &jbIsCopy);
//创建、编辑新字符串以供生成新String对象
wchar_t* pszAddStr = new wchar_t[wcslen((wchar_t*)str1) + wcslen((wchar_t*)str2) + 16];
wcscpy(pszAddStr,(wchar_t*)str1);
wcscat(pszAddStr,L" ");
wcscat(pszAddStr,(wchar_t*)str2);
wcscat(pszAddStr,L" ");
wcscat(pszAddStr,(wchar_t*)str3);
//释放由GetStringChars创建的内存
env->ReleaseStringChars(strA,str1);
env->ReleaseStringChars(strB,str2);
env->ReleaseStringChars(strC,str3);
//创建java String对象
jstring rString = env->NewString((jchar*)pszAddStr,(jsize)wcslen(pszAddStr));
delete[] pszAddStr;
pszAddStr = NULL;
return rString;
}
4.StringBuffer 和 ArrayList 为代表的java类
StringBuffer和ArrayList等类提供了相关方法,所以可以通过入参返回。
大致操作流程:
(1).通过GetObjectClass 或FindClass 获取java类
(2).用GetMethodID 获取类方法的id
(3).CallxxMethod 调用java方法
相关函数说明:
jmethodID GetMethodID( jclass clazz, //由GetObjectClass 或 FindClass 返回的java类
const char *name, //方法名称
const char *sig) //方法签名 形式为: (参数1类型参数2类型...)返回类型
//函数返回值是方法id
jobject CallObjectMethod(jobject obj, //要操作的java对象
jmethodID methodID, //GetMethodID返回的 方法id
...) //对应方法的参数列表
//这是一组函数,CallxxMethod ,其中xx是函数的返回值类型,
//每种返回值类型都有其对应的函数,如CallBooleanMethod,CallByteMethod,CallIntMethod等
示例代码:
StringBuffer:
//JniTest.java 在java中的函数声明
//传入空字符串,返回数据通过入参strBuf返回
public native void strbufTest(StringBuffer strBuf);
//JniTest.h 在c++中的函数声明
JNIEXPORT void JNICALL Java_JniTest_strbufTest
(JNIEnv *, jobject, jobject);
//JniTest.cpp 在c++中的函数实现
//strbufTest
JNIEXPORT void JNICALL Java_JniTest_strbufTest
(JNIEnv *env, jobject, jobject strbuf)
{
//获取java StringBuffer类
jclass cls_sb = env->GetObjectClass(strbuf);
//获取StringBuffer的 setLength 和 add方法
jmethodID mid_setLength = env->GetMethodID(cls_sb,"setLength","(I)V");
jmethodID mid_append = env->GetMethodID(cls_sb,"append","(Ljava/lang/String;)Ljava/lang/StringBuffer;");
//创建java String对象
wchar_t pwstr[128] = L"这是StringBuffer入参返回的字符串";
jstring rstring = env->NewString((jchar*)pwstr,(jsize)wcslen(pwstr));
//调用StringBuffer 的 setLength方法,先清空
env->CallVoidMethod (strbuf, mid_setLength, 0);
//调用StringBuffer 的 append方法
env->CallObjectMethod (strbuf, mid_append, rstring);
return;
}
ArrayList:
//JniTest.java 在java中的函数声明
//通过入参slist 返回
public native void slistTest(ArrayList<String> slist);
//JniTest.h 在c++中的函数声明
JNIEXPORT void JNICALL Java_JniTest_slistTest
(JNIEnv *, jobject, jobject);
//JniTest.cpp 在c++中的函数实现
//slistTest
JNIEXPORT void JNICALL Java_JniTest_slistTest
(JNIEnv *env, jobject, jobject slist)
{
//获取java ArrayList类
jclass cls = env->GetObjectClass(slist);
//获取ArrayList 的 add方法
jmethodID mid = env->GetMethodID (cls, "add", "(Ljava/lang/Object;)Z");
//创建java String对象
wchar_t pwstr[128] = L"这是 ArrayList入参返回的字符串";
jstring rstring = env->NewString((jchar*)pwstr,(jsize)wcslen(pwstr));
//调用ArrayList 的 add方法
env->CallObjectMethod (slist, mid, rstring);
return;
}
5.自定义JAVA类
相关函数说明:
//获取类成员id
jfieldID GetFieldID(jclass clazz, //类
const char *name, //成员名称
const char *sig) ; //成员类型
//返回值是类成员id
//获取类对象 成员的数据
jint GetIntField(jobject obj, //类对象
jfieldID fieldID); //由GetFieldID返回的成员id
//这是一组函数,GetxxField,xx是成员类型,
每种成员类型都有其对应的函数,如GetObjectField、GetIntField、GetCharField等
//设置对象成员的数据,与GetIntField相对
void SetIntField(jobject obj,
jfieldID fieldID,
jint val) ;
//这是一组函数,SetxxField,xx是成员类型
自定义java类TestA定义如下 :
class TestA{
public int A;
public String C;
}
示例代码:
//JniTest.java 在java中的函数声明
//读取a的值,修改后,通过返回值返回ArrayList
public native ArrayList<TestA> talistTest(TestA a);
//JniTest.h 在c++中的函数声明
JNIEXPORT jobject JNICALL Java_JniTest_talistTest
(JNIEnv *, jobject, jobject);
//JniTest.cpp 在c++中的函数实现
JNIEXPORT jobject JNICALL Java_JniTest_talistTest
(JNIEnv *env , jobject, jobject ta)
{
//获取 java自定义类 “TestA”,
// 这里也可以用env->GetObjectClass(ta)获取
jclass cls_ta = env->FindClass("TestA");
jmethodID mid_testa_construct = env->GetMethodID(cls_ta,"<init>","()V");
//获取TestA类的成员变量A 和 C
jfieldID fid_A = env->GetFieldID(cls_ta,"A","I");
jfieldID fid_C = env->GetFieldID(cls_ta,"C","Ljava/lang/String;");
//获取入参的 java类对象成员数据
//获得ta.A
int in_ta_A= env->GetIntField(ta, fid_A );
//获得ta.C,,jstring继承jobject 返回值强制转换成jstring
jstring in_ta_C = (jstring)env->GetObjectField(ta,fid_C);
//获取java ArrayList类
jclass cls_alist = env->FindClass("java/util/ArrayList");
//获取 ArrayList的构造函数 和add方法
jmethodID mid_alist_construct = env->GetMethodID(cls_alist,"<init>","()V");
jmethodID mid_alist_add = env->GetMethodID (cls_alist, "add", "(Ljava/lang/Object;)Z");
//编辑字符串
//由java String类对象 提取c字符串指针
jboolean jbIsCopy = JNI_FALSE;
const jchar* str_taC= env->GetStringChars(in_ta_C, &jbIsCopy);
//创建、编辑新字符串以供生成新String对象
wchar_t* pwtaC = new wchar_t[wcslen((wchar_t*)str_taC) + 16];
ZeroMemory(pwtaC,wcslen((wchar_t*)str_taC) + 16);
wcscpy(pwtaC,(wchar_t*)str_taC);
wcscat(pwtaC,L" 测试");
//释放由GetStringChars 创建的内存
env->ReleaseStringChars(in_ta_C,str_taC);
//创建新的TestA对象
jobject obj_ta = env->NewObject(cls_ta,mid_testa_construct);
//设置TestA对象的 成员 A
env->SetIntField(obj_ta,fid_A,in_ta_A + 1);
//创建java String对象
jstring rjs_ta_C = env->NewString((jchar*)pwtaC,(jsize)wcslen(pwtaC));
delete[] pwtaC;
pwtaC = NULL;
//把TestA对象的 成员 C 设置成刚生成的字符串
env->SetObjectField(obj_ta,fid_C,rjs_ta_C);
//创建ArrayList对象
jobject obj_alist = env->NewObject(cls_alist,mid_alist_construct);
env->CallObjectMethod(obj_alist,mid_alist_add,obj_ta);
return obj_alist;
}
注意事项:
- C/C++ 的 .h文件是由 javah 生成的,一般不要手动修改。
- FindClass 如果要找到类在包中,需要写包名。
- GetMethodID 一定注意函数签名要正确。
- FindClass 、GetMethodID、GetFieldID等函数需要判断返回值是否为0,示例代码中未添加判断。