c++ मैं डीएलएल से और उससे सुरक्षित रूप से ऑब्जेक्ट्स, विशेष रूप से एसटीएल ऑब्जेक्ट्स कैसे पास करूं?




windows dll (4)

मैं क्लास ऑब्जेक्ट्स, खासकर एसटीएल ऑब्जेक्ट्स को सी ++ डीएलएल से कैसे पास करूं?

मेरे आवेदन को डीएलएल फाइलों के रूप में तीसरे पक्ष के प्लगइन के साथ बातचीत करनी है, और मैं इन प्लगइन्स के साथ किस कंपाइलर के साथ बनाया गया है उसे नियंत्रित नहीं कर सकता। मुझे पता है कि एसटीएल वस्तुओं के लिए कोई गारंटीकृत एबीआई नहीं है, और मैं अपने आवेदन में अस्थिरता पैदा करने के बारे में चिंतित हूं।


@ कॉम्प्यूटरफ्रेकर ने एक महान स्पष्टीकरण लिखा है कि एबीआई की कमी सामान्य मामले में डीएलएल सीमाओं में सी ++ ऑब्जेक्ट्स को पार करने से क्यों रोकती है, भले ही टाइप परिभाषा उपयोगकर्ता नियंत्रण में हों और सटीक उसी टोकन अनुक्रम दोनों प्रोग्रामों में उपयोग किया जाए। (दो मामले हैं जो काम करते हैं: मानक-लेआउट कक्षाएं, और शुद्ध इंटरफेस)

सी ++ मानक (मानक टेम्पलेट लाइब्रेरी से अनुकूलित किए गए समेत) में परिभाषित ऑब्जेक्ट प्रकारों के लिए, स्थिति बहुत दूर है। इन प्रकारों को परिभाषित करने वाले टोकन एकाधिक कंपाइलरों में समान नहीं हैं, क्योंकि सी ++ मानक पूर्ण प्रकार की परिभाषा प्रदान नहीं करता है, केवल न्यूनतम आवश्यकताएं। इसके अलावा, इन प्रकार की परिभाषाओं में दिखाई देने वाले पहचानकर्ताओं का नाम लुकअप इसे हल नहीं करता है। यहां तक ​​कि सिस्टम पर जहां सी ++ एबीआई है, मॉड्यूल सीमाओं में ऐसे प्रकारों को साझा करने का प्रयास एक परिभाषा नियम उल्लंघन के कारण भारी अपरिभाषित व्यवहार में होता है।

यह ऐसा कुछ है जो लिनक्स प्रोग्रामर से निपटने के आदी नहीं थे, क्योंकि जी ++ का libstdc ++ एक वास्तविक तथ्य था और लगभग सभी कार्यक्रम इसका इस्तेमाल करते थे, इस प्रकार ओडीआर को संतुष्ट करते थे। क्लैंग के libc ++ ने धारणा तोड़ दी, और फिर सी ++ 11 लगभग सभी मानक पुस्तकालय प्रकारों में अनिवार्य परिवर्तन के साथ आया था।

मॉड्यूल के बीच मानक पुस्तकालय प्रकारों को साझा न करें। यह अपरिभाषित व्यवहार है।


यहां कुछ जवाब सी ++ कक्षाओं को वाकई डरावना ध्वनि देते हैं, लेकिन मैं एक वैकल्पिक दृष्टिकोण साझा करना चाहता हूं। कुछ अन्य प्रतिक्रियाओं में वर्णित शुद्ध वर्चुअल सी ++ विधि वास्तव में आपके विचार से क्लीनर बन जाती है। मैंने अवधारणा के चारों ओर एक संपूर्ण प्लगइन प्रणाली बनाई है और यह वर्षों से बहुत अच्छी तरह से काम कर रहा है। मेरे पास एक "प्लगइन प्रबंधक" श्रेणी है जो लोडलिब () और GetProcAddress () (और लिनक्स समकक्षों को क्रॉस प्लेटफ़ॉर्म बनाने के लिए निष्पादन योग्य) का उपयोग करके निर्दिष्ट निर्देशिका से डीएलएस को गतिशील रूप से लोड करता है।

