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.