इस प्रश्न का संक्षिप्त उत्तर नहीं है । क्योंकि कोई मानक C ++ ABI नहीं है (एप्लिकेशन बाइनरी इंटरफ़ेस, कॉलिंग कन्वेंशन, डेटा पैकिंग / संरेखण, प्रकार आकार आदि के लिए एक मानक), आपको कक्षा से निपटने के मानक तरीके को लागू करने और लागू करने के लिए बहुत सारे हुप्स के माध्यम से कूदना होगा। अपने कार्यक्रम में वस्तुओं। आप उन सभी हुप्स के माध्यम से कूदने के बाद भी काम करेंगे इसकी कोई गारंटी नहीं है, न ही यह गारंटी है कि एक संकलक रिलीज में काम आने वाला समाधान अगले में काम करेगा।
बस का उपयोग कर अंतरफलक एक सादे सी बनाने extern "C"
, के बाद से सी ABI है अच्छी तरह से परिभाषित और स्थिर।
यदि आप वास्तव में, वास्तव में एक डीएलएल सीमा के पार सी ++ वस्तुओं को पारित करना चाहते हैं, तो यह तकनीकी रूप से संभव है। यहाँ कुछ कारकों के बारे में बताया गया है:
डेटा पैकिंग / संरेखण
किसी दिए गए वर्ग के भीतर, व्यक्तिगत डेटा सदस्यों को विशेष रूप से स्मृति में रखा जाएगा ताकि उनके पते कई प्रकार के आकार के अनुरूप हों। उदाहरण के लिए, int
एक 4-बाइट सीमा से जुड़ा जा सकता है।
यदि आपका DLL आपके EXE से भिन्न कंपाइलर के साथ संकलित है, तो किसी दिए गए वर्ग के DLL के संस्करण में EXE के संस्करण की तुलना में अलग पैकिंग हो सकती है, इसलिए जब EXE DLL के लिए क्लास ऑब्जेक्ट को पास करता है, तो DLL ठीक से एक्सेस नहीं कर सकता है उस वर्ग के भीतर डेटा सदस्य दिए गए। DLL वर्ग की अपनी परिभाषा द्वारा निर्दिष्ट पते से पढ़ने का प्रयास करेगा, न कि EXE की परिभाषा, और चूंकि वांछित डेटा सदस्य वास्तव में वहां संग्रहीत नहीं है, इसलिए कचरा मूल्यों का परिणाम होगा।
आप #pragma pack
प्रीप्रोसेसर निर्देश का उपयोग करके इसके चारों ओर काम कर सकते हैं , जो संकलक को विशिष्ट पैकिंग लागू करने के लिए मजबूर करेगा। कंपाइलर अभी भी डिफॉल्ट पैकिंग लागू करेगा, यदि आप एक कंपाइलर द्वारा चुने गए पैक से बड़ा मान चुनते हैं , तो यदि आप एक बड़े पैकिंग मूल्य को चुनते हैं , तो एक वर्ग अभी भी कंपाइलरों के बीच अलग-अलग पैकिंग कर सकता है। इसके लिए समाधान का उपयोग करना है #pragma pack(1)
, जो संकलक को एक-बाइट सीमा (अनिवार्य रूप से, कोई पैकिंग लागू नहीं होगी) पर डेटा सदस्यों को संरेखित करने के लिए मजबूर करेगा। यह एक महान विचार नहीं है, क्योंकि यह प्रदर्शन समस्याओं या कुछ सिस्टम पर क्रैश का कारण बन सकता है। हालाँकि, यह आपकी कक्षा के डेटा सदस्यों को स्मृति में संरेखित करने के तरीके में स्थिरता सुनिश्चित करेगा ।
सदस्य reordering
यदि आपकी कक्षा मानक-लेआउट नहीं है , तो संकलक अपने डेटा सदस्यों को मेमोरी में पुनर्व्यवस्थित कर सकता है । यह कैसे किया जाता है, इसके लिए कोई मानक नहीं है, इसलिए किसी भी डेटा का पुनर्संरचना संकलक के बीच असंगति पैदा कर सकता है। एक DLL के आगे और पीछे डेटा पास करने के लिए मानक-लेआउट कक्षाओं की आवश्यकता होगी।
अधिवेशन बुला रहे हैं
दिए गए फ़ंक्शन में कई कॉलिंग कन्वेंशन हो सकते हैं। ये कॉलिंग कन्वेंशन निर्दिष्ट करते हैं कि डेटा को फ़ंक्शंस में कैसे पास किया जाना चाहिए: क्या रजिस्टरों में या स्टैक पर संग्रहीत पैरामीटर हैं? ढेर पर दिए गए तर्क क्या आदेश हैं? फ़ंक्शन समाप्त होने के बाद स्टैक पर छोड़े गए किसी भी तर्क को कौन साफ करता है?
यह महत्वपूर्ण है कि आप एक मानक कॉलिंग कन्वेंशन बनाए रखें; यदि आप एक फ़ंक्शन को _cdecl
C ++ के लिए डिफ़ॉल्ट घोषित करते हैं , और _stdcall
खराब चीजों का उपयोग करके इसे कॉल करने का प्रयास करेंगे । _cdecl
हालाँकि, C ++ फ़ंक्शंस के लिए डिफ़ॉल्ट कॉलिंग कन्वेंशन है, इसलिए यह एक ऐसी चीज़ है जो तब तक नहीं टूटेगी जब तक कि आप जानबूझकर इसे _stdcall
एक जगह और _cdecl
दूसरे में निर्दिष्ट करके नहीं तोड़ेंगे ।
डेटाटाइप आकार
इस दस्तावेज़ के अनुसार , विंडोज पर, अधिकांश मौलिक डेटाटाइप्स के आकार समान होते हैं चाहे आपका ऐप 32-बिट हो या 64-बिट। हालाँकि, किसी दिए गए डेटाटाइप के आकार को कंपाइलर द्वारा लागू किया जाता है, किसी भी मानक द्वारा नहीं (सभी मानक गारंटी यह है कि 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
), यह निश्चित आकार के डेटाटाइप्स का उपयोग करने के लिए एक अच्छा विचार है, जहां संभव हो, डेटाटाइप आकार संगतता सुनिश्चित करने के लिए।
ढेर मुद्दे
यदि आपका DLL आपके EXE की तुलना में C रनटाइम के किसी भिन्न संस्करण से लिंक करता है, तो दोनों मॉड्यूल अलग-अलग ढेर का उपयोग करेंगे । यह एक विशेष रूप से संभावना समस्या है कि मॉड्यूल विभिन्न संकलक के साथ संकलित किए जा रहे हैं।
इसे कम करने के लिए, सभी मेमोरी को एक साझा ढेर में आवंटित करना होगा, और उसी ढेर से निपटना होगा। सौभाग्य से, विंडोज इस काम में सहायता करने के लिए एपीआई प्रदान करता है: GetProcessHeap आप मेजबान EXE के ढेर का उपयोग करने देगा, और HeapAlloc / HeapFree आप आवंटित और इस ढेर के भीतर मुक्त स्मृति करने देगा। यह महत्वपूर्ण है कि आप सामान्य उपयोग न करें malloc
/ free
जैसा कि कोई गारंटी नहीं है कि वे उस तरह से काम करेंगे जैसे आप उम्मीद करते हैं।
एसटीएल जारी करता है
C ++ मानक लाइब्रेरी का ABI मुद्दों का अपना सेट है। इस बात की कोई गारंटी नहीं है कि किसी दिए गए STL प्रकार को मेमोरी में उसी तरह से रखा गया है, और न ही इस बात की कोई गारंटी है कि किसी दिए गए STL वर्ग का एक कार्यान्वयन से दूसरे में समान आकार है (विशेष रूप से, डिबग बिल्ड अतिरिक्त डीबग जानकारी को एक में डाल सकता है दिए गए STL प्रकार)। इसलिए, किसी भी एसटीएल कंटेनर को डीएलएल सीमा के पार जाने से पहले मौलिक प्रकारों में अनपैक करना होगा और दूसरी तरफ से वापस करना होगा।
नामकरण
आपका DLL संभवतः उन कार्यों को निर्यात करेगा जिन्हें आपका EXE कॉल करना चाहेगा। हालाँकि, C ++ कंपाइलर्स में फ़ंक्शन नामों के मानक तरीके नहीं होते हैं । इसका मतलब है कि GCC और MSVC में एक फंक्शन नाम दिया GetCCDLL
जा सकता है ।_Z8GetCCDLLv
?GetCCDLL@@YAPAUCCDLL_v1@@XZ
आप पहले से ही अपने DLL को स्थैतिक लिंकिंग की गारंटी नहीं दे पाएंगे, क्योंकि GCC के साथ उत्पादित DLL एक .lib फ़ाइल का उत्पादन नहीं करेगा और सांख्यिकीय रूप से MSVC में DLL को जोड़ने की आवश्यकता है। डायनामिकली लिंकिंग एक बहुत क्लीनर विकल्प की तरह लगता है, लेकिन नाम मैनलिंग आपके रास्ते में हो जाता है: यदि आप GetProcAddress
गलत मैंगल्ड नाम की कोशिश करते हैं, तो कॉल विफल हो जाएगी और आप अपने DLL का उपयोग नहीं कर पाएंगे। यह चारों ओर पाने के लिए थोड़ी सी हैकरी की आवश्यकता होती है, और एक काफी प्रमुख कारण है कि एक डीएलएल सीमा के पार सी ++ कक्षाएं पास करना एक बुरा विचार है।
आपको अपना DLL बनाने की आवश्यकता होगी, फिर उत्पादित .def फ़ाइल की जांच करें (यदि एक का उत्पादन किया जाता है, तो यह आपके प्रोजेक्ट विकल्पों के आधार पर अलग-अलग होगा) या मंगली नाम खोजने के लिए डिपेंडेंसी वॉकर जैसे टूल का उपयोग करें। फिर, आपको अपनी .def फाइल को लिखने की आवश्यकता होगी , जो कि मैंगल्ड फंक्शन में एक अनमैन्ड एलियास को परिभाषित करता है। एक उदाहरण के रूप में, चलिए उस GetCCDLL
फ़ंक्शन का उपयोग करते हैं जिसका मैंने थोड़ा और उल्लेख किया है। मेरे सिस्टम पर, निम्न। Gf फाइलें क्रमशः GCC और MSVC के लिए काम करती हैं:
जीसीसी:
EXPORTS
GetCCDLL=_Z8GetCCDLLv @1
MSVC:
EXPORTS
GetCCDLL=?GetCCDLL@@YAPAUCCDLL_v1@@XZ @1
अपने DLL का पुनर्निर्माण करें, फिर उसके द्वारा निर्यात किए जाने वाले कार्यों की पुनः जाँच करें। उनके बीच एक अनमैन्डल फंक्शन नाम होना चाहिए। ध्यान दें कि आप इस तरह से अधिभारित कार्यों का उपयोग नहीं कर सकते हैं : अनमैंगल्ड फ़ंक्शन नाम एक विशिष्ट फ़ंक्शन अधिभार के लिए एक उपनाम है जिसे मैंगल्ड नाम से परिभाषित किया गया है। यह भी ध्यान दें कि आपको हर बार अपने DLL के लिए एक नई .def फ़ाइल बनाने की आवश्यकता होगी, जब आप फ़ंक्शन की घोषणाओं को बदल देंगे, क्योंकि मंगल के नाम बदल जाएंगे। सबसे महत्वपूर्ण बात, नामकरण को दरकिनार करके, आप किसी भी सुरक्षा को ओवरराइड कर रहे हैं जो लिंकर आपको असंगतता के मुद्दों के संबंध में पेश करने की कोशिश कर रहा है।
यदि आप अपने DLL का अनुसरण करने के लिए एक इंटरफ़ेस बनाते हैं, तो यह पूरी प्रक्रिया सरल है , क्योंकि आपके पास अपने DLL में प्रत्येक फ़ंक्शन के लिए एक उपनाम बनाने की आवश्यकता के बजाय एक उपनाम को परिभाषित करने के लिए एक फ़ंक्शन होगा। हालांकि, एक ही कैवेट अभी भी लागू होते हैं।
एक समारोह में कक्षा वस्तुओं को पास करना
यह संभवतः सबसे सूक्ष्म और सबसे खतरनाक है जो क्रॉस-कंपाइलर डेटा पास करने वाले मुद्दों से जुड़ा है। यहां तक कि अगर आप सब कुछ संभालते हैं, तो इस बात के लिए कोई मानक नहीं है कि किसी फ़ंक्शन के लिए तर्क कैसे पारित किए जाते हैं । यह कोई स्पष्ट कारण के साथ सूक्ष्म दुर्घटनाओं का कारण बन सकता है और उन्हें डीबग करने का कोई आसान तरीका नहीं है । आपको किसी भी रिटर्न वैल्यू के लिए बफ़र सहित पॉइंटर्स के माध्यम से सभी तर्क पारित करने होंगे । यह अनाड़ी और असुविधाजनक है, और अभी तक एक और हैकी वर्कअराउंड है जो काम कर सकता है या नहीं।
इन सभी कार्यपत्रकों को एक साथ रखना और टेम्प्लेट और ऑपरेटरों के साथ कुछ रचनात्मक कार्यों पर निर्माण करना , हम वस्तुओं को डीएलएल सीमा में सुरक्षित रूप से पारित करने का प्रयास कर सकते हैं। ध्यान दें कि C ++ 11 समर्थन अनिवार्य है, जैसा कि #pragma pack
इसके वेरिएंट के लिए समर्थन है ; MSVC 2013 यह सहायता प्रदान करता है, जैसा कि GCC और क्लैंग के हाल के संस्करणों में किया गया है।
//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
, 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)
अब हम एक DLL बना सकते हैं जो इन पॉड प्रकारों का उपयोग करता है। पहले हमें एक इंटरफ़ेस की आवश्यकता होती है, इसलिए हमारे पास केवल एक तरीका होगा कि मैंन्यूलिंग के लिए पता लगा सकूं।
//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();
यह सिर्फ DLL और किसी भी कॉलर का उपयोग कर सकते हैं दोनों एक बुनियादी इंटरफ़ेस बनाता है। ध्यान दें कि हम पॉइंटर को पास कर रहे हैं pod
, pod
खुद को नहीं । अब हमें DLL की ओर से इसे लागू करने की आवश्यकता है:
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
और एक संदेश बॉक्स में दिखाता है। आखिरकार, यह सिर्फ एक पीओसी है , पूर्ण उपयोगिता पुस्तकालय नहीं।
अब हम DLL का निर्माण कर सकते हैं। लिंक करने वाले के नाम के आसपास काम करने के लिए विशेष .def फ़ाइलों को न भूलें। (ध्यान दें: सीसीडीएलएल संरचना जिसे मैंने वास्तव में बनाया था और जो मेरे यहां मौजूद था, उससे अधिक कार्य किया था।। यदि फाइलें अपेक्षित रूप से काम नहीं करती हैं।)
अब EXE के लिए DLL पर कॉल करें:
//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;
}
और यहाँ परिणाम हैं। हमारे DLL काम करता है। हम सफलतापूर्वक STL ABI मुद्दों, पिछले C ++ ABI मुद्दों, पिछले mangling मुद्दों पर पहुँच चुके हैं और हमारा MSVC DLL GCC EXE के साथ काम कर रहा है।
अंत में, यदि आप पूरी तरह से DLL सीमाओं के पार C ++ ऑब्जेक्ट को पास करना चाहते हैं, तो यह है कि आप इसे कैसे करते हैं। हालाँकि, इसमें से कोई भी आपके सेटअप या किसी और के साथ काम करने की गारंटी नहीं है। इनमें से कोई भी किसी भी समय टूट सकता है, और संभवत: आपके सॉफ़्टवेयर को एक प्रमुख रिलीज़ होने से पहले दिन को तोड़ देगा। यह रास्ता हैक, जोखिम और सामान्य मूढ़ता से भरा है, जिसके लिए मुझे संभवतः गोली मार दी जानी चाहिए। यदि आप इस मार्ग पर जाते हैं, तो कृपया अत्यधिक सावधानी के साथ परीक्षण करें। और वास्तव में ... बस यह मत करो।