मान लीजिए या नहीं, यह विधि क्षमा कर रही है भले ही आप अपने शुद्ध वर्चुअल इंटरफ़ेस के अंत में एक नया फ़ंक्शन जोड़ने जैसे कुछ बेकार सामान करते हैं और बिना नए फ़ंक्शन के इंटरफ़ेस के विरुद्ध संकलित डीएलएस लोड करने का प्रयास करते हैं - वे ठीक ठीक लोड करेंगे। बेशक ... आपको यह सुनिश्चित करने के लिए एक संस्करण संख्या जांचनी होगी कि आपका निष्पादन योग्य केवल नए डीएलएस के लिए नया फ़ंक्शन कॉल करता है जो फ़ंक्शन को कार्यान्वित करता है। लेकिन अच्छी खबर यह है: यह काम करता है! तो एक तरह से, आपके पास समय के साथ अपने इंटरफेस को विकसित करने के लिए एक कच्ची विधि है।

शुद्ध वर्चुअल इंटरफेस के बारे में एक और अच्छी चीज - आप जितनी चाहें उतनी इंटरफेस प्राप्त कर सकते हैं और आप हीरा की समस्या में कभी भाग नहीं पाएंगे!

मैं कहूंगा कि इस दृष्टिकोण का सबसे बड़ा नकारात्मक पक्ष यह है कि आपको पैरामीटर के रूप में किस प्रकार के पास पास करने के बारे में बहुत सावधान रहना होगा। पहले वर्चुअल इंटरफेस के साथ उन्हें लपेटने के बिना कोई कक्षा या एसटीएल ऑब्जेक्ट्स नहीं। कोई structs (pragma पैक voodoo के माध्यम से जाने के बिना)। अन्य इंटरफेस के लिए बस मूल प्रकार और पॉइंटर्स। साथ ही, आप कार्यों को अधिभारित नहीं कर सकते हैं, जो एक असुविधा है, लेकिन एक शो-स्टॉपर नहीं है।

अच्छी खबर यह है कि कोड की कुछ हद तक आप एसटीएल तार, वैक्टर, और अन्य कंटेनर कक्षाओं को लपेटने के लिए पुन: प्रयोज्य जेनेरिक कक्षाएं और इंटरफेस बना सकते हैं। वैकल्पिक रूप से, आप अपने इंटरफ़ेस में फ़ंक्शन जोड़ सकते हैं जैसे GetCount () और GetVal (n) लोगों को सूचियों के माध्यम से लूप करने दें।

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

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

तो कहानी का नैतिक है ... कुछ चरम परिस्थितियों के अपवाद के साथ, आपको इंटरफेस के प्रभारी एक व्यक्ति की आवश्यकता होती है जो सुनिश्चित कर सकता है कि एबीआई सीमा आदिम प्रकारों से साफ रहती है और ओवरलोडिंग से बचाती है। यदि आप उस शर्त के साथ ठीक हैं, तो मैं कंपाइलरों के बीच डीएलएल / एसओएस में कक्षाओं में इंटरफेस साझा करने से डर नहींूंगा। कक्षाओं को सीधे साझा करना == परेशानी, लेकिन शुद्ध वर्चुअल इंटरफेस साझा करना इतना बुरा नहीं है।


आप डीएलएल सीमाओं में एसटीएल ऑब्जेक्ट्स को सुरक्षित रूप से पास नहीं कर सकते हैं, जब तक कि सभी मॉड्यूल (.EXE और .DLLs) एक ही सी ++ कंपाइलर संस्करण और सीआरटी की समान सेटिंग्स और स्वाद के साथ बनाए जाते हैं, जो अत्यधिक बाधा है, और स्पष्ट रूप से आपका मामला नहीं है।

यदि आप अपने डीएलएल से ऑब्जेक्ट-ओरिएंटेड इंटरफ़ेस का पर्दाफाश करना चाहते हैं, तो आपको सी ++ शुद्ध इंटरफेस का खुलासा करना चाहिए (जो COM के समान है)। CodeProject पर इस दिलचस्प लेख को पढ़ने पर विचार करें:

