如何在Android和iOS上使用相同的C++代码?

2024-01-06

安卓与NDK https://developer.android.com/ndk/index.html支持 C/C++ 代码和 iOS目标C++ https://en.wikipedia.org/wiki/Objective-C#Objective-C.2B.2B也有支持,那么如何使用 Android 和 iOS 之间共享的本机 C/C++ 代码编写应用程序呢?


Update.

这个答案在我写下四年后仍然很受欢迎,在这四年里很多事情都发生了变化,所以我决定更新我的答案以更好地适应我们当前的现实。答案思路不变;实施方式发生了一些变化。我的英语也发生了变化,进步了很多,所以现在这个答案大家都更容易理解了。

请看一下the repo https://github.com/ademar111190/CppAndroidIosExample这样您就可以下载并运行我将在下面显示的代码。

答案

在展示代码之前,请仔细阅读下图。

每个操作系统都有其 UI 和特性,因此我们打算在这方面为每个平台编写特定的代码。另一方面,所有逻辑代码、业务规则和可以共享的东西我们打算使用C++编写,因此我们可以将相同的代码编译到每个平台。

在图中,您可以看到最低级别的 C++ 层。所有共享代码都在该段中。最高层是常规的 Obj-C / Java / Kotlin 代码,这里没有新闻,最困难的部分是中间层。

iOS端的中间层比较简单;你只需要配置你的项目来使用 Obj-c 的变体进行构建,称为目标C++ https://en.wikipedia.org/wiki/Objective-C#Objective-C.2B.2B仅此而已,您可以访问 C++ 代码。

在 Android 方面,事情变得更加困难,Android 上的 Java 和 Kotlin 这两种语言都在 Java 虚拟机下运行。所以访问 C++ 代码的唯一方法是使用JNI https://en.wikipedia.org/wiki/Java_Native_Interface,请花时间阅读JNI的基础知识。幸运的是,今天的 Android Studio IDE 在 JNI 方面有了巨大的改进,并且在您编辑代码时会向您展示很多问题。

代码步骤

我们的示例是一个简单的应用程序,您可以将文本发送到 CPP,它会将该文本转换为其他内容并将其返回。这个想法是,iOS 将发送“Obj-C”,Android 将发送来自各自语言的“Java”,CPP 代码将创建一个文本,如下所示“cpp 向你打招呼”>".

共享 CPP 代码

首先,我们将创建共享 CPP 代码,这样做我们有一个简单的头文件,其中包含接收所需文本的方法声明:

#include <iostream>

const char *concatenateMyStringWithCppString(const char *myString);

以及 CPP 的实现:

#include <string.h>
#include "Core.h"

const char *CPP_BASE_STRING = "cpp says hello to %s";

const char *concatenateMyStringWithCppString(const char *myString) {
    char *concatenatedString = new char[strlen(CPP_BASE_STRING) + strlen(myString)];
    sprintf(concatenatedString, CPP_BASE_STRING, myString);
    return concatenatedString;
}

Unix

一个有趣的好处是,我们还可以在 Linux 和 Mac 以及其他 Unix 系统上使用相同的代码。这种可能性特别有用,因为我们可以更快地测试我们的共享代码,因此我们将创建一个 Main.cpp 如下,从我们的机器上执行它并查看共享代码是否正常工作。

#include <iostream>
#include <string>
#include "../CPP/Core.h"

int main() {
  std::string textFromCppCore = concatenateMyStringWithCppString("Unix");
  std::cout << textFromCppCore << '\n';
  return 0;
}

要构建代码,您需要执行:

$ g++ Main.cpp Core.cpp -o main
$ ./main 
cpp says hello to Unix

iOS

是时候在移动端实现了。至于 iOS 有一个简单的集成,我们就从它开始。我们的 iOS 应用程序是一个典型的 Obj-c 应用程序,只有一个区别:这些文件是.mm并不是.m。即它是 Obj-C++ 应用程序,而不是 Obj-C 应用程序。

为了更好的组织,我们创建 CoreWrapper.mm 如下:

#import "CoreWrapper.h"

@implementation CoreWrapper

+ (NSString*) concatenateMyStringWithCppString:(NSString*)myString {
    const char *utfString = [myString UTF8String];
    const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
    NSString *objcString = [NSString stringWithUTF8String:textFromCppCore];
    return objcString;
}

@end

此类负责将 CPP 类型和调用转换为 Obj-C 类型和调用。一旦您可以在 Obj-C 上调用任何您想要的文件上的 CPP 代码,这不是强制性的,但它有助于保持组织,并且在包装文件之外您可以维护完整的 Obj-C 样式代码,只有包装文件成为 CPP 样式。

一旦您的包装器连接到 CPP 代码,您就可以将其用作标准 Obj-C 代码,例如视图控制器”

#import "ViewController.h"
#import "CoreWrapper.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *label;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* textFromCppCore = [CoreWrapper concatenateMyStringWithCppString:@"Obj-C++"];
    [_label setText:textFromCppCore];
}

@end

看看该应用程序的外观:

Android

