Cocos2d-x 中如何使用jni C++ 调用 Java

2015年03月23日 11:57 0 点赞 0 评论 更新于 2017-05-07 22:08

在 Cocos2d-x 开发中,有时需要在 C++ 代码里调用 Java 方法,借助 JNI(Java Native Interface)就能实现这一需求。下面通过不同类型的示例,详细介绍在 Cocos2d-x 里运用 JNI 从 C++ 调用 Java 方法的具体操作。

1. 简单数据类型示例

假设 Java 中有一个名为 open 的静态方法,该方法没有参数,返回值为 int 类型。那么在 C++ 里该如何调用它呢?

Java 代码

package cb.CbCCBLE;

public class CbCCBLECentralManager {
public static final String TAG = "CbCCBLECentralManager Android";

public static int open() {
Log.d(TAG, "open");
return 1;
}
}

C++ 调用代码

#if defined(ANDROID)
#include "platform/android/jni/JniHelper.h"
#include <iostream>

int CbCCBLECentralManager::open() {
JniMethodInfo minfo;
bool isHave = JniHelper::getStaticMethodInfo(minfo, "cb/CbCCBLE/CbCCBLECentralManager", "open", "()I");
if (!isHave) {
CCLOG("FAIL: CbCCBLECentralManager - open");
return 0;
}
int result = minfo.env->CallStaticIntMethod(minfo.classID, minfo.methodID);
return result;
}
#endif

解释

  • getStaticMethodInfo 方法的参数至关重要。"cb/CbCCBLE/CbCCBLECentralManager" 是安卓的具体包名加上类名,中间用 '/' 分隔;"open" 是方法名;"()I" 表示传入参数和输出参数,括号内为输入参数(此处无输入参数),右边跟着返回值(I 代表 int 类型)。
  • 参数和返回值会用特殊简写来表示,完整的参数对应关系如下:
    Java 类型JNI 简写
    intI
    longJ
    booleanZ
    byteB
    charC
    shortS
    floatF
    doubleD
    voidV

若有多个简单类型参数,示例如下:

Java 代码

public class CbCCBLECentralManager {
public static final String TAG = "CbCCBLECentralManager Android";

public static int open(int a, int b) {
Log.d(TAG, "open");
return 1;
}
}

C++ 调用代码

JniHelper::getStaticMethodInfo(minfo, "cb/CbCCBLE/CbCCBLECentralManager", "open", "(II)I");

注意

要根据调用方法的不同,选择合适的 CallStaticXXXMethod 方法,例如这里调用的是静态的返回 int 的方法,所以使用了 CallStaticIntMethod。具体可参考相关文档。

2. 字符串示例

字符串的处理相对复杂一些。

Java 代码

public static int scanPeripheralWithName(String name, long duration) {
Log.d(TAG, "scanPeripheralWithName name:" + name + " duration:" + duration);
return 1;
}

C++ 调用代码

int CbCCBLECentralManager::scanPeripheralWithName(std::string name, long duration) {
JniMethodInfo minfo;
bool isHave = JniHelper::getStaticMethodInfo(minfo, "cb/CbCCBLE/CbCCBLECentralManager", "scanPeripheralWithName", "(Ljava/lang/String;J)I");
if (!isHave) {
CCLOG("FAIL: CbCCBLECentralManager - scanPeripheralWithName");
return 0;
}
jstring jname = minfo.env->NewStringUTF(name.c_str());
jlong jDuration = (long)duration;
int result = minfo.env->CallStaticIntMethod(minfo.classID, minfo.methodID, jname, jDuration);
return result;
}

解释

  • string 属于 jobject 类型,jobject 要加 L 作为前缀。由于 Java 中的 string 类完整包名是 java/lang/String,所以 string 的完整表示为 Ljava/lang/String,并且因为是 jobject,要用 ';' 作为结束。
  • JNI 有自己的数据类型,通常以 j 开头,它们作为 Java 和 C++ 之间的数据交互媒介。这里的 jstringjlong 就是 JNI 数据类型。

3. 数组示例

返回字符串数组示例

Java 代码

public static String[] getAllPeripherals() {
Log.d(TAG, "getAllPeripherals");
String[] resultArray = {"testPeripheral1", "testPeripheral2"}; // just for test
return resultArray;
}

C++ 调用代码

