第19章 动态链接库

转自: https://blog.csdn.net/u014162133/article/details/46573873

1、Windows API中的所有函数都包含在DLL中。其中有三个最重要的DLL,

Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;

User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;

GDI32.dll,它包含用于画图和显示文本的各个函数。

2、静态库和动态库

静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据,并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件);

动态库:在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行时候,再去加载DLL,访问DLL中导出的函数。

3、使用动态链接库的好处

可以采用多种编程语言来编写。

增强产品的功能。

提供二次开发的平台。

简化项目管理

可以节省磁盘空间和内存。

有助于资源的共享。

有助于实现应用程序的本地化

4、建立DLL文件代码如下:

_declspec(dllexport) int Add(int x,int y)

{

 return x+y;

}

_declspec(dllexport) int Subtract(int x,int y)

{

 return x-y;

}

//必须带_declspec(dllexport)文件,以生成*.lib文件

如果要查找*dll中包含信息,可在命令行下进入Debug所在目录,输入以下命令

dumpbin -exports dll.dll 

有些时候由于某种安装原因,dumpbin被认为是无效命令,接下来在

C:\Program Files\Microsoft Visual Studio\VC98\Bin\下找到VCVARS32.bat并在命令行运行,之后就能执行dumpbin命令了。

新建MFC程序,新建两个按钮,代码如下:

void CDllTestDlg::OnBtnAdd()

{

 // TODO: Add your control notification handler code here

 CString str;

 str.Format("3+5=&d",Add(3,5));

 MessageBox(str);

}

void CDllTestDlg::OnBtnSubtract()

{

 // TODO: Add your control notification handler code here

 CString str;

 str.Format("5-3=%d",Subtract(5,3));

 MessageBox(str);

}

为使编译器认识Add,Subtract,必须在之前使用两个声明:

extern int Add(int x,int y);

extern int Subtract(int x,int y);

可以使用标示符表示这两个函数是从动态链接库的.lib文件引用的,以生成效率更高的代码

_declspec(dllimport) int Add(int x,int y);

_declspec(dllimport) int Subtract(int x,int y);

这两段代码我们也可以在DLL中新建一个头文件放进去,并在MFC程序中添加头文件

如#include "..\Dll\Dll.h"

从原先Dll文件下Debug目录中复制*.lib到MFC程序文件夹下,并添加库函数

在project--->setting--->link--->Object/Library Modules写下所复制的文件名

如果要查看DllTest.exe文件信息,使用命令行dumpbin -imports dlltest.exe

修改动态链接库Dll.h

#ifdef DLL_API

#else

#define DLL_API _declspec(dllimport)

#endif

DLL_API int Add(int x,int y);

DLL_API int Subtract(int x,int y);

修改Dll.cpp文件

#define DLL_API _declspec(dllexport)

#include "Dll.h"

int Add(int x,int y)

{

 return x+y;

}

int Subtract(int x,int y)

{

 return x-y;

}

这样做是为了方便外部程序调用同时方便内部程序使用,因为动态链接库中只有导出的函数才可以被使用,没有导出的函数在外部是看不到的,是不能被访问的

接下来导出整个类,代码:

class DLL_API point

{

public:

 void output(int x,int y);  //如果只想导出一个函数,可把上边的DLL_API剪切        //然后放到void output(int x,int y);前边,虽然累没有被        //导出,但访问仍没有区别

};仍然受制于访问权限

实现:

void Point::output(int x,int y)

{

 HWND hwnd=GetForegroundWindow();

 HDC hdc=GetDC(hwnd);

 char buf[20];

 memset(buf,0,20);

 sprintf(buf,"x=%d,y=%d",x,y);

 TextOut(hdc,x,y,buf,strlen(buf));

 ReleaseDC(hwnd,hdc);

}

接下来在MFC程序新建一个按钮,调用动态链接库函数,代码如下:

 Point pt;

 pt.output(100,200);

因为C++导出或导入动态链接库会发生名字的改编,如果不想发生名字改编,我们可以使用如下代码:

