|
# z/ j% E) e" o/ ]# C / c, M/ M6 R7 t( l- U. p v
摘 要: 本文介绍了DLL技术在Windows编程中的基本运用方法及应用,给出了直
+ r; B/ X- E8 Z z1 `9 \接内存 4 s0 }3 Z- P a' }# A; D7 ?& j! p
访问及端口I/O的两个实用DLL的全部源代码。 3 Q; q0 M3 ]# l7 Q
关键词: DLL Windows编程 内存访问 I/O
# h% A8 @! J0 O# W9 E5 `) r! g 3 n& M; ^# z% B; I- E9 a
一 、引 言 4 L% `8 t; Y2 |( j$ K5 l
由于Windows为微机提供了前所未有的标准用户界面、图形处理能力和简单灵便
2 a. G" h* I5 s8 p的操作,绝大多数程序编制人员都已转向或正在转向Windows编程。在许多用户
+ `# U9 |) i0 l设计的实际应用系统的编程任务中,常常要实现软件对硬件资源和内存资源的访 0 C/ _5 g) ~" b! }3 S, n
问,例如端口I/O、DMA、中断、直接内存访问等等 。若是编制DOS程序,这是轻
8 J1 u+ f2 ^7 }" z而易举的事情,但要是编制Windows程序,尤其是WindowsNT环境下的程序,就会 1 k4 p+ s; k5 Y3 n
显得较困难。 * `( D% h6 z! z7 z) K1 G. W& Y5 o" c
因为Windows具有"与设备无关"的特性,不提倡与机器底层的东西打交道,如果
2 {) o2 g: c; v直接用Windows的 API函数或I/O读写指令进行访问和操作,程序运行时往往就会
1 J, a; \7 ~) t0 K7 w产生保护模式错误甚至死机,更严重的情况会导致系统崩溃。那么在Windows下 ( S8 o! f, o# x7 h* {
怎样方便地解决上述问题呢?用DLL(Dynamic Link Libraries)技术就是良好途 9 x# q2 i6 Z) H: t' r
径之一。 0 R2 a( V/ d2 r( C: v) }( I; B; p
DLL是Windows最重要的组成要素,Windows中的许多新功能、新特性都是通过 9 k1 f) ~% F7 E+ W- [
DLL来实现的,因此掌握它、应用它是非常重要的。其实Windows本身就是由许多 5 l6 D6 P: @- l5 e: c/ f5 _
的DLL组成的,它最基本的三大组成模块Kernel、GDI和User 都是DLL,它所有的
- a. K. X$ ?! z7 u/ u% s( M库模块也都设计成DLL。凡是以.DLL、.DRV、.FON、.SYS和许多以.EXE为扩展名 / x( p" |7 s) z! ^, _2 S
的系统文件都是DLL,要是打开Windows\System目录,就可以看到许多的DLL模块
) E" I7 b! V1 S! X. j" J, z。尽管DLL在Ring3优先级下运行,仍是实现硬件接口的简便途径。DLL可以有自
# w4 u8 E/ ]$ `3 w, `己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式,减
) A4 [0 g/ m9 n7 @" h少了编程设计上的不便;同时,一个DLL在内存中只有一个实例,使之能高效经
4 w- w- z' O6 p0 U7 m7 g R济地使用内存;DLL实现的代码封装性,使得程序简洁明晰;此外还有一个最大 * z% P0 {" P6 P' k: w. z! I
的特点,即DLL的编制与具体的编程语言及编译器无关,只要遵守DLL的开发规范
$ z: D5 R0 Z4 j$ |5 v3 B0 w和编程策略,并安排正确的调用接口,不管用何种编程语言编制的DLL都具有通 : m) c U& S' e( n( j& g( k# ~
用性。例如在BC31中编制的DLL程序,可用于BC、VC、VB、Delphi等多种语言环 0 H0 x8 E& c/ P' {# v( ^5 u' m, U( N
境中。笔者在BC31环境下编译了Windows下直接内存访问和端口I/O两个DLL,用 ! V% \5 w9 |4 U0 |/ z
在多个自制系统的应用软件中,
! N' Q6 A2 }7 K" j8 f运行良好。 $ R: ~7 |5 A4 t( x+ ` ~! ?# M9 X
0 D( J# Q1 W0 O, y5 C# M二、DLL的建立和调用 ! R/ \ K# ]% R! {" x0 M) W: }
DLL的建立及调用方法在许多资料上有详细的介绍,为了节省篇幅,在这里仅作 . x6 A) y- m5 g9 d
一些 ) K1 F$ v# t$ U, o
主要的概括。 9 l. |% c+ w6 z3 C6 q! J/ Z
1.DLL的建立 5 {, T+ p! L9 S: E L4 a8 L! L
关于DLL的建立,有如下几个方面的要素是不可缺少和必须掌握的: & r# ?0 l1 a( F9 v
?. 入口函数LibMain( )
% }8 }5 L6 G; N) y6 v1 g/ P就象C程序中的WinMain( )一样,Windows每次加载DLL时都要执行LibMain( )函 3 R% W- y$ J. v$ _% `9 X) ]
数,主要用来进行一些初始化工作。通常的形式是: 5 a4 H+ [1 L1 ]# ?! [( V
- e( G2 Q9 t6 ~* n# v 1 `5 J. X2 d. |
3 H8 d j; L, j9 b) }1 `int FAR PASCAL LibMain(HINSTANCE hInstance,WORD wDataSeg,WORD
- F+ E7 d6 n [& JwHeapSize,LPSTR lpszCmdLine)
8 g4 Y' L6 E( Q{ ( k4 Z! \ D' C
if(wHeapSize!=0) //使局部堆、数据段可移动
9 N v+ N3 N9 e/ [UnlockData(0); //解锁数据段 X# W& K7 }+ s
/*此处可进行一些用户必要的初始化工作*/ ; g6 M9 [+ ]5 N8 G! h6 @; w9 L0 f
return 1; //初始化成功
: g! L: H3 D2 z4 ?} ' n) h* @9 r! L3 t7 {+ c) ?/ T
?出口函数WEP( ) ! ~. b% q. O) ]- i
Windows从内存中卸载DLL时,调用相应的出口函数WEP( ),主要做一些清理工作
' |9 Z/ t+ Y& |5 k& I' U4 B8 ? M,如释放占用的内存资源;丢弃某些字串、位图等资源;关闭打开的文件等等。 0 F/ o1 J, F( C5 G* w
/ q2 M/ y4 T4 R9 _% o8 H) Q
?自定义的输出函数
# V! D: C8 m& J. W$ o8 R! V为了让位于不同内存段的应用程序进行远程调用,自定义的输出函数必须定义为 ) n; W3 j/ r. o3 B6 d
远程函数(使用FAR关键字),以防使用近程指针而得到意外的结果;同时,加上 3 a2 b4 }# F+ g8 H
PASCAL关键字可加快程序的运行速度,使代码简单高效,提高程序的运行速度。 + z0 T; ~; D1 z4 q9 u% w# c
' M$ \3 U) \& j6 ~7 U6 J V
?输出函数的引出方法
, u/ Y+ Y; B5 r? 在DLL的模块定义文件中(.DEF)由EXPORTS语句对输出函数逐一列出。例如:
) a* d4 K) X/ @6 R0 s2 lEXPORTS WEP @1 residentname //residentname可提高DLL效率和处理速度 3 {: z ~; a @/ k* M$ n/ o: F/ G
PortIn @2 ! Q" b+ s7 b3 Y5 k* f
PortOut @3 //通常对所有输出函数附加系列号 $ I/ `4 l7 {. Q( t. A1 V
? 在每个输出函数定义的说明中使用_export关键字来对其引出。
7 P7 v0 n0 k, h' e* n+ r2 O以上两种方法任选其中的一种即可,不可重复。后面的两个实例分别使用了上述
, j$ T0 \4 K2 ]: P两种不同的引出方式,请留意。 & |6 x( l$ R, c
7 t7 |; X& i' S6 [7 C% g
2.DLL的调用
; [% Y- j6 V' o/ f1 _0 v加载DLL时,Windows寻找相应DLL的次序如下:
# s/ b' d J. W5 Q+ x' e1 j3 M?.当前工作盘。
* \# m% c# w% t @' b' @* U?Windows目录;GetWindowsDirectory( )函数可提供该目录的路径名。 8 {2 l) B% X1 R* I
?Windows系统目录,即System子目录;调用GetSystemDiretory( )函数可获得这
/ q7 R# e _" s! ]/ c/ F! h个目录的路径名。
; y& Q& O d& o& y% H( o, D?DOS的PATH命令中罗列的所有目录。
+ _- k' p: V4 g$ f8 | X?网络中映象的目录列表中的全部目录。
$ `# x+ w C, m# M8 V6 y0 `
6 `: s' t, e3 @7 {1 sDLL模块中输出函数的调用方法: # `3 V0 ^! _' e4 l: ?7 Y, f
不论使用何种语言对编译好的DLL进行调用时,基本上都有两种调用方式,即静
: |9 c: n# g) n2 U态调用方式和动态调用方式。静态调用方式由编译系统完成对DLL的加载和应用 3 u, x0 Z/ ]1 c
程序结束时DLL卸载的编码(如还有其它程序使用该DLL,则Windows对DLL的应用 , L! t% {3 h6 L, y0 s& t
记录减1,直到所有相关程序都结束对该DLL的使用时才释放它),简单实用,但
& ?2 S1 I% Y- i! L4 K l4 }: t! E# x不够灵活,只能满足一般要求。动态调用方式是由编程者用API函数加载和卸载
- K* J6 h- W3 Z% dDLL来达到调用DLL的目的,使用上较复杂,但能更加有效地使用内存,是编制大
: k# i0 ]) W5 i3 c! X4 v7 R型应用程序时的重要方式。具体来说,可用如下的方法调用: " _, {6 [! @3 A2 v
?.在应用程序模块定义文件中,用IMPORTS语句列出所要调用DLL的函数名。如:
, ]( g. J+ T; c M8 u8 h5 Y ! {. i+ h8 w+ {! v7 ~0 \
IMPORTS MEMORYDLL.MemoryRead
' C7 X8 v! f9 tMEMORYDLL.MemoryWrite
8 Z5 X& n/ X5 j( k?让应用程序运行时与DLL模块动态链接
6 i7 [, F/ \# C. }; Y2 }
, G u; d* V* D5 G2 P3 t 6 I# ^( E% t3 ?4 z7 @7 X- U
! p2 s2 `5 }" M$ |$ e& t$ J先用LoadLibrary加载DLL,再用GetProcAddress函数检取其输出函数的地址,获
: l, o a. ?4 `& d得其指针来调用。如:
) j, Y* f4 A; o! IHANDLE hLibrary;
' g J# T* z5 Y2 H' PFARPROC lpFunc; 6 I; ? R& B- { o/ y
int PortValue; : d3 D* s* J1 k% A: R% O
M 7 U+ T% |9 R) _0 _
hLibrary=LoadLibrary("PORTDLL.DLL"); //加载DLL 1 Q. q! C) r5 f( F: P1 ?( U M9 {
if(hLibrary>31) //加载成功 . `. n( D7 Z& q. c
{
7 X$ ^: a/ j. f+ t/ X5 @7 G4 _/ A) ZlpFunc=GetProcAddress(hLibrary,"PortIn"); //检取PortIn函数地址 5 t3 e$ V P8 ^, h0 \0 S! q/ O
if(lpFunc!=(FARPROC)NULL) //检取成功则调用
8 s' q9 a! X* b3 LPortValue=(*lpFunc)(port); //读port端口的值 2 o% ~# B. c+ B. {& y2 W
FreeLibrary(hLibrary); //释放占用的内存
3 B8 p# F2 H. j; O( G}
6 Y$ e, E* S7 \# O9 r' N3 JM
/ {& X7 {6 c# h8 n% e
) |& k' O& C$ U5 e# y三、DLL应用实例源程序
& `; W! H8 j* s/ y' S1.直接内存访问的DLL源代码
* ^/ k1 d' a& e( U//.DEF文件 : a& P& b! i7 K# J# F( r5 s% X" ~! u
LIBRARY MEMORYDLL
4 a6 K2 A) ]" o# x, |% T6 aDESCRIPTION 'DLL FOR MEMORY_READ_WRITE '
) |/ C: @7 o3 B; l- r4 h v1 MEXETYPE WINDOWS
3 }9 v3 R! a; }& C9 H% u6 ~/ L% Z3 gCODE PRELOAD MOVEABLE DISCARDABLE 5 r! e. W/ A9 C3 ~, W* E
DATA PRELOAD MOVEABLE SINGLE
3 K; N6 P% v+ b! j: L0 A1 YHEAPSIZE 1024 //DLL无自己的堆栈,故没有STACKSIZE语句 2 C& g' ~5 K t" h- J: a7 l ?- a' K
EXPORTS WEP @1 residentname . X5 u: k1 q' A0 o
ReadMemory @2 ! H e; Z, r) v7 C
WriteMemory @3 V$ p7 r, i( s3 s) l: o
+ f6 K( |1 E6 D7 t. F//.CPP文件
. f* \# j" A( S* K' m7 l0 B" V( y#include <windows.h>
- p! J& W( A5 ]! z3 U2 p! [' l
5 L) z8 B/ n g, wint FAR PASCAL LibMain(HINSTANCE hInstance,WORD wDataSeg,WORD
7 ?, M" N9 `* z! d% ~' r* \; R4 NwHeapSize,LPSTR lpszCmdLine)
$ q1 W0 z7 L" i- ~1 }{
, Y, V( B o3 Tif(wHeapSize!=0)
# M- `% J$ @0 p% r5 ~+ D# ^# KUnlockData(0); + q/ F7 F O+ L, V! T4 R
return 1; 9 B( M7 \/ I8 q7 }+ ]
} " l" }- [4 l7 C6 O4 C
% D6 p# Z g z
int FAR PASCAL MemoryRead(unsigned int DosSeg,unsigned int 3 k# f9 O8 V3 R
DosOffset) & J/ B2 J. @& n0 r& y. M
{ 6 E/ T" l7 }7 f* @8 U
WORD wDataSelector,wSelector; * E. b) h6 n% h) m4 n$ [5 h7 g
char far *pData; ) n% w; |5 K4 ]! l( e
char value;
$ c5 f- o$ x- X, s9 SwDataSelector=HIWORD((DWORD)(WORD FAR *)&wDataSelector); - n1 k! V& j) y* ?4 o$ Z
wSelector=AllocSelector(wDataSelector); //分配选择器 2 }5 \% h7 t& v( G6 P3 i% N
SetSelectorLimit(wSelector,0x2000); //置存取界限
8 u! y; |- U+ E; }SetSelectorBase(wSelector,(((DWORD)DosSeg)<<4)+(DWORD)DosOffset); //置 . N% [# \" a. g/ v$ N
基地址
) Y" j: u! k1 Q2 A2 EpData=(char far *)((DWORD)wSelector<<16);
6 X; ^- |2 L" b8 b* wvalue=*pData; 6 ~: T3 G6 z* `4 ?
FreeSelector(wSelector); //释放选择器
# M1 f! H0 ]; C4 v9 X) Qreturn (value);
. s8 [( w' i: W8 J4 K ~} 1 @% Y% x& v% ?) S' S. `' Y
% U7 v X/ \, T$ t% B+ a4 T! R7 o $ y$ {: G: L( r6 V" B
/ k3 z! m0 d& \8 J) F% T
void FAR PASCAL MemoryWrite(unsigned int DosSeg,unsigned int 8 n% E5 n# n) i |* w* Q6 m3 g
DosOffset,char Data)
; B/ v2 D1 t3 } i" I' ~{
, ~ W/ q' C$ [0 p- @7 J2 g K( AWORD wDataSelector,wSelector;
! T) y/ w* Y% F7 o, ]5 Gchar far *pData;
. A1 u) }% r/ A4 |% e9 Y. rwDataSelector=HIWORD((DWORD)(WORD FAR *)&wDataSelector); 0 y f7 X3 d# C" ~) c
wSelector=AllocSelector(wDataSelector); 2 F$ G, h) {6 w* h( ]
SetSelectorLimit(wSelector,0x2000); , G4 E$ L( ^( B# l' ]; S. [
SetSelectorBase(wSelector,(((DWORD)DosSeg)<<4)+(DWORD)DosOffset);
+ c5 }- A2 v! [1 ~pData=(char far *)((DWORD)wSelector<<16);
" }9 l0 c: ~4 a! Z*pData=Data;
9 c9 u' y; i" eFreeSelector(wSelector);
: f8 i; h: f' X' a1 W9 E} : e3 f/ X$ r( N+ O8 ?; O
" [$ k% P( E4 {' R/ @int FAR PASCAL WEP(int nParam)
- c8 n9 s% u# L1 N; r7 g{
' y! M$ G! g7 `' p mreturn 1;
# u) L5 ]9 o; C4 s} $ x6 R: H8 A" I) A
7 F4 f0 H# w4 F. i2.端口读写I/O的DLL源代码 0 B+ C0 g* `! a; h
//.DEF文件 7 |" N1 F/ G! d% n
LIBRARY PORTDLL $ c, J% W/ D+ s* R
DESCRIPTION 'DLL FOR PORT_IN_OUT '
* f# Z. ?2 {& c7 ~3 xEXETYPE WINDOWS : Z( c0 C8 U+ V) @$ ~% W
CODE PRELOAD MOVEABLE DISCARDABLE
" c! m" `/ A, r" E% `DATA PRELOAD MOVEABLE SINGLE
8 |" y( t! M ^ u: u* e( \HEAPSIZE 1024
7 X( S3 S" y1 d# |" E
$ p1 ~* s% Q* t( p//.CPP文件 + ~- z9 O$ y7 e, O1 ?9 a7 d0 h
#include <windows.h>
9 P7 G/ s$ K4 }$ B" {) b8 j#include <dos.h>
4 f4 k, r* V6 c& O: L0 A 1 f; `% G6 _* d) x1 C. F
int FAR PASCAL LibMain(HINSTANCE hInstance,WORD wDataSeg,WORD
2 _1 _' t$ w4 Y NwHeapSize,LPSTR lpszCmdLine)
5 e% c( w1 s( X{ ( o8 D R7 Q" E2 p( R) h& x- C
if(wHeapSize!=0)
2 [* I' y! W- R" TUnlockData(0); 3 {% @( z# R( }; q
return 1; 6 b B+ z) m1 l3 H) U
}
' O* h4 o( U4 d: u6 L5 [; ]2 y
6 v4 ?+ j; a. vint FAR PASCAL _export PortOut(int port,unsigned char value) : u- e1 Q& k w4 q, ]+ X
{ 0 P! F" f& D! }9 l$ s) ]% ^
outp(port,value);
' j) T9 P( o! ]/ J( q+ y9 C* R+ @" Mreturn 1;
4 X9 ~" H! e5 `- Q* [} $ ?( g) Y5 a6 N
9 G1 u2 D& e/ W( u
int FAR PASCAL _export PortIn(int port)
9 A+ v2 \2 M5 j, l" Y! ]$ N, E' w5 }{ ' d. u' o+ J* d0 o0 c+ j- P, v# p
int result; 8 i& |, d! A0 O, s& q# n/ s O
result=inp(port);
" o' N- U0 v7 d3 D% a! {return (result); - y5 l( a/ k! f. W
} 8 s y" d" x, `, _
" X! E6 G' ~9 [8 T. m# c8 Y$ [# Iint FAR PASCAL _export WEP(int nParam) ) N& v# R7 s* B m! \8 i$ \
{ 0 H L; ]+ a- {3 M! G, h
return 1;
( I, r B5 C! K& P# o4 f' q, K& S}
' P' Z) n6 ]0 U% }) F' t{
6 \, w8 @6 L' D; \0 D9 E+ \- Oreturn 1;
- ^; G2 W& s2 e7 G# b% S( C% c}
- G. y* E4 P: M# J8 S/ ~ , v) L( ?0 o w1 u
0 y5 {7 s" J" N' w; j- T分别将上面两个实例的.DEF文件和.CPP文件各自组成一个.PRJ文件,并进行编译 # j7 b& k$ _# B) _/ X. S
链接成.EXE或.DLL文件就可以在应用程序中对其进行调用。
6 T: v" c6 n' H9 k
2 l0 q0 Y1 H% Z& T四、结 束 语 / K) n; G9 F, c7 Y
在上面,我们利用DLL技术方便地实现了Windows环境下对内存的直接访问和端口
% x) X) a {4 O8 g, z$ i- N- k' k2 bI/O的访问,仿效这两个例子,还可以编制出更多的适合自己应用系统所需的 * W. @( A% e( [% s: v) R
DLL,如用于数据采集卡的端口操作及扩展内存区访问、视频区缓冲区及BIOS数 # @; f& [$ D7 q
据区操作等许多实际应用的编程任务中。必要时只需直接更新DLL,而用不着对
4 ]9 ~0 e) v7 _. g应用程序本身作任何改动就可以对应用程序的功能和用户接口作较大的改善,实 ( f( E% Y1 [" u, w! h6 \; u2 U
现版本升级。因此,掌握好DLL技术对Windows程序开发者很有裨益。 |