Android और iOS के लिए समान C ++ कोड का उपयोग कैसे करें?


119

NDK वाले Android के पास C / C ++ कोड का समर्थन है और Objective-C ++ के साथ iOS का भी समर्थन है, इसलिए मैं Android और iOS के बीच साझा किए गए मूल C / C ++ कोड के साथ आवेदन कैसे लिख सकता हूं?


1
कोशिश Cocos2d-x ढांचा
glo

@ यह अच्छा लगता है, लेकिन मैं एक और अधिक सामान्य चीज की तलाश कर रहा हूं, सी + + का उपयोग फ्रेमवर्क के बिना, "जेएनआई को छोड़कर, जाहिर है"।
ademar111190

जवाबों:


273

अपडेट करें।

यह उत्तर लिखने के चार साल बाद भी काफी लोकप्रिय है, इस चार वर्षों में बहुत सारी चीजें बदल गई हैं, इसलिए मैंने अपनी वर्तमान वास्तविकता को बेहतर बनाने के लिए अपने उत्तर को अपडेट करने का निर्णय लिया। उत्तर विचार नहीं बदलता है; कार्यान्वयन थोड़ा बदल गया है। मेरी अंग्रेजी भी बदल गई है, इसमें बहुत सुधार हुआ है, इसलिए उत्तर अब सभी के लिए अधिक समझ में आता है।

कृपया रेपो पर एक नज़र डालें ताकि आप नीचे दिखाए गए कोड को डाउनलोड और चला सकें।

उत्तर

इससे पहले कि मैं कोड दिखाऊं, कृपया निम्नलिखित चित्र पर बहुत कुछ करें।

मेहराब

प्रत्येक OS की अपनी UI और ख़ासियत होती है, इसलिए हम इस संबंध में प्रत्येक प्लेटफ़ॉर्म पर विशिष्ट कोड लिखने का इरादा रखते हैं। दूसरे हाथों में, सभी लॉजिक कोड, व्यावसायिक नियम और साझा की जाने वाली चीजें हम C ++ का उपयोग करके लिखने का इरादा रखते हैं, इसलिए हम प्रत्येक प्लेटफ़ॉर्म पर समान कोड संकलित कर सकते हैं।

आरेख में, आप न्यूनतम स्तर पर C ++ परत देख सकते हैं। सभी साझा कोड इस सेगमेंट में हैं। उच्चतम स्तर नियमित ओबज-सी / जावा / कोटलिन कोड है, यहां कोई खबर नहीं है, कठिन हिस्सा मध्य परत है।

IOS की ओर की मध्य परत सरल है; आपको केवल उद्देश्य-सी ++ के रूप में ज्ञात ओज-सी के एक संस्करण का उपयोग करके अपनी परियोजना को कॉन्फ़िगर करने की आवश्यकता है और यह सब है, आपके पास सी ++ कोड तक पहुंच है।

एंड्रॉइड पर बात कठिन हो गई, दोनों भाषाओं, जावा और एंड्रॉइड पर कोटलिन, एक जावा वर्चुअल मशीन के तहत चलती हैं। इसलिए C ++ कोड को एक्सेस करने का एकमात्र तरीका JNI का उपयोग करना है , कृपया JNI की मूल बातें पढ़ने के लिए समय निकालें। सौभाग्य से, आज के एंड्रॉइड स्टूडियो आईडीई में जेएनआई की ओर से व्यापक सुधार हैं, और जब आप अपना कोड संपादित करते हैं तो बहुत सारी समस्याएं आपको दिखाई देती हैं।

चरणों द्वारा कोड

हमारा नमूना एक साधारण ऐप है जिसे आप सीपीपी को एक पाठ भेजते हैं, और यह उस पाठ को किसी और चीज़ में परिवर्तित करता है और उसे वापस करता है। विचार यह है कि, आईओएस "ओबज-सी" भेजेगा और एंड्रॉइड अपनी संबंधित भाषाओं से "जावा" भेजेगा, और सीपीपी कोड एक पाठ बनाएगा जैसा कि एक अनुवर्ती "सीपीपी हैलो से << पाठ प्राप्त >> " कहता है ।

CPP कोड साझा किया

सबसे पहले, हम साझा सीपीपी कोड बनाने जा रहे हैं, यह करते हुए हमारे पास एक सरल हेडर फ़ाइल है जिसमें विधि घोषणा है जो वांछित पाठ प्राप्त करती है:

