写代码学习Windows程序编写---动态链接库编程

Windows API中所有的函数都包含在DLL中,其中最重要的三个DLL分别是HERNEL32.DLL  User32.dll  GDI32.dll 

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

加载的两种方式:隐式链接、显示加载

实例:

打开VC6.0 新建一个DLL工程

图片

工程名为Dll1 点击OK 选择一个空的DLL工程  点击完成

然后新建一个C++源文件

图片

文件名为Dll1

在文件中编写一下两个功能函数:

int add(int a,int b)

{

return a + b;

}

int subtract(int a,int b)

{

return a - b;

}

编译 链接 在工程的Debug目录下会生成一个Dll1.dll文件  

这时这个DLL文件中的功能函数是不能访问的  因为没有被导出  可以用一下方式查看一个DLL文件中都是有哪些函数被导出

打开命令提示符窗口  切换到Dll1工程的Debug目录下 输入如下命令

dumpbin -exports Dll1.dll

图片

在输入此命令之前,直接运行dumpbin命令  如果找不到该命令  则如下

图片

在上图的目录下找到 VCVAS32.BAT文件  见这个文件直接拖拽到命令提示符窗口中  这时在这个命令提示符窗口下便可以用dumpbin命令了  打开新的命令提示符的时候  需要重复上面的操作才行。

怎么样才能将DLL文件中的函数导出呢?如下:

_declspec(dllexport) int add(int a,int b)

{

return a + b;

}

_declspec(dllexport) int subtract(int a,int b)

{

return a - b;

}

这样便声明了两个函数均导出

此时在编译链接   然后再命令提示符窗口下运行 dumpbin -exports Dll1.dll

图片

如图可以看到 有两个函数被导出 但是这时的名字分别是   ?add@@YAHHH@Z?subtract@@YAHHH@Z  这是VC6.0将函数名进行改编的结果 因为名字的改编,会设计到一些其他问题,后面再说如何处理

现在 我们在新建一个工程 如下:

图片

工程名为:Dll1Test  选择基于对话框的的工程

图片

然后Finish  在对话框中添加两个按钮 如图:

图片

将两个按钮的ID号分别修改为:IDC_BTN_ADD 和 IDC_BTN_SUBTRACT

双击两个按钮 分别添加处理函数

添加如下代码:

图片

第一个红色的圈圈标出来的是导入这两个函数  第二个红圈圈提示有三个错误 这是为什么呢?因为我们没有将DLL文件和lib引入库文件添加进来

首先  要进行如下设置:

点开Project菜单 点击Settings 弹出如下对话框  切换到Link下 进行如下设置

图片

再编译运行 还是有一次错误  那么 我们将Dll1这个工程中生成的在Debug目录下的Dll1.lib文件拷贝到Dll1Test这个工程目录下

再进行编译链接  这时候是没有错误的了  那么现在是否可以运行程序了呢?不行!!!为什么?因为我们还要讲第一个工程里面生成的Dll1.dll文件拷贝到Dll1Test这个工程目录下,否则是找不到Dll1.dll这个文件的。

然后编译链接,在点击运行之前,可以用一个工具查看一下我们这个exe程序运行所依赖的所有动态链接库

在命令提示符中,将目录切换到Dll1Test工程下的Debug目录下

然后运行如下命令  dumpbin -imports Dll1Test.exe

图片

显示出来的是Dll1Test.exe运行所需要的所有的动态链接库文件信息

最后,我们点击运行,就可以得到如下的结果了

图片


关于动态链接库中头文件的使用

在编写一个给用户使用的动态链接库的时候,通常还要编写一个头文件  这个头文件包含了动态链接库中导出函数的声明原型  然后让客户程序包含该头文件即可。头文件内容如下:

_declspec(dllimport) int add(int a,int b);

_declspec(dllimport) int subtract(int a,int b);

如果动态链接库自身也想使用该头文件  需要对头文件进行改编一下,如下:

#ifdef DLL1_API

#else

#define DLL1_API _declspec(dllimport)

#endif

DLL1_API int add(int a,int b);

DLL1_API int subtract(int a,int b);

然后修改动态链接库源文件:

#define DLL1_API _declspec(dllimport)

#include "Dll1.h"

int add(int a,int b)

{

return a + b;

}

int subtract(int a,int b)

{

return a - b;

}

动态链接库中类成员函数的导出

想要导出动态链接库中的类,如下:

class DLL1_API Point

{

public:

void output(int x,int y);

};

其中,类中的成员函数和变量的访问属性不变。

下面通过一个例子说明类成员函数的使用。

Dll1工程源文件中添加如下代码:

#include <windows.h>  //因为用到了windows系统API

#include <stdio.h>  //用到了标准输出函数

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

{

HWND hwnd = GetForegroundWindow(); //要在使用该动态链接库的客户端程序中使用该函数,首先要获取客户端程序的句柄,可以用GetForegroundWindow()函数来获取,然后定义一个HWND类型的变量  保存客户端程序的句柄

HDC hdc = GetDC(hwnd);//通过句柄得到DC

char buf[20];   //存放显示信息的buffer

memset(buf,0,20);  

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

TextOut(hdc,0,0,buf,strlen(buf));//调用文本输出函数,将字符串信息输出到客户端程序中

ReleaseDC(hwnd,hdc);  //释放客户端程序的句柄

}

将编译连接之后生成的DLL文件和LIB文件拷贝到Dll1Test测试工程目录下

然后再测试工程的对话框中添加一个按钮output 并且将IDC该为IDC_BTN_OUTPUT

双击该按钮,生成相应函数,在函数中添加如下代码:

Point pt;

pt.output(5,3);

在编译链接之前,将我们前面在工程Dll1中编写的Dll1.h头文件拷贝到Dll1Test工程目录下面,并且在Dll1Test工程的源文件中包含上此头文件  然后编译链接运行,点击output按钮,得到如下图所示的结果:

图片

如果不想导出整个类,只导出类中的某些成员函数 只需要把class后面的DLL1_API去掉,然后添加到要导出的成员函数的声明的前面即可。

导出函数名字改编问题

C++编译器编写的动态链接库会修改动态链接库中函数名字,上面提到了函数名改编的问题。

这时候如果用其他编译器编写一个客户端,然后使用动态链接库,就会出错,找不到相应的函数,因为不同的编译器函数名改编是不一样的。

一种方法是防止编译器进行函数名改编。

Dll1工程中,将源文件做些改动:

#define DLL1_API extern "C" _declspec(dllexport)

然后先将关于类方面的代码注释掉  编译链接  然后同样用dumpbin工具对Dll1.dll文件进行查看:

图片

发现这个时候 函数的名字没有进行改编  在没有加extern  C之前是

?add@@YAHHH@Z       ?subtract@@YAHHH@Z

与此同时,在Dll1.h这个头文件中同样需要添加 extern C 因为客户端包含此头文件时,如果不加入extern C,客户端还是会根据C++编译器的名字改编来访问函数,但是在DLL源文件中我们加入了extern C,因此名字没有被改编,所以不一致。

然而,用extern C的缺点是:

一、只能用来导出全局函数,不能导出类的成员函数。

二、如果所导出的函数的调用约定发生改变,即使加上extern C,函数名仍然会被改编。

举例,在Dll1工程中,将两个函数的调用约定该为标准调用约定的

在头文件中如下修改:

DLL1_API int _stdcall add(int a,int b);

DLL1_API int _stdcall subtract(int a,int b);

在源文件中也进行修改:

int _stdcall add(int a,int b)

{

return a + b;

}

int _stdcall subtract(int a,int b)

{

return a - b;

}

这样,这两个导出函数的调用约定就改成了标准调用约定,标准调用约定是WindowsAPI的调用约定,和C语言的调用约定是不一样的。

这个时候,编译链接一下,然后我们再次到命令提示符窗口下,同样用dumpbin工具对此时生成的Dll1.dll进行查看

如图图片

这时,我们发现,导出函数的名字又被改编了 _add@8 和 _substract@8  其中@后面的8表示函数的参数类型占用8个字节

