Android 및 iOS에 동일한 C ++ 코드를 사용하는 방법은 무엇입니까?


119

NDK 가있는 Android 는 C / C ++ 코드를 지원하고, Objective-C ++ 가있는 iOS 도 지원합니다. 그렇다면 Android와 iOS간에 공유되는 네이티브 C / C ++ 코드를 사용하여 애플리케이션을 작성하려면 어떻게해야합니까?


1
cocos2d-x 프레임 워크를 사용해보세요
glo

@glo 괜찮아 보이지만 프레임 워크없이 C ++를 사용하는 "제외 된 JNI"라는 좀 더 일반적인 것을 찾고 있습니다.
ademar111190 2013-08-20

답변:


273

최신 정보.

이 답변은 제가 쓴 후 4 년이 지났는데도 꽤 인기가 있습니다.이 4 년 동안 많은 것이 바뀌었기 때문에 저는 현재 현실에 더 잘 맞도록 답변을 업데이트하기로 결정했습니다. 대답 아이디어는 변하지 않습니다. 구현이 약간 변경되었습니다. 제 영어도 바뀌었고 많이 향상되었으므로 이제 모든 사람이 대답을 더 이해할 수 있습니다.

아래에 보여줄 코드를 다운로드하고 실행할 수 있도록 저장소를 살펴보십시오 .

대답

코드를보기 전에 다음 다이어그램을 많이 살펴 보시기 바랍니다.

아치

각 OS에는 UI와 특성이 있으므로 이와 관련하여 각 플랫폼에 특정 코드를 작성하려고합니다. 다른 한편으로는 모든 논리 코드, 비즈니스 규칙 및 공유 할 수있는 것들이 C ++를 사용하여 작성하려고하므로 각 플랫폼에 동일한 코드를 컴파일 할 수 있습니다.

다이어그램에서 가장 낮은 수준의 C ++ 계층을 볼 수 있습니다. 모든 공유 코드는이 세그먼트에 있습니다. 가장 높은 수준은 일반 Obj-C / Java / Kotlin 코드이며 여기에는 뉴스가 없습니다. 어려운 부분은 중간 계층입니다.

iOS 쪽의 중간 레이어는 간단합니다. Objective-C ++ 로 알려진 Obj-c의 변형을 사용하여 빌드하도록 프로젝트를 구성하기 만하면 되며 C ++ 코드에 액세스 할 수 있습니다.

안드로이드 쪽에서는 자바와 코 틀린 모두 자바 가상 머신에서 실행되는 자바와 코 틀린이 더 어려워졌습니다. 따라서 C ++ 코드에 액세스하는 유일한 방법은 JNI를 사용하는 것이므로 시간을내어 JNI의 기본 사항을 읽어보십시오. 다행히 오늘날의 Android Studio IDE는 JNI 측면에서 크게 개선되었으며 코드를 편집하는 동안 많은 문제가 표시됩니다.

단계별 코드

우리의 샘플은 CPP에 텍스트를 보내는 간단한 앱으로, 해당 텍스트를 다른 것으로 변환하고 반환합니다. 아이디어는 iOS가 "Obj-C"를 전송하고 Android는 각각의 언어에서 "Java"를 전송하며 CPP 코드는 "cpp says hello to << text received >> " 라는 텍스트를 생성합니다 .

공유 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;
}

유닉스

흥미로운 보너스는 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 코드 (예 : 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 아이폰

기계적 인조 인간

이제 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 ++ 구성에 대한 자세한 내용은 읽어보십시오.

이제 핵심 코드가 앱의 일부입니다. 브리지를 생성 할 때입니다. 더 간단하고 체계적으로 만들기 위해 JVM과 CPP 사이의 래퍼가 될 CoreWrapper라는 특정 클래스를 생성합니다.

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

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

}

