Tác dụng của extern dục Ciên trong C ++ là gì?


1632

Chính xác những gì đưa extern "C"vào mã C ++ làm gì?

Ví dụ:

extern "C" {
   void foo();
}

82
Tôi muốn giới thiệu với bạn bài viết này: http://www.agner.org/optizes/calling_conventions.pdf Nó cho bạn biết nhiều hơn về quy ước gọi và sự khác biệt giữa các trình biên dịch.
Sam Liao

1
@Litherum Trên đỉnh đầu của tôi, nó đang bảo trình biên dịch biên dịch phạm vi mã đó bằng C, với điều kiện là bạn có một trình biên dịch chéo. Ngoài ra, điều đó có nghĩa là bạn có tệp Cpp nơi bạn có foo()chức năng đó .
ha9u63ar

1
@ ha9u63ar Đó là 'trên đỉnh đầu tôi'. Toàn bộ phần còn lại của bình luận của bạn cũng không chính xác. Tôi khuyên bạn nên xóa nó.
TamaMcGlinn

Câu trả lời:


1559

extern "C" tạo một tên hàm trong C ++ có liên kết 'C' (trình biên dịch không xáo trộn tên) để mã C của máy khách có thể liên kết đến (tức là sử dụng) hàm của bạn bằng tệp tiêu đề tương thích 'C' chỉ chứa tuyên bố chức năng của bạn. Định nghĩa hàm của bạn được chứa trong một định dạng nhị phân (được biên dịch bởi trình biên dịch C ++ của bạn) mà trình liên kết 'C' của khách hàng sau đó sẽ liên kết để sử dụng tên 'C'.

Do C ++ có quá tải tên hàm và C thì trình biên dịch C ++ không thể chỉ sử dụng tên hàm làm id duy nhất để liên kết, do đó, nó xáo trộn tên bằng cách thêm thông tin về các đối số. Trình biên dịch AC không cần phải xáo trộn tên vì bạn không thể quá tải tên hàm trong C. Khi bạn nói rằng một hàm có liên kết "C" bên ngoài trong C ++, trình biên dịch C ++ không thêm thông tin loại tham số / tham số vào tên được sử dụng cho liên kết.

Để bạn biết, bạn có thể chỉ định rõ ràng liên kết "C" cho từng khai báo / định nghĩa riêng lẻ hoặc sử dụng một khối để nhóm một chuỗi các khai báo / định nghĩa để có một liên kết nhất định:

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

Nếu bạn quan tâm đến các kỹ thuật, chúng được liệt kê trong phần 7.5 của tiêu chuẩn C ++ 03, đây là một bản tóm tắt ngắn gọn (nhấn mạnh vào "C" bên ngoài):

  • extern "C" là một đặc tả liên kết
  • Mỗi trình biên dịch được yêu cầu để cung cấp liên kết "C"
  • một đặc tả liên kết chỉ xảy ra trong phạm vi không gian tên
  • tất cả các loại hàm, tên hàm và tên biến có một liên kết ngôn ngữ Xem Nhận xét của Richard: Chỉ các tên hàm và tên biến có liên kết ngoài có liên kết ngôn ngữ
  • hai loại chức năng có liên kết ngôn ngữ riêng biệt là các loại khác nhau ngay cả khi khác
  • liên kết thông số kỹ thuật tổ, bên trong xác định liên kết cuối cùng
  • extern "C" được bỏ qua cho các thành viên lớp
  • nhiều nhất một chức năng với một tên cụ thể có thể có liên kết "C" (không phân biệt không gian tên)
  • extern "C" buộc một hàm phải có liên kết ngoài (không thể làm cho nó tĩnh) Xem bình luận của Richard: 'static' bên trong 'extern "C"' là hợp lệ; một thực thể được khai báo có liên kết nội bộ và do đó không có liên kết ngôn ngữ
  • Liên kết từ C ++ đến các đối tượng được xác định bằng các ngôn ngữ khác và với các đối tượng được xác định trong C ++ từ các ngôn ngữ khác là định nghĩa triển khai và phụ thuộc vào ngôn ngữ. Chỉ khi các chiến lược bố trí đối tượng của hai triển khai ngôn ngữ đủ giống nhau thì mới có thể đạt được mối liên kết như vậy

