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

news2024/7/6 17:47:16

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 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"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1405101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业需要的3种供应商管理解决方案

传统的 "管理和监控 "供应商管理解决方案是对时间和金钱的巨大浪费。准入、资格认证和细分等孤立的供应商管理流程无法与其他采购流程整合在一起。 此外&#xff0c;从多个来源获取和管理供应商数据的过程只会增加固有的复杂性。而且&#xff0c;人工操作往往会延误…

【Java IO】设计模式 (装饰者模式)

Java I/O 使用了装饰者模式来实现。 装饰者模式 请参考装饰者模式详解 装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component)&#xff0c;具体组件的方法实现不需要依赖于其它对象&#xff0c;而装饰者组合了一个组件&#xff0c;这样它可以装饰其它装饰者…

Linux中文件IO(open、read、write、close函数使用)

介绍 什么是文件IO&#xff1f; 是操作系统提供的API接口函数。 POSIX接口 &#xff08;了解&#xff09; 注意&#xff1a;文件IO不提供缓冲机制 文件IO的API open close read read 文件描述符概念&#xff1a; 英文&#xff1a;缩写fd&#xff08;file descriptor&#xff…

前端和后端之间的CORS 跨域和解决办法

什么是CORS&#xff08;Cross-Origin Resource Sharing&#xff0c;跨源资源共享&#xff09; 跨源资源共享&#xff08;CORS&#xff0c;或通俗地译为跨域资源共享&#xff09;是一种基于 HTTP 头的机制&#xff0c;该机制通过允许服务器标示除了它自己以外的其他源&#xff0…

go 语言中 json.Unmarshal([]byte(jsonbuff), j) 字节切片得使用场景

struct_tag的使用 在上面的例子看到&#xff0c;我们根据结构体生成的json的key都是大写的&#xff0c;因为结构体名字在go语言中不大写的话&#xff0c;又没有访问权限&#xff0c;这种问题会影响到我们对json的key的名字&#xff0c;所以go官方给出了struct_tag的方法去修改…

【C++杂货铺】三分钟彻底搞懂如何使用C++中max函数

&#x1f308;前言 欢迎收看本期【C杂货铺】&#xff0c;这期内容&#xff0c;我们将围绕C中max函数部分进行讲解&#xff0c;包含了如何查询库函数&#xff0c;max函数的使用方法灯。如果你想学习C&#xff0c;或者刚学完C语言衔接C&#xff0c;那么这篇文章将会非常有效的帮助…

Redis学习——入门篇①

Redis学习——入门篇① 1、2&#xff1a;Redis入门概述3&#xff1a;Redis安装配置10 安装Redis-cli命令 4&#xff1a;Redis——十大数据类型11 redis数据类型12 命令查阅13 key常用命令介绍14 类型大小写和帮助命令15 String 命令&#xff08;上&#xff09;16 String 命令&a…

电脑摄像头设置在哪里?这3种打开方式要记好!

“我需要开一个视频会议&#xff0c;因此可能需要用到电脑的摄像头。但是我不知道我电脑的摄像头设置在哪里&#xff0c;有没有朋友可以告诉我一下呀&#xff1f;” 在日常办公时&#xff0c;我们可能经常要用到电脑摄像头。这不仅为用户提供了实时沟通的机会&#xff0c;还帮助…

机器学习实验报告——APRIORI算法

目录 一、算法介绍 1.1算法背景 1.2算法引入 1.3算法假设 1.4算法基本概念介绍 1.4.1关联规则 1.4.2支持度 1.4.3置信度 1.4.4频繁项集 1.4.5项目 1.4.6提升度 二、算法原理 2.1算法思想 2.2Apriori算法产生频繁项集 2.3Apriori算法的基本步骤 2.4关联分析 三、算法实现 3.1 Ap…

安卓自动化 | autox.js