이 클래스에는 native메서드가 있으며이라는 네이티브 라이브러리를로드합니다 native-lib. 이 라이브러리는 우리가 생성하는 라이브러리이며, 결국 CPP 코드는 .soAPK에 포함 된 공유 객체 파일 이되고 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및 으로 작동하는 데 사용하는 일부 기호도 볼 수 있습니다 JNICALL. 이러한 것들의 의미를 이해하려면 시간을내어 읽어야합니다 .이 튜토리얼에서는 이러한 것들을 상용구로 간주합니다.

한 가지 중요한 것은 일반적으로 많은 문제의 원인이되는 방법의 이름입니다. "Java_package_class_method"패턴을 따라야합니다. 현재 Android 스튜디오는이를 지원하므로이 상용구를 자동으로 생성하고 올바른지 이름이 아닌지 표시 할 수 있습니다. 이 예제에서 메서드 이름은 "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString"입니다. "ademar.androidioscppexample"이 우리 패키지이기 때문에 "." "_"에 의해 CoreWrapper는 네이티브 메서드를 연결하는 클래스이고 "concatenateMyStringWithCppString"은 메서드 이름 자체입니다.

메소드를 올바르게 선언 했으므로 인수를 분석 할 때입니다. 첫 번째 매개 변수는 JNIEnvJNI 항목에 액세스 할 수있는 방법 의 포인터입니다 . 곧 보게 될 변환을 수행하는 것이 중요합니다. 두 번째는 jobject이 메서드를 호출하는 데 사용한 개체의 인스턴스입니다. 당신은 그것을 자바 " this " 로 생각할 수있다. 우리의 예에서는 그것을 사용할 필요가 없지만 우리는 여전히 그것을 선언 할 필요가있다. 이 jobject 후에 우리는 메소드의 인자를받을 것입니다. 우리의 메소드는 오직 하나의 인자 (String "myString")를 가지고 있기 때문에, 우리는 같은 이름을 가진 "jstring"만을 가지고 있습니다. 또한 반환 유형도 jstring입니다. Java 메서드가 문자열을 반환하기 때문입니다. Java / JNI 유형에 대한 자세한 내용은 읽어보십시오.

마지막 단계는 JNI 유형을 CPP 측에서 사용하는 유형으로 변환하는 것입니다. 이 예 에서는를 CPP로 변환 jstringconst char *전송으로 변환하고 결과를 가져와 jstring. JNI의 다른 모든 단계와 마찬가지로 어렵지 않습니다. 그것은 보일러 플레이트 일 뿐이며, 모든 작업은 JNIEnv*우리가 GetStringUTFCharsand를 호출 할 때받는 인수에 의해 수행됩니다 NewStringUTF. 코드가 Android 기기에서 실행될 준비가되면 살펴 보겠습니다.

AndroidStudio 기계적 인조 인간


7
위대한 설명
RED.Skull

9
나는 그것을 얻을하지 않습니다 -하지만 SO에 최고 품질의 답변 중 하나 일
마이클 로드리게스

16
@ ademar111190 지금까지 가장 유용한 게시물입니다. 이것은 닫히지 않아야합니다.
Jared Burrows

6
@JaredBurrows, 동의합니다. 재 개설에 투표했습니다.
OmnipotentEntity

3
@KVISH 먼저 Objective-C에서 래퍼를 구현해야합니다. 그런 다음 브리징 헤더 파일에 래퍼 헤더를 추가하여 신속하게 Objective-C 래퍼에 액세스합니다. 현재로서는 Swift에서 C ++에 직접 액세스 할 수 없습니다. 자세한 내용은 참조를 들어 stackoverflow.com/a/24042893/1853977
크리스

3

위의 훌륭한 답변에서 설명한 접근 방식 은 C ++ 헤더에서 직접 즉시 래퍼 코드를 생성 하는 Scapix Language Bridge에 의해 완전히 자동화 될 수 있습니다 . 다음은 예입니다 .

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});
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.