कंट्रोल सिंबल किसको दिखे

सिंबल की दिखने की सुविधा को कंट्रोल करने से, APK का साइज़ कम हो सकता है और लोड होने में लगने वाला समय कम हो सकता है. साथ ही, इससे अन्य डेवलपर को, लागू करने की जानकारी पर गलती से निर्भरता से बचने में मदद मिल सकती है. ऐसा करने का सबसे बेहतर तरीका, वर्शन स्क्रिप्ट का इस्तेमाल करना है.

वर्शन स्क्रिप्ट, ईएलएफ़ लिंकर की एक सुविधा है. इसका इस्तेमाल -fvisibility=hidden के ज़्यादा मज़बूत फ़ॉर्म के तौर पर किया जा सकता है. ज़्यादा जानकारी के लिए, नीचे दिए गए फ़ायदे देखें या अपने प्रोजेक्ट में वर्शन स्क्रिप्ट का इस्तेमाल करने का तरीका जानने के लिए आगे पढ़ें.

ऊपर दिए गए लिंक वाले GNU दस्तावेज़ और इस पेज पर कुछ और जगहों पर, आपको "सिंबल वर्शन" के रेफ़रंस दिखेंगे. इसकी वजह यह थी कि इन फ़ाइलों का मूल मकसद, किसी सिंबल (आम तौर पर एक फ़ंक्शन) के कई वर्शन को एक ही लाइब्रेरी में रखने की अनुमति देना था, ताकि लाइब्रेरी में गड़बड़ी के साथ काम करने की क्षमता को सुरक्षित रखा जा सके. Android भी इस सुविधा का इस्तेमाल करता है. हालांकि, आम तौर पर इसका इस्तेमाल सिर्फ़ OS लाइब्रेरी वेंडर करते हैं. हम भी Android में इसका इस्तेमाल नहीं करते, क्योंकि targetSdkVersion, ज़्यादा सोच-समझकर ऑप्ट-इन करने की प्रोसेस के साथ वही फ़ायदे देता है. इस दस्तावेज़ के विषय के लिए, "सिंबल के वर्शन" जैसे शब्दों के बारे में चिंता न करें. अगर एक ही सिंबल के कई वर्शन तय नहीं किए जा रहे हैं, तो "symbol version", फ़ाइल में सिंबल की एक ऐसी ग्रुपिंग है जिसे किसी भी नाम से रखा जा सकता है.

अगर आप ऐप्लिकेशन डेवलपर के बजाय लाइब्रेरी के लेखक हैं, तो मिडलवेयर वेंडर के लिए सलाह भी ज़रूर पढ़ें. भले ही, आपका इंटरफ़ेस C/C++ हो या Java/Kotlin और आपका नेटिव कोड सिर्फ़ लागू करने से जुड़ी जानकारी हो.

वर्शन स्क्रिप्ट लिखना

आम तौर पर, नेटिव कोड वाले ऐप्लिकेशन (या AAR) में सिर्फ़ एक शेयर की गई लाइब्रेरी होती है. साथ ही, इस लाइब्रेरी में सभी डिपेंडेंसी स्टैटिक तौर पर लिंक होती हैं. साथ ही, इस लाइब्रेरी का पूरा सार्वजनिक इंटरफ़ेस JNI_OnLoad होता है. इससे इस पेज पर बताए गए फ़ायदों को ज़्यादा से ज़्यादा लोगों पर लागू किया जा सकता है. ऐसे मामले में, यह मानते हुए कि लाइब्रेरी को libapp.so नाम दिया गया है, यहां दिए गए कॉन्टेंट की मदद से libapp.map.txt फ़ाइल (नाम का मेल खाने की ज़रूरत नहीं है और .map.txt सफ़िक्स सिर्फ़ एक कन्वेंशन है) बनाएं (टिप्पणियों को छोड़ दिया जा सकता है):

# The name used here also doesn't matter. This is the name of the "version"
# which matters when the version script is actually used to create multiple
# versions of the same symbol, but that's not what we're doing.
LIBAPP {
  global:
    # Every symbol named in this section will have "default" (that is, public)
    # visibility. See below for how to refer to C++ symbols without mangling.
    JNI_OnLoad;
  local:
    # Every symbol in this section will have "local" (that is, hidden)
    # visibility. The wildcard * is used to indicate that all symbols not listed
    # in the global section should be hidden.
    *;
};

अगर आपके ऐप्लिकेशन में एक से ज़्यादा शेयर की गई लाइब्रेरी हैं, तो आपको हर लाइब्रेरी के लिए एक वर्शन स्क्रिप्ट जोड़ना होगा.

JNI_OnLoad और RegisterNatives() का इस्तेमाल न करने वाली JNI लाइब्रेरी के लिए, JNI के हर तरीके को उनके JNI में बदले गए नामों के साथ सूची में शामिल किया जा सकता है.

