مميزات ما هو تأثير extern "C" في C++؟




من هو مخترع لغة البرمجة السي c (10)

extern "C" هي مواصفة ربط تستخدم في استدعاء وظائف C في ملفات مصدر Cpp . يمكننا الاتصال بوظائف C وكتابة متغيرات وإدراج رؤوس . يتم الإعلان عن الوظيفة في كيان خارجي ويتم تعريفها بالخارج. بناء الجملة هو

النوع الأول:

extern "language" function-prototype

النوع 2:

extern "language"
{
     function-prototype
};

على سبيل المثال:

#include<iostream>
using namespace std;

extern "C"
{
     #include<stdio.h>    // Include C Header
     int n;               // Declare a Variable
     void func(int,int);  // Declare a function (function prototype)
}

int main()
{
    func(int a, int b);   // Calling function . . .
    return 0;
}

// Function definition . . .
void func(int m, int n)
{
    //
    //
}

ما الذي يفعله بالضبط وضع extern "C" في C ++ code؟

فمثلا:

extern "C" {
   void foo();
}

يجعل extern "C" اسمًا وظيفيًا في C ++ يحتوي على رابط "C" (لا يقوم المترجم بتشذيب الاسم) بحيث يمكن أن يرتبط كود C العميل (مثل استخدام) الدالة الخاصة بك باستخدام ملف رأس متوافق مع "C" يحتوي فقط على الإعلان عن وظيفتك. يوجد تعريف الدالة الخاص بك في تنسيق ثنائي (تم تجميعه بواسطة برنامج التحويل البرمجي C ++) الذي سيقوم رابط العميل 'C' بالربط به باستخدام اسم 'C'.

نظرًا لأن C ++ يحتوي على حمولة زائدة من أسماء الدوال و C ، لا يستطيع مترجم لغة C ++ فقط استخدام اسم الوظيفة كمعرف فريد للارتباط به ، لذلك يقوم بتشذيب الاسم عن طريق إضافة معلومات حول الوسيطات. لا يحتاج مترجم AC إلى تعتيق الاسم لأنك لا يمكن تحميل أسماء الدالة في C. عند الإشارة إلى أن دالة له علاقة "C" خارجي في C ++ ، لا يضيف برنامج التحويل البرمجي C ++ معلومات نوع معلمة / معلمة إلى الاسم المستخدم لـ الربط.

فقط لكي تعرف ، يمكنك تحديد ارتباط "C" لكل إعلان / تعريف بشكل صريح أو استخدام كتلة لتجميع سلسلة من التعريفات / التعريفات للحصول على رابط معين:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

إذا كنت تهتم بالأمور الفنية ، فهي مدرجة في القسم 7.5 من معيار C ++ 03 ، وهنا ملخص موجز (مع التركيز على extern "C"):

  • extern "C" هو مواصفات الربط
  • مطلوب كل مترجم لتوفير "C" الربط
  • يجب أن تحدث مواصفات الربط فقط في نطاق مساحة الاسم
  • جميع أنواع الوظائف وأسماء الدوال وأسماء المتغيرات لها ارتباط لغوي انظر تعليق ريتشارد: فقط أسماء الدوال وأسماء المتغيرات التي لها رابط خارجي لها ارتباط لغوي
  • نوعان من الوظائف ذات روابط لغوية متميزة هما نوعان متميزان حتى لو كانا متشابهين
  • تحدد مواصفات linkage nest ، inner one الربط النهائي
  • يتم تجاهل extern "C" لأعضاء الدورة التدريبية
  • على الأكثر وظيفة واحدة مع اسم معين يمكن أن يكون لها ارتباط "C" (بغض النظر عن مساحة الاسم)
  • يفرض extern "C" دالة على وجود ارتباط خارجي (لا يمكن جعله ثابتًا) راجع تعليق Richard: "ثابت" داخل "extern" C "" صالح؛ كيان معلن بذلك له ارتباط داخلي ، وبالتالي لا يوجد لديه ارتباط لغوي
  • إن الربط من C ++ إلى الكائنات المحددة بلغات أخرى وإلى الكائنات المعرفة في لغة C ++ من اللغات الأخرى هو محدد بالتطبيق ويعتمد على اللغة. فقط عندما تكون استراتيجيات تخطيط الكائن للتطبيقات اللغوية متشابهة بما فيه الكفاية يمكن تحقيق مثل هذا الربط

