千家信息网

C++ 中的动态库和静态库(Windows)

发表于:2024-11-16 作者:千家信息网编辑
千家信息网最后更新 2024年11月16日,库:在C/C++中,使用库(Library)的技术,可以将编译好的符号提供给第三方使用。库有两种:1、动态库 Dynamic-Link Library (DLL) (Linux下叫做 Shared L
千家信息网最后更新 2024年11月16日C++ 中的动态库和静态库(Windows)

库:

在C/C++中,使用库(Library)的技术,可以将编译好的符号提供给第三方使用。

库有两种:

1、动态库 Dynamic-Link Library (DLL) (Linux下叫做 Shared Library)

2、静态库 Static Library


一、动态库的创建和使用


创建DLL:

用VC创建一个类型为 "dll"的控制台项目,VC会自动创建DLL的项目框架

它自动生成一个DllMain函数,可以类比普通应用程序中的main函数


VC项目设置:

1、取消"预编译头文件"

2、改为 "/MTd编译"

3、修改输出的DLL的名字 (my.dll)



编译,得到 *.lib 和 *.dll,其中:

*.dll:

包含所有代码编译成的指令


*.lib:包含一个列表,表名my.dll中含有哪些符号,每个符号对应在dll中的位置。(被导出的符号)


所以,*.lib比*.dll的文件体积小得多





如果想导出一个全局函数,就用关键字 __declspec(dllexport)来声明

注意:这是VC平台特有的关键字,在linux平台下不可用

使用如下:

template __declspec(dllexport) void MySwap(T& obj1, T& obj2){        T tmp = obj1;        obj1 = obj2;        obj2 = tml;}



使用dll:

#pragma  comment(lib, "12_18_DLL01")__declspec(dllimport) int Add(int a, int b);int main(){        int ret = Add(1, 2);        std::cout << ret << std::endl;        return 0;}

但这个地方我发现了一个问题:

__declspec(dllimport) int Add(int a, int b);

这行代码表示导入dll文件中的符号

但一开始我错误得写成了 export, 结果居然仍然是正确的

没有深究,初步推测是编译器优化了 (IDE: VS2015),甚至不写前面的关键字,直接写成函数的声明,程序也是能正确执行的


DLL的部署位置:

1、可执行文件所在目录

2、进程当前目录

3、系统目录 (C:\Windows\System32\ 和 C:\Windows\System\)

4、Windows目录 (C:\Windows\)

5、环境变量 PATH 中的目录



通过初步接触DLL的发布和使用,相比普通的声明、定义,会发现DLL有以下作用:

1、隐藏了代码

2、公开了功能


当然,DLL的作用远不止如此





二、DLL的加载和卸载


DLL的加载:

DLL不能独立运行,只有当 *.exe 被运行时, *.dll 才会被加载运行。

在exe文件中有一些标识信息,表示该 exe 依赖哪些 dll 文件,操作系统据此去寻找、加载相应的dll文件。

exe 被加载到内存,形成一个进程 process,dll也被加载到内存,进程可以直接调用DLL中的函数(Code)


数据段和代码段:

在dll文件中,至少分为两个段:

1、代码段:

存储指令(函数体)

2、数据段:

数据段,存放全局变量

当 *.dll 被加载时,代码段只被加载一次,是公共的。

数据段被每个程序各自拷贝一份,是私有的



三、DLL中的动态内存管理

在dll中申请的动态内存,必须在dll中释放,否则会导致内存泄漏 (这是由Windows自己的特点决定的)



四、DLL中使用头文件

按照惯例,由模块的作者提供头文件,头文件中应该用类型、函数声明。

所以,我们在创建DLL时,应该附带一份头文件,而不是让使用者自己去写函数声明。


五、DLL中导出一个类

导出类的定义,其实就是导出其成员函数

类的声明格式:

class __declspec(dllimport) MyClass{};


六、静态库的概念及使用


静态库:

static library,仅一个 *.lib 文件

静态库中直接就含有代码段和数据段,在链接过程中,是直接把里面的东西链接过来,形成完整的可执行程序

exe运行的时候不依赖 .lib 文件


创建:

1、建立一个新工程,工程类型为:静态库

2、不需要其他设置,直接编译链接生成,得到 *.lib文件


使用:

#pragma comment(lib, "MyLib.lib")int MyAdd(int a, int b);    //通常发布会把把声明放到头文件中,一并发布


静态库的注意事项:

1、静态库的使用不太方便:

如果该静态库是VS2008编译的,那么APP也得用VS2008编译,版本必须一致

此外,运行时库(/MT、/MTd、/MD、/MDd)也必须要一致

因此可以看出,静态库的使用,约束条件是比较多的


静态库和动态库的对比:

静态库优点:

使用静态库,最后得到的可执行程序执行时对这个库不再依赖


动态库优点:

便于升级更新,只要保持接口不变,可以通过更新DLL来升级程序,而不需要重新编译程序


目前通常都使用动态库


但动态库也存在更多的安全方面的隐患,毕竟如果有人恶意替换DLL(能做到这一步的人往往是软件团队的内部人员),程序的执行将会违背编程者的本意.......


七、DLL的手动加载

之前加载DLL的方式是自动加载。

自动加载和手动加载:

1、自动加载

在编译时指定dll,则当exe程序启动运行时,首先加载相关的dll

(DLL在程序执行前被加载,在程序结束后被卸载)


2、手动加载

在编译时不指定dll,在运行时调用LoadLibrary来加载dll

(这样做,可以自己决定什么时候加载、什么时候卸载DLL)



手动加载的方式:

#include #include 

使用 LoadLibrary 来加载dll,

使用 FreeLibrary 来卸载dll,

它提供了一种在运行时手动加载dll的技术手段,增加了编程的灵活性

(只要有*.dll就可以,也不需要 *.lib 和 *.h )


对DLL的要求:

1、要求待调用的函数按"C"方式编译(符号名即函数名)

extern "C" __declspec(dllexprot) int MyAdd(int a, int b);

2、dll文件放在可被系统搜索到的路径


代码:

#include #include #include int main(){        wchar_t* Path = L"12_18_DLL01.dll";        HINSTANCE  handle = LoadLibrary(Path);      // 字符集兼容问题        if (handle)        {                // 定义要找的函数原型                typedef int(*DLL_FUNCTION_ADD) (int, int);                // 找到目标函数的地址       注意实现必须用 extern "C" 的方式编译                DLL_FUNCTION_ADD dll_func = (DLL_FUNCTION_ADD)GetProcAddress( handle, "Add");                if (dll_func)                {                        // 调用该函数                        int result = dll_func(1, 2);                        std::cout << result << std::endl;                }        }        FreeLibrary(handle);        return 0;}


八、项目的静态编译

有一种场景,将以往VS生成的 *.exe 程序拷贝到其他 没有VS运行环境的机器上,这时候程序往往是无法运行的,这是因为,VC编译默认采用动态编译的方式,因此 要么给这个机器也安装一套运行环境,要么采用静态编译的方式生成可执行文件(*.exe)

静态编译后的程序,将会包含一切程序运行时所依赖的环境


对VC来说:

静态编译:

/MT /MTd


动态编译:

/MD /MDd




动态编译和静态编译的比较:

1、动态编译不方便发布,但生成的可执行文件体积较小

2、静态编译方便发布,但生成的可执行文件体积较大



0