Java 如何为 Android 和 iOS 使用相同的 C++ 代码?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18334547/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use the same C++ code for Android and iOS?
提问by ademar111190
Android with NDKhas support to C/C++ code and iOS with Objective-C++has support too, so how can I write applications with native C/C++ code shared between Android and iOS?
带有NDK 的Android支持 C/C++ 代码,带有Objective-C++ 的iOS也支持,那么我如何使用在 Android 和 iOS 之间共享的本机 C/C++ 代码编写应用程序?
采纳答案by ademar111190
Update.
更新。
This answer is quite popular even four years after I write it, in this four years a lot of things has changed, so I decided to update my answer to fit better our current reality. The answer idea does not change; the implementation has changed a little. My English also has changed, it has improved a lot, so the answer is more understandable to everyone now.
这个答案在我写它四年后仍然很受欢迎,在这四年里很多事情都发生了变化,所以我决定更新我的答案以更好地适应我们当前的现实。答题思路不变;实施略有变化。我的英文也变了,进步了很多,所以现在答案大家都比较明白了。
Please take a look at the reposo you can download and run the code I'll show below.
请查看repo,以便您可以下载并运行我将在下面显示的代码。
The Answer
答案
Before I show the code, please take a lot on the following diagram.
在我展示代码之前,请仔细阅读下图。
Each OS has its UI and peculiarities, so we intend to write specific code to each platform in this regard. In other hands, all logic code, business rules, and things that can be shared we intend to write using C++, so we can compile the same code to each platform.
每个操作系统都有其 UI 和特性,因此我们打算在这方面为每个平台编写特定的代码。另一方面,我们打算使用 C++ 编写所有逻辑代码、业务规则和可以共享的东西,因此我们可以将相同的代码编译到每个平台。
In the diagram, you can see the C++ layer at the lowest level. All shared code is in this segment. The highest level is regular Obj-C / Java / Kotlin code, no news here, the hard part is the middle layer.
在图中,您可以看到最底层的 C++ 层。所有共享代码都在此段中。最高层是常规的 Obj-C/Java/Kotlin 代码,这里没有消息,难的是中间层。
The middle layer to iOS side is simple; you only need to configure your project to build using a variant of Obj-c know as Objective-C++and it is all, you have access to C++ code.
iOS端的中间层很简单;您只需要配置您的项目以使用称为Objective-C++的 Obj-c 变体来构建,就这样,您可以访问 C++ 代码。
The thing became harder on the Android side, both languages, Java and Kotlin, on Android, run under a Java Virtual Machine. So the only way to access C++ code is using JNI, please take time to read the basics of JNI. Fortunately, today's Android Studio IDE has vast improvements on JNI side, and a lot of problems are shown to you while you edit your code.
Android 方面的事情变得更难了,Java 和 Kotlin 这两种语言在 Android 上都在 Java 虚拟机下运行。所以访问 C++ 代码的唯一方法是使用JNI,请花时间阅读 JNI 的基础知识。幸运的是,今天的 Android Studio IDE 在 JNI 方面有了很大的改进,并且在您编辑代码时向您展示了很多问题。
The code by steps
代码按步骤
Our sample is a simple app that you send a text to CPP, and it converts that text to something else and returns it. The idea is, iOS will send "Obj-C" and Android will send "Java" from their respective languages, and the CPP code will create a text as a follow "cpp says hello to << text received >>".
我们的示例是一个简单的应用程序,您可以将文本发送到 CPP,它会将文本转换为其他内容并返回。这个想法是,iOS 将发送“Obj-C”,Android 将发送来自各自语言的“Java”,CPP 代码将创建一个文本作为“cpp 向<<接收到的文本>>问好”。
Shared CPP code
共享 CPP 代码
First of all, we are going to create the shared CPP code, doing it we have a simple header file with the method declaration that receives the desired text:
首先,我们将创建共享 CPP 代码,这样做我们有一个简单的头文件,其中包含接收所需文本的方法声明:
#include <iostream>
const char *concatenateMyStringWithCppString(const char *myString);
And the CPP implementation:
和 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
Unix
An interesting bonus is, we can also use the same code for Linux and Mac as well as other Unix systems. This possibility is especially useful because we can test our shared code faster, so we are going to create a Main.cpp as follow to execute it from our machine and see if the shared code is working.
一个有趣的好处是,我们还可以为 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;
}
To build the code, you need to execute:
要构建代码,您需要执行:
$ g++ Main.cpp Core.cpp -o main
$ ./main
cpp says hello to Unix
iOS
IOS
It is time to implement on the mobile side. As far as iOS has a simple integration we are starting with it. Our iOS app is a typical Obj-c app with only one difference; the files are .mm
and not .m
. i.e. It is an Obj-C++ app, not an Obj-C app.
是时候在移动端实施了。至于 iOS 有一个简单的集成,我们从它开始。我们的 iOS 应用程序是一个典型的 Obj-c 应用程序,只有一个区别;文件是.mm
和不是.m
。即它是一个 Obj-C++ 应用程序,而不是一个 Obj-C 应用程序。
To a better organization, we create the CoreWrapper.mm as follow:
为了更好的组织,我们创建 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
This class has the responsibility to convert CPP types and calls to Obj-C types and calls. It is not mandatory once you can call CPP code on any file you want on Obj-C, but it helps to keep the organisation, and outside your wrapper files you maintain a complete Obj-C styled code, only the wrappers file become CPP styled.
该类负责将 CPP 类型和调用转换为 Obj-C 类型和调用。一旦您可以在 Obj-C 上的任何文件上调用 CPP 代码,这不是强制性的,但它有助于保持组织,并且在您的包装文件之外,您维护完整的 Obj-C 样式代码,只有包装文件成为 CPP 样式.
Once your wrapper is connected to the CPP code, you can use it as a standard Obj-C code, e.g. ViewController"
一旦您的包装器连接到 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
Take a look of how the app looks:
看看应用程序的外观:
Android
安卓
Now it is time for Android integration. Android uses Gradle as the build system, and to C/C++ code it uses CMake. So the first thing we need to do is to configure the CMake on gradle file:
现在是 Android 集成的时候了。Android 使用 Gradle 作为构建系统,对于 C/C++ 代码,它使用 CMake。所以我们需要做的第一件事就是在 gradle 文件上配置 CMake:
android {
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
...
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
}
}
...
}
And the second step is to add the CMakeLists.txt file:
第二步是添加 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}
)
The CMake file is where you need to add the CPP files and header folders you will use on the project, on our example, we are adding the CPP
folder and the Core.h/.cpp files. To know more about C/C++ configuration please read it.
您需要在 CMake 文件中添加将在项目中使用的 CPP 文件和头文件夹,在我们的示例中,我们将添加CPP
文件夹和 Core.h/.cpp 文件。要了解有关 C/C++ 配置的更多信息,请阅读它。
Now the core code is part of our app it is time to create the bridge, to make the things more simple and organized we create a specific class named CoreWrapper to be our wrapper between JVM and CPP:
现在核心代码是我们应用程序的一部分,是时候创建桥接器了,为了使事情更简单和有条理,我们创建了一个名为 CoreWrapper 的特定类作为 JVM 和 CPP 之间的包装器:
public class CoreWrapper {
public native String concatenateMyStringWithCppString(String myString);
static {
System.loadLibrary("native-lib");
}
}
Note this class has a native
method and loads a native library named native-lib
. This library is the one we create, in the end, the CPP code will become a shared object .so
File embed in our APK, and the loadLibrary
will load it. Finally, when you call the native method, the JVM will delegate the call to the loaded library.
请注意,此类有一个native
方法并加载了一个名为native-lib
. 这个库是我们创建的,最终,CPP 代码会成为.so
嵌入我们 APK 中的共享对象文件,并且loadLibrary
会加载它。最后,当您调用本机方法时,JVM 会将调用委托给加载的库。
Now the most strange part of Android integration is the JNI; We need a cpp file as follow, in our case "native-lib.cpp":
现在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;
}
}
The first thing you will notice is the extern "C"
this part is necessary to JNI work correctly with our CPP code and method linkages. You will also see some symbols JNI uses to works with JVM as JNIEXPORT
and JNICALL
. To you understand the meaning of those things, it is necessary to take a time and read it, for this tutorial purposes just consider these things as boilerplate.
您会注意到的第一件事是,extern "C"
这部分是 JNI 与我们的 CPP 代码和方法链接正确工作所必需的。您还将看到 JNI 用于与 JVM 一起工作的一些符号,如JNIEXPORT
和JNICALL
。为了理解这些东西的含义,有必要花时间阅读它,为了本教程的目的,只需将这些东西视为样板。
One significant thing and usually the root of a lot of problems is the name of the method; it needs to follow the pattern "Java_package_class_method". Currently, Android studio has excellent support for it so it can generate this boilerplate automatically and show to you when it is correct or not named. On our example our method is named "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" it is because "ademar.androidioscppexample" is our package, so we replace the "." by "_", CoreWrapper is the class where we are linking the native method and "concatenateMyStringWithCppString" is the method name itself.
一件重要的事情,通常也是很多问题的根源是方法的名称;它需要遵循“Java_package_class_method”模式。目前,Android studio 对它有很好的支持,所以它可以自动生成这个样板,并在它正确或未命名时显示给你。在我们的示例中,我们的方法名为“Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString”,因为“ademar.androidioscppexample”是我们的包,所以我们替换了“.”。通过“_”,CoreWrapper 是我们链接本地方法的类,“concatenateMyStringWithCppString”是方法名称本身。
As we have the method correctly declared it is time to analyze the arguments, the first parameter is a pointer of JNIEnv
it is the way we have access to JNI stuff, it is crucial to we make our conversions as you will see soon. The second is a jobject
it is the instance of the object you had used to call this method. You can think it as the java "this", on our example we do not need to use it, but we still need to declare it. After this jobject, we are going to receive the arguments of the method. Because our method has only one argument - a String "myString", we have only a "jstring" with the same name. Also notice that our return type is also a jstring. It is because our Java method returns a String, for more information about Java/JNI types please read it.
由于我们正确声明了方法,现在是分析参数的时候了,第一个参数是JNIEnv
它的指针,这是我们访问 JNI 内容的方式,正如您很快将看到的那样,这对我们进行转换至关重要。第二个是jobject
它是您用来调用此方法的对象的实例。您可以将其视为java“ this”,在我们的示例中我们不需要使用它,但我们仍然需要声明它。在此作业之后,我们将接收该方法的参数。因为我们的方法只有一个参数——一个字符串“myString”,我们只有一个同名的“jstring”。还要注意我们的返回类型也是一个 jstring。这是因为我们的 Java 方法返回一个 String,有关 Java/JNI 类型的更多信息,请阅读它。
The final step is to convert the JNI types to the types we use on CPP side. On our example, we are transforming the jstring
to a const char *
sending it converted to the CPP, getting the result and converting back to jstring
. As all other steps on JNI, it is not hard; it is only boilerplated, all the work is done by the JNIEnv*
argument we receive when we call the GetStringUTFChars
and NewStringUTF
. After it our code is ready to run on Android devices, lets take a look.
最后一步是将 JNI 类型转换为我们在 CPP 端使用的类型。在我们的示例中,我们将 转换jstring
为const char *
发送它转换为 CPP,获取结果并转换回jstring
. 与 JNI 上的所有其他步骤一样,这并不难;它只是样板化的,所有的工作都是由JNIEnv*
我们调用GetStringUTFChars
and时收到的参数完成的NewStringUTF
。之后我们的代码就可以在 Android 设备上运行了,让我们来看看。
回答by Boris Rasin
Approach described in the excellent answer above can be completely automated by Scapix Language Bridgewhich generates wrapper code on the fly directly from C++ headers. Here is an example:
上面优秀答案中描述的方法可以由Scapix Language Bridge完全自动化,它直接从 C++ 头文件动态生成包装器代码。这是一个例子:
Define your class in 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);
};
And call it from Swift:
并从 Swift 调用它:
class ViewController: UIViewController {
func send(friend: Contact) {
let c = Contact()
contact.sendMessage("Hello", friend)
contact.addTags(["a","b","c"])
contact.addFriends([friend])
}
}
And from Java:
来自Java:
class View {
private contact = new Contact;
public void send(Contact friend) {
contact.sendMessage("Hello", friend);
contact.addTags({"a","b","c"});
contact.addFriends({friend});
}
}