在 C++ 工程下,调用 C 语言书写的函数,其具体流程如下:
1)准备工作,创建 C++ 工程 06.call_from_cpp,其中需要引入两个头文件,一个头文件利用 C 语言实现,一个头文件利用 C++ 语言实现。
06.call_from_cpp.cpp 文件内容为:
#include <iostream>
#include "factorial.h"
#include "fibonacci.h"
int main() {
std::cout << Factorial(4) << std::endl;
std::cout << Fibonacci(8) << std::endl;
return 0;
}
CMakeLists.txt 文件内容为:
cmake_minimum_required(VERSION 3.17)
project(06_call_from_cpp)
set(CMAKE_CXX_STANDARD 17)
include_directories(mathutils/include) # 设置头文件路径
# 添加可执行程序
add_executable(06_call_from_cpp
06.call_from_cpp.cpp
mathutils/factorial.cpp
mathutils/fibonacci.c)
注意,在具有多个工程的情况下,只能 load 一个工程下的 CMakeLists.txt 文件。此处 C++ 子工程建立在主工程 Chapter 13 之下,但 Chapter13 主工程和 06.call_from_cpp 子工程二者的 CMakeLists.txt 文件只能有一个能运行。图中运行的是 06.call_from_cpp 子工程的 CMakeLists.txt 文件,故右击主工程 CMakeLists.txt 文件,可发现选项:Load CMake Project.
2)此时若直接运行 06.call_from_cpp.cpp,结果如下图所示:提示 Fibonacci() 函数无法找到(Factorial() 函数可以找到)。 表面原因在于 Fibonacci() 函数是使用 C 语言书写。
如下图所示,找寻 C++ 工程下编译过程中生成的目标文件,即 06.call_from_cpp.cpp.obj,factorial.cpp.obj,fibonacci.c.obj。使用 objdump -t 命令,查看各目标文件中编译的函数名。
06.call_from_cpp.cpp.obj 的查询结果如下,可以发现 Factorial() 和 Fibonacci() 函数对应的函数名为 __Z9Factorial 和 __Z9Fibonacci。
factorial.cpp.obj 中生成的 Factorial() 函数的函数名与 06.call_from_cpp.cpp.obj 一致,为 __Z9Factorial。
而使用 objdump -t 查询 fibonacci.c.obj 时,其生成的函数名为 _Fibonacci。
综上所述,06.call_from_cpp.cpp.obj 调用的函数名与 factorial.cpp.obj 生成的函数名一致,而与 fibonacci.c.obj 生成的函数名不一致,导致了程序执行失败。故,C++ 目标文件调用的函数名与 C 目标文件生成的函数名不一致,导致 C 语言书写的函数无法被 C++ 程序获取,此为本质原因。
3)C++ 和 C 目标文件的符号修饰规则不同,故直接执行程序不可行。解决方式是让 C++ 知道这段程序是 C 语言编写的,按照 C 的风格生成符号名(函数名)。故修改 fibonacci.h 文件:
#ifndef CHAPTER13_MATHUTILS_INCLUDE_FIBONACCI_H_
#define CHAPTER13_MATHUTILS_INCLUDE_FIBONACCI_H_
#ifdef __cplusplus // 如果是 C++ 引用该段代码,则添加 extern "C"
extern "C" {
#endif
int Fibonacci(int n);
#ifdef __cplusplus
};
#endif
#endif //CHAPTER13_MATHUTILS_INCLUDE_FIBONACCI_H_
修饰之后,利用 objdump -t 查看 06.call_from_cpp.cpp.obj,可以发现 C++ 目标文件中关于 Fibonacci() 函数的命名发生了变化。
且程序成功执行,其结果为:
需要注意的一点是,上述所有操作均在 mingw 编译器下完成。mingw 编译器和 msvc 编译器对符合的命名规则是不同的,但两者的解决方案是一致的。添加 extern "C" 后,msvc 和 mingw 编译器都可正常执行上述程序。
题外话,06.call_from_cpp.cpp.obj 中有许多未知的函数名,可以使用 c++filt.exe 命令查看修饰后函数名的原始函数。 例如上图中的__ZSt4cout 函数,使用 c++filt.exe 命令的结果如下。说明该函数的原始名称为 std::cout。