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