#include <iostream>

const char *concatenateMyStringWithCppString(const char *myString);

और सीपीपी कार्यान्वयन:

#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;
}

यूनिक्स

एक दिलचस्प बोनस है, हम लिनक्स और मैक के साथ-साथ अन्य यूनिक्स सिस्टम के लिए भी समान कोड का उपयोग कर सकते हैं। यह संभावना विशेष रूप से उपयोगी है क्योंकि हम अपने साझा कोड का तेजी से परीक्षण कर सकते हैं, इसलिए हम अपने मशीन से इसे निष्पादित करने के लिए एक 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 ऐप केवल एक अंतर के साथ एक विशिष्ट ओबज-सी ऐप है; फ़ाइलें हैं .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 प्रकार और कॉल में परिवर्तित करे। एक बार जब आप ओब्ज-सी पर अपनी इच्छित फ़ाइल पर CPP कोड कॉल कर सकते हैं, तो यह अनिवार्य नहीं है, लेकिन यह संगठन को बनाए रखने में मदद करता है, और आपकी रैपर फ़ाइलों के बाहर आप पूरी तरह से Obj-C स्टाइल कोड बनाए रखते हैं, केवल रैपर फ़ाइल CPP कोडित हो जाती है ।

एक बार जब आपका आवरण सीपीपी कोड से जुड़ा होता है, तो आप इसे मानक ओब्ज-सी कोड के रूप में उपयोग कर सकते हैं, उदाहरण के लिए ViewCroller "

#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 आई - फ़ोन

एंड्रॉयड

अब यह एंड्रॉइड इंटीग्रेशन का समय है। एंड्रॉइड ग्रैडल को बिल्ड सिस्टम के रूप में उपयोग करता है, और C / C ++ कोड के लिए यह 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 ++ कॉन्फ़िगरेशन के बारे में अधिक जानने के लिए कृपया इसे पढ़ें।

अब कोर कोड हमारे ऐप का हिस्सा है, यह पुल बनाने का समय है, चीजों को और अधिक सरल और व्यवस्थित बनाने के लिए हम जेवीएम और सीपीपी के बीच हमारे रैपर बनने के लिए एक विशिष्ट वर्ग बनाते हैं जिसका नाम है कोरवैपेर।

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

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

}

ध्यान दें कि इस वर्ग में एक nativeविधि है और एक देशी पुस्तकालय का नाम है native-lib। यह लाइब्रेरी वह है जिसे हम बनाते हैं, अंत में, सीपीपी कोड .soहमारे एपीके में एक साझा ऑब्जेक्ट फ़ाइल एम्बेड बन जाएगा , और loadLibraryइसे लोड करेगा। अंत में, जब आप मूल विधि को कॉल करते हैं, तो JVM लोड की गई लाइब्रेरी को कॉल सौंप देगा।

अब एंड्रॉइड एकीकरण का सबसे अजीब हिस्सा जेएनआई है; हमें निम्न प्रकार से 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"यह है कि यह भाग हमारे सीपीपी कोड और विधि लिंकेज के साथ जेएनआई के लिए सही ढंग से काम करने के लिए आवश्यक है। तुम भी रूप में JVM के साथ काम करता है के लिए कुछ प्रतीकों JNI का उपयोग करता है देखेंगे JNIEXPORTऔर JNICALL। आप उन चीजों के अर्थ को समझने के लिए, एक समय लेना और इसे पढ़ना आवश्यक है , इस ट्यूटोरियल उद्देश्यों के लिए बस इन चीजों को बॉयलरप्लेट के रूप में समझें।

एक महत्वपूर्ण बात और आमतौर पर बहुत सारी समस्याओं की जड़ विधि का नाम है; इसे "Java_package_class_method" पैटर्न का पालन करने की आवश्यकता है। वर्तमान में, एंड्रॉइड स्टूडियो के पास इसके लिए उत्कृष्ट समर्थन है इसलिए यह इस बॉयलरप्लेट को स्वचालित रूप से उत्पन्न कर सकता है और आपको यह दिखा सकता है कि यह सही है या नाम नहीं है। हमारे उदाहरण पर हमारी पद्धति का नाम "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" है, क्योंकि यह "ademar.androidioscppppample" हमारा पैकेज है, इसलिए हम इसका स्थान लेते हैं। " "_" द्वारा, CoreWrapper वह वर्ग है जहाँ हम मूल विधि को जोड़ रहे हैं और "concatenateMyStringWithCppString" विधि नाम है।

