Windows下DLL编程技术及应用

 

 

、引

由于Windows为微机提供了前所未有的标准用户界面、图形处理能力和简单灵便的操

作,绝大多数程序编制人员都已转向或正在转向Windows编程。在许多用户设计的实

际应用系统的编程任务中,常常要实现软件对硬件资源和内存资源的访问,例如端

I/ODMA、中断、直接内存访问等等。若是编制DOS程序,这是轻而易举的事

情,但要是编制Windows程序,尤其是WindowsNT环境下的程序,就会显得较困难。

因为Windows具有"与设备无关"的特性,不提倡与机器底层的东西打交道,如果直接

Windows API函数或I/O读写指令进行访问和操作,程序运行时往往就会产生保

护模式错误甚至死机,更严重的情况会导致系统崩溃。那么在Windows下怎样方便地

解决上述问题呢?用DLL(Dynamic Link Libraries)技术就是良好途径之一。

DLLWindows最重要的组成要素,Windows中的许多新功能、新特性都是通过DLL

实现的,因此掌握它、应用它是非常重要的。其实Windows本身就是由许多的DLL

成的,它最基本的三大组成模块KernelGDIUser 都是DLL,它所有的库模块也都

设计成DLL。凡是以.DLL.DRV.FON.SYS和许多以.EXE为扩展名的系统文件都是

DLL,要是打开Windows\System目录,就可以看到许多的DLL模块。

尽管DLLRing3优先级下运行,仍是实现硬件接口的简便途径。DLL可以有自己的数

据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式,减少了编程

设计上的不便;同时,一个DLL在内存中只有一个实例,使之能高效经济地使用内

存;DLL实现的代码封装性,使得程序简洁明晰;此外还有一个最大的特点,即DLL

的编制与具体的编程语言及编译器无关,只要遵守DLL的开发规范和编程策略,并安

排正确的调用接口,不管用何种编程语言编制的DLL都具有通用性。例如在BC31中编

制的DLL程序,可用于BCVCVBDelphi等多种语言环境中。笔者在BC31环境下编

译了Windows下直接内存访问和端口I/O两个DLL,用在多个自制系统的应用软件中,

运行良好。

 

二、DLL的建立和调用

DLL的建立及调用方法在许多资料上有详细的介绍,为了节省篇幅,在这里仅作一些

主要的概括。

1.DLL的建立

关于DLL的建立,有如下几个方面的要素是不可缺少和必须掌握的:

?. 入口函数LibMain( )

就象C程序中的WinMain( )一样,Windows每次加载DLL时都要执行LibMain( )函数,

 

主要用来进行一些初始化工作。通常的形式是:

 

 

 

int FAR PASCAL LibMain(HINSTANCE hInstance,WORD wDataSeg,WORD wHeapSize,LPSTR lpszCmdLine)

{

if(wHeapSize!=0) //使局部堆、数据段可移动

UnlockData(0); //解锁数据段

/*此处可进行一些用户必要的初始化工作*/

return 1; //初始化成功

}

?出口函数WEP( )

Windows从内存中卸载DLL时,调用相应的出口函数WEP( ),主要做一些清理工作,

如释放占用的内存资源;丢弃某些字串、位图等资源;关闭打开的文件等等。

?自定义的输出函数

为了让位于不同内存段的应用程序进行远程调用,自定义的输出函数必须定义为远

程函数(使用FAR关键字),以防使用近程指针而得到意外的结果;同时,加上PASCAL

关键字可加快程序的运行速度,使代码简单高效,提高程序的运行速度。

?输出函数的引出方法

DLL的模块定义文件中(.DEF)EXPORTS语句对输出函数逐一列出。例如:

EXPORTS WEP @1 residentname //residentname可提高DLL效率和处理速度

PortIn @2 

PortOut @3 //通常对所有输出函数附加系列号

在每个输出函数定义的说明中使用_export关键字来对其引出。

以上两种方法任选其中的一种即可,不可重复。后面的两个实例分别使用了上述两

种不同的引出方式,请留意。

 

2.DLL的调用

加载DLL时,Windows寻找相应DLL的次序如下:

?.当前工作盘。

?Windows目录;GetWindowsDirectory( )函数可提供该目录的路径名。

?Windows系统目录,即System子目录;调用GetSystemDiretory( )函数可获得这个

目录的路径名。

?DOSPATH命令中罗列的所有目录。

?网络中映象的目录列表中的全部目录。

 

DLL模块中输出函数的调用方法:

不论使用何种语言对编译好的DLL进行调用时,基本上都有两种调用方式,即静态调

用方式和动态调用方式。静态调用方式由编译系统完成对DLL的加载和应用程序结束

DLL卸载的编码(如还有其它程序使用该DLL,则WindowsDLL的应用记录减1,直

到所有相关程序都结束对该DLL的使用时才释放它),简单实用,但不够灵活,只能

