C++中extern "C"的作用是什么?
在 C++ 中,extern "C" 的作用是告诉编译器按照 C 语言的规范来处理函数名和变量名。这是因为 C++ 编译器会对函数名和变量名进行名称修饰(name mangling),以区分不同的函数和变量。而在 C 语言中,函数名和变量名不会被名称修饰,因此需要使用 extern "C" 来告诉编译器使用 C 语言的规则。
下面是微软官方文档关于“extern "C"”的使用说明:
extern (C++) | Microsoft Learn
extern (C++) | Microsoft Learn
以下示例演示如何声明具有 C 链接的名称:
// Declare printf with C linkage.
extern "C" int printf(const char *fmt, ...);// Cause everything in the specified
// header files to have C linkage.
extern "C" {// add your #include statements here
#include <stdio.h>
}// Declare the two functions ShowChar
// and GetChar with C linkage.
extern "C" {char ShowChar(char ch);char GetChar(void);
}// Define the two functions
// ShowChar and GetChar with C linkage.
extern "C" char ShowChar(char ch) {putchar(ch);return ch;
}extern "C" char GetChar(void) {char ch;ch = getchar();return ch;
}// Declare a global variable, errno, with C linkage.
extern "C" int errno;
首先看看 C++
中,在未加 extern "C" 声明时,对类似 C
的函数是怎样编译的:
作为一种面向对象的语言, C++
支持函数重载,而过程式语言 C
则不支持。所以,函数被 C++
编译后在符号库中的名字与 C
语言的有所不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被 C
编译器编译后在符号库中的名字为 _foo
,而 C++
编译器则会产生像 _foo_int_int
之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为名称修饰(name mangling) 。 _foo_int_int
这样的名字包含了函数名、函数参数数量及类型信息, C++
就是靠这种机制来实现函数重载的。例如,在 C++
中,函数 void foo( int x, int y )
与 void foo(int x, float y )
编译生成的符号是不相同的,后者为 _foo_int_float
。
同样地, C++
中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以 .
来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
其次,看看在未加 extern "C" 声明时,是如何连接的:
假设在 C++
中,模块 A
的头文件如下:
//模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模块 B
中引用该函数:
// 模块B实现文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);
实际上,在连接阶段,连接器会从模块 A
生成的目标文件 moduleA.obj
中寻找 _foo_int_int
这样的符号!
对于上面例子,如果 B
模块是 C
程序,而A模块是 C++
库头文件的话,会导致链接错误;同理,如果B模块是 C++
程序,而A模块是C库的头文件也会导致错误。
再次,看看加 extern "C" 声明后的编译和连接方式:
加 extern "C" 声明后,模块 A
的头文件变为:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模块 B
的实现文件中仍然调用 foo( 2,3 )
,其结果,将会是 C
语言的编译连接方式:模块 A
编译生成 foo
的目标代码时,没有对其名字进行特殊处理,采用了 C
语言的方式;连接器在为模块 B
的目标代码寻找 foo(2,3)
调用时,寻找的是未经修改的符号名 _foo
。
如果在模块 A
中函数声明了 foo
为 extern "C" 类型,而模块 B
中包含的是 extern int foo( int x, int y )
,则模块 B
找不到模块 A
中的函数(因为这样的声明没有使用 extern "C" 指明采用C语言的编译链接方式);反之亦然。
所以, extern "C" 这个声明的真实目的,就是实现 C++
与 C
及其它语言的混合编程。
使用场景
C++
中引用 C
函数
在 C++
中引用 C
语言中的函数和变量,在包含 C
语言头文件(假设为 cExample.h
)时,需进行下列处理:
extern "C"
{#include "cExample.h"
}
因为, C
库的编译当然是用 C
的方式生成的,其库中的函数标号一般也是类似前面所说的 _foo
之类的形式,没有任何参数信息,所以在 C++
中,要指定使用 extern "C" ,进行 C
方式的声明(如果不指定,那么 C++
中的默认声明方式当然是 C++
方式的,也就是编译器会产生 _foo_int_int
之类包含参数信息的、 C++
形式的函数标号,这样的函数标号在已经编译好了的、可以直接引用的 C
库中当然没有)。通过头文件对函数进行声明,再包含头文件,就能引用到头文件中声明的函数(因为函数的实现在库中呢,所以只声明,然后链接就能用了)。
而在 C
语言中,对其外部函数只能指定为 extern
类型,因为 C
语言中不支持 extern "C" 声明,在 .c
文件中包含了 extern "C" 时,当然会出现编译语法错误。
下面是一个具体代码:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{return x + y;
}// c++实现文件,调用add:cppFile.cpp
extern "C"
{#include "cExample.h"
}
int main(int argc, char* argv[])
{add(2,3);return 0;
}
可见,如果 C++
调用一个 C
语言编写的 .dll
时,在包含 .dll
的头文件或声明接口函数时,应加 extern "C" { }
来告诉 C++
,链接 C
库的时候,采用 C
的方式进行链接(即寻找类似 _foo
的没有参数信息的函数,而不是默认的 _foo_int_int
这样包含了参数信息的 C++
函数)。
C
中引用 C++
函数
在C中引用 C++
语言中的函数和变量时, C++
的头文件需添加 extern "C" ,但是在 C
语言中不能直接引用声明了 extern "C" 的该头文件,应该在 C
文件中用 extern
声明 C++
中定义的 extern "C" 函数(也就是说 C++
中用 extern "C" 声明的函数,在 C
中用 extern
来声明一下,这样 C
就能引用 C++
的函数了)。
下面是一个具体代码:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{return x + y;
}/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );int main( int argc, char* argv[] )
{add( 2, 3 ); return 0;
}
python调用C++ dll
我们可以通过python的内置的ctypes库来调用C++的函数,因为ctypes只能处理C语言风格的函数,因为我们必须在需要暴露给python调用的函数前面“ extern "C" ”,否则ctypes是无法按照正常的函数名来调用 C++ 定义好的函数。
MathLibrary.h:
// MathLibrary.h - Contains declarations of math functions
#pragma once#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif// The Fibonacci recurrence relation describes a sequence F
// where F(n) is { n = 0, a
// { n = 1, b
// { n > 1, F(n-2) + F(n-1)
// for some initial integral values a and b.
// If the sequence is initialized F(0) = 1, F(1) = 1,
// then this relation produces the well-known Fibonacci
// sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, ...// Initialize a Fibonacci relation sequence
// such that F(0) = a, F(1) = b.
// This function must be called before any other function.
extern "C" MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b);// Produce the next value in the sequence.
// Returns true on success and updates current value and index;
// false on overflow, leaves current value and index unchanged.
extern "C" MATHLIBRARY_API bool fibonacci_next();// Get the current value in the sequence.
extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();// Get the position of the current value in the sequence.
extern "C" MATHLIBRARY_API unsigned fibonacci_index();
MathLibarary.cpp:
// MathLibrary.cpp : Defines the exported functions for the DLL.
#include <utility>
#include <limits.h>
#include "MathLibrary.h"// DLL internal state variables:
static unsigned long long previous_; // Previous value, if any
static unsigned long long current_; // Current sequence value
static unsigned index_; // Current seq. position// Initialize a Fibonacci relation sequence
// such that F(0) = a, F(1) = b.
// This function must be called before any other function.
void fibonacci_init(const unsigned long long a,const unsigned long long b)
{index_ = 0;current_ = a;previous_ = b; // see special case when initialized
}// Produce the next value in the sequence.
// Returns true on success, false on overflow.
bool fibonacci_next()
{// check to see if we'd overflow result or positionif ((INT_MAX - previous_ < current_) ||(20 == index_)){return false;}// Special case when index == 0, just return b valueif (index_ > 0){// otherwise, calculate next sequence valueprevious_ += current_;}std::swap(current_, previous_);++index_;return true;
}// Get the current value in the sequence.
unsigned long long fibonacci_current()
{return current_;
}// Get the current index position in the sequence.
unsigned fibonacci_index()
{return index_;
}
通过dumpbin来查看dll中的函数:
D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\bin\Hostx86\x64>dumpbin /exports D:\my_project\VCXXTutorials\PyCallDLL\MathLibrary\x64\Debug\MathLibrary.dll
Microsoft (R) COFF/PE Dumper Version 14.36.32537.0
Copyright (C) Microsoft Corporation. All rights reserved.Dump of file D:\my_project\VCXXTutorials\PyCallDLL\MathLibrary\x64\Debug\MathLibrary.dllFile Type: DLLSection contains the following exports for MathLibrary.dll00000000 characteristicsFFFFFFFF time date stamp0.00 version1 ordinal base4 number of functions4 number of namesordinal hint RVA name1 0 0001100A fibonacci_current = @ILT+5(fibonacci_current)2 1 000112DF fibonacci_index = @ILT+730(fibonacci_index)3 2 00011244 fibonacci_init = @ILT+575(fibonacci_init)4 3 0001129E fibonacci_next = @ILT+665(fibonacci_next)Summary1000 .00cfg1000 .data1000 .idata1000 .msvcjmc3000 .pdata3000 .rdata1000 .reloc1000 .rsrc8000 .text10000 .textbssD:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\bin\Hostx86\x64>
通过ctypes调用dll中的函数:
import ctypes
from ctypes import c_bool,c_int
math_dll = ctypes.cdll.LoadLibrary('./MathLibrary.dll')
# Initialize a Fibonacci relation sequence.
math_dll.fibonacci_init.argtypes = [c_int, c_int]
math_dll.fibonacci_init(1, 1)math_dll.fibonacci_next.restype = c_bool# Write out the sequence values until overflow.
while True:print(math_dll.fibonacci_index(), ": ", math_dll.fibonacci_current())if not math_dll.fibonacci_next():break
# Report count of values written before overflow.
print(math_dll.fibonacci_index() + 1, " Fibonacci sequence values fit in an unsigned 64-bit integer.");
更多内容可以阅读:[Python] 如何通过ctypes库来调用C++ 动态库 DLL?-CSDN博客
参考资料
关于 C++ 中的 extern "C"