VC++学习(19):动态链接库DLL

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

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

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

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

二、静态库和动态库

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

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

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

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

增强产品的功能。

提供二次开发的平台。

简化项目管理

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

有助于资源的共享。

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

四、调用动态链接库的方法:

在程序使用Dll有两个加载方式,一种是动态方式,就是LoadLibrary载入Dll,然后用GetProcAddress来加载需要使用的Dll函数。另一种就是静态连接方式,将dll生成的lib,加入到工程中,然后使用时就像使用Win API一样使用。

    方法一:windows提供了一套函数,用于加载动态链接库中的符号(函数和变量),调用这些函数去加载:

  1. HINSTANCE LoadLibrary( LPCTSTR lpLibFileName);

  2. FARPROC GetProcAddress( HMODULE hModule, LPCWSTR lpProcName);

  3. BOOL FreeLibrary( HMODULE hLibModule);

  这最直观的一种方法,同时也是最麻烦的一种办法。

 

   方法二:直接把动态链接库产生的.lib文件加入到调用者的工程中。需要很多DLL时,进程就会变得臃肿。

   进入Link设置:工程: 属性-》链接-》输入-》附加依赖项 直接输入库的名字

        在属性-》链接-》常规-》附加目录项 中输入动态链接库对应的.lib的路径

 

   五、使用dumpbin查看dll或exe的信息

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

  dumpbin -exports dll.dll 

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

  dumpbin -imports dlltest.exe

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

D:\Program Files\VisualSdudio\VC 下找到vcvarsall.bat并拖动到命令行运行,之后这个命令行窗口就能执行dumpbin命令了。打开新的窗口需要重复刚才的动作。

 

六、Dll名字改编

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

#define DLL_API extern "c" _declspec(dllexport)

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

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

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


下面是具体代码实现:



1、建立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文件

2、新建MFC测试程序,新建两个按钮,代码如下

void CDllTestDlg::OnBtnAdd()

{

   CString str;

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

   MessageBox(str);

}

void CDllTestDlg::OnBtnSubtract()

{

   CString str;

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

   MessageBox(str);

}

  为使编译器认识DLL中的函数,测试项目可以采用下列三种方法之一,这些情况需要提前添加lib和dll文件到项目中

(1)AddSubtract,必须在之前使用外部函数声明

  extern int Add(int x,int y);

  extern int Subtract(int x,int y);

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

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

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

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

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

修改动态链接库Dll.h,是DLL和调用该DLL的程序都可以使用该头文件;

#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);

 

Dll名字改编

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

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 语句。EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。

 

导出 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,MAKEINTRESOURCE1)

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

原文地址:https://www.cnblogs.com/forlina/p/2119790.html