现在是 Android 集成的时候了。 Android 使用 Gradle 作为构建系统,对于 C/C++ 代码,它使用 CMake。所以我们需要做的第一件事是在 gradle 文件上配置 CMake:

android {
...
externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}
...
defaultConfig {
    externalNativeBuild {
        cmake {
            cppFlags "-std=c++14"
        }
    }
...
}

第二步是添加 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.4.1)

include_directories (
    ../../CPP/
)

add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    ../../CPP/Core.h
    ../../CPP/Core.cpp
)

find_library(
    log-lib
    log
)

target_link_libraries(
    native-lib
    ${log-lib}
)

CMake 文件是您需要添加将在项目中使用的 CPP 文件和头文件夹的位置,在我们的示例中,我们添加CPP文件夹和 Core.h/.cpp 文件。要了解更多有关 C/C++ 配置的信息,请read it. https://developer.android.com/studio/projects/add-native-code.html

现在核心代码是我们应用程序的一部分,是时候创建桥梁了,为了使事情更加简单和有组织,我们创建一个名为 CoreWrapper 的特定类作为 JVM 和 CPP 之间的包装器:

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

    static {
        System.loadLibrary("native-lib");
    }

}

注意这个类有一个native方法并加载名为的本机库native-lib。这个库是我们创建的,最终CPP代码将成为一个共享对象.so文件嵌入到我们的 APK 中,并且loadLibrary将加载它。最后,当您调用本机方法时,JVM 会将调用委托给加载的库。

现在Android集成中最奇怪的部分是JNI;我们需要一个 cpp 文件,如下所示,在我们的例子中为“native-lib.cpp”:

extern "C" {

JNIEXPORT jstring JNICALL Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString(JNIEnv *env, jobject /* this */, jstring myString) {
    const char *utfString = env->GetStringUTFChars(myString, 0);
    const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
    jstring javaString = env->NewStringUTF(textFromCppCore);
    return javaString;
}

}

您首先会注意到的是extern "C"这部分对于 JNI 与我们的 CPP 代码和方法链接正确工作是必要的。您还将看到 JNI 用于与 JVM 配合使用的一些符号,如下所示JNIEXPORT and JNICALL。要理解这些事物的含义,需要花时间read it https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html,出于本教程的目的,只需将这些内容视为样板即可。

一件重要的事情,通常是许多问题的根源是方法的名称;它需要遵循“Java_package_class_method”模式。目前,Android studio 对其有很好的支持,因此它可以自动生成此样板,并在命名正确或未命名时向您显示。在我们的示例中,我们的方法被命名为“Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString”,这是因为“ademar.androidioscppexample”是我们的包,所以我们替换“.”。通过“_”,CoreWrapper 是我们链接本机方法的类,“concatenateMyStringWithCppString”是方法名称本身。

由于我们已经正确声明了方法,因此是时候分析参数了,第一个参数是一个指针JNIEnv这是我们访问 JNI 内容的方式,这对于我们进行转换至关重要,您很快就会看到。第二个是一个jobject它是您用来调用此方法的对象的实例。你可以把它想象成java”this”,在我们的示例中,我们不需要使用它,但我们仍然需要声明它。在这个 jobobject 之后,我们将接收该方法的参数。因为我们的方法只有一个参数 - 一个字符串“myString”,我们只有一个同名的“jstring”。还要注意,我们的返回类型也是 jstring。这是因为我们的 Java 方法返回一个 String,有关 Java/JNI 类型的更多信息,请read it. https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html

