আমি কীভাবে নিরাপদে কোনও ডিএলএলে এবং বিশেষত এসটিএল অবজেক্টগুলি পাস করব?


106

আমি শ্রেণি অবজেক্টগুলি, বিশেষত এসটিএল অবজেক্টগুলি কীভাবে একটি সি ++ ডিএলএল থেকে এবং পাস করতে পারি?

আমার অ্যাপ্লিকেশনটিকে তৃতীয় পক্ষের প্লাগইনগুলির সাথে ডিএলএল ফাইল আকারে ইন্টারেক্ট করতে হবে এবং এই প্লাগইনগুলি কী সংকলকটি দিয়ে তৈরি তা আমি নিয়ন্ত্রণ করতে পারি না। আমি সচেতন যে এসটিএল বস্তুর জন্য কোনও গ্যারান্টিযুক্ত এবিআই নেই, এবং আমি আমার অ্যাপ্লিকেশনটিতে অস্থিতিশীলতা সৃষ্টি করার বিষয়ে উদ্বিগ্ন।


4
আপনি যদি সি ++ স্ট্যান্ডার্ড লাইব্রেরি সম্পর্কে কথা বলছেন তবে আপনার সম্ভবত এটি বলা উচিত। প্রসঙ্গের উপর নির্ভর করে এসটিএল বিভিন্ন জিনিস বোঝাতে পারে। (এছাড়াও স্ট্যাকওভারফ্লো.com/ জিজ্ঞাসা / ৫২০৫৯৯১/২ দেখুন )
মিশা উইডেনম্যান

উত্তর:


156

এই প্রশ্নের সংক্ষিপ্ত উত্তর না । যেহেতু কোনও স্ট্যান্ডার্ড সি ++ এবিআই নেই (অ্যাপ্লিকেশন বাইনারি ইন্টারফেস, কনভেনশনগুলি কল করার জন্য একটি স্ট্যান্ডার্ড, ডেটা প্যাকিং / প্রান্তিককরণ, ধরণের আকার ইত্যাদি), আপনাকে ক্লাসের সাথে আচরণের একটি স্ট্যান্ডার্ড পদ্ধতিতে চেষ্টা করতে এবং প্রয়োগ করতে অনেক হুপ করতে হবে আপনার প্রোগ্রামে বস্তু। এই সমস্ত হুপের মধ্য দিয়ে লাফিয়ে ওঠার পরেও এটি কাজ করবে এমন কোনও গ্যারান্টিও নেই, বা কোনও গ্যারান্টি নেই যে একটি সংকলক রিলিজে কাজ করে এমন একটি সমাধান পরবর্তী ক্ষেত্রে কাজ করবে।

কেবল এটিকে ব্যবহার করে একটি সরল সি ইন্টারফেস তৈরি করুন extern "C", যেহেতু সি এবিআই সুস্পষ্টভাবে সংজ্ঞায়িত এবং স্থিতিশীল।


আপনি যদি সত্যই সত্যই ডিএলএল সীমানা জুড়ে সি ++ অবজেক্টগুলি পাস করতে চান তবে এটি প্রযুক্তিগতভাবে সম্ভব। আপনাকে যে কয়েকটি কারণের জন্য অ্যাকাউন্ট করতে হবে তা এখানে রয়েছে:

ডেটা প্যাকিং / প্রান্তিককরণ

প্রদত্ত শ্রেণীর মধ্যে, পৃথক ডেটা সদস্যদের সাধারণত মেমরিতে বিশেষভাবে রাখা হবে যাতে তাদের ঠিকানাগুলি ধরণের আকারের একাধিকের সাথে মিলে যায়। উদাহরণস্বরূপ, intএকটি 4-বাইট সীমানায় সংযুক্ত করা যেতে পারে।

যদি আপনার ডিএলএলটি আপনার এএসইএর চেয়ে আলাদা সংকলক দিয়ে সংকলিত হয় তবে প্রদত্ত শ্রেণীর ডিএলএল সংস্করণে এক্সই এর সংস্করণের চেয়ে আলাদা প্যাকিং থাকতে পারে, সুতরাং যখন EXE ক্লাস অবজেক্টটি ডিএলএলে পাস করে, তখন ডিএলএল একটি সঠিকভাবে অ্যাক্সেস করতে অক্ষম হতে পারে শ্রেণীর মধ্যে দেওয়া ডেটা মেম্বার ডিএলএল শ্রেণীর নিজস্ব সংজ্ঞা দ্বারা নির্দিষ্ট ঠিকানাটি পড়ার চেষ্টা করবে, EXE- এর সংজ্ঞা নয়, এবং যেহেতু পছন্দসই ডেটা সদস্য সেখানে সংরক্ষণ করা হয়নি, ফলে আবর্জনার মানগুলির ফলাফল হবে।

