关于动态链接库、静态链接库

一、概述

1、动态库和静态库的异同点

动态链接库(Dynamic Linkable Library,DLL)它提供一些可以直接使用的变量,类和函数。经历了 “无库 — 静态链接库 — 动态链接库” 的历程后,dll 应用十分广泛。

静态链接库和动态链接库都是共享代码。

如果采用静态链链接库(.lib),lib 中的指令最终都会编译到链接该静态库的 exe(或 dll)文件中,发布软件时,只需要发布 exe(或 dll)文件,不需要.lib 文件。但是若使用动态链接库(. dll),dll 中的指令不会编译到 exe 文件中,而是在 exe 文件执行期间,动态的加载和卸载独立的 dll 文件,需要和 exe 文件一起发布。

静态链接库和动态链接库另一个区别是静态链接库不能再包含其他动态链接库或静态链接库,而动态链接库不受此限制,动态链接库中可以再包含其他的动态链接库和静态链接库。

2、相关常识

(1)只要遵循约定的 dll 接口规范和调用方式,用各种语言编写的 dll 可以相互调用。
(2)dll 的分类有三种,Non-MFC DLL(非 MFC DLL),MFC Regular DLL(MFC 规则 DLL)和 MFC Extension DLL(MFC 拓展 DLL)。非 MFC DLL 不采用 MFC 类库规则,其导出函数为标准 C 接口,能被非 MFC 或 MFC 编写的应用程序调用;MFC 规则 DLL 包含一个 CWinApp 的类,但其无消息循环;MFC 拓展 DLL 采用 MFC 的动态链接库版本创建,它只能被用 MFC 类库编写的应用程序调用。

二、静态库

  静态链接库的后缀是.lib。下面的一个例程将介绍如何生成.lib 文件和如何调用.Lib

1、生成.lib 文件

1)、建立一个空解决方案,方案名称为 StaticLibrary。

2)、添加一个 win32 项目,类型为 Static library,工程名称为 StaticLib,空项目。
选择 win32 project 项目类型

选择 Static library, 取消 Precompiled header

3)、在该项目中添加 lib.h 和 Lib.cpp 文件,两个文件代码如下图所示

lib.h 文件代码如下:

#ifndef __LIB_H__
#define __LIB_H__

int add(int a,int b);

#endif

lib.cpp 文件中提供一个函数,实现两个整数的相加,返回两数的和。代码如下:

#include "lib.h"

int add(int a,int b)
{
    return a+b;
}

4)、库工程不能单独运行,需要右键点击生成

生成成功后,将在解决方案目录的 debug 文件夹中生成一个 StaticLib.lib 文件,该文件就是静态库文件。

2、如何调用.lib 文件

1)、在解决方案里再添加一个项目,项目名称为 StaticLibCall,类型为 Win32,控制台程序,选择空项目。

选择 Console application 和 Empty project

2)、在项目源文件文件中添加 main.cpp 文件

#include <stdio.h>
#include "lib.h"
#pragma comment (lib,"StaticLib.lib")//指定与静态库一起连接

int main()
{
    printf("2+3=%d",add(2,3));
}

3)、将刚刚生成的 StaticLib.lib 文件和 lib.h 两个文件复制到该项目的目录下。(一般使用静态库时必须提供这两个文件,.h 文件提供函数的预定义,而.lib 提供函数的实现)

4)、生成的 exe 文件是可以独立运行的运行程序,lib 文件中的函数实现被链接到 exe 文件中,lib 不再需要了。运行结果如下

三、 动态链接库

只介绍一种 DLL(非 MFC DLL)的创建与调用方法,本 Dll 实现的功能与第 2 节介绍的静态库实现的功能一样。

1、如何生成一个 dll 文件

1)、创建 dll 工程的步骤和上面介绍的建立 lib 的步骤一样,仅仅在选择类型时需要选择 dll。建立工程后,添加 DLib.h 和 DLib.cpp 文件

2)、DLib.h 和 DLib.cpp 代码如下所示:
DLib.h 文件

1 #ifndef __DLIB_H__
2 #define __DLIB_H__
3 
4 extern "C" int __declspec(dllexport) add(int a,int b); 
5 
6 
7 #endif

DLib.cpp 文件

1 #include "Dlib.h"
2 
3 int add(int a,int b)
4 {
5     return a+b;
6 }