因为不同平台和语言编写的程序的调用约定是不一样的,比我我们用Delphi编写的客户端,调用C语言开发的动态链接库的时候,就会出现名字改编引起的调用错误,怎么样解决呢?这就要引入一个“模块定义文件”来解决。

关闭Dll1工程,新建一个Win32 DLL工程,工程名为Dll2 Dll1一样进行相应的操作,然后创建一个C++源文件,在源文件中写两个函数

int  add(int a,int b)

{

return a + b;

}

int  subtract(int a,int b)

{

return a - b;

}

然后到工程目录下,写一个模块定义文件,新建一个TXT文件,见文件名和文件后缀修改为Dll2.def,然后将这个.def文件加入到工程中选择Project->Add To Project->Files 选择所有类型文件,然后加载到工程中去,在工程中对该文件进行编写,编写如下:

LIBRART Dll2     //Dll2与本工程生成的DLL文件的名字一致

EXPORTS   //后面跟要导出的函数的名称

add

Subtract

编译链接,然后用dumpbin查看输出情况

结果显示,没有发生名字改编

即使在函数前面修改调用约定为_stdcall 函数的名字也不会发生改编的,因为在模块定义文件中已经指出了函数的名字。但是如果将DLL文件中的函数调用约定该为标准调用约定的话,那么在客户端程序中 也需要将函数的调用约定修改为标准调用约定,否则会出错。也就是说调用约定要一致。

如何动态加载动态链接库

首先将Dll1Test工程进行修改,将Project->Settings->LINK中的Dll1.lib去掉

然后将所有响应函数中的所有内容全部注释掉,然后添加如下代码进去:

void CDll1TestDlg::OnBtnAdd() 

{

// TODO: Add your control notification handler code here

/*

CString str;

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

MessageBox(str);*/

HINSTANCE hInst;  //定义一个实例的句柄变量

hInst = LoadLibrary("Dll2.dll");//LoadLibrary函数映射指定的可执行模块到一个调用进程的地址空间中,不仅可以加载DLL,也可以加载可执行程序,该函数有一个参数,参数为加载的可执行模块的名字(文件名可以为.dll文件和.exe文件)

typedef int (*ADDPROC)(int a,int b);  //定义一个函数指针类型 

ADDPROC Add = (ADDPROC)GetProcAddress(hInst,"add");//获取导出函数的地址  GetProcAddress用来获取动态链接库导出的函数的地址  成功的话返回函数地址,否则返回NULL

if(!Add)  //判断函数地址是否成功获取

{

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

return;

}

CString str;

str.Format("5+3=%d",Add(5,3));  //函数指针调用函数

MessageBox(str);

}

然后将Dll2工程中编译链接生成的Dll2.dll文件拷贝到Dll1Test工程目录下,然后点击编译链接运行Dll1Test,同样可以成功实现DLL的调用。

这种调用只需要将动态链接库文件拷贝到客户端目录下,lib文件和头文件均不需要拷贝。这种调用成为动态调用,这种调用的好处是,不会将程序用到的所有的DLL全部加在到内存,只有在运行指定功能的时候,才会将用到的DLL加在到内存中,我们可以用dumpbin工具对该程序进行查看

图片

可以看到,此时没有加载Dll2.dll

在一个程序要调用很多的DLL时,如果使用隐式链接,那将会把所有用到的DLL全部加载到内存中,那样程序启动将会很慢,所以建议使用动态调用DLL

如果不进行名字改编约束,可用使用如下两种方法进行调用

ADDPROC Add = (ADDPROC )GetProcAddress(hInst,?add@@YAHHH@2); //使用改编之后的函数名直接调用

ADDPROC Add = (ADDPROC )GetProcAddress(hInst,MAKEINTRESOURCE(1));//使用导出函数序号进行调用  不建议使用

动态链接库的释放

如果在功能函数结束之后,以后都不需要加载该动态链接库的时候,可以用FreeLibrary函数来释放动态链接库 

FreeLibrary(hInst);

注意:

一、动态链接库导出函数的名字改编问题 (模块定义文件)

二、动态链接库导出函数的调用约定要一致

DllMain

DllMain函数 动态链接库的入口点。是一个可选的函数。在这个函数中不要做一些复杂的加载,只需要写一些内存分配等简单的操作