Howto: डीएलएल से सी ++ कक्षाओं का निर्यात करें

आप डीएलएल सीमा पर शुद्ध सी इंटरफेस को उजागर करने और फिर कॉलर साइट पर एक सी ++ रैपर बनाने पर विचार करना चाह सकते हैं।
यह Win32 में क्या होता है: Win32 कार्यान्वयन कोड लगभग C ++ है, लेकिन Win32 एपीआई के बहुत सारे शुद्ध सी इंटरफ़ेस का खुलासा करते हैं (ऐसे एपीआई भी हैं जो COM इंटरफेस का पर्दाफाश करते हैं)। फिर एटीएल / डब्ल्यूटीएल और एमएफसी इन शुद्ध सी इंटरफेस को सी ++ कक्षाओं और वस्तुओं के साथ लपेटें।


इस सवाल का संक्षिप्त जवाब नहीं है । चूंकि कोई मानक सी ++ ABI (एप्लिकेशन बाइनरी इंटरफ़ेस, कॉन्फ़्रेंस कॉल करने के लिए मानक, डेटा पैकिंग / संरेखण, प्रकार का आकार इत्यादि), आपको क्लास से निपटने के मानक तरीके को आजमाने और लागू करने के लिए बहुत सारे हुप्स से कूदना होगा आपके कार्यक्रम में वस्तुओं। उन सभी हुप्स के माध्यम से कूदने के बाद भी यह गारंटी नहीं होगी, न ही यह गारंटी है कि एक संकलक जो एक कंपाइलर रिलीज में काम करता है, अगले में काम करेगा।

extern "C" का उपयोग करके बस एक सादा सी इंटरफेस बनाएं, क्योंकि सी एबीआई अच्छी तरह से परिभाषित और स्थिर है।

यदि आप वास्तव में, वास्तव में एक डीएलएल सीमा में सी ++ ऑब्जेक्ट्स पास करना चाहते हैं, तो यह तकनीकी रूप से संभव है। यहां कुछ कारक हैं जिनके लिए आपको खाता बनाना होगा:

डेटा पैकिंग / संरेखण

किसी दिए गए वर्ग के भीतर, व्यक्तिगत डेटा सदस्यों को आमतौर पर स्मृति में विशेष रूप से रखा जाएगा ताकि उनके पते प्रकार के आकार के एकाधिक से मेल खाते हों। उदाहरण के लिए, एक int को 4-बाइट सीमा से गठबंधन किया जा सकता है।

यदि आपके डीएलएल को आपके EXE की तुलना में एक अलग कंपाइलर के साथ संकलित किया गया है, तो किसी दिए गए वर्ग के डीएलएल के संस्करण में EXE के संस्करण की तुलना में अलग-अलग पैकिंग हो सकती है, इसलिए जब EXE क्लास ऑब्जेक्ट को डीएलएल में पास करता है, तो डीएलएल उचित रूप से एक्सेस करने में असमर्थ हो सकता है उस वर्ग के भीतर दिया गया डेटा सदस्य। डीएलएल कक्षा की अपनी परिभाषा द्वारा निर्दिष्ट पते से पढ़ने का प्रयास करेगा, न कि EXE की परिभाषा, और वांछित डेटा सदस्य वास्तव में वहां संग्रहीत नहीं होता है, कचरा मूल्यों का परिणाम होगा।