std::vector<std::string> CbCCBLECentralManager::getAllPeripherals() {
std::vector<std::string> stdResult;
JniMethodInfo minfo;
bool isHave = JniHelper::getStaticMethodInfo(minfo, "cb/CbCCBLE/CbCCBLECentralManager", "getAllPeripherals", "()[Ljava/lang/String;");
if (!isHave) {
CCLOG("FAIL: CbCCBLECentralManager - getAllPeripherals");
return stdResult;
}
jobjectArray jResult = static_cast<jobjectArray>(minfo.env->CallStaticObjectMethod(minfo.classID, minfo.methodID));
jsize resultSize = minfo.env->GetArrayLength(jResult);
jsize index = 0;
while (index < resultSize) {
jstring eachElement = (jstring)minfo.env->GetObjectArrayElement(jResult, index);
std::string stdString = JniHelper::jstring2string(eachElement);
stdResult.push_back(stdString);
++index;
}
return stdResult;
}

解释

数组前面要加上一个 '['。这里还使用了 GetArrayLengthGetObjectArrayElement 等方法,用于获取数组长度和根据索引获取数组元素。

传入字符串数组示例

Java 代码

public static int scanPeripheralWithServiceUUIDs(String[] serviceUUIDs, long duration) {
Log.d(TAG, "scanPeripheralWithServiceUUIDs:" + duration);
return 0;
}

C++ 调用代码

int CbCCBLECentralManager::scanPeripheralWithServiceUUIDs(std::vector<std::string> serviceUUIDs, long duration) {
JniMethodInfo minfo;
bool isHave = JniHelper::getStaticMethodInfo(minfo, "cb/CbCCBLE/CbCCBLECentralManager", "scanPeripheralWithServiceUUIDs", "([Ljava/lang/String;J)I");
if (!isHave) {
CCLOG("FAIL: CbCCBLECentralManager - scanPeripheralWithServiceUUIDs");
return 0;
}
jint size = serviceUUIDs.size();
jclass StringObject = minfo.env->FindClass("java/lang/String");
jobjectArray jServiceUUIDsArray = minfo.env->NewObjectArray(size, StringObject, NULL);
jlong jDuration = (long)duration;
for (int i = 0; i < serviceUUIDs.size(); i++) {
jstring serviceUUID = minfo.env->NewStringUTF(serviceUUIDs[i].c_str());
minfo.env->SetObjectArrayElement(jServiceUUIDsArray, i, serviceUUID);
}
int result = minfo.env->CallStaticIntMethod(minfo.classID, minfo.methodID, jServiceUUIDsArray, jDuration);
return result;
}

4. 自定义类示例

Java 代码

package OurBLE;

public class OurBlePeripheralAdvertisementData {
public String deviceName;

public String getDeviceName() {
return deviceName;
}
}

public static OurBlePeripheralAdvertisementData getPeripheralAdvertisementData(String peripheralId) {
Log.d(TAG, "getPeripheralAdvertisementData");
OurBlePeripheralAdvertisementData result = new OurBlePeripheralAdvertisementData();
result.deviceName = "deviceName1";
return result;
}

C++ 调用代码

CbCCBLEPeripheralAdvertisementData CbCCBLECentralManager::getPeripheralAdvertisementData(std::string peripheralId) {
CbCCBLEPeripheralAdvertisementData data = CbCCBLEPeripheralAdvertisementData();
JniMethodInfo minfo;
bool isHave = JniHelper::getStaticMethodInfo(minfo, "cb/CbCCBLE/CbCCBLECentralManager", "getPeripheralAdvertisementData", "(Ljava/lang/String;)LOurBLE/OurBlePeripheralAdvertisementData;");
if (!isHave) {
CCLOG("FAIL: CbCCBLECentralManager - getPeripheralAdvertisementData");
return data;
}
jstring jPeripheralId = minfo.env->NewStringUTF(peripheralId.c_str());
jobject result = minfo.env->CallStaticObjectMethod(minfo.classID, minfo.methodID, jPeripheralId);
jclass CbCCBLEPeripheralAdvertisementDataClass = minfo.env->FindClass("OurBLE/OurBlePeripheralAdvertisementData");
jmethodID deviceNameMId = minfo.env->GetMethodID(CbCCBLEPeripheralAdvertisementDataClass, "getDeviceName", "()Ljava/lang/String;");
jstring jDeviceName = (jstring)minfo.env->CallObjectMethod(result, deviceNameMId);
data.deviceName = JniHelper::jstring2string(jDeviceName);
return data;
}

解释

这里使用了 FindClassGetMethodIDCallObjectMethod 等 API,从而实现了自定义类的传递。

总之,JNI 功能强大,能实现 C++ 和 Java 之间的交互。若想了解 Java 调用 C++ 的相关内容,可参考文章 《Cocos2d-x 中使用 jni Java 调用 C++ 方法》

作者信息

boke

boke

共发布了 1025 篇文章