用匯編語言建立DLL的技術

一、引言:
彙編語言是一種運行速度最快,能使用所有機器特殊硬件功能的語言。對速度要求很高的程序,如實時響應處理,圖形圖象處理等都離不開彙編語言。目前,在計算機系統中,無論是操作系統、編譯系統、圖形處理系統及大量應用系統中都還不能完全離開彙編語言編制的程序模塊。衆所周知,彙編語言的特點在於其速度和與硬件打交道的能力,而高級語言則通常具有編程容易、方便、調試快速的特點。因此,兩者的結合將發揮各自的優勢,揚長避短,從而發揮更大的效益。在Windows環境下一般採用動態連接庫(DLL)技術使兩者相結合,即用匯編語言來建立DLL,再在高級語言中調用DLL。本文將首先詳細介紹用匯編建立DLL的方法,然後作爲一個例子,在VB程序中調用所建立的DLL。
二、DLL的建立
1、調用約定
調用約定是在語言中爲實現調用而建立的一種協議。應用程序採用調用約定來規定應用程序以某種順序把變量或參數傳遞給DLL;DLL採用調用約定來決定以何種順序接收所傳遞給它的參數。16位的DLL的參數傳遞遵從PASCAL的調用約定,即對函數參數採用與參數在參數表中出現的順序"從左到右"壓入堆棧的方式進行(見圖1),參數在堆棧中所佔的字節與調用類型,參數類型等有關;且被調用函數已經明確獲知參數的數目,因此在函數結束時,會一併恢復堆棧的狀態。
調用約定使應用程序知道哪一些過程、函數是外部的,從而在連接時能找到這些程序模塊;同時,也在DLL中說明哪些過程、函數是可以爲其它程序模塊所使用。在用匯編語言建立的DLL中,調用約定由過程定義語句中的關鍵字PASCAL指出。

高 地 址
低 地 址
參 數1
參 數2
返 回 地 址
BP 保 護
BP, SP
圖1.PASCAL調用堆棧
2、參數傳遞

應用程序與DLL之間的通訊是經過參數傳遞來完成的,當DLL中的過程或函數定義之後,就可以在應用程序中對它進行調用,而調用與被調用之間的信息傳遞和交換可以通過系統堆棧進行參數傳遞來完成。在定義或說明時用形式參數,調用時則替換成實際參數。如何把實際參數傳遞給相應的形式參數,而且當過程完結後又如何把所得結果送回應用程序,這是編程中要解決的問題。 DLL中接收應用程序傳遞過來的參數由過程定義語句中的EXPORT關鍵字給出。常用的參數傳遞方式有傳值和傳址兩種:傳值是一種最簡單的參數傳遞方法,它把實際參數的值傳遞給相應的形式參數。DLL中的庫函數可用如下方法來接收應用程序傳遞過來的值(假設應用程序用傳值方式傳遞過來兩個整型值):

SUM1 PROC FAR PASCAL EXPORT I:PTR WORD,J:PTR WORD
… …
MOV AX,I ; 將參數I的值送入AX
MOV BX,J ; 將參數J的值送入BX
… …
SUM1 ENDP

傳址是一種用得最多的參數傳遞方式,它把實際參數的地址傳遞給相應的形式參數。假設應用程序用傳址方式傳遞過來兩個整型值,則DLL庫函數可用下列方法來接收地址:
SUM2 PROC FAR PASCAL EXPORT I:FAR PTR WORD
… …
LES SI,I ; 將 參 數I 的 地 址 送 入ES:[SI]
MOV AX,ES:[SI]
… …
SUM2 ENDP

在以後的處理過程中,將針對SI所指的單元進行間接訪問。這種參數傳送方法接口關係較簡單,適用於大量參數的傳送。DLL結果和值的返回,此部分可選擇。當返回值的數據類型是簡單類型時(即不含數組或結構等類型),且返回值長度不超過四個字節時,返回值如表1所示。

表1:

數據大小返回值所放寄存器
1 字 節 AL
2 字 節 AX
3 字 節 高 字( 或 段 地 址) 放DX
4 字 節 低 字( 或 位 移 量) 放AX

