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

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.mfbz.cn/a/341885.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年天津市公务员考试报名开始啦

2024年天津市公务员考试报名开始&#xff0c;详细流程如下&#xff1a; ⭐报名入口&#xff1a;天津市公开招考公务员网上报名信息系统网站 ✅报名时间&#xff1a;2024年1月23日8:30-1月29日18:00 ✅资格审查时间&#xff1a;2024年1月23日8:30-1月30日18:00 ✅打印准考证…

element-ui 打包流程源码解析(下)

目录 目录结构和使用1&#xff0c;npm 安装1.1&#xff0c;完整引入1.2&#xff0c;按需引入 2&#xff0c;CDN3&#xff0c;国际化 接上文&#xff1a;element-ui 打包流程源码解析&#xff08;上&#xff09; 文章中提到的【上文】都指它 ↑ 目录结构和使用 我们从使用方式来…

Raspbian安装云台

Raspbian安装云台 1. 源由2. 选型3. 组装4. 调试4.1 python3-print问题4.2 python函数入参类型错误4.3 缺少mjpg-streamer可执行文件4.4 缺失编译头文件和库4.5 python库缺失4.6 图像无法显示&#xff0c;但libcamera-jpeg测试正常4.7 异常IOCTL报错4.8 Git问题 5. 效果5.1 WEB…

ftp连接报错:227 entering passive mode

用阿里的云服务器&#xff0c;宝塔安装的linux环境&#xff0c;ftp连接总包这个错误&#xff1a;227 entering passive mode 原因是云服务器没有放开39000/40000的端口 如果使用的是阿里云服务器&#xff0c;需要在安全组设置中&#xff0c;对22、21端口放行&#xff0c;并且…

layui 自定义日期选择器今日、昨日 、本周、本月、上个月等

1、layui 日期选择器 laydate日期选择器 <div class"layui-input-inline"><input class"layui-input" id"dateTime" placeholder"日期范围"> </div><script> layui.use([laydate], function () {laydate.ren…

从零学习开发一个RISC-V操作系统(四)丨RISC-V汇编语言编程

本篇文章的内容 一、RISC-V汇编语言简介1.1 RISC-V 汇编语言的基本格式1.2 RISC-V 汇编指令操作对象1.3 RISC-V 汇编指令编码格式1.4 RISC-V 汇编指令分类 二、RISC-V汇编语言详解2.1 add 加法指令2.2 sub 减法指令 本系列是博主参考B站课程学习开发一个RISC-V的操作系统的学习…

瑞_力扣LeetCode_104. 二叉树的最大深度

文章目录 题目 104. 二叉树的最大深度题解后序遍历 递归实现后序遍历 迭代实现层序遍历 &#x1f64a; 前言&#xff1a;本文章为瑞_系列专栏之《刷题》的力扣LeetCode系列&#xff0c;主要以力扣LeetCode网的题进行解析与分享。本文仅供大家交流、学习及研究使用&#xff0c;禁…

关于图像分割项目的可视化脚本

1. 前言 之前实现了目标检测和图像分类任务的可视化脚本&#xff0c;本章将最后一个分割任务的可视化脚本实现 效果展示如下&#xff1a; 代码会在当前目录保存展示好的图片&#xff0c;从左到右依次为&#xff0c;原图、mask图、mask覆盖在原图的掩膜图 关于目标检测的可视化…

最长子字符串的长度(二) - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 给你一个字符串 s&#xff0c;字符串s首尾相连成一个环形 &#xff0c;请你在环中找出’l’、‘o’、‘x’ 字符都恰好出现了偶数次最长子字符串的长度。 输入描…

保护隐私数据:使用Java `transient`关键字

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 保护隐私数据&#xff1a;使用Java transient关键字 前言什么是java对象序列化transient关键字的基础知识序列化与反序列化过程避免transient的陷阱 前言 在数字时代&#xff0c;数据安全至关重要。无…

单片机中MCU跑RTOS相比裸机的优势

经常有读者问关于RTOS的问题&#xff0c;比如&#xff1a;我现在要不要学习RTOS&#xff1f; 学习RTOS有什么好处&#xff1f; 我的项目要不要跑RTOS&#xff1f; 问这些问题&#xff0c;其实归根结底还是对RTOS理解的不够&#xff0c;项目开发的经验还不足等。针对这部分朋友…

JVM系列-4.类加载器

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理&#x1f525;如果感觉博主的文…

RK3568平台 TinyAlsa集成第三方音频算法

一.tinyalsa介绍 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;是一个开源项目&#xff0c;涵盖了用户空间和内核空间对音频设备的操作接口&#xff0c;通过应用层使用alsalib可以实现对音频设备的控制 TinyAlsa是android推出的一个精简的ALSA库&#xff0c…

美易官方《惊爆财务丑闻,有空头已经赚了十倍》

惊爆财务丑闻&#xff0c;“四大粮商”之首ADM股价暴跌&#xff0c;有空头已经赚了十倍 近日&#xff0c;一起惊爆市场的财务丑闻让全球投资者为之震惊。作为全球最大的农业综合企业之一&#xff0c;“四大粮商”之首的ADM&#xff08;Archer Daniels Midland&#xff09;被曝涉…

PLC从HTTP服务端获取JSON文件,解析数据到寄存器

智能网关IGT-DSER集成了多种PLC协议&#xff0c;方便实现各种PLC与HTTP服务端之间通讯。通过网关的参数配置软件绑定JSON文件的字段与PLC寄存器地址&#xff0c;配置URL&#xff0c;即可采用POST命令&#xff0c;将JSON文件提交给HTTP的服务端&#xff1b; 服务端有返回的JSON&…

【Linux系统编程三十】线程池实现

线程池实现 一.线程池的本质二.类内创建线程三.代码实现 一.线程池的本质 线程池里面存储的都是一批已经创建好的线程&#xff0c;当线程池里有数据时&#xff0c;这批线程就会被唤醒去竞争数据&#xff0c;当线程池里没有数据时&#xff0c;这批线程就去休眠等待。 线程池的…

基于SpringBoot Vue汽车租赁系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

QTableWidget 双击单元格修改数据

本章介绍通过双击单元格&#xff0c;进入单元格&#xff0c;进行编辑&#xff0c;并对比是否修改了数据&#xff0c;如果修改了更新到数据库。 其他关于QTableWidget的操作&#xff0c;请查看上一篇文章《QTableWidget 用法-CSDN博客》 修改单元格内容&#xff0c;与原值比较…

jQuery鼠标事件、键盘事件、浏览器事件

鼠标事件 1、.click( ): 点击事件 html文档 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script src"jQuery.js"></script></head><body><p>haha 1</p&g…

C++面试宝典第23题:乌托邦树

题目 乌托邦树每年经历2个生长周期。每年春天,它的高度都会翻倍。每年夏天,他的高度都会增加1米。对于一颗在春天开始时种下的高为1米的树,问经过指定周期后,树的高度为多少? 输入描述:输入一个数字N(0 <= N <= 1000),表示指定周期。 比如:样例输入为3。 输出描…
最新文章