गैर-जेएनआई लाइब्रेरी (आम तौर पर, जेएनआई लाइब्रेरी की डिपेंडेंसी) के लिए, आपको अपने पूरे एपीआई प्लैटफ़ॉर्म की गिनती करनी होगी. अगर आपके इंटरफ़ेस का इंटरफ़ेस C के बजाय C++ है, तो वर्शन स्क्रिप्ट में extern "C++" { ... } का इस्तेमाल, हेडर फ़ाइल की तरह ही किया जा सकता है. उदाहरण के लिए:

LIBAPP {
  global:
    extern "C++" {
      # A class that exposes only some methods. Note that any methods that are
      # `private` in the class will still need to be visible in the library if
      # they are called by `inline` or `template` functions.
      #
      # Non-static members do not need to be enumerated as they do not have
      # symbols associated with them, but static members must be included.
      #
      # The * exposes all overloads of the MyClass constructor, but note that it
      # will also expose methods like MyClass::MyClassNonConstructor.
      MyClass::MyClass*;
      MyClass::DoSomething;
      MyClass::static_member;

      # All members/methods of a class, including those that are `private` in
      # the class.
      MyOtherClass::*;
      #

      # If you wish to only expose some overloads, name the full signature.
      # You'll need to wrap the name in quotes, otherwise you'll get a warning
      # like like "ignoring invalid character '(' in script" and the symbol will
      # remain hidden (pass -Wl,--no-undefined-version to convert that warning
      # to an error as described below).
      "MyClass::MyClass()";
      "MyClass::MyClass(const MyClass&)";
      "MyClass::~MyClass()";
    };
  local:
    *;
};

बिल्ड करते समय वर्शन स्क्रिप्ट का इस्तेमाल करना

बिल्डिंग बनाते समय, वर्शन स्क्रिप्ट लिंकर को पास की जानी चाहिए. अपने बिल्ड सिस्टम के हिसाब से, यहां दिया गया तरीका अपनाएं.

CMake

# Assuming that your app library's target is named "app":
target_link_options(app
    PRIVATE
    -Wl,--version-script,${CMAKE_SOURCE_DIR}/libapp.map.txt
    # This causes the linker to emit an error when a version script names a
    # symbol that is not found, rather than silently ignoring that line.
    -Wl,--no-undefined-version
)

# Without this, changes to the version script will not cause the library to
# relink.
set_target_properties(app
    PROPERTIES
    LINK_DEPENDS ${CMAKE_SOURCE_DIR}/libapp.map.txt
)

एनडीके-बिल्ड

# Add to an existing `BUILD_SHARED_LIBRARY` stanza (use `+=` instead of `:=` if
# the module already sets `LOCAL_LDFLAGS`):
LOCAL_LDFLAGS := -Wl,--version-script,$(LOCAL_PATH)/libapp.map.txt

# This causes the linker to emit an error when a version script names a symbol
# that is not found, rather than silently ignoring that line.
LOCAL_ALLOW_UNDEFINED_VERSION_SCRIPT_SYMBOLS := false

# ndk-build doesn't have a mechanism for specifying that libapp.map.txt is a
# dependency of the module. You may need to do a clean build or otherwise force
# the library to rebuild (such as by changing a source file) when altering the
# version script.

अन्य

अगर आपके इस्तेमाल किए जा रहे बिल्ड सिस्टम में वर्शन स्क्रिप्ट की सुविधा साफ़ तौर पर दी गई है, तो उसका इस्तेमाल करें.

इसके अलावा, इन लिंकर फ़्लैग का इस्तेमाल करें:

-Wl,--version-script,path/to/libapp.map.txt -Wl,--no-version-undefined

इनका नाम कैसे रखा गया है, यह आपके बिल्ड सिस्टम पर निर्भर करता है. हालांकि, आम तौर पर LDFLAGS या इससे मिलता-जुलता कोई विकल्प होता है. path/to/libapp.map.txt को लिंकर की मौजूदा वर्किंग डायरेक्ट्री से हल किया जाना चाहिए. आम तौर पर, ऐब्सोलूट पाथ का इस्तेमाल करना आसान होता है.

अगर किसी बिल्ड सिस्टम का इस्तेमाल नहीं किया जा रहा है या बिल्ड सिस्टम मेंटेनर को वर्शन स्क्रिप्ट के साथ काम करना है, तो लिंक करते समय ये फ़्लैग clang (या clang++) को भेजे जाने चाहिए. कंपाइल करते समय नहीं.

फ़ायदे

