由於Android環境非常複雜,架構都是用Java,因此要使用C/C++都需要做很多配置,使用彙編的話需要做更多的工作。
我這邊使用的是最新的Android4.0的開發工具,NDK也是最新支援4.0的。這個NDK與老版本的有一些比較明顯的不同。
由於我用的是Mac OS X,因此配置起來比瘟抖死上的要容易許多,你不需要再裝些雜七雜八的第三方工具,直接可以使用你下載好的NDK。
首先,設定目標路徑——在你的Terminal中進入NDK的根目錄,隨後打NDK_PROJECT_PATH="<你要編譯的項目路徑>"。斷行符號,再輸入export NDK_PROJECT_PATH
斷行符號。
這裡要注意的是NDK_PROJECT_PATH=後面的路徑需要加引號,否則無效。
由於NDK預設支援的預設編譯選項僅支援ARMv5到ARMv5TE架構,因此如果要使用比較進階的特性的話有兩種方法:
1、你有辦法將TARGET_ARCH_ABI的值變為armeabi-v7a,俺自己試了一下,木有成功。因此可以使用第二種方法,更簡單便捷:
2、在你的NDK目錄下,找到toolchains,然後找到arm-linux-androideabi-x.y.z目錄,在進去可以發現setup.mk檔案。找到-march=armv7-a,將上面的神馬#ifdef都去掉,下面的#endif也都刪了。這樣就能確保編譯器使用ARMv7A來編譯。
完成上述操作之後我們就可以先用最簡單的方式來寫彙編了,即內聯彙編——
static int my_thumb(int dummy){ __asm__("movw r0, #1001 \t\n" "movw r12, #2020 \t\n" "add r0, r0, r12 \t\n" "bx lr"); return dummy;}jstringJava_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ){ my_thumb(0); return (*env)->NewStringUTF(env, "Hello from JNI !");}
上述代碼其實就是基於NDK內建的hello-jni項目修改的。最後用ndk-build可以成功編譯。
上面一段代碼是編譯器預設的使用Thumb/Thumb-2編譯的,因此我裡面寫的內聯彙編的指令都是Thumb代碼。
我們下面將講述一下如何使用ARM代碼並使用NEON指令集。
首先,在你的Android.mk中修改LOCAL_SRC_FILES,要將源檔案名稱後面添加.neon尾碼,比如LOCAL_SRC_FILES := hello-jni.c改成LOCAL_SRC_FILES := hello-jni.c.neon。
這裡要注意的是你真正的源檔案名稱不要修改,就修改LOCAL_SRC_FILES這個符號的值即可。
然後我們再添加新的變數,來指示ARM GCC使用ARM指令集來編譯——LOCAL_ARM_MODE := arm
這樣就OK了。我們修改一下代碼:
static int my_arm(int dummy){ __asm__("movw r0, #1001 \t\n" "movw r12, #2020 \t\n" "add r0, r0, r12 \t\n" "vdup.32 q0, r0 \t\n" "bx lr"); return dummy;}jstringJava_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ){ my_arm(0); return (*env)->NewStringUTF(env, "Hello from JNI !");}
使用ndk-build後能正常通過編譯。
最後再上個最最高端的。直接寫彙編檔案。NDK帶有GAS工具,因此按常理,完全可以寫彙編檔案。一般彙編檔案的尾碼名為.s,因此我們建立一個xxx.s檔案即可。
然後我這邊建立一個叫hey.s。在Android.mk中將這個檔案添加上:LOCAL_SRC_FILES += hey.s.neon
我們這裡看到,為了能在彙編檔案中使用NEON指令集,我們在這裡也把.neon尾碼添加上。彙編器的makefile也認這個標識。
我們編輯hey.s檔案:
.text
.align 4
.arm
.globl my_real_arm
my_real_arm:
add r0, r0, #256
vmov q0, q1
vdup.32 q0, r0
bx lr
這裡要注意的是,在Apple的彙編器中,函數名要加首碼底線,而NDK中提供的彙編器則不需要。
我們修改一下hello-jni.c,把這函數調進去:
extern void my_real_arm(int i); static int my_arm(int dummy){ __asm__("movw r0, #1001 \t\n" "movw r12, #2020 \t\n" "add r0, r0, r12 \t\n" "vdup.32 q0, r0 \t\n" "bx lr"); return dummy;}jstringJava_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ){ my_real_arm(0); my_arm(0); return (*env)->NewStringUTF(env, "Hello from JNI !");}
當然,我們為了確保編譯器能夠正確地將ARM和Thumb指令集做混合串連,我們可以在剛才的setup.mk中強制在TARGET_CFLAGS標誌裡加上-mthumb-interwork
在Windows作業系統中實驗,終於發現,只要將Application.mk中的APP_ABI中的標誌,將armeabi去掉,僅留下armeabi-v7a就能順利使用neon了。這樣不需要修改setup.mk,也不需要將Sample中的那個標誌判斷去掉,非常方便。
下面列一下可用的Android.mk編譯設定檔:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := HelloNeonLOCAL_SRC_FILES := helloneon.cLOCAL_ARM_MODE := armTARGET_CFLAGS += -mthumb-interworkTARGET_CFLAGS += -std=gnu11TARGET_CFLAGS += -O3ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)LOCAL_CFLAGS := -DHAVE_NEON=1LOCAL_SRC_FILES += neontest.s.neonLOCAL_ARM_NEON := trueendifLOCAL_LDLIBS := -lloginclude $(BUILD_SHARED_LIBRARY)$(call import-module,cpufeatures)
在使用JNI時,只需要在你當前項目工程目錄中添加jni檔案夾,然後在裡面根據Sample中所提供的檔案布局來做即可。當你用ndk-build(Windows下要在cygwin控制台中用ndk-build.cmd)來編譯時間, 如果構建成功,則會在libs檔案夾內產生一個libXXX.so。然後用Eclipse ADT重新開啟你的項目工程,就會發現jni檔案目錄以及產生好的so檔案都會在你的工程檔案目錄中展現出來。當然,你後面也能直接在Eclipse IDE下編輯.s彙編檔案,這樣就更容易閱讀了。
最後,在Android彙編器中如果要注釋某條語句,那麼必須使用C89/90中的注釋符——/* ... */
用分號以及後來C++98中所引入的//形式都不管用。
在最新的NDK版本android-ndk-r8d中加入了ARM-Linux GCC4.7以及當前大紅大紫的LLVM Clang3.1。不過由於LLVM Clang3.1的很多編譯選項與GCC有不少區別,因此在使用Clang3.1的時候需要自己去配置相應的編譯選項。這個版本的NDK預設的編譯器工具鏈使用的是GCC4.6版本。如果要使用GCC4.7,那麼可以在Application.mk檔案中添加NDK_TOOLCHAIN_VERSION=4.7
;如果要使用Clang3.1,那麼可以在Application.mk中添加NDK_TOOLCHAIN_VERSION=clang3.1
。下面給出一個合法的Application.mk的內容:
# Build with LLVM Clang3.1#NDK_TOOLCHAIN_VERSION=clang3.1# Build with ARM-Linux GCC4.7NDK_TOOLCHAIN_VERSION=4.7# Build only ARMv7-A machine code.APP_ABI := armeabi-v7a