[C++] external “C“的作用和使用场景(案例)

C++中extern "C"的作用是什么?

在 C++ 中,extern "C" 的作用是告诉编译器按照 C 语言的规范来处理函数名和变量名。这是因为 C++ 编译器会对函数名和变量名进行名称修饰(name mangling),以区分不同的函数和变量。而在 C 语言中,函数名和变量名不会被名称修饰,因此需要使用 extern "C" 来告诉编译器使用 C 语言的规则。

下面是微软官方文档关于“extern "C"”的使用说明:

extern (C++) | Microsoft Learn

[C++] external “C“的作用和使用场景(案例)_第1张图片

extern (C++) | Microsoft Learn

[C++] external “C“的作用和使用场景(案例)_第2张图片

以下示例演示如何声明具有 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 
}

//  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 
#include 
#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 position
    if ((INT_MAX - previous_ < current_) ||
        (20 == index_))
    {
        return false;
    }

    // Special case when index == 0, just return b value
    if (index_ > 0)
    {
        // otherwise, calculate next sequence value
        previous_ += 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.dll

File Type: DLL

  Section contains the following exports for MathLibrary.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           4 number of functions
           4 number of names

    ordinal hint RVA      name

          1    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)

  Summary

        1000 .00cfg
        1000 .data
        1000 .idata
        1000 .msvcjmc
        3000 .pdata
        3000 .rdata
        1000 .reloc
        1000 .rsrc
        8000 .text
       10000 .textbss

D:\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"

你可能感兴趣的:(C++,c++,c语言,ctypes)