22
Trình biên dịch C không sử dụng xáo trộn mà c ++ đang làm. Vì vậy, nếu bạn muốn gọi giao diện ac từ chương trình c ++, bạn phải khai báo rõ ràng rằng giao diện c là "extern c".
Sam Liao

59
@Faisal: đừng cố liên kết mã được xây dựng với các trình biên dịch C ++ khác nhau, ngay cả khi các tham chiếu chéo đều là 'extern "C"'. Thường có sự khác biệt giữa bố cục của các lớp hoặc các cơ chế được sử dụng để xử lý các ngoại lệ hoặc các cơ chế được sử dụng để đảm bảo các biến được khởi tạo trước khi sử dụng hoặc các khác biệt như vậy, cộng với bạn có thể cần hai thư viện hỗ trợ thời gian chạy C ++ riêng biệt (một cho mỗi trình biên dịch).
Jonathan Leffler

8
'extern "C" buộc một hàm phải có liên kết ngoài (không thể làm cho nó tĩnh)' là không chính xác. 'tĩnh' bên trong 'extern "C"' là hợp lệ; một thực thể được khai báo có liên kết nội bộ và do đó không có liên kết ngôn ngữ.
Richard Smith

14
'tất cả các loại hàm, tên hàm và tên biến có một liên kết ngôn ngữ' cũng không chính xác. Chỉ tên hàm và tên biến có liên kết ngoài có liên kết ngôn ngữ.
Richard Smith

9
Lưu ý đó extern "C" { int i; }là một định nghĩa. Đây có thể không phải là những gì bạn dự định, bên cạnh định nghĩa không void g(char);. Để làm cho nó không định nghĩa, bạn sẽ cần extern "C" { extern int i; }. Mặt khác, cú pháp một khai báo không có dấu ngoặc làm cho khai báo không định nghĩa: extern "C" int i;giống nhưextern "C" { extern int i; }
aschepler

327

Chỉ muốn thêm một chút thông tin, vì tôi chưa thấy nó được đăng.

Bạn sẽ rất thường thấy mã trong các tiêu đề C như vậy:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Điều này thực hiện được là nó cho phép bạn sử dụng tệp tiêu đề C đó với mã C ++ của mình, vì macro "__cplusplus" sẽ được xác định. Nhưng bạn có thể cũng vẫn sử dụng nó với mã di sản C của bạn, nơi mà các vĩ mô là không xác định, vì vậy nó sẽ không nhìn thấy duy nhất C ++ xây dựng.

Mặc dù, tôi cũng đã thấy mã C ++ như:

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

mà tôi tưởng tượng hoàn thành nhiều điều tương tự.

Không chắc cách nào tốt hơn, nhưng tôi đã thấy cả hai.


11
Có một sự khác biệt rõ ràng. Trong trường hợp trước đây, nếu bạn biên dịch tệp này với trình biên dịch gcc bình thường, nó sẽ tạo ra một đối tượng trong đó tên hàm không bị sai lệch. Nếu sau đó bạn liên kết các đối tượng C và C ++ với trình liên kết thì nó sẽ KHÔNG tìm thấy các hàm. Bạn sẽ cần bao gồm các tệp "tiêu đề kế thừa" đó với từ khóa extern như trong khối mã thứ hai của bạn.
Anne van Rossum

8
@Anne: Trình biên dịch C ++ cũng sẽ tìm kiếm các tên không bị thay đổi, bởi vì nó đã thấy extern "C"trong tiêu đề). Nó hoạt động tuyệt vời, sử dụng kỹ thuật này nhiều lần.
Ben Voigt

20
@Anne: Điều đó không đúng, cái đầu tiên cũng tốt. Nó bị bỏ qua bởi trình biên dịch C và có tác dụng tương tự như thứ hai trong C ++. Trình biên dịch không quan tâm đến việc nó gặp phải extern "C"trước hay sau khi nó bao gồm tiêu đề. Vào thời điểm nó đến trình biên dịch, dù sao nó cũng chỉ là một dòng văn bản được xử lý trước.
Ben Voigt