वर्शन स्क्रिप्ट का इस्तेमाल करने पर, APK का साइज़ कम किया जा सकता है. इसकी वजह यह है कि इससे लाइब्रेरी में दिखने वाले सिंबल का सेट कम हो जाता है. लिंकर को यह बताकर कि कॉल करने वाले (कॉलर) कौनसे फ़ंक्शन ऐक्सेस कर सकते हैं, लिंकर लाइब्रेरी से उन सभी कोड को हटा सकता है जो पहुंच से बाहर हैं. यह प्रोसेस, डेड-कोड को हटाने का एक तरीका है. लिंकर, ऐसे फ़ंक्शन (या दूसरे सिंबल) की परिभाषा को नहीं हटा सकता जो छिपा नहीं है. भले ही, फ़ंक्शन को कभी भी कॉल न किया गया हो, क्योंकि लिंकर को यह मानना होगा कि दिखने वाला सिंबल, लाइब्रेरी के सार्वजनिक इंटरफ़ेस का हिस्सा है. सिंबल छिपाने से लिंकर उन फ़ंक्शन को हटा सकता है जिन्हें कॉल नहीं किया जाता है. इससे लाइब्रेरी का साइज़ कम हो जाता है.

लाइब्रेरी लोड करने की परफ़ॉर्मेंस को बेहतर बनाने की वजहें भी ये ही हैं: दिखने वाले सिंबल के लिए फिर से जगह तय करना ज़रूरी है, क्योंकि वे सिंबल इंटरपोज़ किए जा सकते हैं. ऐसा कभी नहीं होता कि आपका मकसद ऐसा हो. हालांकि, ELF के स्पेसिफ़िकेशन के मुताबिक, ऐसा करना ज़रूरी है. इसलिए, यह डिफ़ॉल्ट रूप से होता है. हालांकि, लिंकर यह नहीं जान सकता कि आपको किन (अगर कोई है) सिंबल को इंटरपोज़ करने की ज़रूरत है. इसलिए, उसे हर दिखने वाले सिंबल के लिए, रीलोकेशन बनाना होगा. उन सिंबल को छिपाने से, लिंकर को सीधे जंप के लिए उन जगहों को हटाने की अनुमति मिलती है. इससे लाइब्रेरी लोड करते समय, डाइनैमिक लिंकर को कम काम करना पड़ता है.

एपीआई के प्लैटफ़ॉर्म की जानकारी साफ़ तौर पर देने से, आपकी लाइब्रेरी के उपयोगकर्ताओं को लाइब्रेरी को लागू करने से जुड़ी जानकारी पर भरोसा करने से रोका जा सकता है. ऐसा इसलिए, क्योंकि यह जानकारी नहीं दिखेगी.

अन्य विकल्पों से तुलना करना

वर्शन स्क्रिप्ट, वैकल्पिक विकल्पों जैसे कि -fvisibility=hidden या हर फ़ंक्शन के लिए __attribute__((visibility("hidden"))) जैसे ही नतीजे देती हैं. इन तीनों तरीकों से यह कंट्रोल किया जाता है कि किसी लाइब्रेरी के कौनसे सिंबल, दूसरी लाइब्रेरी और dlsym को दिखें.

दूसरे दो तरीकों का सबसे बड़ा नुकसान यह है कि इनकी मदद से, सिर्फ़ बनाई जा रही लाइब्रेरी में तय किए गए सिंबल छिपाए जा सकते हैं. ये लाइब्रेरी की स्टैटिक लाइब्रेरी डिपेंडेंसी से सिंबल नहीं छिपा सकते. आम तौर पर, libc++_static.a का इस्तेमाल करने पर, भले ही, आपका बिल्ड -fvisibility=hidden का इस्तेमाल करता हो, लेकिन लाइब्रेरी के खुद के सिंबल छिपाए जाएंगे, लेकिन libc++_static.a से शामिल किए गए सभी सिंबल आपकी लाइब्रेरी के सार्वजनिक सिंबल बन जाएंगे. वहीं दूसरी ओर, वर्शन स्क्रिप्ट लाइब्रेरी के पब्लिक इंटरफ़ेस को साफ़ तौर पर कंट्रोल करने की सुविधा देती हैं. अगर वर्शन स्क्रिप्ट में सिंबल को साफ़ तौर पर 'दिख रहा है' के तौर पर नहीं दिखाया गया है, तो वह छिपा हुआ रहेगा.

दूसरा अंतर फ़ायदेमंद और नुकसानदेह, दोनों हो सकता है: लाइब्रेरी के सार्वजनिक इंटरफ़ेस को वर्शन स्क्रिप्ट में साफ़ तौर पर बताया जाना चाहिए. JNI लाइब्रेरी के लिए, यह असल में आसान है, क्योंकि JNI लाइब्रेरी के लिए सिर्फ़ JNI_OnLoad इंटरफ़ेस ज़रूरी है. ऐसा इसलिए है, क्योंकि RegisterNatives() के साथ रजिस्टर किए गए JNI तरीकों को सार्वजनिक होने की ज़रूरत नहीं होती. जिन लाइब्रेरी का सार्वजनिक इंटरफ़ेस बड़ा होता है उनके लिए, रखरखाव का यह एक और भार हो सकता है. हालांकि, आम तौर पर यह भार ज़रूरी होता है.