اعتدت "extern" C "'قبل لملفات DLL (مكتبة الارتباط الحيوي) لجعل الخ الرئيسية () وظيفة" للتصدير "بحيث يمكن استخدامها في وقت لاحق في آخر قابل للتنفيذ من دلل. ربما يمكن أن يكون مثالاً على المكان الذي اعتدت استخدامه فيه مفيدًا.

DLL

#include <string.h>
#include <windows.h>

using namespace std;

#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
    MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}

إملف تنفيذى

#include <string.h>
#include <windows.h>

using namespace std;

typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder

int main()
{
    char winDir[MAX_PATH];//will hold path of above dll
    GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
    strcat(winDir,"\\exmple.dll");//concentrate dll name with path
    HINSTANCE DLL = LoadLibrary(winDir);//load example dll
    if(DLL==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if load fails exit
        return 0;
    }
    mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
    //defined variable is used to assign a function from dll
    //GetProcAddress is used to locate function with pre defined extern name "DLL"
    //and matcing function name
    if(mainDLLFunc==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if it fails exit
        return 0;
    }
    mainDLLFunc();//run exported function 
    FreeLibrary((HMODULE)DLL);
}

أردت فقط إضافة بعض المعلومات ، لأنني لم أرها بعد.

سترى في كثير من الأحيان الشفرة في رؤوس C مثل:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

ما يحققه هذا هو أنه يسمح لك باستخدام ملف الرأس C هذا مع رمز C ++ ، لأنه سيتم تعريف الماكرو "__cplusplus". ولكن لا يزال بإمكانك أيضًا استخدامه مع رمز C القديم ، حيث لا يتم تعريف الماكرو ، لذلك لن يرى بنية C ++ الفريدة.

على الرغم من أنني رأيت أيضًا رمز C ++ مثل:

extern "C" {
#include "legacy_C_header.h"
}

الذي أتخيله ينجز الكثير من نفس الشيء.

غير متأكد من الطريقة الأفضل ، لكنني رأيت كلاهما.


يقوم بإعلام مترجم C ++ للبحث عن أسماء تلك الوظائف في نمط C عند الربط ، لأن أسماء الدوال التي تم تجميعها في C و C ++ مختلفة أثناء مرحلة الربط.


المقصود extern "C" ليتم التعرف عليها بواسطة برنامج التحويل البرمجي C ++ وإعلام المحول البرمجي أن الدالة الملاحظة (أو أن يكون) المترجمة في نمط C. حتى أنه أثناء الربط ، فإنه يرتبط بالإصدار الصحيح من الدالة من C.


دعونا decompile ملف الكائن ز + ولدت لترى ما يجري داخل هذا التنفيذ.

توليد المثال

إدخال:

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

ترجمة مع GCC 4.8 Linux ELF الإخراج:

g++ -c a.cpp

فك جدول الرموز:

readelf -s a.o

يحتوي الإخراج على:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

ترجمة

نحن نرى ذلك:

  • ef وعلى eg تم تخزينها في رموز تحمل نفس الاسم كما هو الحال في التعليمات البرمجية

  • تم تشويه الرموز الأخرى. دعونا نتخلص منها:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

الاستنتاج: كلا النوعين من الرموز التالية لم يتم تشويشهما:

  • يعرف
  • تم الإعلان عنها ولكن غير محددة ( Ndx = UND ) ، ليتم توفيرها في الارتباط أو وقت التشغيل من ملف كائن آخر

لذلك سوف تحتاج إلى extern "C" حد سواء عند الاتصال:

  • C من C ++: أخبر g++ أن تتوقع رموزًا غير متشابكة تنتجها gcc
  • C ++ من C: tell g++ لإنشاء رموز غير متحركة لاستخدامها في gcc