满足一般要求。动态调用方式是由编程者用API函数加载和卸载DLL来达到调用DLL

目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方

式。具体来说,可用如下的方法调用:

?.在应用程序模块定义文件中,用IMPORTS语句列出所要调用DLL的函数名。如:

IMPORTS MEMORYDLL.MemoryRead

MEMORYDLL.MemoryWrite

?让应用程序运行时与DLL模块动态链接

 

 

 

先用LoadLibrary加载DLL,再用GetProcAddress函数检取其输出函数的地址,获得

其指针来调用。如:

HANDLE hLibrary;

FARPROC lpFunc;

int PortValue;

M

hLibrary=LoadLibrary("PORTDLL.DLL"); //加载DLL

if(hLibrary>31) //加载成功

{

lpFunc=GetProcAddress(hLibrary,"PortIn"); //检取PortIn函数地址

if(lpFunc!=(FARPROC)NULL) //检取成功则调用

PortValue=(*lpFunc)(port); //port端口的值

FreeLibrary(hLibrary); //释放占用的内存

M

 

三、DLL应用实例源程序

1.直接内存访问的DLL源代码

//.DEF文件

LIBRARY MEMORYDLL

DESCRIPTION 'DLL FOR MEMORY_READ_WRITE '

EXETYPE WINDOWS

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE SINGLE

HEAPSIZE 1024 //DLL无自己的堆栈,故没有STACKSIZE语句

EXPORTS WEP @1 residentname

ReadMemory @2

WriteMemory @3

 

//.CPP文件

#include <windows.h>

 

int FAR PASCAL LibMain(HINSTANCE hInstance,WORD wDataSeg,WORD wHeapSize,LPSTR lpszCmdLine)

{

if(wHeapSize!=0)

UnlockData(0);

return 1;

}

 

int FAR PASCAL MemoryRead(unsigned int DosSeg,unsigned int DosOffset)

{

WORD wDataSelector,wSelector;

char far *pData;

char value;

wDataSelector=HIWORD((DWORD)(WORD FAR *)&wDataSelector);

wSelector=AllocSelector(wDataSelector); //分配选择器

SetSelectorLimit(wSelector,0x2000); //置存取界限

SetSelectorBase(wSelector,(((DWORD)DosSeg)<<4)+(DWORD)DosOffset); //置基

地址

pData=(char far *)((DWORD)wSelector<<16);

value=*pData;

FreeSelector(wSelector); //释放选择器

return (value);

}

 

 

 

void FAR PASCAL MemoryWrite(unsigned int DosSeg,unsigned int DosOffset,char Data)

{

WORD wDataSelector,wSelector;

char far *pData;

wDataSelector=HIWORD((DWORD)(WORD FAR *)&wDataSelector);

wSelector=AllocSelector(wDataSelector);

SetSelectorLimit(wSelector,0x2000);

SetSelectorBase(wSelector,(((DWORD)DosSeg)<<4)+(DWORD)DosOffset);

pData=(char far *)((DWORD)wSelector<<16);

*pData=Data;

FreeSelector(wSelector);

}

 

int FAR PASCAL WEP(int nParam)

{

return 1;

 

2.端口读写I/ODLL源代码

//.DEF文件

LIBRARY PORTDLL

DESCRIPTION 'DLL FOR PORT_IN_OUT '

EXETYPE WINDOWS

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE SINGLE

HEAPSIZE 1024

 

//.CPP文件

#include <windows.h>

#include <dos.h>

 

int FAR PASCAL LibMain(HINSTANCE hInstance,WORD wDataSeg,WORD wHeapSize,LPSTR lpszCmdLine)

{

if(wHeapSize!=0)

UnlockData(0);

return 1;

}

 

int FAR PASCAL _export PortOut(int port,unsigned char value)

{

outp(port,value);

return 1;

}

 

int FAR PASCAL _export PortIn(int port)

{

int result;

result=inp(port);

return (result);

}

 

int FAR PASCAL _export WEP(int nParam)

{

return 1;

}

 

 

分别将上面两个实例的.DEF文件和.CPP文件各自组成一个.PRJ文件,并进行编译链接

.EXE.DLL文件就可以在应用程序中对其进行调用。

 

四、结

在上面,我们利用DLL技术方便地实现了Windows环境下对内存的直接访问和端口I/O

的访问,仿效这两个例子,还可以编制出更多的适合自己应用系统所需的DLL,如用

于数据采集卡的端口操作及扩展内存区访问、视频区缓冲区及BIOS数据区操作等许

多实际应用的编程任务中。必要时只需直接更新DLL,而用不着对应用程序本身作任

何改动就可以对应用程序的功能和用户接口作较大的改善,实现版本升级。因此,

掌握好DLL技术对Windows程序开发者很有裨益。

 

 

 

 

你可能感兴趣的:(windows,能力,程序,实际应用,编程技术)