當返回值長度超過四個字節時,可參考文獻[1]。從表1可看出,庫函數可返回值也可返回地址。當返回值時,應用程序調用庫函數的結果即爲返回值。當返回地址時,應用程序調用庫函數的結果得到的是結果的地址指針。

當返回值含有數組或結構等類型時,可把數組或結構的地址賦給以傳址方式定義的形式參數,相應地應用程序以數組或結構的形式從相應地址取得結果即可。 3、DLL的源代碼的結構

用匯編語言編寫的DLL源代碼的結構由入口函數、出口函數、模塊定義文件和公共函數四部分組成。

(1)入口函數和出口函數

入口函數是當Windows系統把一個模塊裝入內存時要調用的函數,可執行程序必須擁有自己的入口點。如同WinMain函數是Windows應用程序的入口點一樣,LibMain是16位DLL的入口函數,也是DLL的主函數,此函數在WIN.INC文件中聲明。每當DLL被加載時Windows會調用該函數完成一些初始化工作。如果DLL不用初始化,則此函數什麼也不做,僅作爲DLL的入口點。

LibMain函數返回"1"以通知WindowsDLL初始化已正常執行完畢。若返回"0",Windows認爲DLL初始化失敗,從而終止應用程序的執行。

當一個應用程序調用完DLL時,要把它從內存釋放,就需用到出口函數。16位DLL的出口函數是WEP,與LibMain函數一樣,當DLL調用成功時,WEP函數返回True,否則,返回False。

總之,DLL源代碼中的LibMain函數和WEP函數是由Windows調用的,而其它函數則是由應用程序調用。

(2)公共函數

公共函數或庫函數是可被應用程序調用的完成一定功能的子程序或函數,爲DLL的主體部分。彙編語言建立的庫函數由過程語句定義,其格式見參數傳遞。在進入庫函數時,應先保護一些寄存器的值,如DS、ES、SP、SI、DI等。退出返回前應恢復被保護的寄存器的值。最後,用RETn語句退出並恢復堆棧。在DLL的結構中,公共函數必須先聲明後才能定義。聲明格式如下:

SUM1 PROTO FAR PASCAL EXPORT :PTR WORD,:PTR WORD
其 中:SUM1: 庫 函 數 名;
PROTO: 庫 函 數 聲 明 關 鍵 字;
FAR: 遠 程 調 用;
PASCAL: 調 用 約 定;
EXPORT: 說 明 參 數 傳 遞 的 類 型,
此 處 說 明 傳 遞 兩 個 雙 字 節 參 數。

(3) 模塊定義文件

爲了開放DLL中的公共函數,就要用到模塊定義文件,以告訴連接器DLL中哪些函數是公用的。通常DLL是通過將函數列在模塊定義文件中開放其公共函數的,用關鍵字EXPORT列出可爲應用程序所用的函數名。使用.DEF文件的DLL至少應擁有一個公共函數。

mydll.def的文件結構:
LIBRARY mydll ; 動 態 連 接 庫 的 名 字
DESCRIPTION 'DLL FOR WINDOWS3.x & WIN95 '
EXETYPE WINDOWS
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE SINGLE
SEGMENTS CODE2 PRELOAD FIXED
EXPORTS WEP @1 RESIDENTNAME ; 出 口 函 數
SUM1 @2 ; 公 共 函 數
SUM2 @3 ; 公 共 函 數

