C 和 C++ 动态库封装及跨语言调用
在实际开发中,我们常常会将一部分功能封装成动态库(例如 .dll
或 .so
文件),然后通过链接这些库来使用其中的函数。本文将展示如何封装 C 和 C++ 函数为动态库,并在 C 和 C++ 中进行跨语言调用。
1. 封装 C 函数为动态库
步骤:
- 编写 C 代码:定义 C 函数。
- 编译 C 代码为动态库:将 C 代码编译为共享库(.so 或 .dll)。
- 在 C++ 中调用 C 函数:通过动态链接调用 C 库中的函数。
示例代码:
- C 代码(c_func.c):
c
// c_func.c
#include <stdio.h>
// C 函数:打印简单的问候语
void print_hello() {
printf("Hello from C function!\n");
}
- 编译 C 代码为动态库:
在 Linux 环境下,使用 gcc
编译 C 代码为动态库:
bash
gcc -shared -o libcfunc.so -fPIC c_func.c
这将生成 libcfunc.so
动态库。对于 Windows 系统,命令可能是:
bash
gcc -shared -o cfunc.dll -fPIC c_func.c
- C++ 代码(cpp_main.cpp):
cpp
// cpp_main.cpp
#include <iostream>
// 声明 C 函数
extern "C" {
void print_hello(); // 声明 C 函数
}
int main() {
// 使用动态库中的 C 函数
print_hello(); // 调用 C 函数
return 0;
}
- 编译 C++ 代码并链接动态库:
在 Linux 环境下,使用 g++
编译并链接 C++ 代码与 C 动态库:
bash
g++ -o cpp_main cpp_main.cpp -L. -lcfunc
-L.
指定动态库的目录,-lcfunc
表示链接名为 libcfunc.so
的动态库。
在 Windows 环境下,可以使用类似的命令:
bash
g++ -o cpp_main cpp_main.cpp -L. -l cfunc
运行结果:
bash
Hello from C function!
解释:
- 我们编写了一个简单的 C 函数
print_hello
,然后将其封装为动态库libcfunc.so
(在 Windows 上为cfunc.dll
)。 - C++ 代码通过
extern "C"
声明该函数,并通过动态链接库调用它。执行 C++ 程序时,它会动态加载并执行 C 函数。
2. 封装 C++ 函数为动态库
步骤:
- 编写 C++ 代码:定义一个简单的类和一个成员函数。
- 编译 C++ 代码为动态库:将 C++ 代码编译为共享库(.so 或 .dll)。
- 在 C 中调用 C++ 函数:通过 C 风格的接口调用 C++ 动态库中的函数。
示例代码:
- C++ 代码(cpp_func.cpp):
cpp
// cpp_func.cpp
#include <iostream>
class MyClass {
public:
void print_message() {
std::cout << "Hello from C++ function!" << std::endl;
}
};
// 提供 C 风格接口供 C 调用
extern "C" void call_cpp_function() {
MyClass obj; // 创建 C++ 类的对象
obj.print_message(); // 调用成员函数
}
- 编译 C++ 代码为动态库:
在 Linux 环境下,使用 g++
编译 C++ 代码为动态库:
bash
g++ -shared -o libcppfunc.so -fPIC cpp_func.cpp
对于 Windows 系统,命令是:
bash
g++ -shared -o cppfunc.dll -fPIC cpp_func.cpp
- C 代码(c_main.c):
c
// c_main.c
#include <stdio.h>
// 声明 C++ 函数
extern void call_cpp_function();
int main() {
// 调用 C++ 函数
call_cpp_function(); // 通过 C 风格接口调用 C++ 函数
return 0;
}
- 编译 C 代码并链接动态库:
在 Linux 环境下,使用 gcc
编译 C 代码并链接 C++ 动态库:
bash
gcc -o c_main c_main.c -L. -lcppfunc
在 Windows 上,类似的命令为:
bash
gcc -o c_main c_main.c -L. -l cppfunc
运行结果:
bash
Hello from C++ function!
解释:
- C++ 动态库
libcppfunc.so
(或在 Windows 上为cppfunc.dll
)中包含了一个类MyClass
和一个成员函数print_message
,并通过extern "C"
暴露了一个 C 风格接口call_cpp_function
。 - C 代码通过
extern
声明并调用该 C++ 函数,而实际的功能是在 C++ 动态库中执行的。
3. 讨论:C 和 C++ 动态库的跨语言调用原理
动态库的基本概念:
- 动态库(Dynamic Link Library,DLL 或 .so 文件)是一种包含可执行代码和数据的文件,它在程序运行时被加载到内存中,并且可以在多个程序之间共享。
- 当程序需要使用动态库中的函数时,它会通过库文件的路径查找并加载所需的符号(函数或数据)。
extern "C"
的作用:
- C++ 编译器会对函数进行名称修饰(name mangling),以支持函数重载等特性。而 C 编译器不会做名称修饰。
- 使用
extern "C"
关键字可以避免 C++ 编译器对函数名进行名称修饰,使得 C 编译器能够正确地找到并调用这些 C++ 函数。
如何跨语言调用:
-
C 调用 C++ 函数:通过将 C++ 函数封装成 C 风格接口(即不使用类和成员函数的简单函数),然后通过动态库在 C 语言中调用它。这种方式保证了 C 语言能够理解 C++ 函数接口。
-
C++ 调用 C 函数 :C 语言提供的函数没有 C++ 的名称修饰,C++ 可以通过
extern "C"
声明并直接调用 C 函数。
动态库加载与链接:
- 静态链接:编译时将函数和库文件直接集成到最终的可执行文件中,生成的程序更大且更新困难。
- 动态链接:程序在运行时加载动态库,能够节省内存并共享功能模块,同时支持库的独立更新。
总结
-
C 封装为动态库 :我们将一个简单的 C 函数封装成动态库(如
.so
或.dll
),然后通过extern "C"
在 C++ 中调用。 -
C++ 封装为动态库:我们将一个简单的 C++ 类和函数封装成动态库,并提供 C 风格的接口供 C 语言调用。
-
跨语言调用 :通过
extern "C"
关键字,C++ 可以调用 C 函数,C 也可以通过 C 风格接口调用 C++ 函数。动态库使得跨语言调用更加灵活和高效。