本文为 C 语言工程调用 C++ 库的解决方案。
应用场景: 需要 C++ 程序编译成的库提供函数接口,来解决 C 语言工程的需求。
想要快速解决问题,直接看 三、通用解决方法
人的出场顺序真的很重要,很多人如果换一个时间认识,换一个时间共处,一切都将是不一样的场景,不一样的结局。所以,人生有无限种可能,我的人生,是现在这一种。感谢大家恰到好处的出现,组成我最好的一种可能。
------ 大家好啊 我是 暮冬 Z 羡慕
# C++ 库可以编辑的情况
C++ 库由自己编写,可以决定头文件书写的位置
// hello.cpp | |
#include "hello.h" | |
#include "iostream" // 将用到的 C++ 标准库,如 iostream,放在本 cpp 文件中 | |
using namespace std; | |
void sayHello(){ | |
cout<< " # iostream: i am saying hello !" << endl; | |
printf(" # c: i am saying hello !\n"); | |
} |
创建 hello.cpp 文件,实现 sayHello () 功能,分别用 C++ 标准库和 C 标准库的输入输出功能打印 hello!
①需要注意的是 “将 C++ 标准库放在该 cpp 文件中”,原因后续指出。
// hello.h | |
#include "stdio.h" // 这里不能出现 C++ 标准库 | |
void sayHello(); |
创建头文件 hello.h 这里可以添加 C 语言标准库,但是不要把 C++ 标准库放在这里。
以上 hello.h 和 hello.cpp 模拟了 C++ 库。为了使 C 语言工程能够调用该库,需要增加一个中间层
// helloWapper.cpp | |
#include "helloWapper.h" | |
void Wapper_sayHello(){ | |
sayHello(); | |
} |
创建中间层 helloWapper.cpp , 对想要使用的 C++ 库函数进行封装,即: 通过 Wapper_sayHello () 调用 sayHello ()
// helloWapper.h | |
#include "hello.h" | |
#ifdef __cplusplus | |
extern "C"{ | |
#endif | |
void Wapper_sayHello(); | |
#ifdef __cplusplus | |
} | |
#endif |
创建中间层头文件 helloWapper.h,暴露 Wapper_sayHello () 接口。中间出现的 extern "C" {} 是告诉 G++ 编译器,对中间的函数按照 C 语言的方式进行编译。
然后将上述两个 CPP 文件编译成 静态库 ,使用的 CMakeLists.txt 文件如下:
cmake_minimum_required (VERSION 3.5) | |
project (demo) | |
include_directories (${PROJECT_SOURCE_DIR}/include ) # 指定头文件位置 | |
set (test_call_LIST ${PROJECT_SOURCE_DIR}/src/hello.cpp | |
${PROJECT_SOURCE_DIR}/src/helloWapper.cpp) # 指定需要编译的 CPP 文件 | |
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 指定库输出路径 | |
add_library(hello STATIC ${test_call_LIST}) # 编译 |
所有文件结构如下:
在工程根目录输入
mkdir build & cd build | |
cmake .. | |
make |
bin 文件夹下就会出现 封装好的库 libhello.a
创建 mian.c 来模拟 C 语言工程:
#include "stdio.h" | |
#include "helloWapper.h" // 调用 C++ 库接口 | |
int main(){ | |
Wapper_sayHello(); | |
return 0; | |
} |
A:通过命令行进行编译运行
回到工程根目录,编译 main.c , 运行:
cd .. | |
gcc -o Hello ./src/main.c -I./include -L./bin -lhello -lstdc++ | |
./Hello |
B:通过 Cmake 编译运行
在工程根目录,将 CMakeList.txt 内容替换为:
# CMakeList.txt | |
cmake_minimum_required (VERSION 3.5) | |
project (demo) | |
include_directories (${PROJECT_SOURCE_DIR}/include) # 指定头文件目录 | |
find_library(Hello_LIB hello HINTS ${PROJECT_SOURCE_DIR}/bin) # 引入 libhello.a 静态库 | |
set (hello_List ${PROJECT_SOURCE_DIR}/src/main.c) | |
add_executable (Hello ${hello_List}) # 默认 gcc 编译 main.c | |
target_link_libraries (Hello ${Hello_LIB} stdc++) |
cd build && cmake .. | |
make | |
./Hello |
A B 两种方式均输出结果: 至此 C 语言工程能够成功调用 C++ 库
以上 A B 两种方式中均出现 stdc++ ,一般以 libstdc++.so 的方式存在,是 C++ 标准库。
可以看下图 “G++ and GCC”
GCC 在编译时不会自动链接 C++ 标准库, 因此 hello.cpp 用到的 类似 "iostream" 等 C++ 标准库需要手动链接,否则会出现以下错误:
# C++ 库为第三方库,无法编辑的情况
在《一、C 库可以编辑的情况》 中提到 【①需要注意的是 “ 将 C 标准库放在该 cpp 文件中”】 是因为 GCC 编译不仅找不到 C 标准库 stdc ,也找不到 C++ 标准库的头文件。如果在 hello.h 中引用 “iostream", 那么用 GCC 编译 C 语言工程时,会报找不到头文件错误。
// hello.h | |
#include "stdio.h" | |
#include "iostream" // 这里出现了 C++ 标准库 | |
void sayHello(); |
但是当我们想用的 C 库为第三方库,而它在头文件里引用了大量 C 标准库的情况下,该如何处理呢?
再看图 “G++ and GCC”,里面指明 G 编译器能够编译 C 和 C 文件,且能够自动链接 C 标准库。所以在这种情况下,只需要在编译 C 语言工程的时候,指定 G 为编译器(编译 C 文件默认使用的是 GCC 编译器)就可以了。
下面是与《一、C++ 库可以编辑的情况》相似的总体流程(有修改的地方会有注释):
// hello.cpp | |
#include "hello.h" // C++ 标准库头文件转移到 hello.h 中 | |
using namespace std; | |
void sayHello(){ | |
cout<< " # iostream: i am saying hello !" << endl; | |
printf(" # c: i am saying hello !\n"); | |
} |
创建 hello.cpp 文件,实现 sayHello () 功能,分别用 C++ 标准库和 C 标准库的输入输出功能打印 hello!
// hello.h | |
#include "stdio.h" | |
#include "iostream" // C++ 标准库出现在这里 | |
void sayHello(); |
创建头文件 hello.h
以上 hello.h 和 hello.cpp 模拟了 C++ 库。为了使 C 语言工程能够调用该库,需要增加一个中间层:
// helloWapper.cpp | |
#include "helloWapper.h" | |
void Wapper_sayHello(){ | |
sayHello(); | |
} |
创建中间层 helloWapper.cpp , 对想要使用的 C++ 库函数进行封装,即: 通过 Wapper_sayHello () 调用 sayHello ()
// helloWapper.h | |
#include "hello.h" | |
#ifdef __cplusplus | |
extern "C"{ | |
#endif | |
void Wapper_sayHello(); | |
#ifdef __cplusplus | |
} | |
#endif |
创建中间层头文件 helloWapper.h,暴露 Wapper_sayHello () 接口。中间出现的 extern "C" {} 是告诉 G++ 编译器,对中间的函数按照 C 语言的方式进行编译。
然后将上述两个 CPP 文件编译成 静态库 ,使用的 CMakeLists.txt 文件如下:
cmake_minimum_required (VERSION 3.5) | |
project (demo) | |
include_directories (${PROJECT_SOURCE_DIR}/include ) # 指定头文件位置 | |
set (test_call_LIST ${PROJECT_SOURCE_DIR}/src/hello.cpp | |
${PROJECT_SOURCE_DIR}/src/helloWapper.cpp) # 指定需要编译的 CPP 文件 | |
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 指定库输出路径 | |
add_library(hello STATIC ${test_call_LIST}) # 编译 |
文件结构还是图:文件结构图
在工程根目录输入
mkdir build & cd build | |
cmake .. | |
make |
bin 文件夹下就会出现 封装好的库 libhello.a
创建 mian.c 来模拟 C 语言工程:
#include "stdio.h" | |
#include "helloWapper.h" // 调用 C++ 库接口 | |
int main(){ | |
Wapper_sayHello(); | |
return 0; | |
} |
A:通过命令行进行编译运行
回到工程根目录,编译 main.c , 运行:
编译器使用了 g++ ,可以自动搜索 C 标准库路径及链接 C 标准库,因此不需要再加 “-lstdc++”。
cd .. | |
g++ -o Hello ./src/main.c -I./include -L./bin -lhello | |
./Hello |
B:通过 Cmake 编译运行
在工程根目录,将 CMakeList.txt 内容替换为:
# CMakeList.txt | |
cmake_minimum_required (VERSION 3.5) | |
project (demo) | |
SET(CMAKE_C_COMPILER "g++") # 指定使用 g++ 编译器进行编译 | |
include_directories (${PROJECT_SOURCE_DIR}/include) # 指定头文件目录 | |
find_library(Hello_LIB hello HINTS ${PROJECT_SOURCE_DIR}/bin) # 引入 libhello.a 静态库 | |
set (hello_List ${PROJECT_SOURCE_DIR}/src/main.c) | |
add_executable (Hello ${hello_List}) | |
target_link_libraries (Hello ${Hello_LIB}) |
编译、执行:
cd build && cmake .. | |
make | |
./Hello |
# 通用方法
可以忽略方法一和二。方法三可以采用 GCC 编译调用了 C 库的 C 语言工程。
解决的方法是: 在 中间层 helloWapper.cpp 中引用 C 库头文件 #include "hello.h", 而不是在 helloWapper.h 中引用
例子:
创建 C++ 库源文件
// hello.cpp | |
#include "hello.h" | |
using namespace std; | |
void sayHello(){ | |
cout<< " # iostream: i am saying hello !" << endl; | |
printf(" # c: i am saying hello !\n"); | |
} |
创建头文件 hello.h
// hello.h | |
#include "stdio.h" | |
#include "iostream" | |
void sayHello(); |
以上 hello.h 和 hello.cpp 模拟了 C++ 库。为了使 C 语言工程能够调用该库,需要增加一个中间层
其中注意:一定要将 C 库的头文件 "hello.h" 加在 中间层的.cpp 文件中, 而不是放在中间层.h 文件中。这种情况下能够将 "hello.h" 等所有 C 库头文件编译到库当中,防止最后采用 GCC 编译时找不到 g++ 标准库的头文件。
// helloWapper.cpp | |
#include "hello.h" // 一定要将 C++ 库的头文件 "hello.h" 加在 中间层的 cpp 文件中 | |
#include "helloWapper.h" | |
void Wapper_sayHello(){ | |
sayHello(); | |
} | |
// helloWapper.h | |
#ifdef __cplusplus // 不要把 #include "hello.h" 放在这里 | |
extern "C"{ | |
#endif | |
void Wapper_sayHello(); | |
#ifdef __cplusplus | |
} | |
#endif |
创建中间层头文件 helloWapper.h,暴露 Wapper_sayHello () 接口。中间出现的 extern "C" {} 是告诉 G++ 编译器,对中间的函数按照 C 语言的方式进行编译。
然后将上述两个 CPP 文件编译成 静态库 ,使用的 CMakeLists.txt 文件如下:
cmake_minimum_required (VERSION 3.5) | |
project (demo) | |
include_directories (${PROJECT_SOURCE_DIR}/include ) # 指定头文件位置 | |
set (test_call_LIST ${PROJECT_SOURCE_DIR}/src/hello.cpp | |
${PROJECT_SOURCE_DIR}/src/helloWapper.cpp) # 指定需要编译的 CPP 文件 | |
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 指定库输出路径 | |
add_library(hello STATIC ${test_call_LIST}) # 编译 |
文件结构还是图:文件结构图
在工程根目录输入
mkdir build & cd build | |
cmake .. | |
make |
bin 文件夹下就会出现 封装好的库 libhello.a
创建 mian.c 来模拟 C 语言工程:
#include "stdio.h" | |
#include "helloWapper.h" // 调用 C++ 库接口 | |
int main(){ | |
Wapper_sayHello(); | |
return 0; | |
} |
A:通过命令行进行编译运行
回到工程根目录,编译 main.c , 运行:
编译器使用了 gcc ,需要添加 C 标准库 “ -lstdc”(标准库的头文件已经在 C++ 库中了)。
cd .. | |
gcc -o Hello ./src/main.c -I./include -L./bin -lhello -lstdc++ | |
./Hello |
B:通过 Cmake 编译运行
在工程根目录,将 CMakeList.txt 内容替换为:
# CMakeList.txt | |
cmake_minimum_required (VERSION 3.5) | |
project (demo) | |
include_directories (${PROJECT_SOURCE_DIR}/include) # 指定头文件目录 | |
find_library(Hello_LIB hello HINTS ${PROJECT_SOURCE_DIR}/bin) # 引入 libhello.a 静态库 | |
set (hello_List ${PROJECT_SOURCE_DIR}/src/main.c) | |
add_executable (Hello ${hello_List}) | |
target_link_libraries (Hello ${Hello_LIB} stdc++) |
编译、执行:
cd build && cmake .. | |
make | |
./Hello |
方法三是最通用的解决方法,不仅仍然可以使用 GCC 编译,而且想要在其他地方调用 C++ 库时,需要的头文件只有 “helloWapper.h” 一个。
# 后记
本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,或者知乎 私信,并指明哪一篇博客,我看到一定及时回复,感激不尽!