(一)定义
1. 静态库(.lib / .a)
函数和数据被编译进一个二进制文件,在编译可执行文件的时候,链接器从库(.lib/.a)中复制这些函数和数据并把他们和其他模块组合起来创建最终的可执行文件(.exe)。当发布程序时,只需可执行文件并不需要发布静态库
2. 动态库(.dll / .so)
函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供
一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质的区别,对一个DLL文件来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。
(二)区别和使用
1. 动态库的导入库
和 静态库
的区别
- 动态库的导入库包含地址符号表等信息(属性-》配置属性-》链接器-》高级-》导入库中可设置生成的导入库名称)
- 静态库包含实际执行代码、符号表等
2. 使用区别
1. 使用静态库
- 需要的文件:.h 头文件、.lib静态库文件
- 头文件:属性-》配置属性-》C/C+±》常规-》附加包含目录
- .lib静态库目录:属性-》配置属性-》链接器-》常规-》附加库目录
- .lib静态库文件名:属性-》配置属性-》链接器-》输入-》附加依赖项
- 代码中手动引入静态库:
pragma comment(lib,"xxx.lib")
2. 使用动态库
- 需要文件 .h 头文件、 .lib 、 .dll 或者只是 .dll
- 动态调用/显示调用
- 创建一个函数指针,其指针数据类型要与调用的 DLL 引出函数一致
- 通过 Win32 API 函数
LoadLibrary()
显式的调用DLL,此函数返回 DLL 的实例句柄 - 通过 Win32 API 函数
GetProcAddress()
获取要调用的DLL 的函数地 址,把结果赋给自定义函数的指针类型。 - 使用函数指针来调用 DLL函数。
- 最后调用完成后,通过 Win32 API 函数
FreeLibrary()
释放DLL 函数。
注意:显式调用的前提是使用者需要知道想调用的函数的名字、参数、返回值信息,也就是说虽然编译链接用不上.h头文件,但是调用者编程时可能还是要看.h文件作参考来知道函数名字、参数、返回值信息
- 静态调用/隐式调用
- 同使用静态库一样引入 .h 、.lib
- .dll 文件与可执行文件放到同一目录
(三)关键字 extern "C"
- 关于C++和C语言编译器生成目标文件的区别(生成的obj目标文件命名规则不同)
- 由于C++支持多态性,在编译的时候,CPP编译器会将参数类型和函数名连接在一起,于是在程序编译成为目标文件以后,CPP编译器可以直接根据目标文件中的符号名将多个目标文件连接成一个目标文件或者可执行文件
- 在C语言中,由于完全没有多态性的概念,C编译器在编译时除了会在函数名前面添加一个下划线之外,什么也不会做。
例如下面函数在C++ 和 C语言中编译目标文件时命名不同
int add(int a,int b);
C++: _add_int_int
C: _add
所以在使用 C 和 C++ 进行混合编程时,会造成编译器在程序链接阶段无法找到函数具体的实现,导致链接失败。
extern “C” 既可以修饰一句 C++ 代码,也可以修饰一段 C++ 代码,它的功能是让编译器以处理 C 语言代码的方式来处理修饰的 C++ 代码
常用场景代码示例:
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
(四)关键字 __declspec
__declspec
是Microsoft VC中专用的关键字,它配合着一些属性可以对标准C/C++进行扩充。__declspec
关键字应该出现在声明的前面。__declspec(dllexport)
用于Windows中的动态库中,声明导出函数、类、对象等供外面调用,省略给出.def文件。即将函数、类等声明为导出函数,供其它程序调用,作为动态库的对外接口函数、类等。.def
文件(模块定义文件)是包含一个或多个描述各种DLL属性的Module语句的文本文件。.def文件或__declspec(dllexport)
都是将公共符号导入到应用程序或从DLL导出函数。如果不提供__declspec(dllexport)
导出DLL函数,则DLL需要提供.def文件。__declspec(dllimport)
用于Windows中,从别的动态库中声明导入函数、类、对象等供本动态库或exe文件使用。当你需要使用DLL中的函数时,往往不需要显示地导入函数,编译器可自动完成。不使用__declspec(dllimport)
也能正确编译代码,但使用__declspec(dllimport)
使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于DLL中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨DLL边界的函数调用中。声明一个导入函数,是说这个函数是从别的DLL导入。一般用于使用某个DLL的exe中。
示例代码:
#ifndef FBC_LIBRARY_LIBRARY_HPP_
#define FBC_LIBRARY_LIBRARY_HPP_
// reference: http://geoffair.net/ms/declspec.htm
#ifdef _MSC_VER
#ifdef FBC_STATIC
#define FBC_API
#elif defined FBC_EXPORT
#define FBC_API __declspec(dllexport)
#else
#define FBC_API __declspec(dllimport)
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
FBC_API int library_add(int a, int b);
FBC_API int value;
#ifdef __cplusplus
}
#endif
template<typename T>
class FBC_API Simple {
public:
Simple() = default;
void Init(T a, T b);
T Add() const;
private:
T a, b;
};
#endif // FBC_LIBRARY_LIBRARY_HPP_
#include "library.hpp"
#include <iostream>
#include <string>
FBC_API int library_add(int a, int b)
{
value = 11;
fprintf(stdout, "File: %s, Function: %s, Line: %d\n", __FILE__, __FUNCTION__, __LINE__);
return (a+b);
}
template<typename T>
void Simple<T>::Init(T a, T b)
{
this->a = a;
this->b = b;
}
template<typename T>
T Simple<T>::Add() const
{
fprintf(stdout, "File: %s, Function: %s, Line: %d\n", __FILE__, __FUNCTION__, __LINE__);
return (a + b);
}
template class Simple<int>;
template class Simple<std::string>;
#ifndef FBC_CPPBASE_TEST_TEST_LIBRARY_HPP_
#define FBC_CPPBASE_TEST_TEST_LIBRARY_HPP_
#include <library.hpp>
namespace test_library_ {
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllimport) int library_add(int, int);
__declspec(dllimport) int value;
#ifdef __cplusplus
}
#endif
int test_library_1();
int test_library_2();
} // namespace test_library_
#endif // FBC_CPPBASE_TEST_TEST_LIBRARY_HPP_
#include "test_library.hpp"
#include <iostream>
#include <string>
#include <library.hpp>
namespace test_library_ {
int test_library_1()
{
int a{ 4 }, b{ 5 }, c{ 0 };
c = library_add(a, b);
fprintf(stdout, "%d + %d = %d\n", a, b, c);
fprintf(stdout, "value: %d\n", value);
return 0;
}
int test_library_2()
{
Simple<int> simple1;
int a{ 4 }, b{ 5 }, c{ 0 };
simple1.Init(a, b);
c = simple1.Add();
fprintf(stdout, "%d + %d = %d\n", a, b, c);
Simple<std::string> simple2;
std::string str1{ "csdn blog: " }, str2{ "http://blog.csdn.net/fengbingchun" }, str3;
simple2.Init(str1, str2);
str3 = simple2.Add();
fprintf(stdout, "contents: %s\n", str3.c_str());
return 0;
}
} // namespace test_library_
注意理解library.hpp中FBC_API的条件编译可以表示静态链接库接口、动态链接库导出接口、动态链接库导入接口,通过VS中预编译宏(配置属性->C/C+±>预处理器->预处理器定义)的定义来实现不同的功能