ade*_*190 115 c++ java java-native-interface cross-platform objective-c++
带NDK的 Android 支持C/C++代码和iOS,Objective-C++也支持,那么我如何用Android和iOS之间共享的本机C/C++代码编写应用程序?
ade*_*190 258
这个答案在我写完四年后很受欢迎,在这四年里,很多事情都发生了变化,所以我决定更新我的答案以更好地适应我们当前的现实.答案的想法没有改变; 实施已经改变了一点.我的英语也发生了变化,它已经有了很大的改进,所以现在每个人的答案都更容易理解.
请查看repo,以便下载并运行我将在下面显示的代码.
在我展示代码之前,请仔细考虑下图.
每个操作系统都有其UI和特性,因此我们打算在这方面为每个平台编写特定的代码.另一方面,我们打算使用C++编写所有逻辑代码,业务规则和可共享的东西,因此我们可以将相同的代码编译到每个平台.
在图中,您可以看到最低级别的C++层.所有共享代码都在此段中.最高级别是常规的Obj-C/Java/Kotlin代码,这里没有新闻,困难的部分是中间层.
iOS中间层很简单; 您只需要将项目配置为使用Obj-c的变体(即Objective-C++)进行构建,并且完全可以访问C++代码.
在Android方面,事情变得更加困难,Android上的Java和Kotlin语言都在Java虚拟机下运行.因此访问C++代码的唯一方法是使用JNI,请花点时间阅读JNI的基础知识.幸运的是,今天的Android Studio IDE在JNI方面有很大的改进,在编辑代码时会显示很多问题.
我们的示例是一个简单的应用程序,您可以将文本发送到CPP,并将该文本转换为其他内容并将其返回.想法是,iOS将发送"Obj-C",Android将从各自的语言发送"Java",CPP代码将创建一个文本,如下所示"cpp向<< text received >>问好".
首先,我们将创建共享的CPP代码,为此我们有一个简单的头文件,其中包含接收所需文本的方法声明:
#include <iostream>
const char *concatenateMyStringWithCppString(const char *myString);
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
一个有趣的好处是,我们也可以使用相同的代码用于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;
}
Run Code Online (Sandbox Code Playgroud)
要构建代码,您需要执行:
$ g++ Main.cpp Core.cpp -o main
$ ./main
cpp says hello to Unix
Run Code Online (Sandbox Code Playgroud)
是时候在移动端实施了.至于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
Run Code Online (Sandbox Code Playgroud)
该类有责任将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
Run Code Online (Sandbox Code Playgroud)
看看应用程序的外观:
现在是Android集成的时候了.Android使用Gradle作为构建系统,而使用CMake的C/C++代码.所以我们需要做的第一件事就是在gradle文件上配置CMake:
android {
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
...
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
第二步是添加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}
)
Run Code Online (Sandbox Code Playgroud)
您需要在CMake文件中添加将在项目中使用的CPP文件和头文件夹,在我们的示例中,我们将添加CPP文件夹和Core.h/.cpp文件.要了解有关C/C++配置的更多信息,请阅读它.
现在核心代码是我们应用程序的一部分,是时候创建桥梁,为了使事情更简单和有条理,我们创建一个名为CoreWrapper的特定类作为JVM和CPP之间的包装:
public class CoreWrapper {
public native String concatenateMyStringWithCppString(String myString);
static {
System.loadLibrary("native-lib");
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,此类有一个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;
}
}
Run Code Online (Sandbox Code Playgroud)
您将注意到的第一件事是extern "C"这部分是JNI正确使用我们的CPP代码和方法链接所必需的.你还会看到一些符号JNI使用与JVM的作品JNIEXPORT和JNICALL.为了理解这些东西的含义,有必要花点时间阅读它,因为本教程的目的只是将这些东西视为样板.
一个重要的事情,通常是许多问题的根源是方法的名称; 它需要遵循"Java_package_class_method"模式.目前,Android studio对它有很好的支持,因此它可以自动生成这个样板,并在它正确或未命名时显示给你.在我们的示例中,我们的方法名为"Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString",因为"ademar.androidioscppexample"是我们的包,所以我们替换"." 通过"_",CoreWrapper是我们链接本机方法的类,"concatenateMyStringWithCppString"是方法名称本身.
正如我们正确声明的方法是时候分析参数一样,第一个参数是JNIEnv它的指针,就是我们访问JNI的方式,我们很快就会进行转换.第二个是jobject它用来调用此方法的对象的实例.您可以将其视为java" this ",在我们的示例中我们不需要使用它,但我们仍然需要声明它.在这个jobject之后,我们将接收该方法的参数.因为我们的方法只有一个参数 - 一个字符串"myString",所以我们只有一个名字相同的"jstring".还要注意我们的返回类型也是一个jstring.这是因为我们的Java方法返回一个String,有关Java/JNI类型的更多信息,请阅读它.
最后一步是将JNI类型转换为我们在CPP端使用的类型.在我们的示例中,我们将转换为转换为CPP jstring的const char *发送,获取结果并转换回jstring.与JNI的所有其他步骤一样,并不难; 它只是用于电镀,所有的工作都是通过JNIEnv*我们在调用GetStringUTFChars和时收到的参数来完成的NewStringUTF.在我们的代码准备好在Android设备上运行之后,让我们一起来看看吧.
上面优秀答案中描述的方法可以通过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);
};
Run Code Online (Sandbox Code Playgroud)
并从 Swift 调用它:
class ViewController: UIViewController {
func send(friend: Contact) {
let c = Contact()
contact.sendMessage("Hello", friend)
contact.addTags(["a","b","c"])
contact.addFriends([friend])
}
}
Run Code Online (Sandbox Code Playgroud)
来自 Java:
class View {
private contact = new Contact;
public void send(Contact friend) {
contact.sendMessage("Hello", friend);
contact.addTags({"a","b","c"});
contact.addFriends({friend});
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
30474 次 |
| 最近记录: |