extern 是用来进行外部声明的。
谨记:声明可以多次,但是定义只能有一次。
函数的声明extern关键字是可有可无的,因为函数本身不加修饰的话就是extern的。
- //util.cpp
- #include
- using namespace std;
- int num = 20; //全局变量
- void fn(){ //全局函数
- cout << num << endl;
- }
- //index.cpp
- extern int num;
- extern void fn();
- void main(){
- fn();
- num = 100;
- fn();
- }
extern "C" 是C++特有的指令(C无法使用该指令),目的在于支持C++与C混合编程。
extern "C" 告诉C++编译器用C规则编译指定的代码(除函数重载外,extern “C”不影响C++其他特性)。
因为C和C++的编译规则不一样,主要区别体现在编译期间生成函数符号的规则不一致。
由于C++需要支持重载,单纯的函数名无法区分出具体的函数,所以在编译阶段就需要将形参列表作为附加项增加到函数符号中。如以下代码
- void Function(int a, int b)
- {
- printf("Hello!!! a = %d, b = %d\n", a, b);
- }
C和C++对应的的汇编码如下
- ...
- Function:
- .LFB11:
- .cfi_startproc
- movl %esi, %edx
- xorl %eax, %eax
- movl %edi, %esi
- movl $.LC0, %edi
- jmp printf
- .cfi_endproc
- ...
- ...
- _Z8Functionii:
- .LFB12:
- .cfi_startproc
- movl %esi, %edx
- xorl %eax, %eax
- movl %edi, %esi
- movl $.LC0, %edi
- jmp printf
- .cfi_endproc
- ...
容易发现,两段代码的区别仅在于函数 Function(int a, int b) 编译后对应的符号不同
C:Function
C++:_Z8Functionii
C++编出来的函数符号明显比C的多出了一些信息(如ii),这里多出来的后缀信息就是形参列表的参数类型信息。
- /* MyFunction.c */
- void Function(int a, int b)
- {
- printf("Hello!!! a = %d, b = %d\n", a, b);
- }
-
-
- /* main,cpp */
- extern void Function(int a, int b);
-
- int main()
- {
- Function(1, 2);
- }
以上代码,C提供写了一个函数,用C++代码调用该函数,看起来没什么问题,但是编译的时候...
找不到对Function(int, int)的定义
来看看两个文件的汇编结果。
- .file "MyFunction.c"
- .section .rodata.str1.1,"aMS",@progbits,1
- .LC0:
- .string "Hello!!! a = %d, b = %d\n"
- .text
- .p2align 4,,15
- .globl Function
- .type Function, @function
- Function:
- .LFB11:
- .cfi_startproc
- movl %esi, %edx
- xorl %eax, %eax
- movl %edi, %esi
- movl $.LC0, %edi
- jmp printf
- .cfi_endproc
- .LFE11:
- .size Function, .-Function
- .ident "GCC: (GNU) 6.4.1 20170727 (Red Hat 6.4.1-1)"
- .section .note.GNU-stack,"",@progbits
- .file "main.cpp"
- .section .text.startup,"ax",@progbits
- .p2align 4,,15
- .globl main
- .type main, @function
- main:
- .LFB0:
- .cfi_startproc
- subq $8, %rsp
- .cfi_def_cfa_offset 16
- movl $2, %esi
- movl $1, %edi
- call _Z8Functionii
- xorl %eax, %eax
- addq $8, %rsp
- .cfi_def_cfa_offset 8
- ret
- .cfi_endproc
- .LFE0:
- .size main, .-main
- .ident "GCC: (GNU) 6.4.1 20170727 (Red Hat 6.4.1-1)"
- .section .note.GNU-stack,"",@progbits
可以看到,MyFunction.s(源文件为.c文件)中定义的是Function,而main.s(源文件为.cpp文件)中调用的是_Z8Functionii,函数名不一样,所以连接的时候找不到函数实现。到这里我们知道C和C++编译期间后得到的函数符号不同,所以C++代码和C代码不能互相调用。
要想实现C、C++混合编程该怎么办呢?用extern "C"!
所以,extern “C”的作用就是告诉C++编译器,将指定的函数用C规则编译(注意,除了函数重载外,extern “C”不影响C++的其他特性),然后后面的事情就顺理成章了。
在一个dll动态链接库中,头文件如下:声明 fnDll1();
- #ifdef DLL1_EXPORTS
- #define DLL1_API __declspec(dllexport)
- #else
- #define DLL1_API __declspec(dllimport)
- #endif
-
- DLL1_API int fnDll1(); // extern DLL1_API int fnDll1() 也可以
对应cpp中定义 fnDll1() 函数
- int fnDll1() {
- return 100;
- }
在控制台项目中,main.cpp 通过 LoadLibrary 调用.dll
- void dllCall() {
- typedef int(*FnPoint)();
-
- HMODULE HD = LoadLibrary(L"Dll1.dll");
- if (!HD) {
- cout << "HD is null." << endl;
- return;
- }
- FnPoint FD = (FnPoint)GetProcAddress(HD, "fnDll1");
- if (!FD) {
- cout << "FD is null." << endl;
- return;
- }
- cout << FD() << endl;
- }
此时运行程序,结果如下
dll文件引入成功了,但是fnDll1函数却没有拿到。造成这种结果是因为,dll中的fnDll1通过C++进行编译,导致最终的名字并不是原来的了。
解决方法:
方法1:使用extern "C",修改dll头文件。这样fnDll1 仍然是 fnDll1;
- #ifdef __cplusplus // 如果是C++就渲染`extern "C"{`;
- extern "C" {
- #endif // __cplusplus
-
- DLL1_API int fnDll1();
-
- #ifdef __cplusplus
- }
- #endif // __cplusplus
-
- 或
-
- extern "C" DLL1_API int fnDll1();
方法2:使用.def导出,创建Source.def,并在配置中设置。最终名称就是.def文件中的名称。
- LIBRARY "Dll1"
-
- EXPORTS
- fnDll1 @1
方法3(不推荐,仅供参考):使用fnDll1被C++编译之后的名字
在命令行中cd进入.dll文件的目录下。执行:
dumpbin -exports Dll1.dll
将 main.cpp 修改:
FnPoint FD = (FnPoint)GetProcAddress(HD, "?fnDll1@@YAHXZ");