C/C++在Java項目、Android和Objective-C三大平臺下實現混合編程

来源:http://www.cnblogs.com/taoweiji/archive/2016/08/06/5729340.html
-Advertisement-
Play Games

Android和iOS開發都支持C++開發,可以一套代碼多平臺使用。同時C++難以反編譯的特性也可以為Android開髮帶來代碼的保密,另一native特性也可以提高代碼的運行效率。 一、為什麼使用C/C++ 1. 便於移植,用C/C++寫得庫可以方便在其他的平臺上再次使用。 2. 代碼的保護,由於 ...


Android和iOS開發都支持C++開發,可以一套代碼多平臺使用。同時C++難以反編譯的特性也可以為Android開髮帶來代碼的保密,另一native特性也可以提高代碼的運行效率。

一、為什麼使用C/C++

  1. 便於移植,用C/C++寫得庫可以方便在其他的平臺上再次使用。
  2. 代碼的保護,由於java層代碼很容易被反編譯,而C/C++庫反匯難度較大。
  3. 提高程式的執行效率,將要求高性能的應用邏輯使用C/C++開發,從而提高應用程式的執行效率。
  4. 訪問現有開源庫,需要訪問底層的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++項目

image

image

第二步:關聯JavaVM的Framework

路徑:

/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaNativeFoundation.framework/

image

第三步:創建頭文件,用於和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).

編譯後文件
image

根據自己的電腦環境,查找編譯後的文件,我的路徑是

/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
  1. STLport

STLport是一個開源的、多平臺的C標準庫實現。它提供一個C標準庫頭文件的完整集合以及對RTTI的支持。

  1. GNU STL

GNU標準C庫,也叫libstdc-v3,是Android NDK最全面的標準C運行庫。它是一個正在開發的、以實現ISO標準C庫為目標的開源項目。

gradle配置

  1. STL運行庫引用
  2. "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a"等平臺配置
  3. C++輸出logcat配置
  4. 一個編譯異常解決
  5. 異常捕獲

    // 生成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介面的包名不能帶有下劃線‘_’。
相關源代碼:

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文記載boot 導航條組件使用方法 導航條組件 導航條是在您的應用或網站中作為導航頁頭的響應式基礎組件。它們在移動設備上可以摺疊(並且可開可關),且在視口(viewport)寬度增加時逐漸變為水平展開模式。 屏幕解析度變化時,頁面導航呈現不同效果,變小時會出現漢堡包按鈕,將頁面導航部分隱藏,以下拉 ...
  • × 目錄 [1]定義 [2]創建 [3]組成[4]引用 前面的話 javascript中的難點是函數、對象和繼承,前面已經介紹過函數系列。從本系列開始介紹對象部分,本文是該系列的第一篇——初識對象 對象定義 javascript的基本數據類型包括undefined、null、boolean、stri ...
  • 前言 這一章的內容學到了事件隊列和非同步的API。js只是運行在其他應用程式的腳本語言。js即依賴於應用程式,也獨立與應用程式。可以使它可以在多平臺,多種環境上運行。ECMAScript標準中沒有關於併發的說明。這章討論的是一些常用的方法,使用事件和非同步API是js編程的基礎部分。非同步API,有set... ...
  • 寫在前面 1.在客戶端、服務端架構中,HTTP協議是主流通信技術; 2.HTTP協議的無狀態特性,節省帶寬,較少伺服器的負載,緩衝技術具有重要的運用;這裡主要講解在客戶端瀏覽器中angular如何讀寫緩存... 如何實現 1.angular提供了ngCookies模塊來實現讀寫緩存的操作,基於ang ...
  • HTML基本元素(HTML框架、HTML表單、HTML5表單新屬性) ...
  • (1)瞭解背景:歷史、現狀、發展、應用領域、特點 ——要瞭解我要學的這門技術/語言的來龍去脈,尤其是現狀,這樣我們才知道學習它的價值等。 (2)搭建運行/測試環境 ——前端還好,要是後臺語言的話據說光搭建環境有的就需要半天,畢竟這些東西做好了,我們才能看到效果,事半功倍。 (3)編寫Hello Wo ...
  • 本以為解決跨域上傳後沒有問題了,不成想被測試找出一個問題,那就是在手機上拍照上傳後圖片會旋轉。很頭痛,不過沒有辦法,問題還是需要解決的。在查閱了一系列資料後我找到了相應的解決方案,利用exif.js獲取圖片旋轉的方向,然後再轉過來圖片,之後再上傳。這個方案需要修改前面的腳本,同樣的,由於要傳base ...
  • 先舉個簡單的例子, var myCanvas = document.getElementById("myCanvas"); var context= myCanvas.getContext("2d"); context.beginPath(); context.moveTo(150, 50); co ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...