8
@Anne, không, tôi nghĩ rằng bạn đã bị ảnh hưởng bởi một số lỗi khác trong nguồn, vì những gì bạn đang mô tả là sai. Không có phiên bản nào g++có lỗi này, cho bất kỳ mục tiêu nào, tại bất kỳ thời điểm nào trong ít nhất 17 năm qua. Điểm chung của ví dụ đầu tiên là không quan trọng bạn sử dụng trình biên dịch C hay C ++, việc xáo trộn tên sẽ không được thực hiện cho các tên trong extern "C"khối.
Jonathan Wakely

7
"cái nào tốt hơn" - chắc chắn, biến thể đầu tiên tốt hơn: Nó cho phép bao gồm tiêu đề trực tiếp, không có bất kỳ yêu cầu nào khác, cả trong mã C và C ++. Cách tiếp cận thứ hai là một cách giải quyết cho các tiêu đề C, tác giả đã quên các bộ bảo vệ C ++ (tuy nhiên, không có vấn đề gì, nếu chúng được thêm vào sau đó, các khai báo "C" bên ngoài được chấp nhận ...).
Aconcagua

267

Biên dịch một g++nhị phân được tạo để xem những gì đang xảy ra

main.cpp

void f() {}
void g();

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

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

Biên dịch và tháo rời đầu ra ELF được tạo :

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

Đầu ra chứa:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
     9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
    10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

Diễn dịch

Chúng ta thấy rằng:

  • efegđược lưu trữ trong các ký hiệu có cùng tên như trong mã

  • các biểu tượng khác được đọc sai Chúng ta hãy tháo gỡ chúng:

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

Kết luận: cả hai loại ký hiệu sau không được đọc sai:

  • xác định
  • khai báo nhưng không xác định ( Ndx = UND), được cung cấp tại liên kết hoặc thời gian chạy từ tệp đối tượng khác

Vì vậy, bạn sẽ cần extern "C"cả hai khi gọi:

  • C từ C ++: nói g++để mong đợi các biểu tượng không bị thay đổi được tạo bởigcc
  • C ++ từ C: yêu g++cầu tạo các ký hiệu không thay đổi gccđể sử dụng

Những thứ không hoạt động ở bên ngoài C

Rõ ràng là bất kỳ tính năng C ++ nào yêu cầu xáo trộn tên sẽ không hoạt động bên trong 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 runnable tối thiểu từ ví dụ C ++

Để hoàn thiện và cho các newbs ngoài kia, hãy xem thêm: Làm thế nào để sử dụng các tệp nguồn C trong một dự án C ++?

Gọi C từ C ++ khá dễ dàng: mỗi hàm C chỉ có một biểu tượng không bị sai lệch, do đó không cần phải làm thêm.

main.cpp

#include <cassert>

#include "c.h"

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

ch

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

cc

#include "c.h"

int f(void) { return 1; }

Chạy:

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

Không có extern "C"liên kết thất bại với:

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

bởi vì g++hy vọng sẽ tìm thấy một máng xối f, mà gcckhông sản xuất.

Ví dụ trên GitHub .

C ++ tối thiểu có thể chạy được từ ví dụ C

Gọi C ++ từ C khó hơn một chút: chúng ta phải tự tạo các phiên bản không bị xáo trộn của từng chức năng mà chúng ta muốn phơi bày.

Ở đây chúng tôi minh họa cách phơi bày quá tải chức năng C ++ cho C.

C chính

#include <assert.h>

#include "cpp.h"

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

cpp.h

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

Chạy:

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

Không có extern "C"nó thất bại với:

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

bởi vì g++tạo ra các biểu tượng mangled mà gcckhông thể tìm thấy.

Ví dụ trên GitHub .

Đã thử nghiệm trong Ubuntu 18.04.


21
Câu trả lời hay nhất kể từ khi bạn 1) đề cập rõ ràng extern "C" {giúp bạn gọi các hàm C không bị biến dạng từ trong các chương trình C ++ , cũng như các hàm C ++ không bị biến dạng từ trong các chương trình C , mà các câu trả lời khác không quá rõ ràng, 2) vì bạn hiển thị các ví dụ khác biệt về mỗi. Cảm ơn!
Gabriel Staples

3
Tôi rất thích câu trả lời này
selfboot

