नेटिव Mac प्लग-इन की बंडल की गई निर्भरता को निजी रखने के लिए, उसके कार्यान्वयन को छिपी हुई दृश्यता के साथ कंपाइल करें और अंतिम लिंक पर केवल प्लग-इन के सार्वजनिक प्रवेश बिंदुओं का निर्यात करें। ये नियंत्रण उस बाइनरी पर लागू करें जिसमें निर्भरता वास्तव में मौजूद है।
यह मार्गदर्शिका ऐसे Mac डेस्कटॉप डेवलपर के लिए है जो C या C++ निर्भरता वाला, रनटाइम पर लोड होने वाला प्लग-इन बना रहा है। ठोस काम यह है कि प्लग-इन का अपना कार्यान्वयन बरकरार रखा जाए और होस्ट के लिए एक छोटा इंटरफ़ेस उपलब्ध कराया जाए। एक कृत्रिम उदाहरण में C निर्भरता को C++ प्लग-इन में बनाया जाता है और उसके परिणाम तथा निर्यात किए गए सिंबल, दोनों की जाँच की जाती है।
किन बाइनरी के निर्यात सीमित करने हैं?
पहले पता लगाएँ कि निर्भरता का मशीन कोड कहाँ जाता है। स्टैटिक आर्काइव लिंक की जा रही बाइनरी को ऑब्जेक्ट फ़ाइलें देता है; डायनेमिक लाइब्रेरी अलग इमेज बनी रहती है। इसलिए, प्लग-इन में सिंबल छिपाने से अलग निर्भरता dylib की निर्यात तालिका फिर से नहीं लिखी जा सकती। Apple इस अंतर का वर्णन अपनी Dynamic Library Usage Guidelines में करता है।
| निर्भरता की व्यवस्था | नियंत्रण कहाँ लागू करें | क्या जाँचना है |
|---|---|---|
| निर्भरता का सोर्स प्लग-इन में कंपाइल किया गया | निर्भरता कंपाइलेशन और अंतिम प्लग-इन लिंक | निजी परिभाषाएँ मौजूद हों, लेकिन निर्यात न हों |
| प्लग-इन से लिंक किया गया स्टैटिक आर्काइव | जहाँ संभव हो, आर्काइव कंपाइलेशन के साथ अंतिम प्लग-इन लिंक | आवश्यक आर्काइव सदस्य शामिल हुए हों; निर्भरता के निर्यात अनुपस्थित हों |
| अलग निर्भरता dylib | निर्भरता dylib का अपना बिल्ड | उसका सार्वजनिक इंटरफ़ेस और प्लग-इन में दर्ज इंपोर्ट |
| C++ हेडर से इंस्टैंशिएट किया गया निर्भरता कोड | उन हेडर को कंपाइल करने वाला हर टार्गेट | उत्सर्जित इनलाइन फ़ंक्शन, टेम्पलेट और उनसे जुड़ा डेटा |
यह तालिका कंपाइलेशन और लिंकिंग की सीमाओं से निकला निर्णय-सहायक है। उदाहरण के लिए, रैपर टार्गेट से जुड़ी निर्यात सूची किसी दूसरे टार्गेट में उत्सर्जित सिंबल को अपने आप नहीं छिपाती।
macOS डिफ़ॉल्ट रूप से दो-स्तरीय नेमस्पेस भी इस्तेमाल करता है: dylib के विरुद्ध हल किए गए रेफ़रेंस में दर्ज होता है कि उन्हें किस लाइब्रेरी ने दिया। Flat namespace इस व्यवहार को बदलता है। Weak external परिभाषाएँ एक और बात जोड़ती हैं, क्योंकि लोडर उन्हें एक साथ मिला सकता है। Apple दोनों तरीकों का वर्णन अपने linker manual में करता है। इसलिए केवल नामों का मेल यह साबित नहीं करता कि प्लग-इन ने किसी अप्रत्याशित कार्यान्वयन को क्यों कॉल किया।
छिपी हुई कंपाइलेशन को स्पष्ट निर्यात सूची के साथ जोड़ें
कंपाइलर और लिंकर के नियंत्रण अलग-अलग चरणों पर काम करते हैं। Clang का -fvisibility=hidden कंपाइल की जा रही सोर्स फ़ाइलों में ग्लोबल परिभाषाओं की डिफ़ॉल्ट दृश्यता तय करता है। यह मौजूदा आर्काइव को फिर से कंपाइल नहीं करता। Clang इनलाइन C++ सदस्य फ़ंक्शन के लिए -fvisibility-inlines-hidden को अलग से दर्ज करता है; इसे ऐसा लक्षित विकल्प मानें जिसकी उपयुक्तता इंटरफ़ेस पर निर्भर करती है। Clang command-line reference देखें।
जिन सार्वजनिक प्रवेश बिंदुओं को उपलब्ध रखना है, उन्हें __attribute__((visibility("default"))) से चिह्नित करें। दृश्यता एट्रिब्यूट कंपाइलेशन की डिफ़ॉल्ट सेटिंग को बदल सकते हैं, इसलिए निर्भरता के हेडर में स्पष्ट निर्यात एनोटेशन की जाँच करें। Apple इस व्यवहार को Controlling Symbol Visibility में समझाता है।
अंतिम लिंक के समय सार्वजनिक इंटरफ़ेस की सूची देने के लिए -exported_symbols_list का उपयोग करें। Apple के दर्ज निर्यात-सूची फ़ॉर्मैट में लिंकर सिंबल नाम होते हैं, जिनमें C सिंबल के आगे अंडरस्कोर भी शामिल है। निर्यात सूची या गैर-निर्यातित सिंबल की सूची में से एक रणनीति चुनें; दोनों को साथ न मिलाएँ। Dynamic Library Design Guidelines देखें।
छोटे प्लग-इन इंटरफ़ेस के लिए स्पष्ट निर्यात सूची से समीक्षा आसान होती है: हर निर्यातित नाम के सार्वजनिक होने का कारण होना चाहिए। सोर्स एनोटेशन और सूची को एक जैसा रखें; एनोटेशन कोड में API की पहचान करता है और सूची अंतिम बाइनरी को सीमित करती है।
पूरा स्थानीय उदाहरण बनाएँ
इस उदाहरण में जानबूझकर C निर्भरता का उपयोग किया गया है: यह परस्पर असंगत C++ परिभाषाओं को पोर्टेबल भाषा तकनीक के रूप में पेश किए बिना Mach-O निर्यात नियंत्रण दिखाता है। होस्ट की निर्भरता 10 लौटाती है; प्लग-इन का निजी बिल्ड 20 लौटाता है। ये मान केवल उदाहरण के लिए हैं।
Xcode Command Line Tools उपलब्ध वाले Mac पर, एक ही Terminal शेल में नीचे दिए गए ब्लॉक क्रम से चलाएँ। ये नया अस्थायी डायरेक्टरी बनाते हैं और सक्रिय टूलचेन के डिफ़ॉल्ट आर्किटेक्चर के लिए बिल्ड करते हैं। इस उदाहरण का निरीक्षण किया गया, लेकिन इसे इस शोध के दौरान कंपाइल या रन नहीं किया गया, क्योंकि उपलब्ध वातावरण Linux था और उसमें Apple का टूलचेन नहीं था।
निर्भरता और प्लग-इन बनाएँ
set -eu
example_dir=$(mktemp -d)
cd "$example_dir"
cat > dependency.h <<'EOF'
#ifndef DEMO_DEPENDENCY_H
#define DEMO_DEPENDENCY_H
#ifdef __cplusplus
extern "C" {
#endif
int demo_dependency_value(void);
#ifdef __cplusplus
}
#endif
#endif
EOF
cat > dependency.c <<'EOF'
#include "dependency.h"
#ifndef DEP_VALUE
#error DEP_VALUE must be supplied at compilation
#endif
int demo_dependency_value(void) {
return DEP_VALUE;
}
EOF
cat > plugin.cpp <<'EOF'
#include "dependency.h"
extern "C" __attribute__((visibility("default")))
int demo_plugin_value(void) {
return demo_dependency_value();
}
EOF
cat > exports.txt <<'EOF'
_demo_plugin_value
EOF
निर्भरता हेडर C++ रैपर को सही C-भाषा लिंकिंग घोषणा देता है। सार्वजनिक रैपर भी C लिंकिंग का उपयोग करता है, जिससे इस प्रवेश बिंदु का लिंकर नाम सरल रहता है। C++ नामों में आम तौर पर मैंगलिंग होती है; Apple इस अंतर पर Controlling Symbol Visibility में चर्चा करता है।
होस्ट बनाएँ
होस्ट प्लग-इन लोड करता है और लौटाए गए लाइब्रेरी हैंडल से उसका सार्वजनिक फ़ंक्शन प्राप्त करता है। Apple दर्ज करता है कि dlopen डायनेमिक लाइब्रेरी और Mach-O बंडल, दोनों के लिए समर्थित है। इसका RTLD_LOCAL विकल्प लोड की गई इमेज के निर्यातित सिंबल की उपलब्धता सीमित करता है; यह बिल्ड-समय के दृश्यता नियंत्रणों का स्थान नहीं लेता। Apple का dlopen manual देखें।
cat > host.cpp <<'EOF'
#include "dependency.h"
#include <cstdio>
#include <dlfcn.h>
int main() {
void* handle = dlopen("./DemoPlugin.dylib", RTLD_NOW | RTLD_LOCAL);
if (!handle) {
std::fprintf(stderr, "dlopen: %s\n", dlerror());
return 1;
}
dlerror();
void* address = dlsym(handle, "demo_plugin_value");
const char* error = dlerror();
if (error || !address) {
std::fprintf(stderr, "dlsym: %s\n",
error ? error : "null function address");
dlclose(handle);
return 1;
}
using PluginValue = int (*)(void);
PluginValue plugin_value = reinterpret_cast<PluginValue>(address);
const int host_result = demo_dependency_value();
const int plugin_result = plugin_value();
std::printf("host=%d plugin=%d\n", host_result, plugin_result);
const bool correct = host_result == 10 && plugin_result == 20;
if (dlclose(handle) != 0) {
std::fprintf(stderr, "dlclose: %s\n", dlerror());
return 1;
}
return correct ? 0 : 2;
}
EOF
कंपाइल और लिंक करें
# Build the plug-in's dependency with hidden definitions.
xcrun clang -fvisibility=hidden -DDEP_VALUE=20 \
-c dependency.c -o plugin_dependency.o
xcrun ar rcs libPluginDependency.a plugin_dependency.o
# Compile the public wrapper, then constrain the final exports.
xcrun clang++ -std=c++17 -fvisibility=hidden \
-c plugin.cpp -o plugin.o
xcrun clang++ -dynamiclib plugin.o libPluginDependency.a \
-Wl,-exported_symbols_list,exports.txt \
-o DemoPlugin.dylib
# Give the host a separately compiled implementation.
xcrun clang -DDEP_VALUE=10 -c dependency.c -o host_dependency.o
xcrun clang++ -std=c++17 host.cpp host_dependency.o \
-Wl,-exported_symbol,_demo_dependency_value -o host
./host
अपेक्षित आउटपुट host=10 plugin=20 है। उदाहरण में बाहरी रूप से दिखने वाला समान C नाम वाला कार्यान्वयन शामिल करने के लिए होस्ट स्पष्ट रूप से अपना निर्भरता सिंबल निर्यात करता है। फिर भी, प्लग-इन में उसका निजी कार्यान्वयन मौजूद होना चाहिए।
यह एक सत्यापन उदाहरण है; इसका दावा यह नहीं है कि नियंत्रण हटाने पर परिणाम अनिवार्य रूप से विफल होगा। कई व्यवस्थाओं में डिफ़ॉल्ट Mach-O बाइंडिंग पहले से ही परिभाषाओं में अंतर कर सकती है। उपयोगी जाँचें हैं चुना गया कार्यान्वयन और अंतिम निर्यात सतह।
निर्यात और इंपोर्ट अलग-अलग जाँचें
उदाहरण डायरेक्टरी में ये कमांड चलाएँ:
xcrun nm -g -U -j DemoPlugin.dylib
xcrun dyld_info -exports DemoPlugin.dylib
xcrun dyld_info -imports DemoPlugin.dylib
xcrun otool -L DemoPlugin.dylib
पहले कमांड में -g बाहरी सिंबल चुनता है, -U अपरिभाषित सिंबल हटाता है और -j नाम प्रिंट करता है। इस न्यूनतम प्लग-इन के लिए अपेक्षित परिभाषित बाहरी नाम _demo_plugin_value है; _demo_dependency_value अनुपस्थित होना चाहिए। Apple का nm manual इन विकल्पों का वर्णन करता है और समझाता है कि सिंबल-तालिका की जानकारी, लोडर द्वारा इस्तेमाल की जाने वाली जानकारी से अलग हो सकती है।
लोडर को दिखने वाले निर्यातों की जाँच के लिए dyld_info -exports और इंपोर्ट किए गए सिंबल की जाँच के लिए -imports का उपयोग करें। निर्भरता फ़ंक्शन दोनों में से किसी सूची में नहीं दिखना चाहिए: इस प्लग-इन के भीतर इसका निजी कार्यान्वयन है। जाँच के ये विकल्प Apple के dyld_info manual में दर्ज हैं।
वास्तविक dylib निर्भरताएँ जाँचने के लिए otool -L का उपयोग करें। स्टैटिक आर्काइव अलग रनटाइम निर्भरता के रूप में नहीं दिखना चाहिए। Apple इस निर्भरता जाँच का वर्णन Dynamic Library Usage Guidelines में करता है।
किसी वास्तविक प्लग-इन के लिए तीन सवाल अलग-अलग रखें: क्या आवश्यक कार्यान्वयन शामिल है? क्या वह निर्यात किया गया है? क्या कोई कॉल अब भी किसी प्रतिस्पर्धी कार्यान्वयन को इंपोर्ट करता है? सफल बिल्ड अपने आप में इनमें से किसी सवाल का जवाब नहीं देता।
Xcode में यही नियंत्रण लागू करें
अंतिम प्लग-इन बाइनरी बनाने वाले टार्गेट के लिए, Exported Symbols File (EXPORTED_SYMBOLS_FILE) में निर्यात सूची का प्रोजेक्ट-सापेक्ष पथ सेट करें। निजी कार्यान्वयन कंपाइल करने वाले C और C++ टार्गेट पर Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN) सक्षम करें। Apple की मौजूदा Build settings reference में दोनों सेटिंग दर्ज हैं।
यदि निर्भरता अलग स्टैटिक-लाइब्रेरी टार्गेट है, तो उसे भी कॉन्फ़िगर करें। केवल प्लग-इन की कंपाइलर सेटिंग बदलने से पहले से कंपाइल किए गए निर्भरता ऑब्जेक्ट नहीं बदलते। प्रभावित टार्गेट फिर से बनाएँ और हर वितरित आर्किटेक्चर के लिए बने उत्पाद की जाँच करें।
पहले से बने आर्काइव के लिए भी अंतिम निर्यात सूची उपयोगी रहती है। Apple का प्रकाशित लिंकर मैनुअल सामान्य आर्काइव निष्कर्षण का उपयोग करते हुए आर्काइव के ग्लोबल सिंबल को छिपा हुआ मानने के लिए -load_hidden का दस्तावेज़ भी देता है। वह मैनुअल ld-classic के लिए है; इस अतिरिक्त विकल्प को चुनने से पहले इंस्टॉल किए गए लिंकर का दस्तावेज़ देखें। -force_load आर्काइव सदस्यों को शामिल करने को नियंत्रित करता है, जो एक अलग मुद्दा है। Apple linker manual देखें।
C++ और Swift इंटरफ़ेस की ज़रूरतें बनाए रखें
C++ दृश्यता असंगत परिभाषाओं को ठीक नहीं करती
C++ का one-definition rule, एक से अधिक परिभाषाओं की अनुमति होने पर, अलग-अलग ट्रांसलेशन यूनिट में परिभाषाओं के लिए शर्तें तय करता है, जिनमें टोकन और नाम-खोज से जुड़ी शर्तें भी शामिल हैं। सिंबल छिपाने का अर्थ परस्पर टकराने वाली C++ परिभाषाओं को जोड़ने की अनुमति नहीं है। स्वतंत्र रूप से संस्करणित कार्यान्वयनों के लिए जहाँ संभव हो, अलग नेमस्पेस या सिंबल नाम इस्तेमाल करें। C++ working draft का one-definition rule देखें।
जानबूझकर बनाए गए C++ इंटरफ़ेस के लिए आवश्यक दृश्यता भी बनाए रखें। Apple बताता है कि लाइब्रेरी के पार RTTI, अपवाद, डायनेमिक कास्ट, इनलाइन फ़ंक्शन के पते की पहचान और साझा स्टैटिक ऑब्जेक्ट ऐसे मामले हैं जिनमें सावधानी चाहिए। एक व्यावहारिक डिज़ाइन यह है कि निर्भरता के प्रकार और उनका स्वामित्व प्लग-इन के भीतर रखें और सहमत स्केलर मानों या अपारदर्शी हैंडल से छोटा इंटरफ़ेस उपलब्ध कराएँ। Controlling Symbol Visibility देखें।
Swift इंपोर्ट एक्सेस एक अलग सीमा को नियंत्रित करता है
Swift का internal import यह सीमित करता है कि इंपोर्ट की गई घोषणाओं का संदर्भ कहाँ दिया जा सकता है। Swift 6.0 में लागू SE-0409, @_implementationOnly import के उपयोगों को internal import या उससे निचले स्तर वाले इंपोर्ट से बदलने की सिफ़ारिश करता है। यह Mach-O निर्भरता का नाम बदलने या निर्यात फ़िल्टर करने का तरीका तय नहीं करता। नेटिव बाइनरी को अलग से कॉन्फ़िगर और जाँचते रहें। Access-level modifiers on import declarations देखें।
यदि सीधे Swift/C++ इंटरऑपरेबिलिटी का उपयोग कर रहे हैं, तो टूलचेन की शर्तें बनाए रखें: समर्थन Swift 5.9 से शुरू होता है, मिश्रित कोड को एक ही C++ स्टैंडर्ड लाइब्रेरी का उपयोग करना चाहिए और Apple प्लेटफ़ॉर्म पर libc++ इस्तेमाल होता है। इंपोर्ट किए गए C++ रेफ़रेंस टाइप के लिए macOS 13.3 का न्यूनतम डिप्लॉयमेंट संस्करण भी आवश्यक है। निर्यात फ़िल्टरिंग से इनमें से कोई शर्त नहीं बदलती। Supported Features and Constraints of C++ Interoperability देखें।
प्लग-इन तब इंटीग्रेशन टेस्टिंग के लिए तैयार है जब उसके इच्छित प्रवेश बिंदु उपलब्ध रहें, उसकी निजी निर्भरता परिभाषाएँ लोडर निर्यात में अनुपस्थित हों और उसके इंपोर्ट उन लाइब्रेरी से मेल खाते हों जिनका उसे उपयोग करना चाहिए। इन जाँचों को कृत्रिम उदाहरण जैसे व्यवहार परीक्षण के साथ रखें, ताकि बाद के निर्भरता अपडेट से इंटरफ़ेस में हुए बदलाव और कार्यान्वयन के चयन में हुए बदलाव, दोनों सामने आएँ।
स्रोत
- Build settings reference — Apple।
- Dynamic Library Design Guidelines — Apple।
- Dynamic Library Usage Guidelines — Apple।
- Controlling Symbol Visibility — Apple।
- Clang command line argument reference — LLVM Project।
- ld-classic(1) manual — Apple।
- dlopen(3) manual — Apple।
- nm(1) manual — Apple।
- dyld_info(1) manual — Apple।
- One-definition rule — C++ working draft, Tim Song द्वारा प्रस्तुत।
- Access-level modifiers on import declarations — Swift Project।
- Supported Features and Constraints of C++ Interoperability — Swift.org।