分析该代码,该工程的.cpp 文件中代码和第 2 节的.cpp 中代码完全一样。而.h 文件不一样,该工程的.h 文件中对 add 函数添加 extern “C” 是告诉编译器该函数采用 C 调用方式进行编译,而__declspec (impoet) 是声明函数 add 为 dll 的导出函数,dll 的函数分两种:
(a) DLL 导出函数,可供调用 dll 的应用程序调用
(b) DLL 内部函数,只能在 DLL 程序使用,调用 DLL 的应用程序无法调用
3)、右键–生成;生成成功后,将在 debug 文件夹中生成一个 DynamicLib.dll 文件,同时,在此路径下也生成 DynamicLib.lib 文件,该 lib 文件不同于第一节中的静态库文件,此 lib 文件只是 dll 文件中导出函数的声明和定位信息,并不包含函数的实现(而第一节中的静态库文件,包含了函数的实现),因此此 lib 文件只是在调用对应 dll 库的工程编译时使用,不需要随 exe 发布。

2、如何调用.dll 文件

dll 文件的调用方式有两种,一种是动态调用,一种是静态调用。
动态调用是由编程者调用系统 API 函数加载和卸载 dll,程序员可以决定 dll 文件何时加载,何时卸载,加载哪个 dll 文件,将 dll 文件的使用权完全交给程序员。
静态调用是由编译系统完成对 dll 文件的加载和应用程序结束时完成对 dll 的卸载,当调用某 dll 的应用程序结束时,则 windows 系统对该 dll 的应用记录减 1,直到使用该 dll 的所有应用程序都结束,即对该 dll 应用记录为 0,操作系统会卸载该 dll,静态调用方法简单,但不如动态调用适用。
(1)、动态调用 dll
1)、新建控制台项目,添加 main.cpp 文件,将刚刚生成的 dynamicLib.dll 文件拷贝到项目目录下,main.cpp 代码如下

 1 #include "stdio.h"
 2 #include "windows.h"
 3 
 4 typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型
 5 
 6 int main()
 7 {
 8     HINSTANCE hDll;//DLL 句柄
 9     lpAddFun addFun;//函数指针
10     hDll = LoadLibrary(L"DynamicLib.dll");//动态获取dll文件的路径
11     if (hDll!=NULL)
12     {
13         addFun =(lpAddFun)GetProcAddress(hDll,"add");//根据函数名在dll文件中获取该函数的地址    
14         if (addFun!=NULL)
15         {
16             int result =addFun(2,3);
17             printf("2+3=%d",result);
18         }
19 
20     FreeLibrary(hDll);
21     }
22     return 0;
23 }
24

main.cpp 代码分析:
语句 typedef int (*lpAddFun)(int ,int ) 定义了一个与 add 函数接收参数类型和返回值均相同的函数指针类型,随后在 main 函数中定义了 lpAddFun 的实例 addFun;
在函数 main 中定义了一个 DLL HISTANCE 句柄实例 hDll,通过 Win32API 函数 LoadLibrary 动态加载 DLL 模块并将 DLL 模块句柄赋给 hDll;
在 main 函数中通过 Win32API 函数 GetProcAddress 得到所加载的 DLL 模块中函数 add 的地址并赋值给 addFun,经由函数指针 addFun 进行了对该 DLL 中 add 函数的调用;
在完成对 dll 的调用后,在 main 函数中通过 Win32API 函数 FreeLibrary 释放已加载的 DLL 模块。

通过以上的分析可知:
(a) 动态调用只需要 dll 文件即可,不需要对应的.h 头文件和.lib 文件,一般情况下,只要有 dll,就可以调用此 dll 中的导出函数。
(b) 在调用 dll 中的函数时,需要知道导出函数的函数签名,若拥有 dll 对应的头文件,可以参照头文件即可,若没有头文件,使用特定工具也可以得到 dll 中导出函数的函数签名
(c) DLL 需要已某种特定的方式声明导出函数(或变量,类)
(d) 应用程序需要以特定的方式调用 DLL 的淡出函数(或变量,类)

(2)、静态调用 dll

1)、新建一个工程,命名为 DllStaticCall,添加 main.cpp 文件,并将刚刚生成的 DynamicLib.dll 和 DynamicLib.lib 两个文件拷贝到工程目录下。

main.cpp 代码如下

 1 #include "stdio.h"
 2 
 3 //.lib文件中仅仅是关于其对应DLL文件中函数的定位信息
 4 #pragma comment(lib,"DynamicLib.lib")
 5 
 6 extern "C" int __declspec(dllimport) add(int a,int b);
 7 
 8 int main()
 9 {
10     int result =add(2,3);
11     printf("2+3=%d",result);
12     scanf("%d");
13     return 0;
14 }

从上述代码可以看出,静态调用需要完成两个动作:
a)#pragma comment (lib,“DynamicLib.lib”) 是告诉编译器与该 dll 相对应的.lib 文件所在的路径和文件名。
在生成 dll 文件时,链接器会自动为其生成一个对应的.lib 文件,该文件包含了 dll 导出函数的符号名和序号(并没有实际的代码)。在应用程序中,.lib 文件将作为 dll 的替代文件参与编译,编译完成后,.lib 文件就不需要了。
b)extern “C” int __declspec (dllimport) add (int a,int b) 是声明导入函数,这需要与 dll 工程中.h 文件中的函数声明一致。
c)可以将 dll 工程中的头文件包含在工程中,这样上述代码中就不需要写 extern “C” int __declspec (dllimport) add (int a,int b) 声明了,但若没有提供对应的头文件,只有采用本文这种方式声明函数。