आप #pragma pack प्रीप्रोसेसर निर्देश का उपयोग करके इस पर काम कर सकते हैं, जो संकलक को विशिष्ट पैकिंग लागू करने के लिए मजबूर करेगा। कंपाइलर अभी भी डिफ़ॉल्ट पैकिंग लागू करेगा यदि आप संकलक को चुनने वाले एक से अधिक पैक मान का चयन करते हैं , इसलिए यदि आप एक बड़ा पैकिंग मान चुनते हैं , तो कक्षा में अभी भी कंपाइलर्स के बीच अलग-अलग पैकिंग हो सकती है। इसका समाधान #pragma pack(1) का उपयोग करना है, जो संकलक को एक-बाइट सीमा पर डेटा सदस्यों को संरेखित करने के लिए मजबूर करेगा (अनिवार्य रूप से, कोई पैकिंग लागू नहीं की जाएगी)। यह एक अच्छा विचार नहीं है, क्योंकि यह कुछ मुद्दों पर प्रदर्शन समस्याओं या यहां तक ​​कि दुर्घटनाओं का कारण बन सकता है। हालांकि, यह आपके वर्ग के डेटा सदस्यों को स्मृति में गठबंधन करने के तरीके में स्थिरता सुनिश्चित करेगा

सदस्य पुनर्गठन

यदि आपकी कक्षा standard-layout , तो संकलक स्मृति में अपने डेटा सदस्यों को पुनर्व्यवस्थित कर सकता है । यह कैसे किया जाता है इसके लिए कोई मानक नहीं है, इसलिए किसी भी डेटा पुनर्वितरण से कंपाइलरों के बीच असंगतता हो सकती है। डीएलएल को डेटा आगे और आगे पास करने के लिए मानक-लेआउट कक्षाओं की आवश्यकता होगी।

कॉलिंग सम्मेलन

एक दिए गए फ़ंक्शन में कई कॉलिंग सम्मेलन हो सकते हैं। ये कॉलिंग सम्मेलन निर्दिष्ट करते हैं कि कार्यों को डेटा कैसे पास किया जाना है: रजिस्टरों में या स्टैक पर संग्रहीत पैरामीटर हैं? स्टैक पर धक्का देने वाले तर्क किस क्रम में हैं? फ़ंक्शन समाप्त होने के बाद स्टैक पर छोड़े गए किसी भी तर्क को कौन साफ ​​करता है?

यह महत्वपूर्ण है कि आप एक मानक कॉलिंग सम्मेलन बनाए रखें; यदि आप _cdecl रूप में फ़ंक्शन घोषित करते हैं, तो C ++ के लिए डिफ़ॉल्ट, और _stdcall खराब चीजों का उपयोग करके इसे कॉल करने का प्रयास करें। _cdecl सी ++ फ़ंक्शंस के लिए डिफ़ॉल्ट कॉलिंग सम्मेलन है, हालांकि, यह एक चीज है जो तब तक नहीं टूट जाएगी जब तक कि आप जानबूझकर एक स्थान पर _stdcall निर्दिष्ट करके और किसी _stdcall में _stdcall निर्दिष्ट करके इसे तोड़ नहीं देते।

डेटाटाइप आकार

इस दस्तावेज़ीकरण के अनुसार, विंडोज़ पर, अधिकांश मौलिक डेटाटाइपों के समान आकार होते हैं चाहे आपका ऐप 32-बिट या 64-बिट हो। हालांकि, किसी दिए गए डेटाटाइप के आकार को संकलक द्वारा लागू किया जाता है, किसी भी मानक से नहीं (सभी मानक गारंटी यह है कि 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) ), जहां संभव हो वहां डेटाटाइप आकार संगतता सुनिश्चित करने के लिए निश्चित आकार डेटाटाइप का उपयोग करना एक अच्छा विचार है।

ढेर मुद्दे

यदि आपका डीएलएल आपके EXE की तुलना में सी रनटाइम के एक अलग संस्करण से लिंक करता है, तो दो मॉड्यूल अलग-अलग ढेर का उपयोग करेंगे । यह एक विशेष रूप से संभावित समस्या है क्योंकि मॉड्यूल को विभिन्न कंपाइलरों के साथ संकलित किया जा रहा है।