介绍: 不需要Root权限 的 JavaScript 自动化软件 官方文档: http://doc.autoxjs.com/ 开发准备 安装Releases kkevsekk1/AutoX (github.com)到手机上 安装vscode插件 插件介绍中有中文的使用说明, 如何连接电脑 Usage Step 1 按 CtrlShiftP 或点击"查看"->&…

司铭宇老师:房地产中介培训班:房地产中介培训课程

房地产中介培训班&#xff1a;房地产中介培训课程 在竞争激烈的房地产市场中&#xff0c;成为一名杰出的中介代表不仅需要对行业有深刻的理解&#xff0c;还要具备一系列专业技能和高效的销售策略。我们的房地产中介培训课程旨在为有志于提升个人能力和业绩的中介人员提供全面、…

C++: vector

目录 1.vector的介绍 2.vector常用的接口 1.vector构造 2.迭代器iterator的使用 3.vector空间增长 4.vector的增删改查 3.vector模拟实现 如果在reverse时使用memcpy会怎么样&#xff1f; 1.vector的介绍 C中的vector是一个动态数组容器&#xff0c;可以存储任意类型的…

element plus表格的表头和内容居中

文章目录 需求分析 需求 对于 element-plus 中的 table 进行表头和内容的居中显示 分析 单列的表头和内容居中 &#xff1a; 在对应的那一列加上align“center” 即可 <el-table-column prop"name" label"商品名称" align"center" />…

Golang 中如何实现 Set

在Go编程中&#xff0c;数据结构的选择对解决问题至关重要。本文将探讨如何在 GO 中实现 set 和 bitset 两种数据结构&#xff0c;以及它们在Go中的应用场景。 Go 的数据结构 Go 内置的数据结构并不多。工作中&#xff0c;我们最常用的两种数据结构分别是 slice 和 map&#…

安防视频监控EasyCVR平台HTTP-FMP4播放协议在分屏播放时的性能优化

安防视频监控EasyCVR平台兼容性强&#xff0c;可支持的接入协议众多&#xff0c;包括国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&#xff1a;海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等。平台能将接入的视频…

String对象的探究(new String到底创建了多少对象?)

String对象的探究 1. 前言&#xff08;String&#xff09;2. new String()到底创建了多少对象&#xff08;java8举例&#xff09;&#xff1f;2.1. new String("abc")&#xff1a;2/12.2. String str “a” "b" “c”&#xff1a;12.3. String abc &quo…

【MySQL源码】Seconds_Behind_Master是如何计算的

作为MySQL DBA&#xff0c;相信大家对参数 Seconds_Behind_Master 并不陌生&#xff0c;该字段的值可以通过 show slave status\G的输出&#xff0c;表示主从延迟的时间&#xff0c;单位为秒。监控主从延迟一般取这个值就足够了。0 表示无延迟&#xff0c;理想状态该值不要超…

JavaWeb之JavaScript-Vue --黑马笔记

什么是JavaScript&#xff1f; JavaScript&#xff08;简称&#xff1a;JS&#xff09; 是一门跨平台、面向对象的脚本语言。是用来控制网页行为的&#xff0c;它能使网页可交互。 JavaScript 和 Java 是完全不同的语言&#xff0c;不论是概念还是设计。但是基础语法类似。 …

【动态规划】879. 盈利计划

作者推荐 【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径 本文涉及知识点 动态规划汇总 LeetCode879. 盈利计划 集团里有 n 名员工&#xff0c;他们可以完成各种各样的工作创造利润。 第 i 种工作会产生 profit[i] 的利润&#xff0c;它要求 group[…

用flinkcdc debezium来捕获数据库的删除内容

我在用flinkcdc把数据从sqlserver写到doris 正常情况下sqlserver有删除数据&#xff0c;doris是能捕获到并很快同步删除的。 但是我现在情况是doris做为数仓&#xff0c;数据写到ods&#xff0c;ods的数据还会通过flink计算后写入dwd层&#xff0c;所以此时ods的数据是删除了…