【C++ 语言】 C 与 C++ 兼容 ( extern "C" )
创建项目
创建 并运行 CMake 项目 :
-
1. 选择创建选项 : 在打开的欢迎界面中 , 点击 右侧最下方的 "创建新项目 " 选项 ;
-
2. 选择项目类型 : 选择创建 “CMake 项目” , 在 Android 中主要使用的也是 CMake 配置 NDK C/C++ 代码 ;
-
3. 项目配置 : 输入项目名称 , 选择项目位置 , 下面的解决方案名称会自动生成 , 不勾选最下方的选项 ; 点击 “创建” 按钮 , 创建项目 ;
等待项目创建完毕 , 会自动跳转到程序主界面 ;
- 4. 自动生成解决方案 : 进入程序主界面后 , 系统会自动生成 CMake 解决方案 , 如果一切顺利 , 会有如下结果 :
5. 选择启动项 : 点击绿色的小三角按钮 “选择启动项” , 选择上面生成的解决方案 “001_CMake_1.exe” 选项 , 如下图示 ;
- 6. 运行程序 : 再次点击 “001_CMake_1.exe” 选项 , 即可运行该控制台程序 , 在控制台中打印 “Hello CMake。” ;
项目源码说明
相关源码说明 :
① 001_CMake_1.h : 项目头文件 ;
// 001_CMake_1.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。
#pragma once
#include <iostream>
// TODO: 在此处引用程序需要的其他标头。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
② 001_CMake_1.cpp : 项目主代码文件 ;
// 001_CMake_1.cpp: 定义应用程序的入口点。
//
#include "001_CMake_1.h"
using namespace std;
int main()
{
cout << "Hello CMake。" << endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
C 与 C++ 标准输出 :
- C 中的标准输出 : 直接调用 printf
printf("Hello");
- 1
- C++ 中的标准输出 : << 此处是 操作符重载 ,
cout
在std
命名空间中 ;
cout << "Hello" << endl;
- 1
③ CMakeLists.txt ( 工程目录下 ) : 项目构建配置文件 , 配置 构建工具版本号 , 项目编译所需的源代码 ;
# CMakeList.txt: 001_CMake_1 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
# 将源代码添加到此项目的可执行文件。
add_executable (001_CMake_1 "001_CMake_1.cpp" "001_CMake_1.h")
# TODO: 如有需要,请添加测试并安装目标。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
④ CMakeLists.txt ( 总目录下 ) : 顶层的 CMake 文件, 配置全局所有子项目信息 , 这里只有一个子项目 ;
# CMakeList.txt: 顶层 CMake 项目文件,在此处执行全局配置
# 并包含子项目。
#
cmake_minimum_required (VERSION 3.8)
project ("001_CMake_1")
# 包含子项目。
add_subdirectory ("001_CMake_1")
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
C++ 中直接调用 C 代码 ( 无法解析的外部符号 错误 )
C++ 向下兼容 : C 中大部分代码都可以在 C++ 中直接使用 ; 但是需要做兼容处理 , 不能直接使用 ;
1. 创建测试文件 : 在上述创建的项目中 , 创建 c_extern.c 和 c_extern.h 两个文件 ;
2. c_extern.h 头文件内容 : 在头文件中定义一个带参数的方法 ;
#pragma once
//任意定义一个方法 , 该方法有若干个参数和返回值
int add(int a, int b);
- 1
- 2
- 3
- 4
3. c_extern.c 源文件内容 : 在 C 语言文件中实现上述头文件中定义的带参数的方法 ;
#include "c_extern.h"
//实现的头文件中的方法, 用于测试 C 与 C++ 兼容问题
int add(int a, int b)
{
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4. CMake 配置源码 : 将 “c_extern.c” ( C文件 ) 和 “c_extern.h” ( 头文件 ) 配置到 CMakeLists.txt 中 ;
# CMakeList.txt: 001_CMake_1 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
# 将源代码添加到此项目的可执行文件。
add_executable (001_CMake_1 "001_CMake_1.cpp" "001_CMake_1.h" "c_extern.c" "c_extern.h")
# TODO: 如有需要,请添加测试并安装目标。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
5. 执行结果 : 点击 001_CMake_1.exe 选项 , 运行程序 ; 弹出 “生成失败 , 是否要继续调试?” 的对话框 , 此时
6. 错误提示 : 无法解析在 main 函数中调用的 add 方法 ;
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 LNK1120 1 个无法解析的外部命令 ...\CMakeLists.txt ...\001_CMake_1.exe 1
错误 LNK2019 无法解析的外部符号 "int __cdecl add(int,int)" (?add@@YAHHH@Z),
该符号在函数 main 中被引用
...\CMakeLists.txt ...\001_CMake_1.cpp.obj 1
- 1
- 2
- 3
- 4
- 5
在 C++ 源码中直接调用 C 源码 , 一定会报该错误 , 下面分析产生该错误的原因 , 以及如何进行兼容处理 ;
C++ 与 C 编译结果对比
1. 创建对比文件 : 创建 下面 两个文件 , 分别是 C 代码 和 C++ 代码 ;
① c_code.c :
int add (int a, int b){
return a+b;
}
int main(){
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
② c_plus_code.cpp :
int add (int a, int b){
return a+b;
}
int main(){
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
C 和 C++ 中代码内容一模一样 ;
2. 获取 c_code.c 编译过程中的 机器码文件 : 使用 gcc c_code.c -o c_code.o
命令 , 可以获取编译的中间文件 , 输出到 c_code.o 文件中 ;
3. 获取 C语言文件编译后的 机器码文件中对应的符号 : 使用 nm -A c_code.o
命令 , 可以查看 c_code.o 二进制文件中的符号 ;
输出详细内容 :
root@ubuntu:~/001_c_c++# gcc c_code.c -o c_code.o
root@ubuntu:~/001_c_c++#
root@ubuntu:~/001_c_c++# ls
c_code.c c_code.o c_plus_code.cpp
root@ubuntu:~/001_c_c++#
root@ubuntu:~/001_c_c++# nm -A c_code.o
c_code.o:00000000004004d6 T add
c_code.o:0000000000601030 B __bss_start
c_code.o:0000000000601030 b completed.7594
c_code.o:0000000000601020 D __data_start
c_code.o:0000000000601020 W data_start
c_code.o:0000000000400410 t deregister_tm_clones
c_code.o:0000000000400490 t __do_global_dtors_aux
c_code.o:0000000000600e18 t __do_global_dtors_aux_fini_array_entry
c_code.o:0000000000601028 D __dso_handle
c_code.o:0000000000600e28 d _DYNAMIC
c_code.o:0000000000601030 D _edata
c_code.o:0000000000601038 B _end
c_code.o:0000000000400574 T _fini
c_code.o:00000000004004b0 t frame_dummy
c_code.o:0000000000600e10 t __frame_dummy_init_array_entry
c_code.o:00000000004006d0 r __FRAME_END__
c_code.o:0000000000601000 d _GLOBAL_OFFSET_TABLE_
c_code.o: w __gmon_start__
c_code.o:0000000000400584 r __GNU_EH_FRAME_HDR
c_code.o:0000000000400390 T _init
c_code.o:0000000000600e18 t __init_array_end
c_code.o:0000000000600e10 t __init_array_start
c_code.o:0000000000400580 R _IO_stdin_used
c_code.o: w _ITM_deregisterTMCloneTable
c_code.o: w _ITM_registerTMCloneTable
c_code.o:0000000000600e20 d __JCR_END__
c_code.o:0000000000600e20 d __JCR_LIST__
c_code.o: w _Jv_RegisterClasses
c_code.o:0000000000400570 T __libc_csu_fini
c_code.o:0000000000400500 T __libc_csu_init
c_code.o: U __libc_start_main@@GLIBC_2.2.5
c_code.o:00000000004004ea T main
c_code.o:0000000000400450 t register_tm_clones
c_code.o:00000000004003e0 T _start
c_code.o:0000000000601030 D __TMC_END__
root@ubuntu:~/001_c_c++#
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
4. 分析上述输出内容 : 由 第一行 c_code.o:00000000004004d6 T add
可以看出 , add 方法编译后的符号为 add
;
5. 获取 c_plus_code.cpp 编译过程中的 机器码文件 : 使用 gcc c_plus_code.cpp -o c_plus_code.o
命令 , 可以获取编译的中间文件 , 输出到 c_plus_code.o 文件中 ;
root@ubuntu:~/001_c_c++# gcc c_plus_code.cpp -o c_plus_code.o
root@ubuntu:~/001_c_c++# ls
c_code.c c_code.o c_plus_code.cpp c_plus_code.o
root@ubuntu:~/001_c_c++#
root@ubuntu:~/001_c_c++# nm -A c_plus_code.o
c_plus_code.o:0000000000601030 B __bss_start
c_plus_code.o:0000000000601030 b completed.7594
c_plus_code.o:0000000000601020 D __data_start
c_plus_code.o:0000000000601020 W data_start
c_plus_code.o:0000000000400410 t deregister_tm_clones
c_plus_code.o:0000000000400490 t __do_global_dtors_aux
c_plus_code.o:0000000000600e18 t __do_global_dtors_aux_fini_array_entry
c_plus_code.o:0000000000601028 D __dso_handle
c_plus_code.o:0000000000600e28 d _DYNAMIC
c_plus_code.o:0000000000601030 D _edata
c_plus_code.o:0000000000601038 B _end
c_plus_code.o:0000000000400574 T _fini
c_plus_code.o:00000000004004b0 t frame_dummy
c_plus_code.o:0000000000600e10 t __frame_dummy_init_array_entry
c_plus_code.o:00000000004006d0 r __FRAME_END__
c_plus_code.o:0000000000601000 d _GLOBAL_OFFSET_TABLE_
c_plus_code.o: w __gmon_start__
c_plus_code.o:0000000000400584 r __GNU_EH_FRAME_HDR
c_plus_code.o:0000000000400390 T _init
c_plus_code.o:0000000000600e18 t __init_array_end
c_plus_code.o:0000000000600e10 t __init_array_start
c_plus_code.o:0000000000400580 R _IO_stdin_used
c_plus_code.o: w _ITM_deregisterTMCloneTable
c_plus_code.o: w _ITM_registerTMCloneTable
c_plus_code.o:0000000000600e20 d __JCR_END__
c_plus_code.o:0000000000600e20 d __JCR_LIST__
c_plus_code.o: w _Jv_RegisterClasses
c_plus_code.o:0000000000400570 T __libc_csu_fini
c_plus_code.o:0000000000400500 T __libc_csu_init
c_plus_code.o: U __libc_start_main@@GLIBC_2.2.5
c_plus_code.o:00000000004004ea T main
c_plus_code.o:0000000000400450 t register_tm_clones
c_plus_code.o:00000000004003e0 T _start
c_plus_code.o:0000000000601030 D __TMC_END__
c_plus_code.o:00000000004004d6 T _Z3addii
root@ubuntu:~/001_c_c++#
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
6. 分析上述输出内容 : 由 最后一行 c_plus_code.o:00000000004004d6 T _Z3addii
可以看出 , add 方法编译后的符号为 _Z3addii
;
处理完毕后的文件内容 :
7. 总结 :
- ① 编译结果对比 : C 语言 add 方法编译后的符号是
add
, C++ 编译后的符号是_Z3addii
, 显然二者不能互相调用 , 因此一旦在 C++ 中调用 add , 就会出现上述无法解析外部符号错误 ; - ② 兼容 : 如果在 C++ 文件中调用 C 语言库 , 需要做兼容处理 ;
- ③ 示例 : 在 Android 中的 NDK 接口是 C++ 语言的 , 但是调用的库 如 OpenSL ES , FFMPEG 等都是 C语言的库 , 因此这里就需要用到 C 与 C++ 的兼容 ;
- ④ 兼容方法 : 使用 extern “C”{} 指定让大括号中的内容 以 C 语言的方式进行编译 ; 这样才能在 C++ 中找到对应的 C 语言中的函数 ; 如下示例 :
extern "C"{
#include "c_extern.c"
}
- 1
- 2
- 3
编译过程 : 预处理 -> 编译 -> 汇编 -> 链接;
1. 编译预处理 : 产生 .i 后缀的预处理文件;
2. 编译操作 : 产生 .s 后缀的汇编文件;
3. 汇编操作 : 产生 .o 后缀的机器码二进制文件;
4. 链接操作 : 产生可执行文件 ;
extern “C” 在头文件中的标准用法
extern “C” 用法 :
- 1. 在引用处使用 : extern “C” {} 可以写在 引用 头文件的位置 , 如下 :
// 001_CMake_1.cpp: 定义应用程序的入口点。
//
#include "001_CMake_1.h"
extern "C" {
#include "c_extern.h"
}
using namespace std;
int main()
{
cout << "Hello CMake。" << endl;
//调用 c_extern.h 头文件中定义的方法
//该方法定义在了 C 语言文件中
add(1, 2);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 2. 在头文件中定义 : extern “C” {} 也可以写在 头文件 中 , 一般情况下我们编写的 C 代码需要同时兼容 C 和 C++ , 因此 C 语言的头文件都进行如下定义 :
- ① __cplusplus 宏 : 该宏定义在 C++ 编译器中 , 如果是 C 语言编译器 , 就不会定义该宏 ;
- ② 使用效果 : 如果在 C++ 编译环境中 , extern “C” { 和 } 生效 , 在 C 语言编译环境中 , 不生效 ;
#pragma once
//兼容 C 与 C ++ 语言 , 在 C++ 中也可以编译 C 语言程序
// __cplusplus 是编译器中定义的 宏
//如果编译的是 C++ 代码 , 定义了__cplusplus 宏 , #ifdef __cplusplus 宏会生效
//这一组判定 extern "C" { 声明 , 是否生效 , 如果在 C++ 环境中生效 , C 语言环境中不生效
#ifdef __cplusplus
extern "C" {
#endif
//任意定义一个方法 , 该方法有若干个参数和返回值
int add(int a, int b);
//这一组判定 } 声明 , 是否生效 , 如果在 C++ 环境中生效 , C 语言环境中不生效
#ifdef __cplusplus
}
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
注意上述兼容二选一 , 不能同时使用 , 否则会报错 ;
最终的 C / C ++ 兼容 代码
最终的 C / C ++ 兼容 代码 :
- 1.程序结构 :
- 2.顶层 CMakeLists.txt : 配置多个项目 ;
# CMakeList.txt: 顶层 CMake 项目文件,在此处执行全局配置
# 并包含子项目。
#
cmake_minimum_required (VERSION 3.8)
project ("001_CMake_1")
# 包含子项目。
add_subdirectory ("001_CMake_1")
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 3.项目 CMakeLists.txt : 配置单个项目中的多个源文件 ;
# CMakeList.txt: 001_CMake_1 的 CMake 项目,在此处包括源代码并定义
# 项目特定的逻辑。
#
cmake_minimum_required (VERSION 3.8)
# 将源代码添加到此项目的可执行文件。
add_executable (001_CMake_1 "001_CMake_1.cpp" "001_CMake_1.h" "c_extern.c" "c_extern.h")
# TODO: 如有需要,请添加测试并安装目标。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 4.c_extern.h :
#pragma once
//兼容 C 与 C ++ 语言 , 在 C++ 中也可以编译 C 语言程序
// __cplusplus 是编译器中定义的 宏
//如果编译的是 C++ 代码 , 定义了__cplusplus 宏 , #ifdef __cplusplus 宏会生效
//这一组判定 extern "C" { 声明 , 是否生效 , 如果在 C++ 环境中生效 , C 语言环境中不生效
#ifdef __cplusplus
extern "C" {
#endif
//任意定义一个方法 , 该方法有若干个参数和返回值
int add(int a, int b);
//这一组判定 } 声明 , 是否生效 , 如果在 C++ 环境中生效 , C 语言环境中不生效
#ifdef __cplusplus
}
#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 5.c_extern.c :
#include "c_extern.h"
//实现的头文件中的方法, 用于测试 C 与 C++ 兼容问题
int add(int a, int b)
{
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 7.001_CMake_1.h :
// 001_CMake_1.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。
#pragma once
#include <iostream>
// TODO: 在此处引用程序需要的其他标头。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 8.001_CMake_1.cpp :
// 001_CMake_1.cpp: 定义应用程序的入口点。
//
#include "001_CMake_1.h"
#include "c_extern.h"
using namespace std;
int main()
{
cout << "Hello CMake。" << endl;
//调用 c_extern.h 头文件中定义的方法
//该方法定义在了 C 语言文件中
add(1, 2);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 9.运行结果 :
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/98840708
- 点赞
- 收藏
- 关注作者
评论(0)