最后一步是将 JNI 类型转换为我们在 CPP 端使用的类型。在我们的例子中,我们正在改变jstring to a const char *将其发送转换为CPP,获取结果并转换回jstring。与 JNI 上的所有其他步骤一样,这并不难;它只是样板文件,所有工作都是由JNIEnv*当我们调用时我们收到的参数GetStringUTFChars and NewStringUTF。之后我们的代码就可以在 Android 设备上运行了,让我们看一下。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在Android和iOS上使用相同的C++代码? 的相关文章

  • Java - 从 XML 文件读取注释

    我必须从 XML 文件中提取注释 我找不到使用 JDOM 或其他东西来让它们使用的方法 目前我使用 Regex 和 FileReader 但我不认为这是正确的方法 您可以使用 JDOM 之类的东西从 XML 文件中获取注释吗 或者它仅限于元
  • Karaf / Maven - 无法解决:缺少需求 osgi.wiring.package

    我无法在 Karaf 版本 3 0 1 中启动捆绑包 该包是使用 Maven 构建的并导入gson http mvnrepository com artifact com google code gson gson 2 3 1 我按照要求将
  • 使用Java绘制维恩图

    我正在尝试根据给定的布尔方程绘制维恩图 例如 a AND b AND c我想在 Android 手机上执行此操作 因此我需要找到一种使用 Java 来执行此操作的方法 我找到了一个完美的小部件 它可以完成我在这方面寻找的一切布尔代数计算器
  • 在 azure blob 存储中就地创建 zip 文件

    我将文件存储在 Blob 存储帐户内的一个容器中 我需要在第二个容器中创建一个 zip 文件 其中包含第一个容器中的文件 我有一个使用辅助角色和 DotNetZip 工作的解决方案 但由于 zip 文件的大小最终可能达到 1GB 我担心在进
  • 让网络摄像头在 OpenCV 中工作

    我正在尝试让我的网络摄像头在 Windows 7 64 位中的 OpenCV 版本 2 2 中捕获视频 但是 我遇到了一些困难 OpenCV 附带的示例二进制文件都无法检测到我的网络摄像头 最近我发现这篇文章表明答案在于重新编译一个文件 o
  • 如何在多线程应用程序中安全地填充数据并 Refresh() DataGridView?

    我的应用程序有一个 DataGridView 对象和一个 MousePos 类型的列表 MousePos 是一个自定义类 它保存鼠标 X Y 坐标 类型为 Point 和该位置的运行计数 我有一个线程 System Timers Timer
  • 无需登录即可直接从 Alfresco 访问文件/内容

    我的场景是这样的 我有一个使用 ALFRESCO CMS 来显示文件或图像的 Web 应用程序 我正在做的是在 Java servlet 中使用用户名和密码登录 alfresco 并且我可以获得该登录的票证 但我无法使用该票证直接从浏览器访
  • 我可以限制分布式应用程序发出的请求吗?

    我的应用程序发出 Web 服务请求 提供商处理的请求有最大速率 因此我需要限制它们 当应用程序在单个服务器上运行时 我曾经在应用程序级别执行此操作 一个对象跟踪到目前为止已发出的请求数量 并在当前请求超出允许的最大负载时等待 现在 我们正在
  • 如何将NSTextView的格式化内容转换为字符串

    我需要将 NSTextView 的内容从 Mac 应用程序传输到 iOS 应用程序 我使用 XML 作为传输文件格式 所以我需要将 NSTextView 的内容 文本 字体 颜色等 保存为字符串 有什么办法可以做到这一点吗 一种方法是存档
  • .NET 和 Mono 之间的开发差异

    我正在研究 Mono 和 NET C 将来当项目开发时我们需要在 Linux 服务器上运行代码 此时我一直在研究 ASP NET MVC 和 Mono 我运行 Ubuntu 发行版 想要开发 Web 应用程序 其他一些开发人员使用 Wind
  • Unity3D - 将 UI 对象移动到屏幕中心,同时保持其父子关系

    我有一个 UI 图像 它的父级是 RectTransform 容器 该容器的父级是 UI 面板 而 UI 面板的父级是 Canvas 我希望能够将此 UI 图像移动到屏幕中心 即画布 同时保留父级层次结构 我的目标是将 UI 图像从中心动画
  • C:设置变量范围内所有位的最有效方法

    让我们来int举个例子 int SetBitWithinRange const unsigned from const unsigned to To be implemented SetBitWithinRange应该返回一个int其中所有
  • ObjectiveC 和 JavaScriptCore:使用这种调用回调的方法会导致内存问题吗?

    免责声明 这是一篇很长的文章 但对于那些努力使用新的 ObjectiveC JavascriptCore 框架并在 ObjC 和 JS 之间进行异步编码的人来说可能非常有价值 您好 我对 Objective C 非常陌生 正在将 javas
  • 记录类名、方法名和行号的性能影响

    我正在我的 java 应用程序中实现日志记录 以便我可以调试应用程序投入生产后可能出现的潜在问题 考虑到在这种情况下 人们不会奢侈地使用 IDE 开发工具 以调试模式运行事物或单步执行完整代码 因此在每条消息中记录类名 方法名和行号将非常有
  • 任何人都可以清楚地告诉如何在不使用像 这样的预定义函数的情况下找到带有小数值或小数值的指数吗? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 例如 2 0 5 1 414 所以想要 我是 c 的新手 所以请解释简单的逻辑 如果不是复杂的逻辑也足够了 在数学中 从整数取幂到实数
  • GCD 与自定义队列

    我想知道这两者的性能有什么区别 dispatch async dispatch get global queue DISPATCH QUEUE PRIORITY HIGH 0 perform complex operation dispat
  • 如何组合两个 lambda [重复]

    这个问题在这里已经有答案了 可能的重复 在 C 中组合两个 lambda 表达式 https stackoverflow com questions 1717444 combining two lamba expressions in c
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从
  • 如果找不到指定的图像文件,显示默认图像的最佳方式?

    我有一个普通的电子商务应用程序 我将 ITEM IMAGE NAME 存储在数据库中 有时经理会拼错图像名称 为了避免 丢失图像 IE 中的红色 X 每次显示产品列表时 我都会检查服务器中是否有与该产品相关的图像 如果该文件不存在 我会将其
  • 即使调整大小,如何获得屏幕的精确中间位置

    好的 这个问题有两部分 当我做一个JFrame 并在其上画一些东西 即使我将宽度设置为 400 并使其在一个项目击中它时 当然 允许项目宽度 它会反弹回来 但由于某种原因 它总是偏离屏幕约 10 个像素 有没有办法解决这个问题 或者我只需要

随机推荐