আপনি #pragma packপ্রিপ্রোসেসর নির্দেশিকা ব্যবহার করে এটি ঘিরে কাজ করতে পারেন , যা সংকলককে নির্দিষ্ট প্যাকিং প্রয়োগ করতে বাধ্য করবে। আপনি যদি সংকলকটি বেছে নিয়েছে তার চেয়ে বড় প্যাক মান নির্বাচন করে সংকলকটি এখনও ডিফল্ট প্যাকিং প্রয়োগ করবে , সুতরাং আপনি যদি একটি বৃহত প্যাকিংয়ের মান চয়ন করেন তবে একটি শ্রেণীর এখনও সংকলকগুলির মধ্যে পৃথক প্যাকিং থাকতে পারে। এর সমাধানটি হ'ল ব্যবহার করা #pragma pack(1), যা সংকলককে ডেটা সদস্যদের এক বাইট সীমানায় সারিবদ্ধ করতে বাধ্য করবে (মূলত, কোনও প্যাকিং প্রয়োগ করা হবে না)। এটি দুর্দান্ত ধারণা নয়, কারণ এটি কার্যকারিতা সংক্রান্ত সমস্যা বা নির্দিষ্ট সিস্টেমে ক্রাশও করতে পারে। তবে এটি আপনার শ্রেণীর ডেটা সদস্যদের মেমরিতে একত্রিত হওয়ার সাথে সামঞ্জস্যতা নিশ্চিত করবে

সদস্য পুনর্নির্মাণ

আপনার বর্গ না হয়, তাহলে মান-বিন্যাস , কম্পাইলার মেমরি, তার ডেটা সদস্যদের নতুন করে সাজানো পারেন । এটি কীভাবে হয় তার কোনও মান নেই, সুতরাং কোনও ডেটা পুনরায় সাজানো কম্পাইলারগুলির মধ্যে অসঙ্গতি সৃষ্টি করতে পারে। একটি ডিএলএল এর পিছনে পিছনে ডেটা পাস করার জন্য স্ট্যান্ডার্ড-লেআউট ক্লাসগুলির প্রয়োজন হবে।

আহ্বান সম্মেলন

একটি প্রদত্ত ফাংশন থাকতে পারে এমন একাধিক কলিং কনভেনশন রয়েছে। এই কলিং কনভেনশনগুলি ডেটা ফাংশনে কীভাবে প্রেরণ করা হবে তা নির্দিষ্ট করে: প্যারামিটারগুলি কি রেজিস্টারগুলিতে বা স্ট্যাকের মধ্যে সঞ্চিত থাকে? কী অর্ডার স্ট্যাকের উপর চাপানো হয়? ফাংশন শেষ হওয়ার পরে স্ট্যাকের মধ্যে থাকা কোন যুক্তি পরিষ্কার করেন?

এটি গুরুত্বপূর্ণ যে আপনি একটি স্ট্যান্ডার্ড কলিং কনভেনশন বজায় রাখুন; যদি আপনি কোনও ফাংশন _cdeclসি ++ এর জন্য ডিফল্ট হিসাবে ঘোষণা করেন এবং _stdcall খারাপ জিনিসগুলি ব্যবহার করে এটি কল করার চেষ্টা করবেন_cdeclতবে সি ++ ফাংশনগুলির জন্য ডিফল্ট কলিং কনভেনশন হ'ল, সুতরাং এটি এমন একটি জিনিস যা আপনি ইচ্ছাকৃতভাবে একটি _stdcallজায়গায় এবং _cdeclঅন্য কোনও স্থানে নির্দিষ্ট করে ভেঙে না ফেললে তা ভাঙবে না ।

ডেটাটাইপ আকার