静态调用不需要使用 Win32API 函数来加载和卸载 Dll 以及获取 Dll 中导出函数的地址,这是因为当通过静态链接方式编译生成程序时,编译器会将.lib 文件中导出函数的函数符号链接到生成的 exe 文件中,.lib 文件中包含的与之对应的 dll 文件的文件名也被编译存储在 exe 文件内部,当应用程序运行过程中需要加载 dll 文件时,windows 将根据这些信息查找并加载 dll,然后通过符号名实现对 dll 函数的动态链接,这样,exe 将能直接通过函数名调用 dll 的输出函数,就像调用程序内部的其他函数一样。

四、 动态链接库的 def 文件

dll 导出函数是前面添加__declspec (impoet) 语句,声明该函数为 dll 的导出函数,还有另一种方式声明函数为导出函数–通过 def 文件

1、如何使用 def 文件

(1)新建解决方案,添加两个项目,DllLib 是生成 dll 文件的项目,Dllcall 是调用该 dll 的项目。

(2)在 dllLib 中添加 lib.cpp 和 dlllib.def 两个文件(不需要.h 头文件),代码如下
Lib.cpp 代码:声明两个函数,加法和减法

1 int __stdcall Add(int numa, int numb)
2 {
3     return (numa + numb);
4 }
5 
6 int __stdcall Sub(int numa, int numb)
7 {
8     return (numa - numb);
9 }

dllLib.def 代码如下:

1 LIBRARY DllLib
2 EXPORTS
3 Add @ 1
4 Sub @ 2

.def 文件的规则为:
(1)LIBRARY 语句说明该.def 文件相对于的 dll(不需要后缀 dll)
(2)EXPORTS 语句后面列出要到处的函数名称,可以在.def 文件中的导出函数名后加 @n,表示要导出函数的序号为 n,在进行函数调用时,可以根据这个编号调用该函数(参见下面的调用过程)
(3).def 文件中的注释由每行开始处的分号(;)指定,且注释不能与语句共需一行。
由此可以看出,例子中的.def 文件的含义是生成名为 DllLib 的动态链接库,导出其中的 add 和 sub 函数,并且指定 add 函数序号为 1,sub 序号为 2。

2、调用 dll

调用的方法与上面介绍的一样,本例使用动态调用。将刚刚生成的 dll 拷贝到项目目录下。
main.cpp 代码如下:

 1 #include <stdio.h>
 2 #include <windows.h>
 3 
 4 typedef int (__stdcall *FUN)(int, int);
 5 HINSTANCE hInstance;
 6 
 7 FUN   fun;
 8 
 9 int main()
10 {
11     hInstance = LoadLibrary(L"DllLib.dll");
12     if(hInstance!=NULL)
13     {
14         fun = (FUN)GetProcAddress(hInstance, "Add");
15         //当在Def文件中指定函数序号时,可以通过序号导出,否则只能通过函数名称导出
16         //fun = (FUN)GetProcAddress(hInstance, MAKEINTRESOURCE(1));
17         if (fun!=NULL)
18         {
19                 printf("1+2=%d",fun(1, 2));
20         }
21     }
22     FreeLibrary(hInstance);
23     return 0;
24 }

注:def 文件中定义了函数序号,在动态加载 dll 时,可以根据这个序号加载函数,这样做的好处时,当 dll 工程的导出函数的函数名有变化,而功能没有变化时,只要 def 中定义的函数序号没有变化,则调用 dll 的代码不需要任何改变。

五、 DllMain 函数

Windows 在加载 dll 时候,需要一个入口函数,就如同控制台需要 main 函数,win32 程序需要 WinMain 函数一样,在前面的例子中 dll 并没有提供 DllMain 函数,应用程序也能成功的调用 dll,这是因为 Windows 在找不到 DllMain 函数时,系统会从其他运行库中引用一个不做任何操作的缺省 DllMain 函数版本,并不是意味着 dll 可以放弃 DllMain 函数,
根据编写规范,Windows 必须查找并执行 dll 里面的 DllMain 函数作为加载 dll 的依据,它使得 dll 能够驻留在内存里,DllMain 函数不属于导出函数,而是 dll 的内部函数,这意味着不能直接在应用程序中引用 DllMain 函数,DllMain 函数是自动被调用的。DLLMain 函数的作用是可以做一下初始化操作,相当于类的构造函数,关于 DLLMain 函数和导出类,在这里不做过多的讲解,后续需要时,再深入研究。