4
Trả lời đúng nhất vì nó chỉ ra cách gọi các chức năng quá tải từ c
Gaspa79

1
@JaveneCPPMcGowan điều gì khiến bạn nghĩ rằng tôi đã có một giáo viên C ++? :-)
Ciro Santilli 冠状 病毒 审查 六四 事件

205

Trong mọi chương trình C ++, tất cả các hàm không tĩnh được biểu diễn trong tệp nhị phân dưới dạng các ký hiệu. Các ký hiệu này là các chuỗi văn bản đặc biệt xác định duy nhất một chức năng trong chương trình.

Trong C, tên biểu tượng giống như tên hàm. Điều này là có thể bởi vì trong C không có hai hàm không tĩnh nào có thể có cùng tên.

Bởi vì C ++ cho phép quá tải và có nhiều tính năng mà C không - như các lớp, hàm thành viên, đặc tả ngoại lệ - không thể đơn giản sử dụng tên hàm làm tên biểu tượng. Để giải quyết điều đó, C ++ sử dụng cái gọi là xáo trộn tên, biến đổi tên hàm và tất cả các thông tin cần thiết (như số lượng và kích thước của các đối số) thành một chuỗi trông lạ được xử lý chỉ bởi trình biên dịch và trình liên kết.

Vì vậy, nếu bạn chỉ định một hàm là extern C, trình biên dịch sẽ không thực hiện việc xáo trộn tên với nó và nó có thể được truy cập trực tiếp bằng cách sử dụng tên ký hiệu của nó làm tên hàm.

Điều này có ích trong khi sử dụng dlsym()dlopen()để gọi các chức năng như vậy.


bạn có ý nghĩa gì bởi tiện dụng? là tên biểu tượng = tên hàm sẽ làm cho tên biểu tượng được truyền cho dlsym được biết đến, hoặc điều khác?
Lỗi ngày

1
@Error: vâng. Về cơ bản, trong trường hợp chung, dlopen () một thư viện chia sẻ C ++ chỉ được cung cấp một tệp tiêu đề và chọn đúng chức năng để tải. (Trên x86, có một đặc tả xáo trộn tên được xuất bản dưới dạng Itanium ABI mà tất cả các trình biên dịch x86 tôi biết sử dụng để mang tên hàm C ++, nhưng không có gì trong ngôn ngữ yêu cầu điều này.)
Jonathan Tomer

52

Tên hàm của C ++ mangle để tạo ngôn ngữ hướng đối tượng từ ngôn ngữ thủ tục

Hầu hết các ngôn ngữ lập trình không được xây dựng dựa trên các ngôn ngữ lập trình hiện có. C ++ được xây dựng trên đỉnh C và hơn nữa, nó là ngôn ngữ lập trình hướng đối tượng được xây dựng từ ngôn ngữ lập trình thủ tục và vì lý do đó, có các biểu thức C ++ giống như extern "C"cung cấp khả năng tương thích ngược với C.

Hãy xem ví dụ sau:

#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 trình biên dịch sẽ không biên dịch ví dụ trên, vì các chức năng tương tự printMeđược định nghĩa hai lần (mặc dù họ có thông số khác nhau int avs char a).

gcc -o printMe printMe.c && ./printMe;
1 lỗi. PrintMe được định nghĩa nhiều lần.

Một trình biên dịch C ++ sẽ biên dịch ví dụ trên. Nó không quan tâm printMeđược xác định hai lần.

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

Điều này là do trình biên dịch C ++ hoàn toàn đổi tên các hàm ( mangles ) dựa trên các tham số của chúng. Trong C, tính năng này không được hỗ trợ. Tuy nhiên, khi C ++ được xây dựng trên C, ngôn ngữ được thiết kế theo hướng đối tượng và cần hỗ trợ khả năng tạo các lớp khác nhau bằng các phương thức (hàm) cùng tên và ghi đè các phương thức (ghi đè phương thức ) dựa trên khác nhau thông số.

extern "C" nói "không mang tên hàm C"