এই ডকুমেন্টেশন অনুসারে , উইন্ডোজে, আপনার অ্যাপ্লিকেশন 32-বিট বা 64-বিট কিনা তা নির্বিশেষে বেশিরভাগ মৌলিক ডেটাটাইপগুলির আকার একই আকার থাকে have যাইহোক, প্রদত্ত ডেটাটাইপের আকার যেহেতু সংকলক দ্বারা প্রয়োগ করা হয়, কোনও মানক দ্বারা নয় (সমস্ত মানক গ্যারান্টি হ'ল 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)), সম্ভব যেখানে সম্ভব ডেটাটাইপ আকারের সামঞ্জস্যতা নিশ্চিত করতে স্থির-আকারের ডেটাটাইপগুলি ব্যবহার করা ভাল ধারণা ।

গাদা ইস্যু

যদি আপনার ডিএলএল আপনার EXE এর তুলনায় সি রানটাইমের ভিন্ন সংস্করণে লিঙ্ক করে তবে দুটি মডিউল আলাদা হ্যাপ ব্যবহার করবে । মডিউলগুলি বিভিন্ন সংকলকগুলির সাথে সংকলিত হচ্ছে এটি প্রদত্ত একটি বিশেষত সমস্যা।

এটি প্রশমিত করতে, সমস্ত মেমরি ভাগ করা হিপগুলিতে বরাদ্দ করতে হবে এবং একই গাদা থেকে বিচ্ছিন্ন করতে হবে। সৌভাগ্যক্রমে, উইন্ডোজ এটিকে সাহায্যের জন্য এপিআই সরবরাহ করে: গেটপ্রসেসহীপ আপনাকে হোস্টের এক্সইএইপি'র হিপ অ্যাক্সেস করতে দেবে এবং হিপএলোক / হিপফ্রি আপনাকে এই স্তূপের মধ্যে বরাদ্দ করতে এবং ফ্রি মেমরি দেয়। এটি গুরুত্বপূর্ণ যে আপনি স্বাভাবিক ব্যবহার করবেন না malloc/ freeকারণ কোনও গ্যারান্টি নেই যে তারা আপনার প্রত্যাশা অনুযায়ী কাজ করবে।

এসটিএল ইস্যু

সি ++ স্ট্যান্ডার্ড লাইব্রেরিতে এবিআই ইস্যুগুলির নিজস্ব সেট রয়েছে। নেই কোন গ্যারান্টি যে একটি প্রদত্ত STL টাইপ আউট মেমরি একই ভাবে পাড়া হল কিংবা এখানে একটি গ্যারান্টি একটি প্রদত্ত STL বর্গ অন্য এক বাস্তবায়ন থেকে একই আকার আছে (বিশেষ করে, ডিবাগ তৈরী করে একটি মধ্যে অতিরিক্ত ডিবাগ তথ্য করা হতে পারে প্রদত্ত এসটিএল প্রকার)। অতএব, যে কোনও এসটিএল ধারককে ডিএলএল সীমানা পেরোনোর ​​আগে এবং অন্যদিকে পুনরায় প্যাক করার আগে মৌলিক ধরণের প্যাকগুলি আনতে হবে।

নাম ম্যাঙ্গালিং

আপনার ডিএলএল সম্ভবত পূর্বনির্ধারিত ফাংশনগুলি রফতানি করবে যা আপনার EXE কল করতে চাইবে। যাইহোক, সি ++ সংকলকগুলির ফাংশন নামগুলির ম্যাঙ্গলিংয়ের মানক উপায় নেই । এই নাম দেওয়া একটি ফাংশন মানে GetCCDLLকরতে mangled করা যেতে পারে _Z8GetCCDLLvজিসিসি এবং ?GetCCDLL@@YAPAUCCDLL_v1@@XZMSVC হবে।

আপনি ইতিমধ্যে আপনার ডিএলএল সাথে স্থিতিশীল সংযোগের গ্যারান্টি দিতে সক্ষম হবেন না, যেহেতু জিসিসির সাথে উত্পাদিত কোনও ডিএলএল একটি .lib ফাইল তৈরি করতে পারে না এবং এমএসভিসিতে কোনও ডিএলএলকে স্ট্যাটিকভাবে সংযুক্ত করার জন্য একটি প্রয়োজন। ডায়নামিকভাবে লিঙ্কিং অনেকটা ক্লিনার বিকল্পের মতো মনে হয় তবে নাম ম্যাংলিংটি আপনার পথে আসে: আপনি যদি GetProcAddressভুল ম্যাংলেড নামের চেষ্টা করেন , কলটি ব্যর্থ হবে এবং আপনি আপনার ডিএলএল ব্যবহার করতে পারবেন না। এটিকে ঘুরে দেখার জন্য সামান্য কিছু হ্যাকারি প্রয়োজন এবং এটি একটি ডিএলএল সীমানা পেরিয়ে সি ++ ক্লাস পাস করা মোটামুটি একটি বড় কারণ bad

আপনাকে আপনার ডিএলএল তৈরি করতে হবে, তারপরে উত্পাদিত .ডিএফ ফাইল পরীক্ষা করতে হবে (যদি একটি উত্পন্ন হয়; এটি আপনার প্রকল্পের বিকল্পগুলির উপর ভিত্তি করে পরিবর্তিত হবে) বা ম্যাংডেড নামটি খুঁজে পেতে নির্ভরতা ওয়াকারের মতো একটি সরঞ্জাম ব্যবহার করুন। তারপরে, আপনাকে ম্যাংলেড ফাংশনটিতে একটি নিরবিচ্ছিন্ন ওরফে সংজ্ঞা দিয়ে আপনার নিজের .def ফাইলটি লিখতে হবে। উদাহরণস্বরূপ, আসুন GetCCDLLআমি আরও কিছুটা উপরে উল্লিখিত ফাংশনটি ব্যবহার করি। আমার সিস্টেমে, নিম্নলিখিত .def ফাইলগুলি যথাক্রমে জিসিসি এবং এমএসভিসির জন্য কাজ করে:

জিসিসি:

EXPORTS
    GetCCDLL=_Z8GetCCDLLv @1

MSVC:

EXPORTS
    GetCCDLL=?GetCCDLL@@YAPAUCCDLL_v1@@XZ @1

আপনার ডিএলএল পুনর্নির্মাণ করুন, তারপরে এটি রফতানি করে ফাংশনগুলি আবার পরীক্ষা করুন। একটি নিরবিচ্ছিন্ন ফাংশন নাম তাদের মধ্যে থাকা উচিত। মনে রাখবেন যে আপনি ওভারলোডেড ফাংশনগুলি এইভাবে ব্যবহার করতে পারবেন না : ম্যামলেগড নাম দ্বারা সংজ্ঞায়িত অনুসারে আনম্যাংড ফাংশন নামটি একটি নির্দিষ্ট ফাংশন ওভারলোডের একটি নাম alias এছাড়াও মনে রাখবেন যে প্রতিবার আপনি ফাংশন ঘোষণাগুলি পরিবর্তন করার সময় আপনার ডিএলএলটির জন্য একটি নতুন .def ফাইল তৈরি করতে হবে, কারণ মাংযুক্ত নামগুলি পরিবর্তন হবে। সর্বাধিক গুরুত্বপূর্ণভাবে, নাম ম্যাংলিংকে বাইপাস করে আপনি লিঙ্কারটি যে কোনও সুরক্ষা ওভারাইড করছেন যা আপনাকে বেমানান ইস্যুতে অফার করার চেষ্টা করছে।

আপনি যদি আপনার ডিএলএল অনুসরণ করার জন্য একটি ইন্টারফেস তৈরি করেন তবে এই পুরো প্রক্রিয়াটি সহজ since তবে, একই সতর্কতা এখনও প্রযোজ্য।

কোনও ফাংশনে ক্লাস অবজেক্টগুলি পাস করা

ক্রস-সংকলক ডেটা পাস করার সমস্যাগুলিতে এটি সম্ভবত সবচেয়ে সূক্ষ্ম এবং সবচেয়ে বিপজ্জনক। এমনকি আপনি যদি সমস্ত কিছু পরিচালনা করেন তবে কোনও কার্যক্রমে কীভাবে আর্গুমেন্ট সরবরাহ করা হয় তার কোনও মান নেই । এটি কোনও আপাত কারণ এবং তাদের ডিবাগ করার সহজ কোনও উপায় ছাড়াই সূক্ষ্ম ক্র্যাশ ঘটাতে পারে । আপনাকে কোনও রিটার্ন মানগুলির জন্য বাফার সহ পয়েন্টারগুলির মাধ্যমে সমস্ত যুক্তিগুলি পাস করতে হবে । এটি আনাড়ি এবং অসুবিধাগুলি এবং এটি অন্য একটি হ্যাকি ওয়ার্কআউন্ড যা কাজ করতে পারে বা নাও পারে।


এই সমস্ত কর্মক্ষেত্রকে একত্রিত করে এবং টেম্পলেট এবং অপারেটরগুলির সাথে কিছু সৃজনশীল কাজের উপর ভিত্তি করে , আমরা নিরাপদে কোনও ডিএলএল সীমানা পেরিয়ে অবজেক্টগুলি পাস করার চেষ্টা করতে পারি। মনে রাখবেন যে সি ++ 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, 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একটি সাধারণকে অনুলিপি করে এবং এটি একটি বার্তা বাক্সে দেখায়। সর্বোপরি, এটি কেবল একটি পোক , সম্পূর্ণ ইউটিলিটি লাইব্রেরি নয়।

এখন আমরা ডিএলএল তৈরি করতে পারি। লিঙ্কারের নামের ম্যাঙ্গলিংয়ের চারপাশে কাজ করার জন্য বিশেষ .def ফাইলগুলি ভুলে যাবেন না। (দ্রষ্টব্য: আমি যে সিসিডিএলএল স্ট্রাক্টটি তৈরি করেছি তা এখানে উপস্থিত আমি তার চেয়ে বেশি ফাংশন রেখেছি। .ডিফ ফাইলগুলি প্রত্যাশার মতো কাজ করতে পারে না))