#define DLL_API  extern "c" _declspec(dllexport)

这样编译器就不会进行名字改编,一个用C语言编写的客户端程序就可以调用这个用C++编写的动态链接库。其缺点是,不能导入类中的函数

如果函数使用标准调用约定_stdcall,即使使用了extern "c",此函数仍会发生改编

接下来新建一个动态链接库文件,文件名为Dll2,cpp文件代码为:

int Add(int x,int y)

{

 return x+y;

}

int Subtract(int x,int y)

{

 return x-y;

}

为了最终解决问题,我们可以新建一个模块文件Dll.def,以使得其他语言编制的程序也能使用我们的动态链接库。

添加代码

LIBRARY Dll2

EXPORTS   //即使调用_stdcall约定,也不会发生改编,而只会调用这里显示的Add     //字符串

Subtract

EXPORTS 语句引入了一个由一个或多个 definitions(导出的函数或数据)组成的节。每个定义必须在单独一行上。EXPORTS 关键字可以在第一个定义所在的同一行上或在前一行上。.def 文件可以包含一个或多个 EXPORTS 语句。

导出 definitions 的语法为:

entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]

entryname 是要导出的函数名或变量名。这是必选项。如果导出的名称与 DLL 中的名称不同,则通过 internalname 指定 DLL 中导出的名称。例如,如果 DLL 导出函数 func1(),要将它用作 func2(),则应指定:

EXPORTS

func2=func1

@ordinal 允许指定是序号而不是函数名将进入 DLL 的导出表。这有助于最小化 DLL 的大小。.LIB 文件将包含序号与函数之间的映射,这使您得以像通常在使用 DLL 的项目中那样使用函数名。

可选的 NONAME 关键字允许只按序号导出,并减小结果 DLL 中导出表的大小。但是,如果要在 DLL 上使用 GetProcAddress,则必须知道序号,因为名称将无效。

可选的 PRIVATE 关键字禁止将 entryname 放到由 LINK 生成的导入库中。它对同样是由 LINK 生成的图像中的导出无效。

可选的 DATA 关键字指定导出的是数据,而不是代码。例如,可以导出数据变量,如下所示:

EXPORTS

i DATA

接下来在MFC文件中改写按钮void CDllTestDlg::OnBtnAdd() 代码:

 HINSTANCE hInst;

 hInst=LoadLibrary("Dll2.dll");  //使用动态加载

 typedef int (*ADDPROC)(int a,int b);

//如果在DLL的函数中调用_stdcall,相应的应该把代码改为

//typedef int (_stdcall *ADDPROC)(int a,int b);  //注意到处函数的调用约定

 ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"Add");//构造一个函数指针

 if(!Add)

 {

  MessageBox("获取函数地址失败!");

  return ;

 }

 CString str;

 str.Format("3+5=%d",Add(3,5));

 MessageBox(str);

因为调用LoadLibrary时动态加载动态链接库,所以不需要头文件和.lib文件

如果我们在动态链接库中使用标准调用约定_stdcall,而在可执行程序中使用动态加载DLL,会发生名字重编,如果知道DLL中函数的序号,这时可以使用宏MAKEINTRESOURCE把序号转变成名字,如:

ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));

DllMain

The DllMain function is an optional entry point into a dynamic-link library (DLL). If the function is used, it is called by the system when processes and threads are initialized and terminated, or upon calls to the LoadLibrary and FreeLibrary functions.

DllMain is a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools.

BOOL WINAPI DllMain(

  HINSTANCE hinstDLL,

  DWORD fdwReason,

  LPVOID lpvReserved

);

当我们的动态链接库不再使用时可以调用FreeLibrary使动态链接库使用计数减1,当使用计数为零时,系统会收回模块资源

FreeLibrary

The FreeLibrary function decrements the reference count of the loaded dynamic-link library (DLL). When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid.

BOOL FreeLibrary(

  HMODULE hModule

);

猜你喜欢

转载自www.cnblogs.com/happykoukou/p/9481291.html