{"id":1014,"date":"2015-02-25T20:25:38","date_gmt":"2015-02-26T01:25:38","guid":{"rendered":"http:\/\/littlesvr.ca\/grumble\/?p=1014"},"modified":"2015-05-29T11:06:42","modified_gmt":"2015-05-29T16:06:42","slug":"using-libxmp-with-the-ndk-in-an-android-app","status":"publish","type":"post","link":"http:\/\/littlesvr.ca\/grumble\/2015\/02\/25\/using-libxmp-with-the-ndk-in-an-android-app\/","title":{"rendered":"Using libXMP with the NDK in an Android app"},"content":{"rendered":"<p>I&#8217;ve done this work to help out with the open source programming course at Seneca (DPS911).<\/p>\n<p>The goal: see if it&#8217;s possible (and realistic) to use <a href=\"http:\/\/www.adobe.com\/products\/xmp.html\">XMP<\/a> in an Android app.<\/p>\n<p>I&#8217;ve spent about 20 hours working on it, mostly going round in circles. The XMP library is shit developed by idiots and Android Studio is a pain in the neck because if Apple can be, so can Google.<\/p>\n<p>I can&#8217;t possibly log everything I&#8217;ve done in this post. The problem itself is complex to begin with: cross-compile a major library, and make it usable (via JNI) in an Android app. I was expecting that figuring out cross-compilers, toochains, ABIs, and APIs (which is hard) but the what made it nearly impossible was Android Studio with it&#8217;s Gradle nonsense.<\/p>\n<p>Every time I thought I solved a major problem another one took its place. But enough whining, here&#8217;s what I found:<\/p>\n<p><a href=\"https:\/\/github.com\/janrueegg\/xmp\">janrueegg&#8217;s Android port<\/a> of the library works. <a href=\"https:\/\/github.com\/asmith15\/xmp\">I&#8217;ve forked it on github<\/a> just so I know which version that was. Compiling it is simple, from the build directory:<\/p>\n<pre>export ANDROID_NDK=~\/programs\/android-ndk-r10d\/\r\nmake StaticReleaseAndroid_armeabi-v7a<\/pre>\n<p>Note the armeabi-v7a part, you&#8217;ll need to build several versions if you&#8217;re actually planning to release an app using it or if you want to run it in an x86 emulator. The result is two .ar files which I need to turn into one .a file (for a long time because of retarded Android documentation I was trying to make a .so file from it but that turned out to be unnecessary):<\/p>\n<pre>ar xv staticXMPCore.ar\r\nar xv staticXMPFiles.ar\r\nar cr libXMP.a *.o\r\ncp libXMP.a ~\/temp\/MyApplication\/app\/src\/main\/prebuilt\/<\/pre>\n<p>The following steps were so many and confusing I&#8217;ve simply posted the resulting <a href=\"https:\/\/github.com\/asmith15\/xmp-android-app-test\">sample application on github<\/a>. Pull it to better understand what I&#8217;m talking about.<\/p>\n<p>I added my libXMP.a to app\/src\/main\/prebuilt so I can build it in.<\/p>\n<p>I needed a .cpp file to do the JNI (app\/src\/main\/jni\/xmp-jni.cpp). That file needs to include the headers for the library. I&#8217;ve dumped all the contents of the directory xmp\/public\/include from the library into app\/src\/main\/jni.<\/p>\n<p>XMPlib.java is the glue between the JNI code and whatever wants to use it in the rest of my java.<\/p>\n<p>The best for last, the build part. After spending many hours trying to get it to work with Gradle (first tried using a static version of the library, then dynamic) I figured out that it&#8217;s just not going to work, I found snippets of google employees saying online they won&#8217;t even try to fix it. Instead I configured gradle to call ndk-build manually, which uses my own Android.mk and Application.mk<\/p>\n<p>I&#8217;ll just post the files here with a couple of notes, explaining it all would take too long.<\/p>\n<p><strong>build.gradle<\/strong>:<\/p>\n<pre>apply plugin: 'com.android.application'\r\n\r\nandroid {\r\n    compileSdkVersion 21\r\n    buildToolsVersion \"21.1.2\"\r\n\r\n    defaultConfig {\r\n        applicationId \"com.example.andrew.myapplication\"\r\n        minSdkVersion 15\r\n        targetSdkVersion 21\r\n        versionCode 1\r\n        versionName \"1.0\"\r\n\r\n        ndk {\r\n            moduleName \"xmp-jni\"\r\n            \/**\r\n             *  Need this so that I get STL support, needed by XMP.incl_cpp\r\n             * Sadly I think this makes the apk much bigger because\r\n             * of the included libgnustl_shared.so.\r\n            *\/\r\n            stl \"gnustl_shared\"\r\n            \/**\r\n             * gnustl_shared has exceptions turned off by default, but I need for XMP.\r\n            *\/\r\n            cFlags \"-fexceptions\"\r\n\r\n        }\r\n    }\r\n\r\n    sourceSets.main {\r\n        \/\/ Uncomment this line to use Android.mk instead\r\n        \/\/jni.srcDirs = [\"src\/main\/jni\"]\r\n        jni.srcDirs = []\r\n        jniLibs.srcDir \"src\/main\/jniLibs\"\r\n    }\r\n\r\n    \/\/ call regular ndk-build(.cmd) script from app directory\r\n    task ndkBuild(type: Exec, description: 'Build XMP JNI object files') {\r\n        commandLine '\/home\/andrew\/programs\/android-ndk-r10d\/ndk-build',\r\n                '-C', file('src\/main\/jni').absolutePath,\r\n                'NDK_OUT=..\/..\/..\/build\/intermediates\/ndk\/debug\/obj',\r\n                'NDK_LIBS_OUT=..\/..\/..\/build\/intermediates\/ndk\/debug\/lib'\r\n    }\r\n    tasks.withType(JavaCompile) {\r\n        compileTask -&gt; compileTask.dependsOn ndkBuild\r\n    }\r\n\r\n    buildTypes {\r\n        release {\r\n            minifyEnabled false\r\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\r\n        }\r\n    }\r\n}\r\n\r\ndependencies {\r\n    compile fileTree(dir: 'libs', include: ['*.jar'])\r\n    compile 'com.android.support:appcompat-v7:21.0.3'\r\n}<\/pre>\n<p>Note in particular the ndkBuild with its hard-coded paths.<\/p>\n<p><strong>Android.mk<\/strong>:<\/p>\n<pre># This makefile is only used if the magic string is uncommented in build.gradle\r\n#\r\n\r\nLOCAL_PATH := $(call my-dir)\r\n\r\n# static library info\r\nLOCAL_MODULE := libXMP\r\nLOCAL_SRC_FILES := ..\/prebuilt\/libXMP.a\r\ninclude $(PREBUILT_STATIC_LIBRARY)\r\n\r\n# wrapper info\r\ninclude $(CLEAR_VARS)\r\nLOCAL_MODULE    := xmp-jni\r\nLOCAL_SRC_FILES := xmp-jni.cpp\r\nLOCAL_CFLAGS    += -fexceptions\r\nLOCAL_STATIC_LIBRARIES := libXMP\r\nLOCAL_LDLIBS += -lz\r\ninclude $(BUILD_SHARED_LIBRARY)<\/pre>\n<p><strong>Application.mk<\/strong>:<\/p>\n<pre>APP_STL := gnustl_static\r\nAPP_PLATFORM := android-21<\/pre>\n<p><strong>local.properties<\/strong>:<\/p>\n<pre>sdk.dir=\/home\/andrew\/programs\/android-sdk-linux\r\nndk.dir=\/home\/andrew\/programs\/android-ndk-r10<\/pre>\n<p>Note that here also the paths need to be changed to work on your machine.<\/p>\n<p>I wish I could say the exercise was a huge success. I got to work everything I planned to, but it was really, really hard and even though I managed to call one XMP function from Java, that was the end of my patience. See the comment in the xmp-jni.cpp below:<\/p>\n<pre>#include &lt;jni.h&gt;\r\n\r\n#define UNIX_ENV 1 \/\/ for XMP_Environment.h\r\n\r\n#include &lt;string&gt;\r\n#define XMP_INCLUDE_XMPFILES 1 \/\/if using XMPFiles\r\n#define TXMP_STRING_TYPE std::string\r\n\/**\r\n\u00a0* Clients must compile XMP.incl_cpp to ensure that all client-side glue code is generated. Do this by\r\n\u00a0* including it in exactly one of your source files.\r\n\u00a0* This has to happen before the #include \"XMP.hpp\" below\r\n\u00a0*\/\r\n#include \"XMP.incl_cpp\"\r\n\r\n#include \"XMP.hpp\"\r\n\r\nconst char* doSomeXMPStuffInCPP()\r\n{\r\n\u00a0\u00a0\u00a0 SXMPMeta::Initialize();\r\n}\r\n\r\nextern \"C\" {\r\n\r\n\u00a0\u00a0\u00a0 jstring\r\n\u00a0\u00a0\u00a0 Java_com_example_andrew_myapplication_XMPlib_doSomeXMPStuff( JNIEnv* env,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 jobject thiz )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(!SXMPMeta::Initialize())\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return env-&gt;NewStringUTF(\"Could not initialize SXMPMeta\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ This returns true but causes the app to crash like this:\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ 02-25 18:27:12.852\u00a0\u00a0\u00a0 2960-2960\/com.example.andrew.myapplication A\/libc\ufe55 Fatal signal 6 (SIGABRT), code -6 in tid 2960 (w.myapplication)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/*if (!SXMPFiles::Initialize())\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return env-&gt;NewStringUTF(\"Could not initialize SXMPFiles.\");\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }*\/\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ Do XMP stuff here\r\n\r\n\u00a0\u00a0 \u00a0\/\/SXMPFiles::Terminate();\r\n\u00a0\u00a0 \u00a0\/\/SXMPMeta::Terminate();\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return env-&gt;NewStringUTF(\"XMP toolkit seems to be working!\");\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>I hope for your sake you don&#8217;t have to work with this crap, but if you do, here again are the URLs for the two projects:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/asmith15\/xmp\">https:\/\/github.com\/asmith15\/xmp<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/asmith15\/xmp-android-app-test\"> https:\/\/github.com\/asmith15\/xmp-android-app-test<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve done this work to help out with the open source programming course at Seneca (DPS911). The goal: see if it&#8217;s possible (and realistic) to use XMP in an Android app. I&#8217;ve spent about 20 hours working on it, mostly going round in circles. The XMP library is shit developed by idiots and Android Studio &hellip; <\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,4],"tags":[],"class_list":{"0":"entry","1":"post","2":"publish","3":"author-andrew","4":"post-1014","6":"format-standard","7":"category-creative-commons","8":"category-safeforseneca"},"_links":{"self":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts\/1014","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/comments?post=1014"}],"version-history":[{"count":11,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts\/1014\/revisions"}],"predecessor-version":[{"id":1025,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts\/1014\/revisions\/1025"}],"wp:attachment":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/media?parent=1014"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/categories?post=1014"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/tags?post=1014"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}