Tuy nhiên, hãy tưởng tượng chúng ta có một tệp C kế thừa có tên là "Parent.c" include tên hàm từ các tệp C kế thừa khác, "Parent.h", "child.h", v.v ... Nếu tệp "Parent.c" kế thừa được chạy thông qua trình biên dịch C ++, sau đó các tên hàm sẽ được xáo trộn và chúng sẽ không còn khớp với các tên hàm được chỉ định trong "Parent.h", "child.h", v.v. - vì vậy tên hàm trong các tệp bên ngoài đó cũng cần phải được đọc sai Tên hàm của Mangling trong một chương trình C phức tạp, những tên có nhiều phụ thuộc, có thể dẫn đến mã bị hỏng; vì vậy có thể thuận tiện khi cung cấp một từ khóa có thể báo cho trình biên dịch C ++ không sử dụng tên hàm.

Các extern "C"từ khóa kể một trình biên dịch C ++ không mangle (đổi tên) C chức năng tên.

Ví dụ:

extern "C" void printMe(int a);


chúng ta không thể sử dụng extern "C"nếu chúng ta chỉ có một dlltập tin? Ý tôi là nếu chúng ta không có tệp tiêu đề và chỉ có tệp nguồn (chỉ triển khai) và sử dụng chức năng của nó thông qua con trỏ hàm. ở trạng thái này, chúng ta chỉ sử dụng các hàm (bất kể tên của nó).
BattleTested

@tfmontague, đối với tôi bạn đóng đinh nó ngay! thẳng vào đầu.
Artanis Zeratul

29

Không phải bất kỳ tiêu đề C nào cũng có thể tương thích với C ++ bằng cách chỉ gói trong "C" bên ngoài. Khi các định danh trong xung đột tiêu đề C với các từ khóa C ++, trình biên dịch C ++ sẽ phàn nàn về điều này.

Ví dụ, tôi đã thấy đoạn mã sau thất bại trong g ++:

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

Kinda có ý nghĩa, nhưng là điều cần lưu ý khi chuyển mã C sang C ++.


14
extern "C"có nghĩa là sử dụng liên kết C, như được mô tả bởi các câu trả lời khác. Nó không có nghĩa là "biên dịch nội dung là C" hoặc bất cứ điều gì. int virtual;không hợp lệ trong C ++ và chỉ định liên kết khác nhau không thay đổi điều đó.
MM

1
... hoặc chế độ nói chung, mọi mã chứa lỗi cú pháp sẽ không được biên dịch.
Valentin Heinitz

4
@ValentinHeinitz tự nhiên, mặc dù sử dụng "ảo" làm định danh trong C không phải là lỗi cú pháp. Tôi chỉ muốn chỉ ra rằng bạn không thể tự động sử dụng bất kỳ tiêu đề C nào trong C ++ bằng cách đặt "C" bên ngoài xung quanh nó.
Sander Mertens

28

Nó thay đổi liên kết của hàm theo cách mà hàm có thể gọi được từ C. Trong thực tế, điều đó có nghĩa là tên hàm không bị sai lệch .


3
Mangled là thuật ngữ thường được sử dụng ... Đừng tin rằng tôi từng thấy "trang trí" được sử dụng với ý nghĩa này.
Matthew Scharley

1
Microsoft (ít nhất là một phần) sử dụng trang trí thay vì đọc sai trong tài liệu của họ. họ thậm chí còn đặt tên cho công cụ của mình để hủy bỏ tên (còn gọi là unle mangle) một tên undname.
René Nyffalanger

20

Nó thông báo cho trình biên dịch C ++ tìm kiếm tên của các hàm đó theo kiểu C khi liên kết, vì tên của các hàm được biên dịch trong C và C ++ khác nhau trong giai đoạn liên kết.


12

extern "C" có nghĩa là được trình biên dịch C ++ nhận ra và để thông báo cho trình biên dịch rằng hàm được ghi chú là (hoặc được) biên dịch theo kiểu C. Vì vậy, trong khi liên kết, nó liên kết đến phiên bản chính xác của chức năng từ C.


6

Tôi đã sử dụng 'extern "C"' trước đây cho các tệp dll (thư viện liên kết động) để tạo ra hàm main () "có thể xuất" để nó có thể được sử dụng sau này trong một tệp thực thi khác từ dll. Có lẽ một ví dụ về nơi tôi từng sử dụng nó có thể hữu ích.

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