এখন কোনও এক্সই এর জন্য ডিএলএল কল করুন:

//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;
}

এবং ফলাফল এখানে। আমাদের ডিএলএল কাজ করে। আমরা অতীতের এসটিএল এবিআই ইস্যুগুলি, বিগত সি ++ এবিআইয়ের সমস্যাগুলি, অতীত ম্যাংলিংয়ের সমস্যাগুলিতে সাফল্যের সাথে পৌঁছেছি এবং আমাদের এমএসভিসি ডিএলএল একটি জিসিসি এক্সি নিয়ে কাজ করছে।

চিত্রটি পরে ফলাফল দেখায়।


উপসংহারে, যদি আপনাকে অবশ্যই ডিএলএল সীমানা জুড়ে অবশ্যই সি ++ অবজেক্টগুলি পাস করতে হয় তবে আপনি এটি এটি করেন। যাইহোক, এর কোনওটিই আপনার সেটআপ বা অন্য কারও সাথে কাজ করার গ্যারান্টিযুক্ত নয়। এর যে কোনও সময় যে কোনও সময় ভেঙে যেতে পারে এবং সম্ভবত আপনার সফ্টওয়্যারটির একটি বড় প্রকাশের সময় নির্ধারিত হওয়ার আগের দিনটি ভেঙ্গে যাবে। এই পথটি হ্যাক, ঝুঁকি এবং সাধারণ বুদ্ধি দিয়ে পূর্ণ যা সম্ভবত আমার জন্যই গুলি করা উচিত। আপনি যদি এই রুটে যান তবে চরম সতর্কতার সাথে পরীক্ষা করুন। এবং সত্যই ... কেবল এটি কিছু করবেন না।