जैसा कि हमारे पास विधि सही ढंग से घोषित है यह तर्कों का विश्लेषण करने का समय है, पहला पैरामीटर इसका एक संकेतक JNIEnvहै जिस तरह से हमारे पास जेएनआई सामान तक पहुंच है, यह महत्वपूर्ण है कि हम अपना रूपांतरण करें, जैसा कि आप जल्द ही देखेंगे। दूसरा jobjectयह है कि यह उस वस्तु का उदाहरण है जिसे आपने इस पद्धति को कॉल करने के लिए उपयोग किया था। आप इसे " यह " जावा के रूप में सोच सकते हैं , हमारे उदाहरण पर हमें इसका उपयोग करने की आवश्यकता नहीं है, लेकिन हमें अभी भी इसे घोषित करने की आवश्यकता है। इस काम के बाद, हम विधि के तर्क प्राप्त करने जा रहे हैं। क्योंकि हमारी पद्धति का केवल एक तर्क है - एक स्ट्रिंग "मायस्ट्रिंग", हमारे पास केवल एक ही नाम के साथ "जस्ट्रिंग" है। यह भी ध्यान दें कि हमारा रिटर्न टाइप भी एक जेस्ट्रिंग है। ऐसा इसलिए है क्योंकि जावा / जेएनआई प्रकारों के बारे में अधिक जानकारी के लिए कृपया हमारे जावा विधि एक स्ट्रिंग लौटाता है, कृपया इसे पढ़ें।

अंतिम चरण जेएनआई प्रकारों को सीपीपी की ओर से हमारे द्वारा उपयोग किए जाने वाले प्रकारों में बदलना है। हमारे उदाहरण पर, हम बदलने रहे हैं jstringएक करने के लिए const char *भेज रहा यह सीपीपी में परिवर्तित, परिणाम हो रही है और वापस करने के लिए परिवर्तित jstring। जेएनआई पर अन्य सभी चरणों के रूप में, यह कठिन नहीं है; यह केवल boilerplated है, सब काम करके किया जाता है JNIEnv*तर्क हमें प्राप्त होने वाली है जब हम कहते हैं GetStringUTFCharsऔर NewStringUTF। इसके बाद हमारा कोड एंड्रॉइड डिवाइस पर चलने के लिए तैयार है, आइए एक नज़र डालते हैं।

AndroidStudio एंड्रॉयड


7
महान व्याख्या
RED.Skull

9
मुझे यह नहीं मिलता है - लेकिन SO
माइकल रोड्रिग्स

16
@ ademar111190 अब तक की सबसे उपयोगी पोस्ट है। इसे बंद नहीं किया जाना चाहिए था।
जारेड ब्रास

6
@JaredBurrows, मैं समाप्‍त करता हूं। फिर से खोलने के लिए वोट दिया।
ओमनीपोएंटेंटिटी

3
@ आपको पहले ऑब्जेक्टिव-सी में रैपर को लागू करना होगा, फिर आप अपने ब्रिजिंग हेडर फाइल में रैपर हेडर को जोड़कर स्विफ्ट में ऑब्जेक्टिव-सी रैपर को एक्सेस करेंगे। अब तक स्विफ्ट में C ++ को सीधे एक्सेस करने का कोई तरीका नहीं है। अधिक जानकारी के लिए देखें stackoverflow.com/a/24042893/1853977
क्रिस

3

ऊपर दिए गए उत्कृष्ट उत्तर में वर्णित दृष्टिकोण को स्कैपर लैंग्वेज ब्रिज द्वारा पूरी तरह से स्वचालित किया जा सकता है जो सी ++ हेडर से सीधे फ्लाई पर रैपर कोड उत्पन्न करता है। यहाँ एक उदाहरण है :

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);
};

और स्विफ्ट से इसे कॉल करें:

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

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

और जावा से:

class View {
    private contact = new Contact;

    public void send(Contact friend) {
        contact.sendMessage("Hello", friend);
        contact.addTags({"a","b","c"});
        contact.addFriends({friend});
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.