الأشياء التي لا تعمل في extern C

يصبح من الواضح أن أي ميزة C ++ تتطلب تشويش الاسم لن توضع داخل extern C :

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

عند خلط C و C ++ (أي ، استدعاء دالة C من C ++ ؛ و b. استدعاء دالة C ++ من C) ، يتسبب تشويش اسم C ++ في حدوث مشكلات الارتباط. من الناحية الفنية ، تحدث هذه المشكلة فقط عندما يتم بالفعل تجميع الوظائف المستدعية في ثنائي (على الأرجح ، ملف مكتبة * .a) باستخدام المترجم المقابل.

لذلك نحن بحاجة إلى استخدام extern "C" لتعطيل اسم تشابك في C ++.


لن يتم ترجمة أي C-header مع extern "C". عندما تتعارض معرفات في رأس C مع الكلمات الأساسية C ++ سيشتكي مترجم C ++ حول هذا.

على سبيل المثال ، لقد رأيت فشل التعليمات البرمجية التالية في g ++:

extern "C" {
struct method {
    int virtual;
};
}

كيندا من المنطقي ، ولكن شيء أن نأخذ في الاعتبار عند ترقية C-code إلى C ++.


تقوم C ++ بتشكيل أسماء دالة لإنشاء لغة موجهة للكائن من لغة إجرائية

لا يتم إنشاء معظم لغات البرمجة على قمة لغات البرمجة الحالية. بنيت C ++ على قمة C ، وعلاوة على ذلك فهي لغة برمجة موجهة للكائنات تم تصميمها من لغة برمجة إجرائية ، ولهذا السبب توجد كلمات رئيسية C ++ مثل extern التي توفر التوافق مع C.

لنلق نظرة على المثال التالي:

#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

مترجم AC لن يقوم بتجميع المثال أعلاه ، لأن نفس الوظيفة printMe يتم تعريفها مرتين (على الرغم من أنها تحتوي على معاملات مختلفة int a vs char a ).

gcc -o printMe printMe.c && ./printMe؛
1 خطأ. يتم تعريف PrintMe أكثر من مرة.

سيقوم مترجم C ++ بتجميع المثال أعلاه. لا يهمني أن يتم تعريف printMe مرتين.

g ++ -o printMe printMe.c && ./printMe؛

ويرجع ذلك إلى أن المترجم C ++ يعيد ضمنيًا وظائف ( en.wikipedia.org/wiki/Name_mangling ) استنادًا إلى المعلمات الخاصة بهم. في C ، هذه الميزة غير مدعومة. ومع ذلك ، عندما تم بناء C ++ على C ، تم تصميم اللغة لتكون موجهة للكائنات ، وتحتاج إلى دعم القدرة على إنشاء فئات مختلفة بأساليب (دالات) بنفس الاسم ، وتجاوز الأساليب ( تجاوز الأسلوب ) بناءً على اختلاف المعلمات.

Extern يقول "لا تقطع أسماء الدوال"

ومع ذلك ، تخيل أن لدينا ملف C قديمًا باسم "parent.c" include أسماء دالة s من ملفات C القديمة الأخرى ، "parent.h" ، "child.h" ، إلخ. إذا كان الملف "parent.c" القديم من خلال برنامج التحويل البرمجي C ++ ، ثم سيتم mangled أسماء الدوال ، وأنها لن تتطابق مع أسماء الدوال المحددة في "parent.h" ، "child.h" ، الخ - لذلك فإن أسماء الدوال في تلك الملفات الخارجية تحتاج إلى تكون مشوهة كذلك. وهذا يمكن أن يصبح فوضوي جدا. لذلك قد يكون من الملائم تقديم كلمة أساسية يمكن أن تخبر مترجم لغة C ++ بعدم تعريض اسم الدالة.

يخبر الكلمة الأساسية extern مترجم C ++ لا تعادل أسماء الدوال (إعادة تسمية). مثال للاستخدام: extern void printMe(int a);





extern-c