इसे कम करने के लिए, सभी मेमोरी को एक साझा ढेर में आवंटित किया जाना चाहिए, और उसी ढेर से हटा दिया जाना चाहिए। सौभाग्य से, विंडोज़ इस के साथ मदद करने के लिए एपीआई प्रदान करता है: GetProcessHeap आपको मेजबान EXE के ढेर तक HeapFree देगा, और HeapFree / HeapFree आपको इस ढेर के भीतर मेमोरी आवंटित और मुक्त करने देगा। यह महत्वपूर्ण है कि आप सामान्य malloc / free उपयोग न free क्योंकि कोई गारंटी नहीं है कि वे आपके द्वारा अपेक्षित तरीके से काम करेंगे।

एसटीएल मुद्दे

सी ++ मानक पुस्तकालय का एबीआई मुद्दों का अपना सेट है। इस बात की कोई गारंटी नहीं है कि दिया गया एसटीएल प्रकार स्मृति में उसी तरह से रखा गया है, न ही यह गारंटी है कि किसी दिए गए एसटीएल वर्ग के पास एक कार्यान्वयन से दूसरे आकार में समान आकार होता है (विशेष रूप से, डीबग बिल्ड अतिरिक्त डीबग जानकारी को एक में डाल सकता है एसटीएल प्रकार दिया)। इसलिए, किसी भी एसटीएल कंटेनर को डीएलएल सीमा पारित होने से पहले मौलिक प्रकारों में अनपॅक किया जाना चाहिए और दूसरी तरफ मरम्मत की जानी चाहिए।

नाम उलझन

आपका डीएलएल संभावित रूप से उन कार्यों को निर्यात करेगा जो आपका EXE कॉल करना चाहते हैं। हालांकि, सी ++ कंपाइलर्स में फ़ंक्शन नामों को मैंगलिंग का मानक तरीका नहीं है । इसका मतलब है कि GetCCDLL नामक एक फ़ंक्शन को जीसीसी में _Z8GetCCDLLv में _Z8GetCCDLLv जा सकता है और _Z8GetCCDLLv में [email protected]@[email protected]@XZ है।

आप पहले से ही अपने डीएलएल से स्थिर लिंकिंग की गारंटी नहीं दे पाएंगे, क्योंकि जीसीसी के साथ उत्पादित डीएलएल एक .lib फ़ाइल नहीं देगा और एमएसवीसी में डीएलएल को स्थिर रूप से जोड़ने के लिए एक की आवश्यकता होगी। गतिशील रूप से लिंकिंग एक बहुत ही क्लीनर विकल्प की तरह प्रतीत होता है, लेकिन नाम मैंगलिंग आपके रास्ते में आता है: यदि आप GetProcAddress को गलत GetProcAddress गए नाम को GetProcAddress करने का प्रयास करते हैं, तो कॉल विफल हो जाएगी और आप अपने डीएलएल का उपयोग नहीं कर पाएंगे। इसके लिए घूमने के लिए हैकरी की थोड़ी सी आवश्यकता होती है, और डीएलएल सीमा में सी ++ कक्षाओं को पार करने का एक बड़ा कारण यह एक बुरा विचार है।

आपको अपना डीएलएल बनाना होगा, फिर उत्पादित .def फ़ाइल की जांच करें (यदि कोई उत्पादित होता है; यह आपके प्रोजेक्ट विकल्पों के आधार पर अलग-अलग होगा) या मैंगल्ड नाम खोजने के लिए निर्भरता वॉकर जैसे टूल का उपयोग करें। फिर, आपको अपनी खुद की .def फ़ाइल लिखनी होगी, जो उलझन वाले फ़ंक्शन को एक उलझन वाले उपनाम को परिभाषित करेगी। उदाहरण के तौर पर, चलिए GetCCDLL फ़ंक्शन का उपयोग करते हैं GetCCDLL मैंने थोड़ा और उल्लेख किया है। मेरे सिस्टम पर, निम्न .def फ़ाइलें क्रमशः जीसीसी और एमएसवीसी के लिए काम करती हैं:

जीसीसी:

EXPORTS
    GetCCDLL=_Z8GetCCDLLv @1

MSVC:

EXPORTS
    [email protected]@[email protected]@XZ @1