1
হুম, খারাপ না! আপনি উইন্ডোজ ডিএলএল সাথে ইন্টারঅ্যাক্ট করতে স্ট্যান্ডার্ড সি ++ প্রকারের বিরুদ্ধে যথেষ্ট পরিমাণে যুক্তিগুলির সংগ্রহ সংগ্রহ করেছেন এবং সেই অনুসারে ট্যাগ করেছেন। এই নির্দিষ্ট এবিআই বিধিনিষেধগুলি এমএসভিসির চেয়ে অন্য সরঞ্জামচেনের জন্য প্রয়োগ করবে না। এটি এমনকি উল্লেখ করা উচিত ...
21 r

12
পুনঃটুইট তবে এটি আমার জন্য কয়েক সপ্তাহের গবেষণার ফলাফল, তাই আমি ভেবেছিলাম যে আমি যা শিখেছি তা নথিভুক্ত করা সার্থক হবে যাতে অন্যদের একই গবেষণার প্রয়োজন হয় না এবং একটি কার্যকর সমাধান একসাথে হ্যাক করার ক্ষেত্রে সেই একই প্রচেষ্টা করা প্রয়োজন। আরও প্রায় তাই যেহেতু এটি প্রায় কাছাকাছি একটি অর্ধ-সাধারণ প্রশ্ন বলে মনে হচ্ছে।
সিএফ মনিকা

@ πάνταῥεῖ এই নির্দিষ্ট এবিআই বিধিনিষেধগুলি এমএসভিসির চেয়ে অন্য সরঞ্জামচেনের জন্য প্রয়োগ করবে না। এটি এমনকি উল্লেখ করা উচিত ... আমি নিশ্চিত যে আমি এটি সঠিকভাবে বুঝতে পেরেছি না। আপনি কি এবিআইয়ের এই বিষয়গুলি এমএসভিসির সাথে একচেটিয়া নির্দেশ করছেন, এবং বলুন, ঝনঝন দিয়ে নির্মিত একটি ডিএলএল সফলভাবে জিসিসি দিয়ে নির্মিত কোনও এক্সই দিয়ে সফলভাবে কাজ করবে? আমি কিছুটা বিভ্রান্ত, যেহেতু এটি আমার সমস্ত গবেষণার
বিরোধী

@ কম্পিউটার কম্পিউটার সম্পর্কে আমি বলছি না যে পিই এবং ইএলএফ বিভিন্ন এবিআই ফর্ম্যাট ব্যবহার করছে ...
r

