سؤال

ماذا يفعل بالضبط وضع extern "C" في C++ كود تفعل ؟

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

extern "C" {
   void foo();
}
هل كانت مفيدة؟

المحلول

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

منذ C++ وقد الحمولة الزائدة الدالة أسماء C, C++ compiler لا يمكن فقط استخدام الدالة اسم معرف فريد رابط, لذلك mangles اسم بإضافة معلومات عن الحجج.ج مترجم لا تحتاج إلى فسد الاسم منذ أن كنت لا يمكن أن تفرط وظيفة الأسماء في C.عند القول أن وظيفة extern "C" الربط في C++, C++ compiler لا تضيف الحجة/المعلمة نوع المعلومات إلى اسم المستخدمة في الربط.

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

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

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

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

نصائح أخرى

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

وعليك في كثير من الأحيان نرى التعليمات البرمجية في رؤوس 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 ++ يسمح الزائد، ولها العديد من الميزات التي C لا - فئات مثل، وظائف الأعضاء، مواصفات استثناء - أنه ليس من الممكن ببساطة استخدام اسم الدالة كاسم الرمز. لحل ذلك، يستخدم C ++ ما يسمى تغيير اسم الاسم، والذي يحول اسم الدالة وجميع المعلومات اللازمة (مثل عدد وحجم الحجج) في بعض سلسلة أبحث غريبة المصنعة فقط من قبل مترجم ورابط.

وحتى إذا كنت تحديد وظيفة ليكون خارجي ... C، المترجم لا يؤدي تغيير اسم اسم معها وأنها يمكن أن تكون مباشرة الوصول إليها باستخدام اسم رمزها كاسم وظيفة.

وهذا الأمر مفيد أثناء استخدام dlsym() وdlopen() لاستدعاء مثل هذه الوظائف.

فك a g++ ولدت الثنائية لمعرفة ما يجري

main.cpp

void f() {}
void g();

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

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

ترجمة مع دول مجلس التعاون الخليجي 4.8 لينكس قزم الإخراج:

g++ -c main.cpp

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

readelf -s main.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 تم تخزينها في الرموز مع نفس الاسم مثل في كود

  • غيرها من الرموز كانت المهترئ.دعونا unmangle لهم:

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

الخلاصة:كل من الرموز التالية أنواع لا المهترئ:

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

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

  • C C++:اقول g++ نتوقع unmangled الرموز التي تنتجها gcc
  • C++ من C:اقول g++ لتوليد unmangled رموز gcc استخدام

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

يصبح من الواضح أن أي 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) { }
}

الحد الأدنى runnable C C++ سبيل المثال

لأجل اكتمال عن newbs هناك ، انظر أيضا: كيفية استخدام المصدر C الملفات في C++ المشروع ؟

داعيا C C++ هي سهلة جدا:كل وظيفة C فقط ممكن واحد غير المهترئ رمز لذا أي عمل إضافي مطلوب.

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

ج.ح

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

ج.ج

#include "c.h"

int f(void) { return 1; }

تشغيل:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

دون extern "C" الرابط فشل مع:

main.cpp:6: undefined reference to `f()'

لأن g++ تتوقع أن تجد المهترئ f, ، gcc لم تنتج.

مثال على جيثب.

الحد الأدنى runnable C++ من C مثلا

داعيا C++ من C هو أصعب قليلا:يجب أن يدويا إنشاء غير المهترئ إصدارات كل وظيفة نريد فضح.

نحن هنا توضح كيفية كشف C++ وظيفة الزائدة إلى C.

الرئيسية.ج

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.ح

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

تشغيل:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

دون extern "C" فشل مع:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

لأن g++ ولدت مشوهة الرموز التي gcc لا يمكن العثور على.

مثال على جيثب.

اختبار في أوبونتو 18.04.

C ++ mangles أسماء وظيفة لخلق لغة وجوه المنحى من لغة إجرائية

لا تبنى

ومعظم لغات البرمجة على رأس لغات البرمجة الحالية. بنيت C ++ على رأس C، وعلاوة على انها لغة برمجة كائنية التوجه بنيت من لغة البرمجة الإجرائية، ولهذا السبب هناك تعبيرات C ++ مثل extern "C" التي توفر التوافق مع 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;
}

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

<اقتباس فقرة>   

ودول مجلس التعاون الخليجي -o printMe printMe.c && ./printMe،
   1 خطأ. ويعرف PrintMe أكثر من مرة واحدة.

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

<اقتباس فقرة>   

وز ++ -o printMe printMe.c && ./printMe؛

وذلك لأن مترجم C ++ إعادة تسمية ضمنا ( mangles ) وظائف على أساس المعلمات الخاصة بهم. في C، وهذه الميزة غير مدعومة. ومع ذلك، عندما تم بناء C ++ على C، وقد تم تصميم اللغة لتكون وجوه المنحى، واللازمة لدعم القدرة على خلق فئات مختلفة مع أساليب (وظائف) الذي يحمل نفس الاسم، وتجاوز الأساليب (<لأ href = "HTTPS : //en.wikipedia.org/wiki/Method_overriding "يختلط =" نوفولو noreferrer "> طريقة تجاوز ) استنادا إلى معايير مختلفة

extern "C" يقول "لا فسد أسماء C وظيفة"

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

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

ويتغير الربط بين وظيفة في مثل هذه الطريقة التي هي وظيفة للاستدعاء من C. في الممارسة العملية وهذا يعني أن اسم الوظيفة ليست <وأ href = "http://en.wikipedia.org/wiki/Name_mangling "يختلط =" noreferrer "> المهترئ.

لا يمكن إجراء أي C-رأس متوافقة مع C ++ بمجرد التفاف في خارجي ... "C". عندما معرفات في صراع C-رأس مع C ++ الكلمات الرئيسية مترجم C ++ سوف يشكو حول هذا الموضوع.

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

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

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

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

والمقصود

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

ولقد استخدمت "خارجي" 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);
}

وEXE

#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);
}

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

النوع 1:

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)
{
    //
    //
}

هذا الجواب هو الصبر/ المواعيد النهائية لتلبية فقط جزء/تفسير بسيط هو أدناه:

  • في C++, هل يمكن أن يكون نفس الاسم في الصف عن طريق التحميل الزائد (على سبيل المثال ، لأنها كلها بنفس الاسم لا يمكن تصديرها من dll, etc.) حل هذه المشاكل يتم تحويلها إلى سلاسل مختلفة (تسمى الرموز), رموز الحسابات اسم الوظيفة أيضا الحجج ، لذلك كل من هذه المهام حتى مع نفس الاسم ، يمكن تحديدها بشكل فريد (وتسمى أيضا اسم من الضغط)
  • في " ج " لا يجب الزائد ، اسم وظيفة فريدة من نوعها (لذلك ، سلسلة منفصلة من أجل تحديد وظيفة اسم فريد غير مطلوب ، لذلك الرمز هو اسم الدالة نفسها)

لذلك
في C++, مع اسم تغيير اسم فريد هويات كل وظيفة
في C ، حتى من دون اسم تغيير اسم فريد هويات كل وظيفة

إلى تغيير سلوك C++ ، أي أن تحديد هذا الاسم من الضغط لا ينبغي أن يحدث عن وظيفة معينة ، يمكنك استخدام extern "C" قبل اسم الدالة ، لأي سبب من الأسباب, مثل تصدير وظيفة مع اسم معين من dll ، للاستخدام من قبل العملاء.

قراءة أخرى إجابات أكثر تفصيلا/أكثر الإجابات الصحيحة.

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

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top