4
Không có gì cả extern "C"__declspec(dllexport)không liên quan. Các điều khiển trang trí biểu tượng trước, sau này chịu trách nhiệm tạo ra một mục xuất. Bạn cũng có thể xuất một biểu tượng bằng cách sử dụng trang trí tên C ++. Bên cạnh việc hoàn toàn thiếu điểm của câu hỏi này, có những lỗi khác trong mẫu mã. Đối với một, mainxuất từ ​​DLL của bạn không khai báo giá trị trả về. Hoặc gọi hội nghị, cho vấn đề đó. Khi nhập, bạn gán một quy ước gọi ngẫu nhiên ( WINAPI) và sử dụng ký hiệu sai cho các bản dựng 32 bit (nên là _mainhoặc _main@0). Xin lỗi, -1.
IInspectable

1
Điều đó chỉ lặp đi lặp lại, mà bạn không biết, những gì bạn đang làm, nhưng làm theo cách này dường như có hiệu quả với bạn, đối với một số danh sách nền tảng mục tiêu không được tiết lộ. Bạn đã không giải quyết các vấn đề tôi nêu trong bình luận trước đây của tôi. Đây vẫn là một cuộc bỏ phiếu, do sai lầm dữ dội (có nhiều hơn, không phù hợp trong một bình luận).
Không

1
Đăng một câu trả lời trên Stack Overflow loại ngụ ý, rằng bạn biết những gì bạn đang làm. Điều này được mong đợi. Đối với nỗ lực của bạn "để ngăn chặn tham nhũng ngăn xếp khi chạy" : Chữ ký hàm của bạn chỉ định giá trị trả về của loại void*, nhưng việc triển khai của bạn không trả về bất cứ điều gì. Nó sẽ bay rất tốt ...
Không

1
Nếu bạn thực hiện một cái gì đó, có vẻ như hoạt động, bởi may mắn thuần túy, thì rõ ràng bạn không biết bạn đang làm gì ( mẫu "làm việc" của bạn rơi vào loại đó). Đó là hành vi không xác định và dường như hoạt động là một dạng hành vi không xác định hợp lệ. Nó vẫn chưa được xác định. Tôi sẽ đánh giá rất cao nó, nếu bạn luyện tập siêng năng hơn trong tương lai. Một phần trong đó có thể xóa câu trả lời được đề xuất này.
Không

1
Bạn đang diễn giải lại một hàm không trả về bất cứ thứ gì dưới dạng hàm trả về một con trỏ. Đó là may mắn thuần túy, x86 rất tha thứ đối với chữ ký hàm không khớp và đặc biệt là các giá trị trả về của kiểu tích phân. Mã của bạn chỉ hoạt động bởi sự trùng hợp. Nếu bạn không đồng ý, bạn cần giải thích, tại sao mã của bạn hoạt động đáng tin cậy.
Không

5

extern "C"là một đặc tả liên kết được sử dụng để gọi các hàm C trong các tệp nguồn Cpp . Chúng ta có thể gọi các hàm C, viết biến và bao gồm các tiêu đề . Hàm được khai báo trong thực thể bên ngoài & nó được định nghĩa bên ngoài. Cú pháp là

Loại 1:

extern "language" function-prototype

Loại 2:

extern "language"
{
     function-prototype
};

ví dụ:

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

3

Câu trả lời này dành cho những người thiếu kiên nhẫn / có thời hạn để đáp ứng, chỉ có một phần / giải thích đơn giản dưới đây:

  • trong C ++, bạn có thể có cùng tên trong lớp thông qua quá tải (ví dụ: vì tất cả chúng đều có cùng tên không thể xuất như là từ dll, v.v.) cho các vấn đề này là chúng được chuyển đổi thành các chuỗi khác nhau (được gọi là các ký hiệu ), các ký hiệu chiếm tên của hàm, cũng là các đối số, do đó, mỗi hàm này thậm chí có cùng tên, có thể được xác định duy nhất (còn được gọi là tên xáo trộn)
  • Trong C, bạn không bị quá tải, tên hàm là duy nhất (vì vậy, một chuỗi riêng biệt để xác định tên hàm duy nhất là không bắt buộc, vì vậy ký hiệu là chính tên hàm)

