कैसे मैं सुरक्षित रूप से वस्तुओं को, विशेष रूप से एसटीएल वस्तुओं को, एक डीएलएल से और पास कर सकता हूं?


106

मैं C ++ DLL से क्लास ऑब्जेक्ट्स, खासकर STL ऑब्जेक्ट्स, को कैसे और कैसे पास करूं?

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


4
यदि आप C ++ मानक पुस्तकालय के बारे में बात कर रहे हैं, तो आपको शायद इसे कॉल करना चाहिए। संदर्भ के आधार पर एसटीएल का मतलब अलग-अलग चीजें हो सकती हैं। (यह भी देखें stackoverflow.com/questions/5205491/… )
मीका विडेनमैन

जवाबों:


156

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

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


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

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

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

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

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

सदस्य reordering

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

अधिवेशन बुला रहे हैं

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

यह महत्वपूर्ण है कि आप एक मानक कॉलिंग कन्वेंशन बनाए रखें; यदि आप एक फ़ंक्शन को _cdeclC ++ के लिए डिफ़ॉल्ट घोषित करते हैं , और _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 ++ ऑब्जेक्ट को पास करना चाहते हैं, तो यह है कि आप इसे कैसे करते हैं। हालाँकि, इसमें से कोई भी आपके सेटअप या किसी और के साथ काम करने की गारंटी नहीं है। इनमें से कोई भी किसी भी समय टूट सकता है, और संभवत: आपके सॉफ़्टवेयर को एक प्रमुख रिलीज़ होने से पहले दिन को तोड़ देगा। यह रास्ता हैक, जोखिम और सामान्य मूढ़ता से भरा है, जिसके लिए मुझे संभवतः गोली मार दी जानी चाहिए। यदि आप इस मार्ग पर जाते हैं, तो कृपया अत्यधिक सावधानी के साथ परीक्षण करें। और वास्तव में ... बस यह मत करो।


1
हम्म, बुरा नहीं है! आपने विंडोज़ डीएलएल के साथ बातचीत करने के लिए मानक सी ++ प्रकारों का उपयोग करने के खिलाफ तर्कों का एक अच्छा संग्रह निकाला और तदनुसार टैग किया। ये विशेष ABI प्रतिबंध MSVC की तुलना में अन्य टूलचिन के लिए लागू नहीं होंगे। यह भी उल्लेख किया जाना चाहिए ...
evenν:α

12
@DavidHeffernan राइट। लेकिन यह मेरे लिए कई हफ्तों के शोध का नतीजा है, इसलिए मुझे लगा कि जो मैंने सीखा है, उसे दस्तावेज बनाना सार्थक होगा, ताकि अन्य लोगों को भी वही शोध करने की जरूरत न पड़े और साथ में काम करने के समाधान में भी यही प्रयास हो। इस सब के बाद से यहाँ चारों ओर एक अर्द्ध आम सवाल लगता है।
cf

@ wνontαῥεῖ ये विशेष ABI प्रतिबंध MSVC की तुलना में अन्य टूलचैन के लिए लागू नहीं होंगे। यह भी उल्लेख किया जाना चाहिए ... मुझे यकीन नहीं है कि मैं इसे सही ढंग से समझता हूं। क्या आप ये संकेत दे रहे हैं कि ABI के मुद्दे MSVC के लिए अनन्य हैं, और कहते हैं, एक डीएलएल, जो कि क्लेंग के साथ बनाया गया है, सफलतापूर्वक GCC के साथ निर्मित EXE के साथ काम करेगा? मैं थोड़ा उलझन में हूँ, क्योंकि यह मेरे सभी शोधों के विरोधाभासी लगता है ...
सीएफ मोनिका

@computerfreaker नहीं, मैं कह रहा हूँ कि PE और ELF अलग-अलग ABI प्रारूपों का उपयोग कर रहे हैं ...
fντα 14

3
@computerfreaker अधिकांश प्रमुख C ++ कंपाइलर (GCC, Clang, ICC, EDG, आदि) Itanium C ++ ABI का अनुसरण करते हैं। MSVC नहीं करता है। तो हाँ, ये ABI मुद्दे बड़े पैमाने पर MSVC के लिए विशिष्ट हैं, हालांकि यूनिक्स प्लेटफार्मों पर विशेष रूप से-सी कंपाइलर भी नहीं हैं (और एक ही कंपाइलर के अलग-अलग संस्करण!) कम-से-सही अंतर से ग्रस्त हैं। वे आम तौर पर पर्याप्त पास होते हैं, हालांकि, मुझे यह जानकर बिल्कुल आश्चर्य नहीं होगा कि आप एक जीसीसी-निर्मित निष्पादन योग्य के साथ क्लैंग-निर्मित डीएलएल को सफलतापूर्वक लिंक कर सकते हैं
स्टुअर्ट ऑलसेन

17

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

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

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

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


16

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

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

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

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

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

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

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

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


यह एक अच्छी बात है ... मुझे कहना चाहिए "कक्षाओं में इंटरफेस साझा करने से डरो मत"। मैं अपना उत्तर संपादित करूंगा।
Ph0t0n

2
हे यह एक महान जवाब है, धन्यवाद! मेरी राय में यह और भी बेहतर होगा कि आगे पढ़ने के लिए कुछ लिंक होंगे जो उन चीजों के कुछ उदाहरण दिखाते हैं जिनका आप उल्लेख कर रहे हैं (या कुछ कोड भी) - उदाहरण के लिए एसटीएल कक्षाओं को लपेटने के लिए, आदि अन्यथा मैं पढ़ रहा हूं। यह जवाब लेकिन फिर मैं थोड़ा खो गया हूं कि ये चीजें वास्तव में कैसी दिखेंगी और उन्हें कैसे खोजना है।
इला .२ El२

8

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

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

HowTo: C ++ कक्षाओं को DLL से निर्यात करें

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.