C/C++在Java、Android和Objective-C三大平臺下實現混合程式設計
Android和iOS開發都支援C++開發,可以一套程式碼多平臺使用。同時C++難以反編譯的特性也可以為Android開發帶來程式碼的保密,另一native特性也可以提高程式碼的執行效率。
一、為什麼使用C/C++
- 便於移植,用C/C++寫得庫可以方便在其他的平臺上再次使用。
- 程式碼的保護,由於java層程式碼很容易被反編譯,而C/C++庫反匯難度較大。
- 提高程式的執行效率,將要求高效能的應用邏輯使用C/C++開發,從而提高應用程式的執行效率。
- 訪問現有開源庫,需要訪問底層的API或引用一些只有C/C++的庫。
二、開發工具介紹
儘管Android Studio可以同時編寫C++和Java程式碼,寫完就可以編譯執行,但是對聯想和錯誤提示並不是非常友好,個人建議C++的整體程式碼使用Visual Studio或Xcode編譯開發,聯想功能非常友好,編譯速度很快,除錯也非常方便。
- Visual Studio(PC)
- Xcode(Mac)
- Android Studio(多平臺)
- eclipse(多平臺)
三、第一行程式碼
1. 如何在Objective-C專案中使用C++;
在Objective-C使用C/C++非常簡單,僅僅需要把.m字尾的檔案改成.mm即可使用C++,我們通常不會把.mm的檔案寫到整個專案都有,而是設計一個介面,用來做兩個語言之間的橋樑,他們之間的互動僅僅在這個介面。
要點:String型別轉換
// Objective-C(NSString) -> C++(std::string) NSString * ocString = @"Hello World,OC"; std::string cppString = [ocString UTF8String]; std::cout<<cppString<<std::endl; // C++(std::string) -> Objective-C(NSString) std::string cppString2 = "Hello World,C++"; NSString *ocString2= [NSString stringWithCString:cppString2.c_str() encoding:[NSString defaultCStringEncoding]]; NSLog(@"%@",ocString2);
記得要include相關的檔案
#include <stdio.h> #include <iostream>
2.在普通的JAVA專案中使用JNI程式設計
由於我是在MAC下辦公,所以這裡就介紹如何在MAC下進行JNI開發,在Windows平臺下的Virtual Studio也很簡單。
第一步:在Xcode下建立一個普通的C++專案
第二步:關聯JavaVM的Framework
路徑:
/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaNativeFoundation.framework/
第三步:建立標頭檔案,用於和Java互動 cn_taoweiji_nativemodule_NativeDemo.h
#include <JavaVM/jni.h> #ifndef _Included_cn_taoweiji_nativemodule_NativeDemo #define _Included_cn_taoweiji_nativemodule_NativeDemo #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add (JNIEnv *, jclass, jint, jint); JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_say (JNIEnv *, jclass, jstring); JNIEXPORT jstring JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_getInfo (JNIEnv *, jclass); JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_nativeToJava (JNIEnv *, jclass, jobject); #ifdef __cplusplus } #endif #endif
第四步:建立實現 NativeDemo.cpp
#include "cn_taoweiji_nativemodule_NativeDemo.h" #include <string> JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add(JNIEnv *, jclass, jint param1, jint param2) { jint result = param1 + param2; return result; } JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_say(JNIEnv *env, jclass, jstring param) { // std::string -> jstring const char *param_char = env->GetStringUTFChars(param, NULL); std::string str = param_char; } JNIEXPORT jstring JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_getInfo(JNIEnv *env, jclass) { // jstring -> std::string std::string str = "Hi,I am C++."; jstring result = env->NewStringUTF(str.c_str()); return result; } JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_nativeToJava(JNIEnv * env, jclass, jobject obj) { // 呼叫Java方法 jclass cls = env->FindClass("cn/taoweiji/nativemodule/NativeDemo"); //int subtract(int param1, int param2) -> (II)I jmethodID mid = env->GetMethodID(cls, "subtract", "(II)I"); int result = (int) env->CallIntMethod(obj, mid, 10, 2); // std::cout<<result<<std::endl; //常見型別轉換例子 //String getInfo(); //-> ()Ljava/lang/String; //PackageInfo getPackageInfo(String packageName, int flags); //-> (Ljava/lang/String;I)Landroid/content/pm/PackageInfo;; }
第五步:編譯生成JNI檔案,按 ⌘+B (Product -> Build).
編譯後檔案
根據自己的電腦環境,查詢編譯後的檔案,我的路徑是
/Users/Wiki/Library/Developer/Xcode/DerivedData/DEMO_MAC_JNI-clxymnzifegyfaajsaattzgxqfbr/Build/Products/Debug/DEMO_MAC_JNI
第六步:編寫JNI介面
package cn.taoweiji.nativemodule; /** * 包名和類名稱一定要和前面的C++標頭檔案對應 * cn_taoweiji_nativemodule_NativeDemo.h */ public class NativeDemo { public static native int add(int param1, int param2); public static native void say(String name); public static native String getInfo(); public static native void nativeToJava(NativeDemo nativeDemo); public int subtract(int param1, int param2) { System.out.println("NativeDemo:" + String.format("%s - %s = %s", param1, param2, param1 - param2)); return param1 - param2; } }
第七步:呼叫C++
public class Main { static { System.load("/Users/Wiki/Library/Developer/Xcode/DerivedData/DEMO_MAC_JNI-clxymnzifegyfaajsaattzgxqfbr/Build/Products/Debug/DEMO_MAC_JNI"); } public static void main(String[] args) { System.out.println("Hello World!"); int result = NativeDemo.add(1, 2); System.out.println("1+2=" + String.valueOf(result)); NativeDemo.say("Hello,I am Java."); System.out.println("getInfo:" + NativeDemo.getInfo()); NativeDemo.nativeToJava(new NativeDemo()); } }
3. 在ANDROID專案中使用NDK
Android的JNI開發,C++檔案必須編寫在獨立的module裡面,Java介面程式碼可以編寫在app(module),也可以和C++放在同一個module,通過gradle關聯。詳細程式碼請自行下載demo瀏覽
gradle配置(NativeModule) apply plugin: 'com.android.library' android { compileSdkVersion 23 buildToolsVersion "24.0.0 rc2" defaultConfig { minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk { moduleName "joyrun" stl "stlport_static" ldLibs "log"//用於解決__android_log_print abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a" //add -fexceptions to allow throw error //add -w to "format not a string literal and no format arguments [-Werror=format-security" cFlags "-w -fexceptions" } } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) }
編寫JNI介面
// NativeDemo.java package cn.taoweiji.nativemodule; public class NativeDemo { public static native int add(int param1, int param2); }
編寫C++介面程式碼,JNI檔案目錄預設是module/src/main/jni,可以通過gradle配置改變
// cn_taoweiji_nativemodule_NativeDemo.h #include <jni.h> #ifndef _Included_cn_taoweiji_nativemodule_NativeDemo #define _Included_cn_taoweiji_nativemodule_NativeDemo #ifdef __cplusplus extern "C" { #endif /* * Class: cn_taoweiji_nativemodule_NativeDemo * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add(JNIEnv *, jclass, jint, jint); #ifdef __cplusplus } #endif #endif
// NativeDemo.cpp #include "cn_taoweiji_nativemodule_NativeDemo.h" JNIEXPORT jint JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_add(JNIEnv *, jclass, jint param1, jint param2) { jint result = param1 + param2; return result; }
呼叫
// 靜態載入 static { System.loadLibrary("joyrun"); } //呼叫 int result = NativeDemo.add(1,2); Log.i("1+2=",String.valueOf(result));
四、NDK開發要點
編譯檔案分析
將NativeModule編譯後生成的aar檔案字尾改成.zip解壓,可以發現裡面有個jni檔案,開啟可以看到”armeabi”, “armeabi-v7a”, “x86”, “x86_64”, “arm64-v8a”等資料夾,再開啟可以看到是以lib字首的so格式檔案,這就是編譯後的native層檔案,我們平常引用的第三方庫(百度地圖)也是要新增這些檔案到我們的libs資料夾,不同的名稱代表著不同平臺相關的編譯檔案,市面上大多數的手機都是arm架構CPU,x86架構的手機幾乎沒人用(genymotion模擬器屬於x86平臺),所以我們通常釋出APP不會考慮x86平臺,僅僅新增armeabi檔案即可,但是在開發過程中建議也新增x86的so檔案,以方便我們在模擬器上執行。
執行庫
Android平臺帶有一個微型的C執行庫支援庫,成為系統執行庫。該執行庫不支援一下特性:C標準庫、異常支援、RTTI支援。NDK提供了用於補充系統執行庫功能的一些額外的C++執行庫。
C++執行庫 | C++異常支援 | C++RTTI | C++標準庫 |
---|---|---|---|
系統庫 | No | No | No |
GAbi++ | No | Yes | No |
STLport | No | Yes | Yes |
GNU STL | Yes | Yes | Yes |
- STLportSTLport是一個開源的、多平臺的C標準庫實現。它提供一個C標準庫標頭檔案的完整集合以及對RTTI的支援。
- GNU STLGNU標準C庫,也叫libstdc-v3,是Android NDK最全面的標準C執行庫。它是一個正在開發的、以實現ISO標準C庫為目標的開源專案。
gradle配置
- STL執行庫引用
- “armeabi”, “armeabi-v7a”, “x86”, “x86_64”, “arm64-v8a”等平臺配置
- C++輸出logcat配置
- 一個編譯異常解決
- 異常捕獲
// 生成so檔案的名稱 moduleName "joyrun" // 引入STL標準庫 stl "stlport_static"//gnustl_static //用於解決__android_log_print ldLibs "log" abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a" //新增編譯的平臺 //add -fexceptions to allow throw error //add -w to "format not a string literal and no format arguments [-Werror=format-security" cFlags "-w -fexceptions"
LOGCAT輸出
#include <android/log.h> #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "tag_joyrun", __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "tag_joyrun", __VA_ARGS__) LOGE("Hello Logcat");
型別轉換
// std::string -> jstring std::string str = "Hello World"; jstring result = env->NewStringUTF(str.c_str()); // jstring -> std::string jstring param; const char *param_char = env->GetStringUTFChars(param, NULL); std::string str = param_char; // jboolean 兩個值 JNI_TRUE、JNI_FALSE
C++呼叫JAVA程式碼
//Java
public static native void nativeToJava(NativeDemo nativeDemo); public int subtract(int param1, int param2) { Log.e("NativeDemo", String.format("%s - %s = %s", param1, param2, param1 - param2)); return param1 - param2; }
//C++
JNIEXPORT void JNICALL Java_cn_taoweiji_nativemodule_NativeDemo_nativeToJava(JNIEnv * env, jclass, jobject obj) { // 呼叫Java方法 jclass cls = env->FindClass("cn/taoweiji/nativemodule/NativeDemo"); jmethodID mid = env->GetMethodID(cls, "subtract", "(II)I"); int result = (int) env->CallIntMethod(obj, mid, 10, 2); //常見型別轉換例子 //String getInfo(); //-> ()Ljava/lang/String; //PackageInfo getPackageInfo(String packageName, int flags); //-> (Ljava/lang/String;I)Landroid/content/pm/PackageInfo;; }
一鍵生成從JAVA到C++介面程式碼指令碼
檔案:autojavah.sh
#!/bin/sh export ProjectPath=$(cd "../$(dirname "$1")"; pwd) export TargetClassName="co.runner.app.jni.NativeDemo" export SourceFile="${ProjectPath}/app/src/main/java" export TargetPath="${ProjectPath}/jni-joyrun/src/main/jni" cd "${SourceFile}" javah -d ${TargetPath} -classpath "${SourceFile}" "${TargetClassName}" echo -d ${TargetPath} -classpath "${SourceFile}" "${TargetClassName}"
五、C++物件導向及標準庫入門
C++類定義
// Demo.hpp #ifndef Demo_hpp #define Demo_hpp #include <stdio.h> #include <string> class Demo{ public: std::string name; int age = 0; void say(); static int add(int param1,int param2) { return param1 + param2; } }; #endif /* Demo_hpp */
類方法的實現
// Demo.cpp #include "Demo.hpp" #include <iostream> void Demo::say() { std::cout<<"name = "<<name<<",age = "<<age<<std::endl; }
物件建立及訪問物件的成員
//物件建立 Demo d1; Demo * d2 = new Demo; // 運算子訪問 d1.say(); // 指標訪問 d2->say(); //靜態函式訪問 int result = Demo::add(1,2); std::cout<<"1 + 2 = "<<result<<std::endl;
LIST連結串列
//include相關檔案 #include <stdio.h> #include <iostream> #include <list> #include "Demo.hpp" // 連結串列定義 std::list<Demo> * demos = new std::list<Demo>; Demo * demo = new Demo; demo->name = "Wiki"; demo->age = 24; // 在後面插入資料 demos->push_back(*demo); demo = new Demo; demo->name = "Wiki2"; demo->age = 25; // 在前面插入資料 demos->push_front(*demo); // 順序連結串列遍歷 for (std::list<Demo>::iterator iter = demos->begin(); iter != demos->end(); ++iter) { iter->say(); } // 反順序連結串列遍歷 for (std::list<Demo>::reverse_iterator iter = demos->rbegin(); iter != demos->rend(); ++iter) { iter->say(); } // 獲取指定位置元素 std::list<Demo>::iterator iter = demos->begin(); advance(iter, 1); iter->say();
指標、引用和值
在C++中,函式可以用許多不同的方法傳遞引數,比如通過指標、引用或是直接傳值。
// 通過指標 void handle1(Demo *p); // 通過引用 void handle1(Demo& p); // 通過值 void handle1(Demo *demo);
堆和棧的理解
- 棧(stack):由作業系統自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧;
- 堆(heap): 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收,分配方式倒是類似於連結串列。
Demo d1;//棧 Demo * d2 = new Demo;//堆 char c; //棧上分配 char *p = new char[3]; //堆上分配,將地址賦給了p;
垃圾回收
new和C++ delete運算子用於動態分配和撤銷記憶體的運算子,自己new出來的物件就要通過delete釋放記憶體。
delete demos;
六、開發中常用的方法及注意事項(坑)
- 時間戳獲取方法。
long time = time(0);
- 注意so檔案防盜用問題。
- 儘量避免呼叫與平臺有關的方法,避免移植帶來問題。
- JNI介面的包名不能帶有下劃線‘_’。
相關原始碼:
- Android: https://github.com/taoweiji/DEMO_NDK
- Objective-C: https://github.com/taoweiji/DEMO_CPP_OC
- MAC-JNI: https://github.com/taoweiji/DEMO_MAC_JNI
相關文章
- XCode 中 Swift / Objective-C / C / C++ 混合程式設計XCodeSwiftObjectC++程式設計
- Swift和Objective-C混合程式設計——Swift呼叫OCSwiftObject程式設計
- Swift和Objective-C混合程式設計——OC呼叫SwiftSwiftObject程式設計
- Qt 與 Objective-C 的混合程式設計QTObject程式設計
- QML之C++混合程式設計C++程式設計
- C++ & Intel MKL 混合程式設計C++Intel程式設計
- C++程式設計實現C++程式設計
- Objective-C——實現物件導向程式設計Object物件程式設計
- 【混合程式設計】C/C++呼叫Fortran的DLL程式設計C++
- C/C++與Matlab混合程式設計初探C++Matlab程式設計
- C++:與C混合程式設計 CMake undefined reference toC++程式設計Undefined
- 一個在 Objective-C 和 Swift 中實現剖面導向程式設計的故事ObjectSwift程式設計
- linux下php實現C/C++擴充套件程式設計LinuxPHPC++套件程式設計
- OC/Swift/C/C++混合使用的程式設計姿勢SwiftC++程式設計
- Android混合程式設計:WebView實踐Android程式設計WebView
- Linux下C和matlab混合程式設計學習(1)LinuxMatlab程式設計
- Google C++程式設計風格指南(三):C++ 類GoC++程式設計
- socket程式設計實現tcp伺服器_C/C++程式設計TCP伺服器C++
- (轉)Linux下 C++呼叫C 實現socket網路通訊程式設計LinuxC++程式設計
- 用 C++ 實現類似於 JAVA HttpServlet 的程式設計介面C++JavaHTTPServlet程式設計
- 程式設計技術點滴三(C/C++)程式設計C++
- 純C++遊戲程式設計: Tic-Tac-Toe(三連棋遊戲)的實現C++遊戲程式設計
- 泛型程式設計在非C++語言中的實現之探討 (轉)泛型程式設計C++
- 在C語言中實現泛型程式設計C語言泛型程式設計
- C++程式設計C++程式設計
- C++ concurrency::task實現非同步程式設計(WindowsC++非同步程式設計Windows
- Visual C++ 6.0程式設計實現列印功能(轉)C++程式設計
- 實驗1 現代C++程式設計初體驗C++程式設計
- 全球有多少 C/C++ 程式設計師?Java 和 Python 又有多少?C++程式設計師JavaPython
- [C++]C++程式設計例項C++程式設計
- 深入實踐c++模板程式設計C++程式設計
- C++程式設計最佳實踐(轉)C++程式設計
- C++程式設計思想筆記之三 (轉)C++程式設計筆記
- 現代c++與模板超程式設計C++程式設計
- Groovy + Java 混合程式設計方案:GMavenJava程式設計Maven
- Java與Matlab混合程式設計JavaMatlab程式設計
- 狀態機程式設計思想:刪除程式碼註釋(支援C/C++和Java)程式設計C++Java
- 程式設計師接私活的7大平臺利器程式設計師