实现一个在JNI中调用Java对象的工具类,从此只需一行代码
前言
我們知道在jni中執(zhí)行一個(gè)java函數(shù)需要調(diào)用幾行代碼才行,如
jclass objClass = (*env).GetObjectClass(obj); jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig); jobject result = (*env).CallObjectMethod(obj, methodID, ...);這樣使用起來(lái)很不方便,尤其當(dāng)需要大量的調(diào)用java函數(shù)就會(huì)產(chǎn)生大量的上述代碼,由此我產(chǎn)生了一個(gè)開(kāi)發(fā)封裝這些操作的工具類(lèi),以便大量簡(jiǎn)化我們的開(kāi)發(fā)。
簡(jiǎn)單封裝
其實(shí)可以看到整個(gè)過(guò)程基本是固定不變的:先獲取Class,然后獲取method,然后在執(zhí)行call。所以可以簡(jiǎn)單的先封裝成一系列工具函數(shù),如:
jobject callObjMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jobject result = (*env).CallObjectMethodV(obj, methodID, args);va_end(args);return result; }jint callIntMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jint result = (*env).CallIntMethodV(obj, methodID, args);va_end(args);return result; }jboolean callBooleanMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jboolean result = (*env).CallBooleanMethodV(obj, methodID, args);va_end(args);return result; }這樣當(dāng)我們要通過(guò)jni執(zhí)行某個(gè)java函數(shù)的時(shí)候,就一行代碼就可以搞定了,比如String.length():
jint len = callIntMethod(env, str, "length", "()I")這樣就可以大大減少了代碼量,而且代碼也更易讀了。
優(yōu)化
通過(guò)上面可以看到這些函數(shù)大部分代碼都非常類(lèi)似,只有一行代碼和返回值有區(qū)別,所以我考慮使用函數(shù)模版來(lái)進(jìn)行優(yōu)化,如下:
template <typename T> T callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);T result;if(typeid(T) == typeid(jobject)){result = (*env).CallObjectMethodV(obj, methodID, args);}if(typeid(T) == typeid(jdouble)){result = (*env).CallDoubleMethodV(obj, methodID, args);}...va_end(args);return *result; }這樣只要調(diào)用callMethod<return type>即可,愿望很美好,但是上面代碼實(shí)際上是無(wú)法通過(guò)編譯。
因?yàn)槟0婧瘮?shù)實(shí)際上是在編譯時(shí),根據(jù)調(diào)用的類(lèi)型,拷貝生成多個(gè)具體類(lèi)型的函數(shù)以便使用。
所以如果有這樣的調(diào)用callMethod<jobject>(...),在編譯時(shí)就會(huì)拷貝成一個(gè)如下的函數(shù):
jobject callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);jobject result;if(typeid(jobject) == typeid(jobject)){result = (*env).CallObjectMethodV(obj, methodID, args);}if(typeid(jobject) == typeid(jdouble)){result = (*env).CallDoubleMethodV(obj, methodID, args);}...va_end(args);return *result; }注意這行代碼:
if(typeid(jobject) == typeid(jdouble)){result = (*env).CallDoubleMethodV(obj, methodID, args); }雖然實(shí)際上是無(wú)法執(zhí)行的代碼,但是編譯時(shí)還是會(huì)進(jìn)行檢查,由于將jdouble類(lèi)型的賦值給jobject類(lèi)型的result,所以編譯不通過(guò),類(lèi)型無(wú)法轉(zhuǎn)換。而且這里用強(qiáng)轉(zhuǎn)static_cast等方法都不行。
我考慮兩種方法來(lái)解決這個(gè)問(wèn)題,一種是保證編譯不報(bào)錯(cuò),因?yàn)檫\(yùn)行時(shí)不會(huì)執(zhí)行的代碼,只要通過(guò)編譯就可以。另外一種是不同的類(lèi)型編譯不同的代碼。
void指針
在c++中void指針可以被賦值任何類(lèi)型指針,且void指針強(qiáng)轉(zhuǎn)為任何類(lèi)型指針在編譯時(shí)不會(huì)報(bào)錯(cuò)。代碼如下:
template <typename T> T callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);T* result = new T();if(typeid(T) == typeid(jobject)){jobject objec = (*env).CallObjectMethodV(obj, methodID, args);void *p = &objec;result = (T*)p;}if(typeid(T) == typeid(jdouble)){jdouble doub = (*env).CallDoubleMethodV(obj, methodID, args);void *p = &doub;result = (T*)p;}va_end(args);return *result; }當(dāng)然利用void指針很不安全,雖然可以通過(guò)編譯,但是執(zhí)行時(shí)如果類(lèi)型不同會(huì)直接造成crash。所以并不建議這種方式。
模版函數(shù)特例化
將差異代碼部分封裝到另一個(gè)模版函數(shù)中,并且對(duì)每種類(lèi)型進(jìn)行特例化,這樣還可以去掉if-else判斷,代碼如下:
template <typename K> K call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){return *(new K()); }template <> jobject call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){return (*env).CallObjectMethodV(obj, methodID, args); }template <> jdouble call2Result(JNIEnv *env, jobject obj, jmethodID methodID, va_list args){return (*env).CallDoubleMethodV(obj, methodID, args); } ...template <typename T> T callMethod(JNIEnv *env, jobject obj, const char *methodName, const char *methodSig, ...){va_list args;jclass objClass = (*env).GetObjectClass(obj);jmethodID methodID = (*env).GetMethodID(objClass, methodName, methodSig);va_start(args,methodSig);T result = call2Result<T>(env, obj, methodID, args);va_end(args);return result; }這樣在編譯時(shí),如果返回值是jobject類(lèi)型的,當(dāng)編譯到call2Result時(shí),就會(huì)直接調(diào)用jobject call2Result(...)這個(gè)函數(shù),就不再涉及類(lèi)型轉(zhuǎn)換的問(wèn)題。
這樣去掉了if判斷,但是由于沒(méi)有通用的函數(shù),所以所有使用的類(lèi)型都需要特例化,如果某個(gè)類(lèi)型未特例化,代碼執(zhí)行可能就會(huì)有問(wèn)題。而在jni中,與java對(duì)應(yīng)的類(lèi)型其實(shí)就那么十幾種,所以我們只要全部實(shí)現(xiàn)一遍call2Result即可。
undefined reference to
使用模版函數(shù)出現(xiàn)這個(gè)問(wèn)題,是因?yàn)闆](méi)有將模版函數(shù)的實(shí)現(xiàn)寫(xiě)在頭文件中,只將模版函數(shù)的聲明在頭文件中,而在源文件中實(shí)現(xiàn)的。
所以我們應(yīng)該將模版函數(shù)的實(shí)現(xiàn)也寫(xiě)進(jìn)頭文件中,而模版函數(shù)特例化則可以在源文件中實(shí)現(xiàn),但是注意要include頭文件。
返回值是void類(lèi)型
因?yàn)関oid的特殊性,所以如果當(dāng)成泛型來(lái)處理會(huì)有很多問(wèn)題,這里把返回值是void類(lèi)型的單獨(dú)實(shí)現(xiàn)一個(gè)函數(shù)即可。
總結(jié)
上面我們僅僅是實(shí)現(xiàn)了調(diào)用普通函數(shù)的工具,根據(jù)這個(gè)思路我們還可以實(shí)現(xiàn)調(diào)用靜態(tài)函數(shù)、獲取成員變量、賦值成員變量等,這樣當(dāng)我們?cè)谶M(jìn)行jni開(kāi)發(fā)的時(shí)候,如果需要對(duì)java對(duì)象或類(lèi)進(jìn)行操作,只需要一行代碼就可以了。
源碼
關(guān)注公眾號(hào):BennuCTech,發(fā)送“JNIObjectTools”獲取源碼。
總結(jié)
以上是生活随笔為你收集整理的实现一个在JNI中调用Java对象的工具类,从此只需一行代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: c++文件中jni库找不到报红
- 下一篇: mac上搭建vue环境及webstorm