19动态链接库

转自  http://blog.sina.com.cn/s/articlelist_1815328704_0_1.html

第19课 DLL编程
1.DLL简介,动态库,静态库。动态库节约磁盘空间,静态库体积大。可以用多种语言编写DLL文件。动态库有两种加载方式:隐式调用和动态加裁!
2. 新建一个DLL1的dll工程,加入一源文件名为dll1.cpp,加入add和subtract两个函数,注意此时须在函数名前加 _declspec(dllexport),并且编译。用dumpbi -exports dll1.dll查看其导出的函数,发现函数名字已经被改成了 ?add@@YAHHH@Z,这种现象叫做名字粉碎,是为了支持函数重载而做的。
3.编写一个程序测试DLL,工程名为DllTest,基于对话框的,放置两个按纽add和subtract,响应按纽消息,调用这个Dll的add和subtract函数。使用这两个函数前要先声明函数,//extern int add(int a,int b);
//extern int subtract(int a,int b);
还 需要将Dll1.lib和Dll1.dll拷贝到当前目录下!另外还需要在 Project->Setting->Link->Object/Library中加入Dll1.lib,此种方式为隐式调用!OK! 用Dumpbin -imports DllTest.exe查看它的输入信息,可以看到它加载了dll1.dll。同时也可以用depends程序查看程序需要哪些dll文件!除了用 extern外,还可以用//_declspec(dllimport) int add(int a,int b);
//_declspec(dllimport) int subtract(int a,int b);
告诉编译器,此函数是动态链接库中的函数,这样可以提高效率。
4.通常写Dll时在dll1.h中声明函数,然后在DllTest.h中包含这个头文件,另外会用一组宏来取代_declspec(dllimport)
Dll1.h
#ifdef DLL1_API
#else
#define DLL1_API extern "C" _declspec(dllimport)
#endif
DLL1_API int _stdcall add(int a,int b);
DLL1_API int _stdcall subtract(int a,int b);

Dll1.cpp的代码:
#define DLL1_API extern "C" _declspec(dllexport)
#i nclude "Dll1.h"
#i nclude <Windows.h>
#i nclude <stdio.h>
int _stdcall add(int a,int b)
{
return a+b;
}
int _stdcall subtract(int a,int b)
{
return a-b;
}
5. 在Dll1中加入类Point它有一个函数output(int a,int b),它的功能是在屏幕上输出x,y值。须包含头文件windows.h和stdio.h.然后在DllTest中加入一个按纽来测试这个函数!此时我们 可以dumpbin来查看dll1.dll和dllTest.exe的导出导入情况。注意,也可以只导出类的某个函数。
6.我们希望导出的函数名 不被改变,加extern "C"大写的C!即可,#define DLL1_API extern "C" _declspec(dllexport),但它只能导出全局函数,不能导出类的成员函数,并且如果调用约定被改成了别的方式,此时函数名也被改变。所以 这种方式不太好。
7.解决之道是用模块定义文件。
  1.新建dll2.dll工程;
  2.加dll2.cpp中写两个函数add和subtract
  3.在目录中新建dll2.def文件,增加到工程。
  4.在dll2.def中加入如下代码:
LIBRARY Dll2
EXPORTS
add
subtract
   5.编译后用dumpbin查看函数名是否被改变?
   6.测试,我们这次用动态加载的方法来调用dll文件。以前是用隐式链接的方法,嘿嘿。动态加载的好处是需要时再加载,可以提高执行的效率。代码如下:
HINSTANCE hInst;
hInst=LoadLibrary("Dll3.dll");
typedef int ( *ADDPROC)(int a,int b);
//ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
if(!Add)
{
  MessageBox("获取函数地址失败!");
  return;
}
CString str;
str.Format("5+3=%d",Add(5,3));
MessageBox(str);
FreeLibrary(hInst);

7.此时你改变调用约定,函数名不会被改变,但如果你加上_stdcall定义函数,调用时也需要加入_stdcall,否则会出错!
8.DllMain()是Dll的入口点,不过不是必须的。但在DllMain中不要做复杂的调用。为什么?因为DllMain加载时,某些核心Dll文件不一定已经被加载。
9.创建一个基于MFC的DLL工程,简介。
10.当不使用DLL时,调用FreeLibrary减少DLL的使用计数,释放DLL资源,减少系统负担。明白?
11.上面总结:1.*.def使函数名不改变;
             2.定义时为_stdcall,调用时也必须用_stdcall.
 
 
 
动态链接库
Windows API中的所有函数都包含在DLL中。其中有三个最重要的DLL,
Kernel32.dll,它包含用于管理内存、进程和线程的各个函 数;
User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;
GDI32.dll,它包含用于画图和显示文本的各个函 数。
静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件);
在 使用动态库的时候,往往提供两个文件:一个引入库(也是lib的)和一个DLL。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可 执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行时候,再去加载DLL,访问DLL中导出的函数。
使用动态链接库的好处
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化
 

建立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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值