3
@ কম্পিউটার কম্পিউটারে সবচেয়ে বড় সি ++ কম্পাইলার (জিসিসি, ক্ল্যাং, আইসিসি, ইডিজি ইত্যাদি) ইটানিয়াম সি ++ এবিআই অনুসরণ করে। এমএসভিসি না। সুতরাং হ্যাঁ, এবিআইয়ের এই সমস্যাগুলি মূলত এমএসভিসি-তে সুনির্দিষ্ট, যদিও একচেটিয়াভাবে নয় - এমনকি ইউনিক্স প্ল্যাটফর্মের সি সংকলক (এমনকি একই সংকলকটির বিভিন্ন সংস্করণ!) কম-নিখুঁত আন্তঃব্যবযোগিতা থেকে ভুগছে। তারা সাধারণত যথেষ্ট কাছাকাছি থাকে, যদিও, আপনি জিসিসি-বিল্ট এক্সিকিউটেবলের সাথে ক্ল্যাং-বিল্ট ডিএলএলকে সাফল্যের সাথে সংযুক্ত করতে পারেন তা জানতে পেরে আমি মোটেও অবাক হব না ।
স্টুয়ার্ট ওলসেন

17

টাইপ সংজ্ঞা ব্যবহারকারীর নিয়ন্ত্রণাধীন এবং ঠিক একই টোকেন ক্রম উভয় প্রোগ্রামেই ব্যবহার করা হলেও এমনকী, সাধারণ কম্পিউটারে এবিআইর অভাব সাধারণভাবে ডিএলএল সীমানা পেরিয়ে সি ++ অবজেক্টগুলি কেন আটকাতে পারে তার একটি দুর্দান্ত ব্যাখ্যা @ কম্পিউটার কম্পিউটারে লিখেছেন। (দুটি কাজ রয়েছে যা কাজ করে: স্ট্যান্ডার্ড-লেআউট ক্লাস এবং খাঁটি ইন্টারফেস)

সি ++ স্ট্যান্ডার্ডে সংজ্ঞায়িত অবজেক্টের ধরণের জন্য (স্ট্যান্ডার্ড টেম্পলেট লাইব্রেরি থেকে অভিযোজিত অন্তর্ভুক্ত) পরিস্থিতি অনেক বেশি খারাপ। এই ধরণের সংজ্ঞা দেওয়া টোকেনগুলি একাধিক সংকলক জুড়ে একই নয়, কারণ সি ++ স্ট্যান্ডার্ড একটি সম্পূর্ণ ধরণের সংজ্ঞা দেয় না, কেবল ন্যূনতম প্রয়োজনীয়তা। এছাড়াও, এই ধরণের সংজ্ঞাগুলিতে প্রদর্শিত শনাক্তকারীদের নাম অনুসন্ধান একই সমাধান করে না। এমনকি এমন সিস্টেমে যেখানে সি ++ এবিআই রয়েছে, মডিউল সীমানা জুড়ে এ জাতীয় ধরণের ভাগ করে নেওয়ার চেষ্টা করার ফলে ওয়ান সংজ্ঞা বিধি লঙ্ঘনের কারণে ব্যাপক অপরিজ্ঞাত আচরণের ফলস্বরূপ।

এটি এমন একটি বিষয় যা লিনাক্স প্রোগ্রামাররা ডিল করার ক্ষেত্রে অভ্যস্ত ছিল না, কারণ জি ++ এর লিস্টস্টিক ++ একটি ডি-ফ্যাক্টো স্ট্যান্ডার্ড ছিল এবং কার্যত সমস্ত প্রোগ্রাম এটি ব্যবহার করেছিল, এইভাবে ওডিআর সন্তুষ্ট করে। ক্ল্যাংয়ের লিবিসি ++ এই ধারণাটি ভেঙে ফেলেছে এবং তারপরে সি ++ 11 প্রায় সমস্ত স্ট্যান্ডার্ড লাইব্রেরি ধরণের বাধ্যতামূলক পরিবর্তন সহ এসেছিল।

শুধু মডিউলগুলির মধ্যে স্ট্যান্ডার্ড লাইব্রেরি প্রকারগুলি ভাগ করবেন না। এটি অপরিবর্তিত আচরণ।


16

এখানে কিছু উত্তর সি ++ শ্রেণি পাস করা সত্যই ভীতিজনক বলে মনে করে তবে আমি একটি বিকল্প দৃষ্টিকোণ ভাগ করে নিতে চাই। অন্যান্য প্রতিক্রিয়াগুলির কয়েকটিতে উল্লিখিত খাঁটি ভার্চুয়াল সি ++ পদ্ধতিটি আসলে আপনি যা ভাবেন তার থেকে পরিষ্কার হয়ে যায়। আমি ধারণার চারপাশে একটি সম্পূর্ণ প্লাগইন সিস্টেম তৈরি করেছি এবং এটি বছরের পর বছর ধরে খুব ভালভাবে কাজ করছে। আমার একটি "প্লাগইন ম্যানেজার" শ্রেণি রয়েছে যা লোডলিব () এবং গেটপ্রোকএড্রেস () (এবং লিনাক্সের সমতুল্য যাতে এটি ক্রস প্ল্যাটফর্মটি তৈরি করতে এক্সিকিউটেবল) ব্যবহার করে একটি নির্দিষ্ট ডিরেক্টরি থেকে গতিসম্পন্নভাবে ঘরগুলি লোড করে।