[上一页 [下一页] 前言 Windows Driver Model(WDM)的根源可追溯到几年前一种叫做Windows for Workgroups 3.10的操作系统。那时候我们努力地支持无数不同的SCSI控制器,我长期地注意WindowsNT开发组创建的小端口驱动程序类型。不久就认识到重新构造必要的映象加载器(image loader)和小端口驱动程序需要的执行环境比把这些小端口驱动程序成某些VXD形式的驱动程序并调试完毕所花费的努力要少得多。 不幸的是,Windows from Workgroups 3.10已经停止发行带有SCSI小端口支持的版本,主要是由于象ASPI(高级SCSI编程接口)这样的外围问题。然而,跨Windowswindows NT操作系统共享同样的驱动程序执行映象的基础是适当的并且可以在win95中见到,它(win95)可以与NT共享SCSI和NDIS小端口驱动程序二进制代码。 共享驱动程序模式的潜在意义是重大的。驱动程序开发人员感兴趣的是支持双平台,共享驱动模式能降低开发和调试的一半开消。对微软来说,共享模式意味着更容易地从win9x迁移到Windows2000或这个平台的未来版本。对最终用户来说,大量不同种类的稳定驱动程序可以在这个家族中的所有成员之间通用。 下一个(逻辑)步骤是 The next logical step, then, was to create a driver model with the ability to share general purpose drivers across both platforms. But what form should it take? Three requirements were immediately obvious: it must be multiprocessor-capable, it must be processor-independent, and it must support Plug and Play (PnP). Fortunately, the Windows NT 4.0 driver model met the first two requirements, and it seemed clear that the next major release of Windows NT would support PnP as well. As a result, WDM can be considered a proper subset of what is now the Windows NT driver model. The potential benefits of a shared driver model can be realized today for many classes of devices, and choosing the WDM driver model will continue to pay dividends in the future. For example, a correctly written WDM driver requires only a recompile before functioning in an NT 64-bit environment prototype. WDM will continue to evolve as new platforms and device classes are supported. Future versions of Windows 9x and Windows 2000 will contain upwardly revised WDM execution environments. Fortunately, WDM is designed to be "backward compatible," meaning that WDM drivers written according to the Windows 2000 DDK and designed to work for the intended environment will continue to work in a subsequent WDM environments. There is a lot to WDM, and in this book Walter does an excellent job in offering an in-depth tour of every aspect as well as the philosophy of the Windows Driver Model. Forrest Foltz 微软公司Windows开发体系结构设计者
所用工具:Jcreator 一.新建(ctrl+n)→判断:1.保存了,直接新建,框架名为:“新建 文本文档.txt--★海龙记事本★” 2.未保存:提示未保存,要求选则是否保存 选是,保存后新建;选否,不保存直接新建。 二.打开(strl+o)→判断:1.判断文件是否保存(同上) 然后打开,选择文件,只能选(.txt)文件 选择后在记事本中显示出来。 三.保存(ctrl+s)→判断:弹出保存对话框→输入文件名: 1. 如果没有填后缀名→直接命名在名字后添加未.txt后缀 2. 如果填了后缀名→则直接以保存为用户要求的后缀名。但是在关闭文件时还是提示未保存(原因是未保存为.txt文件) 四.另存为(F12)→判断:同上。 五.退出(Alt+F4)→判断:1.已保存,直接关闭。 3. 未保存→提示未保存,询问用户是否保存 选择→是:转到保存步骤 选择→否:直接关闭。 六.粘贴(ctrl+c)、复制(ctrl+v)、剪切(ctrl+x)、全选(ctrl+a)、 删除(delete)、时间/日期(F5): 点击或使用快捷键实现编写文件的编辑操作 功能全部实现 七、字体颜色(Ctrl+F): 点击或使用快捷键弹出字体颜色对话框 选择字体颜色后,文字颜色全部改变为所选颜色 八、字体(Ctrl+Q):点击或使用快捷弹出字体对话框 选择字体后,文字全部改变为所选字体 九、自动换行(Ctrl+Q):点击后实现自动换行 再点击后恢复 十、关于记事本(F1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值