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

带有 NDK 的 Android 支持 C/C++ 代码,而带有 Objective-C++ 的 iOS 也支持,那么如何编写具有在 Android 和 iOS 之间共享的本机 C/C++ 代码的应用程序?


答案 1

更新。

即使在我写完它四年后,这个答案也很受欢迎,在这四年里,很多事情都发生了变化,所以我决定更新我的答案,以更好地适应我们当前的现实。答案的想法不会改变;实现发生了一些变化。我的英语也变了,提高了很多,所以答案现在每个人都更容易理解。

请查看存储库,以便您可以下载并运行我将在下面显示的代码。

答案

在我展示代码之前,请在下面的图表上做很多。

Arch

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

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

iOS端的中间层很简单;您只需要使用Obj-c的变体(称为Objective-C++来构建项目即可,仅此而已,您可以访问C++代码。

在Android方面,事情变得更加困难,两种语言,Java和Kotlin,在Android上,都在Java虚拟机下运行。因此,访问C++代码的唯一方法是使用JNI,请花点时间阅读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应用程序是一个典型的Obj-c应用程序,只有一个区别;文件是 而不是 。也就是说,它是一个Obj-C++应用程序,而不是Obj-C应用程序。.mm.m

为了建立一个更好的组织,我们创建 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代码,例如ViewController”

#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

看看应用程序的外观:

Xcode iPhone

人造人

现在是时候进行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文件和头文件夹的地方,在我们的示例中,我们正在添加文件夹和Core.h / .cpp文件。要了解有关 C/C++ 配置的更多信息,请阅读它。CPP

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

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

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

}

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

现在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;
}

}

您会注意到的第一件事是,这部分是JNI正确使用我们的CPP代码和方法链接所必需的。您还将看到 JNI 用于与 JVM 一起使用的一些符号,如 和 。为了理解这些东西的含义,有必要花点时间阅读它,对于本教程的目的,只需将这些东西视为样板。extern "C"JNIEXPORTJNICALL

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

由于我们正确声明了该方法,因此是时候分析参数了,第一个参数是指针,它是我们访问JNI内容的方式,这对于我们进行转换至关重要,您很快就会看到。第二个是它是您用于调用此方法的对象的实例。你可以把它想象成java的“this”,在我们的例子中,我们不需要使用它,但我们仍然需要声明它。在此 jobject 之后,我们将接收该方法的参数。因为我们的方法只有一个参数 - 字符串“myString”,所以我们只有一个同名的“jstring”。另请注意,我们的返回类型也是一个 jstring。这是因为我们的Java方法返回一个字符串,有关Java / JNI类型的更多信息,请阅读它。JNIEnvjobject

最后一步是将 JNI 类型转换为我们在 CPP 端使用的类型。在我们的示例中,我们将 转换为发送到 CPP,获取结果并转换回 。与JNI上的所有其他步骤一样,这并不难;它只是样板的,所有的工作都是由我们在调用 and 时收到的参数完成的。之后,我们的代码就可以在Android设备上运行了,让我们来看看。jstringconst char *jstringJNIEnv*GetStringUTFCharsNewStringUTF

AndroidStudio Android


答案 2

上述优秀答案中描述的方法可以通过Scapix Language Bridge完全自动化,Scapix Language Bridge直接从C++标头动态生成包装器代码。下面是一个示例

在C++中定义您的类:

#include <scapix/bridge/object.h>

class contact : public scapix::bridge::object<contact>
{
public:
    std::string name();
    void send_message(const std::string& msg, std::shared_ptr<contact> from);
    void add_tags(const std::vector<std::string>& tags);
    void add_friends(std::vector<std::shared_ptr<contact>> friends);
};

从 Swift 调用它:

class ViewController: UIViewController {
    func send(friend: Contact) {
        let c = Contact()

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}

来自Java:

class View {
    private contact = new Contact;

    public void send(Contact friend) {
        contact.sendMessage("Hello", friend);
        contact.addTags({"a","b","c"});
        contact.addFriends({friend});
    }
}

推荐