বিশ্বাস করুন বা না করুন, এই পদ্ধতিটি ক্ষমা করে দিচ্ছে এমনকি যদি আপনি কিছু বিশ্রী জিনিস যেমন আপনার খাঁটি ভার্চুয়াল ইন্টারফেসের শেষে একটি নতুন ফাংশন যুক্ত করেন এবং সেই নতুন ফাংশন ছাড়াই ইন্টারফেসের বিরুদ্ধে সংকলিত ডেলগুলি লোড করার চেষ্টা করেন - তারা ঠিক জরিমানা চাপবে। অবশ্যই ... আপনার এক্সিকিউটেবল কেবল নতুন dlls যে ফাংশনটি বাস্তবায়ন করে তার জন্য নতুন ফাংশনটি কল করে তা নিশ্চিত করার জন্য আপনাকে একটি সংস্করণ নম্বর পরীক্ষা করতে হবে। তবে সুসংবাদটি হ'ল: এটি কার্যকর! সুতরাং একটি উপায়ে আপনার সময়ের সাথে আপনার ইন্টারফেসটি বিকশিত করার জন্য একটি অশোধিত পদ্ধতি রয়েছে।

খাঁটি ভার্চুয়াল ইন্টারফেস সম্পর্কে আরেকটি দুর্দান্ত জিনিস - আপনি যতটা ইন্টারফেস নিজের ইচ্ছে মতো উত্তরাধিকারী হতে পারেন এবং আপনি কখনই হীরক সমস্যার মুখোমুখি হন না!

আমি বলব এই পদ্ধতির সবচেয়ে বড় ক্ষয়ক্ষতিটি হ'ল আপনি কী ধরণের পরামিতি হিসাবে পাস করেন সে সম্পর্কে আপনাকে খুব সতর্কতা অবলম্বন করতে হবে। প্রথমে খাঁটি ভার্চুয়াল ইন্টারফেস দিয়ে মোড়ানো ছাড়া কোনও ক্লাস বা এসটিএল অবজেক্ট নেই। কোনও স্ট্রাক্ট নেই (প্রগমা প্যাক ভুডো দিয়ে যাওয়া ছাড়াই)। কেবল প্রাথমিক প্রকার এবং অন্যান্য ইন্টারফেসের দিকে নির্দেশক। এছাড়াও, আপনি ফাংশনগুলি ওভারলোড করতে পারবেন না, যা কোনও অসুবিধা হলেও শো-স্টপার নয়।

সুসংবাদটি হ'ল মুষ্টিমেয় কোডের লাইন দিয়ে আপনি পুনরায় ব্যবহারযোগ্য জেনেরিক ক্লাস এবং এসটিএল স্ট্রিং, ভেক্টর এবং অন্যান্য ধারক ক্লাসগুলি মোড়ানোর জন্য ইন্টারফেস তৈরি করতে পারেন। বিকল্পভাবে, আপনি তালিকার মাধ্যমে লোকেরা লুপ করতে আপনার গেটকাউন্ট () এবং গেটভাল (এন) এর মতো আপনার ইন্টারফেসে ফাংশন যুক্ত করতে পারেন।

আমাদের জন্য প্লাগইন তৈরি করা লোকেরা এটি বেশ সহজ খুঁজে পায়। তাদের এবিআই সীমানা বা যে কোনও কিছুর বিশেষজ্ঞ হতে হবে না - তারা কেবল আগ্রহী ইন্টারফেসের উত্তরাধিকারী হয়, তাদের সমর্থন করা ফাংশনগুলি কোড করে এবং তারা যা করে না তার জন্য মিথ্যা ফিরিয়ে দেয়।

