Been working on some libs on both iOS and Android platforms for a long time, recently needed to support Cocos2d-x framework, I have no interest to write a whole new lib based on C++, nightmare for maintenance, So I worked on to integrate both libs to Cocos2d-x framework.
iOS
Imagine my lib name is libHumbleAssistant.a, with a header file HumbleAssistant.h.
@interface HumbleAssistant : NSObject
+ (void)doSomething:(NSString *)order;
@end
Lucky me, Objective C belongs to C family, which could integrate with C and C++ seamlessly.
At first, create a .h header file as the real interface for C++ code, and name it as humble_assistant_cpp.h.
class HumbleAssistantCpp {
public:
static void do_something(char *order);
};
Then, create its source humble_assistant_cpp.mm, .mm stands for Objective-C++, in which, people can hybrid programming with Objective C and C++.
#import "HumbleAssistant.h"
#import "humble_assistant_cpp.h"
void HumbleAssistantCpp::do_something(char *order) {
[HumbleAssistant doSomething:[NSString stringWithUTF8String:order]];
}
Now, the file structures looks like this
-mylibs
-ios
- HumbleAssistant.h
- libHumbleAssistant.a
- humble_assistant_cpp.h
- humble_assistant_cpp.mm
In a Cocos2d-x project, add those files, and include the humble_assistant_cpp.h to any C++ code that is about to ask the humble assistant a favor. It’s that simple.
Android
JNI is used to bind Cocos2d-x and Android Java interfaces.
In order to call a method in a jar lib from C++, JNI code must be written.
Imagine my android lib is humbleAssistant.jar, and has an interface as below.
package com.my.libs;
public final class HumbleAssistant {
public static void doSomething(String order) {
// do something
}
}
To expose this method to C++, first, create a header file humble_assistant_jni.h.
extern "C"
{
extern void j_do_something(char *order);
}
Then, its implementation humble_assistant_jni.cpp, a little complicated due to the JNI’s obscure grammar.
#include "platform/android/jni/JniHelper.h"
#include "humble_assistant_jni.h"
// java Class location
#define CLASS_NAME "com/my/libs/HumbleAssistant"
extern "C"
{
void j_do_something(char *order) {
cocos2d::JniMethodInfo methodInfo;
if (cocos2d::JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
"doSomething", "(Ljava/lang/String;)V")) {
jstring j_order = methodInfo.env->NewStringUTF(order);
methodInfo.env->CallStaticVoidMethod(methodInfo.classID,
methodInfo.methodID,
j_order);
methodInfo.env->DeleteLocalRef(j_order);
}
}
}
Cocos2d-x is a cross platform framework, our libs need to be that too, only one single interface is needed.
In iOS, a humble_assistant_cpp.h is created, which is the universal header file for both platforms. And humble_assistant_cpp.mm is the source file.
So, in Android, we have to make a new source file humble_assistant_cpp.cpp.
#include "humble_assistant_cpp.h"
#include "humble_assistant_jni.h"
void HumbleAssistant::do_something(char *order) {
j_do_something(order);
}
Now, the file structures looks like this
-mylibs
-ios
- HumbleAssistant.h
- libHumbleAssistant.a
- humble_assistant_cpp.h
- humble_assistant_cpp.mm
-android
- humble_assistant_jni.h
- humble_assistant_jni.cpp
- humble_assistant_cpp.cpp
But it’s not finished.
We have to change the makefile in “proj.android/jni/android.mk” to add source files and the header file.
# Add source files
LOCAL_SRC_FILES := ... \
../../sdk/android/humble_assistant_jni.cpp \
../../sdk/android/humble_assistant_cpp.cpp
# Include header file
LOCAL_C_INCLUDES := ... $(LOCAL_PATH)/../../mylibs/ios
And also, put the jar lib into “proj.android/libs” folder.
It’s done. Looks much more complicated than iOS way, and could be even worse.
If the doSomething interface needs a HashMap parameter, JNI code will be full of stupid code.
void j_do_something(char *order, std::map<char*, char*> attributes) {
cocos2d::JniMethodInfo methodInfo;
if (cocos2d::JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME,
"doSomething", "(Ljava/lang/String;Ljava/util/Map;)V")) {
jobject jattributes = 0;
jclass hashMapClass = methodInfo.env->FindClass("java/util/HashMap");
if (hashMapClass) {
jsize mapSize = attributes.size();
jmethodID init = methodInfo.env->GetMethodID(hashMapClass, "<init>", "(I)V");
jobject hashMap = methodInfo.env->NewObject(hashMapClass, init, mapSize);
jmethodID putMethod = methodInfo.env->GetMethodID(hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
for (std::map<char*, char*>::const_iterator it = attributes.begin();
it != attributes.end(); ++it) {
jstring key = methodInfo.env->NewStringUTF(it->first);
jstring value = methodInfo.env->NewStringUTF(it->second);
methodInfo.env->CallObjectMethod(hashMap, putMethod, key, value);
methodInfo.env->DeleteLocalRef(value);
methodInfo.env->DeleteLocalRef(key);
}
jattributes = hashMap;
methodInfo.env->DeleteLocalRef(hashMapClass);
}
jstring jorder = methodInfo.env->NewStringUTF(order);
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID,
jorder, jattributes);
if (jattributes)
methodInfo.env->DeleteLocalRef(jattributes);
methodInfo.env->DeleteLocalRef(jorder);
}
}
Feel lucky again that my lib only exposes static methods, with simple parameters.
Maybe that’s why I don’t like cross platform frameworks, and Android.