1、为 dll 添加 DllMain 函数

(1)新建解决方案,添加两个工程。一个是生成 dll,一个是调用 dll。

Lib.h 代码

1 #ifndef __LIB_H__
2 #define __LIB_H__
3     extern "C" int __declspec(dllexport) add(int a,int b); 
4 #endif

Lib.cpp 代码

 1 #include "lib.h"
 2 #include "stdio.h"
 3 #include "windows.h"
 4 
 5 BOOL APIENTRY DllMain(HANDLE hModule,
 6     DWORD ul_reason_for_call,
 7     LPVOID lpReserved)
 8 {
 9     switch (ul_reason_for_call)
10     {
11     case DLL_PROCESS_ATTACH:
12         printf("\nprocess attach of dll");
13         break;
14     case DLL_THREAD_ATTACH:
15         printf("\nthread attach of dll");
16         break;
17     case DLL_THREAD_DETACH:
18         printf("\nprocess detach of dll");
19         break;
20     case DLL_PROCESS_DETACH:
21         printf("\nprocess detach of dll");
22         break;
23     }
24     return TRUE;
25 }
26 
27 int add(int a,int b)
28 {
29     return a+b;
30 }

Main.cpp 代码

 1 #include "stdio.h"
 2 #include "windows.h"
 3 
 4 typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型
 5 
 6 int main()
 7 {
 8     HINSTANCE hDll;//DLL 句柄
 9     lpAddFun addFun;//函数指针
10     hDll = LoadLibrary(L"DllLib.dll");//动态获取dll文件的路径
11     if (hDll!=NULL)
12     {
13         addFun =(lpAddFun)GetProcAddress(hDll,"add");
14         if (addFun!=NULL)
15         {
16             int result =addFun(2,3);
17             printf("\ncall add in dll %d",result);
18         }
19 
20         FreeLibrary(hDll);
21     }
22     return 0;
23 }

六、 导出类

point.h

 1 #ifndef __POINT_H__
 2 #define __POINT_H__
 3 
 4 #ifdef DLL_FILE
 5 class _declspec (dllexport) point //导出类point
 6 #else 
 7 class _declspec(dllimport) point //导入类point
 8 #endif
 9 {
10 public: 
11     float y;
12     float x;
13     point();
14     point(float x_coordinate,float y_coordinate);
15 };
16 
17 #endif

point.cpp

 1 #ifndef DLL_FILE
 2 #define DLL_FILE
 3 #endif
 4 #include"point.h"
 5 //类point的缺省构造函数
 6 point::point()
 7       {
 8           x=0.0;
 9           y=0.0;
10       }
11 point::point(float x_coordinate,float y_coordinate)
12       {
13           x=x_coordinate ;
14           y=y_coordinate;
15       }

circle.h

 1 #ifndef __CIRCLE_H__
 2 #define __CIRCLE_H__
 3 #include "point.h"
 4 #ifdef DLL_FILE
 5 class _declspec (dllexport) circle //导出类circle
 6 #else 
 7 class _declspec(dllimport) circle //导入类circle
 8 #endif
 9 {
10 public: 
11     void SetCenter(const point crePoint);
12     void SetRadius(float r);
13     float GetGirth();
14     float GetArea();
15     circle();
16 private:
17     float radius;
18     point center;
19 };
20 
21 #endif

circle.cpp

 1 #ifndef DLL_FILE
 2 #define DLL_FILE
 3 #endif
 4 #include"circle.h"
 5 #define  PI 3.1415926
 6 //类circle的缺省构造函数
 7 circle::circle()
 8 {
 9     center =point(0,0);
10     radius =0;
11 }
12 //得到圆的面积
13 float circle::GetArea()
14 {
15     return PI*radius*radius;
16 }
17 //得到圆从周长
18 float circle::GetGirth()
19 {
20     return 2*PI*radius;
21 }
22 //设置圆心坐标
23 void circle::SetCenter(const point crePoint)
24 {
25     center =crePoint;
26 }
27 //设置圆的半径
28 void circle::SetRadius(float r)
29 {
30     radius     =r;
31 }

将 circle.h,point.h 和生成的.lib 文件放在同级目录

main.cpp

 1 #include "circle.h"
 2 #include "stdio.h"
 3 
 4 //.lib文件中仅仅是关于其对应DLL文件中函数的定位信息
 5 #pragma comment(lib,"DllExportClass.lib")
 6 
 7 int main()
 8 {
 9     circle c;
10     point p(2.0,2.0);
11     c.SetCenter(p);
12     c.SetRadius(1.0);
13     printf("area:%f,girth:%f",c.GetArea(),c.GetGirth());
14     scanf("%d");
15     return 0;
16 }

结果为

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值