अपने डीएलएल को पुनर्निर्माण करें, फिर निर्यात किए गए कार्यों की पुन: जांच करें। उनमें से एक असंगत कार्य नाम होना चाहिए। ध्यान दें कि आप ओवरलोडेड फ़ंक्शंस का इस तरह से उपयोग नहीं कर सकते हैं : असम्बद्ध फ़ंक्शन नाम एक विशिष्ट फ़ंक्शन अधिभार के लिए उपनाम है जैसा कि उलझन वाले नाम से परिभाषित किया गया है। यह भी ध्यान रखें कि जब भी आप फ़ंक्शन घोषणाएं बदलते हैं, तो आपको अपने DLL के लिए एक नई .def फ़ाइल बनाने की आवश्यकता होगी, क्योंकि उलझन वाले नाम बदल जाएंगे। सबसे महत्वपूर्ण बात यह है कि नाम मैंगलिंग को छोड़कर, आप किसी भी सुरक्षा को ओवरराइड कर रहे हैं जो लिंकर असंगतता के मुद्दों के संबंध में आपको पेश करने की कोशिश कर रहा है।

यदि आप अपने डीएलएल का पालन करने के लिए एक इंटरफेस बनाते हैं, तो यह पूरी प्रक्रिया सरल है, क्योंकि आपके पास अपने डीएलएल में प्रत्येक फ़ंक्शन के लिए उपनाम बनाने की आवश्यकता के बजाय उपनाम को परिभाषित करने के लिए केवल एक फ़ंक्शन होगा। हालांकि, वही चेतावनी अभी भी लागू होती है।

एक समारोह में कक्षा वस्तुओं को पारित करना

यह शायद उन मुद्दों का सबसे सूक्ष्म और सबसे खतरनाक है जो क्रॉस-कंपाइलर डेटा पास करते हैं। यहां तक ​​कि यदि आप सब कुछ संभालते हैं, तो फ़ंक्शन में तर्क कैसे पारित किए जाते हैं इसके लिए कोई मानक नहीं है । इससे सूक्ष्म दुर्घटनाओं का कोई स्पष्ट कारण नहीं हो सकता है और उन्हें डीबग करने का कोई आसान तरीका नहीं है । आपको किसी भी वापसी मूल्यों के लिए बफर समेत पॉइंटर्स के माध्यम से सभी तर्कों को पारित करने की आवश्यकता होगी। यह बेकार और असुविधाजनक है, और अभी तक एक और हैकी कामकाज है जो काम कर सकता है या नहीं।

इन सभी कामकाजों को एक साथ रखकर और टेम्पलेट्स और ऑपरेटरों के साथ कुछ रचनात्मक कार्यों पर निर्माण, हम एक डीएलएल सीमा में वस्तुओं को सुरक्षित रूप से पास करने का प्रयास कर सकते हैं। ध्यान दें कि सी ++ 11 समर्थन अनिवार्य है, जैसा कि #pragma pack और इसके रूपों के लिए समर्थन है; एमएसवीसी 2013 जीसीसी और क्लैंग के हाल के संस्करणों के रूप में, इस समर्थन की पेशकश करता है।

//POD_base.h: defines a template base class that wraps and unwraps data types for safe passing across compiler boundaries

//define malloc/free replacements to make use of Windows heap APIs
namespace pod_helpers
{
  void* pod_malloc(size_t size)
  {
    HANDLE heapHandle = GetProcessHeap();
    HANDLE storageHandle = nullptr;

    if (heapHandle == nullptr)
    {
      return nullptr;
    }

    storageHandle = HeapAlloc(heapHandle, 0, size);

    return storageHandle;
  }

  void pod_free(void* ptr)
  {
    HANDLE heapHandle = GetProcessHeap();
    if (heapHandle == nullptr)
    {
      return;
    }

    if (ptr == nullptr)
    {
      return;
    }

    HeapFree(heapHandle, 0, ptr);
  }
}

//define a template base class. We'll specialize this class for each datatype we want to pass across compiler boundaries.
#pragma pack(push, 1)
// All members are protected, because the class *must* be specialized
// for each type
template<typename T>
class pod
{
protected:
  pod();
  pod(const T& value);
  pod(const pod& copy);
  ~pod();