Vì vậy,
trong C ++, với việc xáo trộn tên nhận dạng duy nhất từng chức năng
trong C, thậm chí không có tên xáo trộn nhận dạng duy nhất mỗi chức năng

Để thay đổi hành vi của C ++, nghĩa là, để chỉ định việc xáo trộn tên đó không nên xảy ra đối với một chức năng cụ thể, bạn có thể sử dụng extern "C" trước tên hàm, vì bất kỳ lý do gì, như xuất một hàm có tên cụ thể từ dll , để sử dụng bởi khách hàng của nó.

Đọc các câu trả lời khác, để có câu trả lời chi tiết hơn / chính xác hơn.


1

Khi trộn C và C ++ (nghĩa là gọi hàm C từ C ++; và gọi hàm C ++ từ C), việc xáo trộn tên C ++ gây ra vấn đề liên kết. Về mặt kỹ thuật, vấn đề này chỉ xảy ra khi các hàm callee đã được biên dịch thành tệp nhị phân (rất có thể là tệp thư viện * .a) bằng trình biên dịch tương ứng.

Vì vậy, chúng ta cần sử dụng extern "C" để vô hiệu hóa tên xáo trộn trong C ++.


0

Không xung đột với các câu trả lời tốt khác, tôi sẽ thêm một chút ví dụ của tôi.

Chính xác Trình biên dịch C ++ làm gì: nó xử lý các tên trong quá trình biên dịch, do đó chúng tôi yêu cầu trình biên dịch xử lý C việc thực hiện một cách đặc biệt.

Khi chúng tôi tạo các lớp C ++ và thêm extern "C", chúng tôi sẽ nói với trình biên dịch C ++ của mình rằng chúng tôi đang sử dụng quy ước gọi C.

Lý do (chúng tôi đang gọi triển khai C từ C ++): hoặc chúng tôi muốn gọi hàm C từ C ++ hoặc gọi hàm C ++ từ C (các lớp C ++ ... vv không hoạt động trong C).


Chào mừng bạn đến với Stack Overflow. Nếu bạn quyết định trả lời một câu hỏi cũ hơn và có câu trả lời đúng, việc thêm câu trả lời mới vào cuối ngày có thể không giúp bạn nhận được bất kỳ tín dụng nào. Nếu bạn có một số thông tin mới đặc biệt hoặc bạn tin chắc rằng các câu trả lời khác đều sai, bằng mọi cách hãy thêm một câu trả lời mới, nhưng 'một câu trả lời khác' đưa ra thông tin cơ bản tương tự trong một thời gian dài sau khi câu hỏi được hỏi thường thắng ' t kiếm được cho bạn nhiều tín dụng. Thành thật mà nói, tôi không nghĩ có gì mới trong câu trả lời này.
Jonathan Leffler

Chà tôi nên nhớ quan điểm của bạn - được thôi
Susobhan Das

-1

Hàm void f () được biên dịch bởi trình biên dịch C và hàm có cùng tên void f () được biên dịch bởi trình biên dịch C ++ không cùng chức năng. Nếu bạn đã viết hàm đó trong C, và sau đó bạn đã cố gắng gọi nó từ C ++, thì trình liên kết sẽ tìm hàm C ++ và không tìm thấy hàm C.

extern "C" cho trình biên dịch C ++ biết rằng bạn có một hàm được biên dịch bởi trình biên dịch C. Một khi bạn nói với nó rằng nó được biên dịch bởi trình biên dịch C, trình biên dịch C ++ sẽ biết cách gọi nó một cách chính xác.

Nó cũng cho phép trình biên dịch C ++ biên dịch hàm C ++ theo cách mà trình biên dịch C có thể gọi nó. Hàm đó sẽ chính thức là hàm C, nhưng do nó được biên dịch bởi trình biên dịch C ++, nên nó có thể sử dụng tất cả các tính năng của C ++ và có tất cả các từ khóa C ++.


Trình biên dịch C ++ có thể biên dịch một extern "C"hàm - và (chịu một số ràng buộc) nó sẽ có thể gọi được bằng mã được biên dịch bởi trình biên dịch C.
Jonathan Leffler
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.