যে প্রযুক্তিটি এই সমস্ত কাজ করে তা কোনও স্ট্যান্ডার্ডের ভিত্তিতে নয় যতটা আমি জানি। আমি যা সংগ্রহ করি তা থেকে মাইক্রোসফ্ট তাদের ভার্চুয়াল টেবিলগুলি সেভাবেই করার সিদ্ধান্ত নিয়েছে যাতে তারা সিওএম তৈরি করতে পারে এবং অন্যান্য সংকলক লেখকরা মামলা অনুসরণ করার সিদ্ধান্ত নিয়েছিলেন। এর মধ্যে জিসিসি, ইন্টেল, বোরল্যান্ড এবং অন্যান্য প্রধান সি ++ সংকলক অন্তর্ভুক্ত রয়েছে। যদি আপনি কোনও অস্পষ্ট এমবেডড সংকলক ব্যবহার করার পরিকল্পনা করে থাকেন তবে সম্ভবত এই পদ্ধতিটি আপনার পক্ষে কার্যকর হবে না। তাত্ত্বিকভাবে যে কোনও সংকলক সংস্থা যে কোনও সময় তাদের ভার্চুয়াল টেবিলগুলি পরিবর্তন করতে এবং জিনিসগুলি ভেঙে ফেলতে পারে, কিন্তু এই প্রযুক্তির উপর নির্ভরশীল কয়েক বছর ধরে প্রচুর কোড লেখা হয়েছে তা বিবেচনা করে, বড় খেলোয়াড়দের মধ্যে কেউ যদি র‌্যাঙ্ক ভাঙার সিদ্ধান্ত নেন তবে আমি খুব অবাক হব।

গল্পটির নৈতিকতা হ'ল ... কয়েকটি চরম পরিস্থিতি ব্যতীত আপনার ইন্টারফেসের দায়িত্বে থাকা একজন ব্যক্তির প্রয়োজন যারা এবিআইয়ের সীমানা আদিম ধরণের সাথে পরিষ্কার থাকে এবং অতিরিক্ত লোড এড়ানো যায় তা নিশ্চিত করতে পারে। যদি আপনি এই শর্তটি ঠিক করেন তবে আমি সংস্থাগুলির মধ্যে ডিএলএল / এসও-তে ক্লাসে ইন্টারফেস ভাগ করতে ভয় পাব না। ক্লাসগুলি সরাসরি ভাগ করা == সমস্যা, তবে খাঁটি ভার্চুয়াল ইন্টারফেসগুলি ভাগ করা এত খারাপ নয়।


এটি একটি ভাল বিষয় ... আমার বলা উচিত ছিল "ক্লাসে ইন্টারফেস ভাগ করতে ভয় পাবেন না"। আমি আমার উত্তর সম্পাদনা করব।
Ph0t0n

2
আরে এটি দুর্দান্ত উত্তর, ধন্যবাদ! আমার মতে এটি আরও কী উন্নত করবে তা হ'ল আরও পড়ার কয়েকটি লিঙ্ক যা আপনার উল্লেখ করা জিনিসগুলির কিছু উদাহরণ (বা এমনকি কিছু কোড) দেখায় - যেমন এসটিএল ক্লাসগুলি মোড়ানো ইত্যাদির জন্য, নইলে আমি পড়ছি এই উত্তরটি কিন্তু তারপরে এই জিনিসগুলি আসলে কেমন দেখায় এবং কীভাবে তাদের অনুসন্ধান করা যায় সে সম্পর্কে আমি কিছুটা হারিয়েছি।
Ela782

8

আপনি সমস্ত মডিউল (.EXE এবং .DLLs) একই সি ++ সংকলক সংস্করণ এবং সিআরটির একই সেটিংস এবং স্বাদগুলির সাথে নির্মিত না হলে আপনি নিরাপদে ডিএলএল সীমানা অতিক্রম করতে পারবেন না, যা আপনার ক্ষেত্রে সীমাবদ্ধ নয় এবং স্পষ্টভাবে আপনার ক্ষেত্রে নয়।

আপনি যদি আপনার ডিএলএল থেকে কোনও অবজেক্ট-ভিত্তিক ইন্টারফেসটি প্রকাশ করতে চান, আপনার সি ++ খাঁটি ইন্টারফেসগুলি প্রকাশ করা উচিত (যা সিওএমের মতো হয়)। কোডপ্রজেক্টে এই আকর্ষণীয় নিবন্ধটি পড়ার বিষয়ে বিবেচনা করুন:

হাওটো: একটি ডিএলএল থেকে সি ++ ক্লাস রফতানি করুন

আপনি ডিএলএল সীমানায় খাঁটি সি ইন্টারফেসটি প্রকাশের কথা বিবেচনা করতে এবং তারপরে কলার সাইটে একটি সি ++ র‌্যাপার তৈরি করতেও বিবেচনা করতে পারেন।
এটি উইন 32 এ যা ঘটে তার অনুরূপ: উইন 32 বাস্তবায়ন কোডটি প্রায় সি ++, তবে প্রচুর উইন 32 এপিআই একটি খাঁটি সি ইন্টারফেস প্রকাশ করে (এমন এপিআই রয়েছে যা সিওএম ইন্টারফেসগুলি প্রকাশ করে)। তারপরে এটিএল / ডাব্লুটিএল এবং এমএফসি এই খাঁটি সি ইন্টারফেসগুলিকে সি ++ শ্রেণি এবং অবজেক্টের সাথে মোড়া করে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.