(4) DLL 舉 例
mydll.asm 文 件
.model medium,pascal,farstack ;
用 中 模 式 進 行 編 譯, 採 用pascal 調 用 約定,
; 用 系 統 堆 棧 傳 遞 參 數
.386
include mydll.inc ;mydll.inc 中 定 義 了Prolog 和
Epilog 這 二 個 保 護 現 場 和 恢 復 現 場 宏
.data
.stack
.code
Libmain proc far pascal
ret
Libmain endp
SUM1 proto far pascal export :ptr word,:ptr word
SUM2 proto far pascal export :far ptr dword
; 傳值傳送兩個雙字節數,
相加後將結果(雙字節)用AX返回
SUM1 proc far pascal export i:ptr word,j:ptr word
Prolog ; 保 護 現 場 的 宏
mov ax, i
mov bx, j
add ax, bx
Epilog ; 恢 復 現 場 的 宏
ret 4 ; 恢 復 堆 棧
SUM1 endp
; 傳址傳送三個雙字節參數,將前兩個參數
相加後用第三個參數返回(返回值爲兩個字節),
;過程本身無返回值
SUM2 proc far pascal export x:far ptr dword
Prolog
mov ax, 0
les di, x
mov ax, es:[di]
mov bx, es:[di-2]
add ax , bx
mov es:[di-4], ax
Epilog
ret 4
SUM2 endp
CODE2 segment word 'code'
assume cs:CODE2
wep proc far pascal export ; 出 口 函 數
Prolog
mov ax, 1
Epilog
ret
wep endp
CODE2 ends
end

4、 編譯與連接

MasmV6.11版本提供了把DLL的源代碼編譯連接成16位DLL文件的工具。對於Windows3.x,可在MASMV6.11提供的PWB集成環境中把DLL源代碼編譯連接成DLL。但在Windows95中,須用行彙編連接程序ML.EXE和LINK.EXE將DLL的源代碼編譯連接成16位的DLL文件,其方法如下:

1)ML/Cmydll.asm

命令開關/C的作用是指示編譯連接器ML只編譯不連接,即只把mydll.asm文件編譯成mydll.obj文件。

2)LINKmydll.obj,mydll.dll,mydll.map,libw,mydll.def

即用連接器LINK把mydll.obj,mydll.def和libw連接生成mydll.dll文件。其中libw是MasmV6.11提供的連接生成Windows下的文件所需的庫,mydll.map是在連接生成DLL過程中附帶產生的文件,此項可省略。

三.調用DLL
爲了調用DLL中的函數,程序有兩種選擇方法。隱含(或裝載)連接和運行連接。在此我們只討論運行連接,即在運行時,程序在需要時將明確地發命令來裝載和釋放DLL。下面以Windows下最流行的開發軟件VB爲例來說明調用所建的DLL的方法。
16位的DLL只能被16位的VB調用,即只能在VB3.0或VB4.0的16位版本中調用所建的DLL。VB在調用DLL前須先聲明。

1.VB中DLL的聲明

假設所建立的DLL已被放到Windows的system子目錄下了。如果DLL是在別的目錄下,則在DLL函數的聲明中mydll.dll前必須帶有路徑。

VB中對DLL的調用實現是通過DECLARE語句來引入的。

(1)DLL函數的聲明

當DLL中的庫函數有返回值時,VB中應把庫函數聲明爲Function,聲明格式如下所示:

Private(Public)Declare Function
SUM1 Lib "mydll.dll"(ByVal I as Integer,_
ByVal J as Integer) as Integer
其中:SUM1:要調用的庫函數名;
ByVal:說明用傳值方式傳送整型參數;
Lib:指出需調用的DLL的名字。

(2)DLL過程的聲明

當DLL中的庫函數無返回值時,VB中應把庫函數聲明爲Sub,聲明格式如下所示:

Private(Public) DeclareSub SUM2 Lib "mydll.dll"(ByRef I as Integer)

其中:SUM2:要調用的庫函數名;

ByRef:說明用傳址方式傳送整型參數,因VB中參數的缺省傳送方式是傳址傳送,故可省略關鍵字ByRef。

2.VB中DLL的調用

DLL聲明完後,就可以調用了。如果庫函數聲明爲私有的(Private),則DLL只能在聲明的窗體代碼中被調用;否則,就可在模塊中的任何窗體代碼中調用它。對於已聲明過的庫函數,VB可象自己的子過程或子函數一樣使用它。

(1)函數的調用

Private Sub Add1()
Dim X , Y ,Z as Integer
… …
X=10
Y=20
Z=SUM1(X,Y) ' 調 用 後Z=30
… …
End Sub
(2) 過 程 的 調 用
Private Sub Add2()
Dim X , Y ,Z as Integer
X=10
Y=20
Z=0
CALL SUM2(X) ' 調 用 後Z=30
… …
End Sub

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章