  pod<T>& operator=(pod<T> value);
  operator T() const;

  T get() const;
  void swap(pod<T>& first, pod<T>& second);
};
#pragma pack(pop)

//POD_basic_types.h: holds pod specializations for basic datatypes.
#pragma pack(push, 1)
template<>
class pod<unsigned int>
{
  //these are a couple of convenience typedefs that make the class easier to specialize and understand, since the behind-the-scenes logic is almost entirely the same except for the underlying datatypes in each specialization.
  typedef int original_type;
  typedef std::int32_t safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  safe_type* data;

  original_type get() const
  {
    original_type result;

    result = static_cast<original_type>(*data);

    return result;
  }

  void set_from(const original_type& value)
  {
    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type))); //note the pod_malloc call here - we want our memory buffer to go in the process heap, not the possibly-isolated DLL heap.

    if (data == nullptr)
    {
      return;
    }

    new(data) safe_type (value);
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data); //pod_free to go with the pod_malloc.
      data = nullptr;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
  }
};
#pragma pack(pop)

pod क्लास प्रत्येक मूल डेटाटाइप के लिए विशिष्ट है, ताकि int स्वचालित रूप से int32_t लपेटा जा int32_t , uint को uint32_t आदि पर लपेटा जाएगा। यह सभी दृश्यों के पीछे होता है, ओवरलोडेड = और () ऑपरेटरों के लिए धन्यवाद। मैंने शेष मूल प्रकार की विशेषज्ञता को छोड़ दिया है क्योंकि अंतर्निहित डेटाटाइप के अलावा वे लगभग पूरी तरह से समान हैं ( bool विशेषज्ञता में थोड़ा तर्क है, क्योंकि यह int8_t परिवर्तित हो गया है और फिर int8_t की तुलना 0 से की जाती है वापस bool परिवर्तित करें, लेकिन यह काफी मामूली है)।

हम इस तरह से एसटीएल प्रकार भी लपेट सकते हैं, हालांकि इसे थोड़ा अतिरिक्त काम की आवश्यकता है:

#pragma pack(push, 1)
template<typename charT>
class pod<std::basic_string<charT>> //double template ftw. We're specializing pod for std::basic_string, but we're making this specialization able to be specialized for different types; this way we can support all the basic_string types without needing to create four specializations of pod.
{
  //more comfort typedefs
  typedef std::basic_string<charT> original_type;
  typedef charT safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const charT* charValue)
  {
    original_type temp(charValue);
    set_from(temp);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  //this is almost the same as a basic type specialization, but we have to keep track of the number of elements being stored within the basic_string as well as the elements themselves.
  safe_type* data;
  typename original_type::size_type dataSize;

  original_type get() const
  {
    original_type result;
    result.reserve(dataSize);

    std::copy(data, data + dataSize, std::back_inserter(result));

    return result;
  }

  void set_from(const original_type& value)
  {
    dataSize = value.size();

    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type) * dataSize));

    if (data == nullptr)
    {
      return;
    }

    //figure out where the data to copy starts and stops, then loop through the basic_string and copy each element to our buffer.
    safe_type* dataIterPtr = data;
    safe_type* dataEndPtr = data + dataSize;
    typename original_type::const_iterator iter = value.begin();

    for (; dataIterPtr != dataEndPtr;)
    {
      new(dataIterPtr++) safe_type(*iter++);
    }
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data);
      data = nullptr;
      dataSize = 0;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
    swap(first.dataSize, second.dataSize);
  }
};
#pragma pack(pop)

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

//CCDLL.h: defines a DLL interface for a pod-based DLL
struct CCDLL_v1
{
  virtual void ShowMessage(const pod<std::wstring>* message) = 0;
};

CCDLL_v1* GetCCDLL();

यह सिर्फ डीएलएल और किसी भी कॉलर दोनों का बुनियादी उपयोग कर सकता है। ध्यान दें कि हम एक pod को पॉइंटर पास कर रहे हैं, न कि एक pod खुद। अब हमें इसे डीएलएल पक्ष पर लागू करने की आवश्यकता है:

struct CCDLL_v1_implementation: CCDLL_v1
{
  virtual void ShowMessage(const pod<std::wstring>* message) override;
};

CCDLL_v1* GetCCDLL()
{
  static CCDLL_v1_implementation* CCDLL = nullptr;

  if (!CCDLL)
  {
    CCDLL = new CCDLL_v1_implementation;
  }

  return CCDLL;
}

और अब ShowMessage फ़ंक्शन को लागू करते हैं:

#include "CCDLL_implementation.h"
void CCDLL_v1_implementation::ShowMessage(const pod<std::wstring>* message)
{
  std::wstring workingMessage = *message;

  MessageBox(NULL, workingMessage.c_str(), TEXT("This is a cross-compiler message"), MB_OK);
}

कुछ भी फैंसी नहीं: यह सिर्फ पारित pod को सामान्य wstring में कॉपी करता है और इसे एक संदेश बॉक्स में दिखाता है। आखिरकार, यह सिर्फ एक POC , पूर्ण उपयोगिता पुस्तकालय नहीं है।

अब हम डीएलएल बना सकते हैं। लिंकर के नाम मैंगलिंग के आसपास काम करने के लिए विशेष .def फ़ाइलों को न भूलें। (नोट: सीसीडीएलएल स्ट्रक्चर जो मैंने वास्तव में बनाया और भाग लिया था, उसमें से अधिक कार्यों में था। डीडीएफ फाइलें अपेक्षित काम नहीं कर सकती हैं।)

डीएलएल को कॉल करने के लिए अब एक EXE के लिए:

//main.cpp
#include "../CCDLL/CCDLL.h"

typedef CCDLL_v1*(__cdecl* fnGetCCDLL)();
static fnGetCCDLL Ptr_GetCCDLL = NULL;

int main()
{
  HMODULE ccdll = LoadLibrary(TEXT("D:\\Programming\\C++\\CCDLL\\Debug_VS\\CCDLL.dll")); //I built the DLL with Visual Studio and the EXE with GCC. Your paths may vary.

  Ptr_GetCCDLL = (fnGetCCDLL)GetProcAddress(ccdll, (LPCSTR)"GetCCDLL");
  CCDLL_v1* CCDLL_lib;

  CCDLL_lib = Ptr_GetCCDLL(); //This calls the DLL's GetCCDLL method, which is an alias to the mangled function. By dynamically loading the DLL like this, we're completely bypassing the name mangling, exactly as expected.

  pod<std::wstring> message = TEXT("Hello world!");

  CCDLL_lib->ShowMessage(&message);

  FreeLibrary(ccdll); //unload the library when we're done with it

  return 0;
}

और यहां परिणाम हैं। हमारा डीएलएल काम करता है। हम एसटीएल एबीआई के मुद्दों से पहले सफलतापूर्वक पहुंचे हैं, पिछले सी ++ एबीआई मुद्दों, पिछले मैंगलिंग मुद्दों और हमारे एमएसवीसी डीएलएल जीसीसी एक्सईई के साथ काम कर रहे हैं।

अंत में, यदि आपको पूरी तरह से डीएलएल सीमाओं में सी ++ ऑब्जेक्ट्स को पास करना होगा , तो आप इसे कैसे करते हैं। हालांकि, इनमें से कोई भी आपके सेटअप या किसी और के साथ काम करने की गारंटी नहीं है। इनमें से कोई भी किसी भी समय तोड़ सकता है, और शायद आपके सॉफ़्टवेयर को एक बड़ी रिलीज होने के पहले दिन तोड़ देगा। यह पथ हैक्स, जोखिम, और सामान्य मूर्खता से भरा है जिसे शायद मुझे शूट किया जाना चाहिए। यदि आप इस मार्ग पर जाते हैं, तो कृपया अत्यधिक सावधानी के साथ परीक्षण करें। और वास्तव में ... बस यह मत करो।







abi