第 20 章 動態連結程式庫 ( Dynamic-Link Library )


動態連結程式庫簡介

在 DOS 作業系統堛組合語言教學中,小木偶曾在第十章介紹了製作程式庫 ( 或翻成函式庫,英文是 library ) 的方法,這種程式庫是在連結過程中,由連結器在程式庫奡M找要呼叫的副程式,接著把這個副程式整段地嵌入主程式而產生可執行檔。像這種直接把副程式的程式碼寫入到可執行檔中的連結方式稱為靜態連結,而被連結的程式庫稱為靜態程式庫。當靜態連結程式庫被插入主程式後,即使靜態連結程式庫被刪掉,主程式仍能正常執行。

靜態連結的缺點大約有兩個:第一個是靜態連結會佔據比較多的硬碟空間或記憶體,假如有 100 個程式要呼叫某一段副程式,那麼就有 100 個相同的副程式會被寫入不同的程式中,亦即在硬碟機奡N重複了這 100 個副程式。第二個缺點是維護上的不便,如果發現某段副程式的程式碼錯誤或者要更新版本,那麼只能把所有呼叫這段副程式的主程式重新組譯、連結才能修正錯誤,假如有一個程式遺漏了,那麼這個遺漏的主程式還是會使用舊版或錯誤的副程式。

那麼,Windows 作業系統有沒有方法改進這兩個缺點呢?答案當然是肯定的,否則就不會有這一章了。Windows 採用動態連結的方式呼叫副程式,而把這些副程式集合起來所成的程式庫就叫做動態連結程式庫 ( dynamic-link library,縮寫為 DLL ),動態連結程式庫的副檔名通常是 DLL,但是也有以 EXE、OCX 等為副檔名的動態連結程式庫。以這種方式所產生的主程式在連結階段,並不會嵌入副程式的程式碼,而僅僅把副程式所在的程式庫 ( 副檔名為 DLL ) 及副程式相關訊息 ( 如副程式名稱等等 ) 插入程式堙A而這些被插入資料是由副檔名為 LIB 的匯入程式庫提供。

事實上,Windows 作業系統就是由許多的動態連結程式庫所組成的,例如最主要的三個動態連結程式庫:
  1.USER32.DLL 中的副程式,主要是控制使用者界面
  2.GDI32.DLL 中的副程式,主要負責圖形的顯示、操作
  3.KERNEL32.DLL 中的副程式,主要處理記憶體管理和工作調度

當主程式被執行時,作業系統才會把所需要用到動態連結程式庫載入至記憶體,如果這個動態連結程式庫已經載入記憶體了,作業系統將會把這個副程式的程式碼映射到主程式所在記憶體空間,所以在實體記憶體中還是僅保存一份程式碼。因為動態連結程式庫的程式碼位址是映射至主程式的位址空間,所以稱為『動態』。假如系統找不著動態連結程式庫,那麼程式就無法執行。

動態連結程式庫的原始檔

動態連結程式庫可以看成是一些副程式的集合 ( 不過也有例外,也有些是某些資源的集合 ),一般而言,這些副程式是被其他程式所呼叫,所以不需要建立視窗,也不需要訊息迴圈或終結視窗的程式碼。每個動態連結程式庫都會有一個稱為『進入/離開副程式』的副程式,它的名稱可以任意訂定,只要符合命名規則即可。『進入/離開副程式』並不像動態連結庫堛漕銗L副程式一樣給其他程式呼叫,而是作業系統載入或卸載動態連結程式庫時,由作業系統呼叫,讓作業系統有機會做一些事前處理或是離開時作一些清除動作用的。『進入/離開副程式』有固定的格式,假如不需要做這些動作的話,『進入/離開副程式』也可以只是把 TRUE 存入 EAX 堙A然後把 TRUE 當成返回值就跳離。整個動態連結程式庫的樣子像底下這樣:

         .386
         .MODEL FLAT,STDCALL
         OPTION CASEMAP:NONE

;假如需要用到包含檔,那麼可以在此處定義
INCLUDE          WINDOWS.INC
INCLUDE          USER32.INC
INCLUDE          KERNEL32.INC
INCLUDELIB       USER32.LIB
INCLUDELIB       KERNEL32.LIB

;*********************************************************************
         .DATA?
;此處定義未初始化的變數
;*********************************************************************
         .CODE
;---------------------------------------------------------------------
;進入/離開副程式的進入點
DLLEntry        PROC    hInstance,dwReason,dwReserved
                mov     eax,dwReason
.IF eax==DLL_PROCESS_ATTACH
        ;保存hInstance
        ;初始化動態連結程式庫需要的各種資源
   .IF  ;初始化成功
                mov     eax,TRUE
   .ELSE
                mov     eax,FALSE
   .ENDIF
.ELSEIF eax==DLL_THREAD_ATTACH
        ;釋放動態連結程式庫使用的資源
.ELSEIF eax==DLL_THREAD_DETACH
        ;為新的執行緒分配資源
.ELSEIF eax==DLL_PROCESS_DETACH
        ;為執行緒釋放資源
.ENDIF

DLLEntry        ENDP
;進入/離開副程式結束
;---------------------------------------------------------------------
SubRoutine1     PROC    Param1,Param2   ;此處為第一個副程式的參數
                        ;此處為第一個副程式的程式碼
                ret
SubRoutine1     ENDP
;---------------------------------------------------------------------
SubRoutine2     PROC    ParamA,ParamB   ;此處為第二個副程式的參數
                        ;此處為第二個副程式的程式碼
                ret
SubRoutine2     ENDP
;---------------------------------------------------------------------
SubRoutine3     PROC    ParamX,ParamY   ;此處為第三個副程式的參數
                        ;此處為第三個副程式的程式碼
                ret
SubRoutine3     ENDP
;*********************************************************************
         END    DLLEntry

上面的這個例子,『進入/離開副程式』名字是『DLLEntry』,對於要做事前處理或清理動作的動態連結程式庫,分別由不同的條件做處理,就是像上面的『DLLEntry』副程式所表示的。作業系統會傳來三的參數,分別是 hInstance、dwReason 及 dwReserved。hInstance 是動態連結程式庫的模組代碼,這個模組代碼與主程式的模組代碼不同,所以無法以 GetModuleHandle 取得。dwReason 表示因為哪一種原因而使得作業系統載入動態連結程式庫,我們可在此處做前置處理或是清除動作,dwReason 可分為四種原因:

  1. DLL_PROCESS_ATTACH:表示動態連結程式庫剛被映射到主程式的位址空間,我們可以在此做一些程式庫載入時的初始化工作,如果成功則返回 TRUE;否則返回 FALSE,表示程式庫無法正常工作。當使用者使用滑鼠,在桌面上雙擊某一個程式的圖示時,Windows 作業系統會為該程式建立位址空間,接著把這程式以及所需的動態連結程式庫載入進剛建立的位址空間,然後系統會為這個程式建立主執行緒,再由這個主執行緒呼叫每一個動態連結程式庫的進入/離開副程式,此時 dwReason 參數就是 DLL_PROCESS_ATTACH。只要有一個動態連結程式庫的傳回值是 FALSE,那麼系統會終止程式繼續執行。

  2. DLL_PROCESS_DETACH:表示程式庫被卸載時,系統會以 DLL_PROCESS_DETACH 為 dwReason 參數之值,呼叫動態連結程式庫的進入/離開副程式,可以在此做一些清除的動作。自程式庫載入到卸載的過程中,DLL_PROCESS_ATTACH 和 DLL_PROCESS_DETACH 訊息只發生一次。

  3. DLL_THREAD_ATTACH:當主程式建立了新的執行緒時,系統會以 DLL_THREAD_ATTACH 為參數呼叫動態連結副程式,以使程式有機會作初始化執行緒的工作。

  4. DLL_THREAD_DETACH:當主程式終止執行緒時,系統會以 DLL_THREAD_DETACH 為參數呼叫動態連結副程式。如果沒有建立執行緒,DLL_THREAD_ATTACH、DLL_THREAD_DETACH 就不會發生。

動態連結程式庫 ( DLL 檔 ) 的格式與視窗可執行檔 ( EXE 檔 ) 一樣,都是 32 位元視窗的可執行檔,也就是 PE 格式 ( portable executable ),所以 DLL 檔也像 EXE 檔一樣,可以包含程式碼、資料和資源,但是 DLL 檔與 EXE 檔還是有不同的地方,那就是 DLL 檔中含有一個稱為匯出表單的表格,只有在這個表格中所列出的副程式才能為其他程式所呼叫。那麼要怎麼樣做,在 DLL 檔中的副程式才能被外部程式呼叫呢?答案是由模組定義檔中定義。

模組定義檔 ( DEF 檔 )

模組定義檔 ( Module-Definition File ) 的副檔名是 DEF,它是在連結階段,用來指定動態連結程式庫中的哪些副程式可以為外部主程式所呼叫,並由連結器連結寫入到匯出表單堙CDEF 檔的格式是由幾個段落組成,每個段落都是由一個關鍵字開始,這些關鍵字有 LIBRARY、EXPORT、SECTIONS 等等。DEF 檔也可以加入註解,與組合語言原始碼相同,『;』之後就是註解。其他還有許多關鍵字,請參考微軟的 MSDN 網站。底下是幾個在模組定義檔中常見的關鍵字:


實作一個 DLL 並測試

實作 BCDDLL

講了這麼枯燥無味的理論,如果不實際操作一番,猶如紙上談兵,毫無意義,底下小木偶就演示製作動態連結程式庫的過程。底下小木偶把附錄三的大數的加法與乘法製作成動態連結程式庫為例,說明其製作步驟。

首先,依照上述格式,撰寫底下大數加法與大數乘法副程式,並依照上述格式存成 UBCDDLL.ASM:

        .586
        .MODEL  FLAT,STDCALL
        OPTION  CASEMAP:NONE

;包含檔與匯入程式庫
INCLUDE         KERNEL32.INC
INCLUDELIB      KERNEL32.LIB

TRUE            EQU     1
GMEM_SHARE      EQU     2000h
GMEM_FIXED      EQU     0h
GMEM_ZEROINIT   EQU     40h
GPTR            EQU     GMEM_FIXED OR GMEM_ZEROINIT
;*******************************************************************************
.CODE
;-------------------------------------------------------------------------------
;進入/離開副程式
DLLEntry        PROC    hInstDLL,dwReason,dwReserved
                mov     eax,TRUE        ;不需事前處理或事後清理,故僅返回TRUE
                ret
DLLEntry        ENDP
;-------------------------------------------------------------------------------
ubcd_add        PROC    USES ebx edx esi edi lpLow1,lpLow2,lpSum,n1,n2
;計算兩非聚集BCD數之和
;輸入:lpLow1:被加數最低位址,以未聚集BCD方式儲存,大位數在高位址
;      lpLow2:加數最低位址,以未聚集BCD方式儲存,大位數在高位址
;      lpSum:和最低位址
;      n1:被加數位數
;      n2:加數位數
;輸出:EAX:為和所儲存的位址,以未聚集BCD方式儲存,大位數在高位址
;      ECX:和的長度,即位數
;      EBX、EDX、ESI、EDI均被保存起來
        mov     esi,lpLow1      ;ESI=被加數最低位址
        mov     ebx,lpLow2      ;EBX=加數最低位址
        mov     eax,n1
        cmp     eax,n2  ;比較被加數與加數那一個位數大
        jae     u_a0
        xchg    eax,n2  ;若加數位數大,則使被加數與加數交換最低位址
        xchg    esi,ebx ;(ESI與EBX),同時也交換位數(n1、n2)
        mov     n1,eax
        mov     lpLow2,esi
        mov     lpLow1,ebx

;開始相加時,ESI指向兩數位數較大者的位址,n1為其位數
u_a0:   sub     eax,n2
        mov     n1,eax  ;n1=重疊部份的位數
        mov     edi,lpSum

;計算重疊部份,例如
;  987654
;+    912
;---------
;  988566
;被加數、加數都有個位、十位、百位數,所以是重疊的部份。
        clc
        sub     eax,eax
u_a1:   lodsb           ;取得加數中的一位
        add     al,ah   ;加上進位的數
        cbw
        add     al,[ebx]
        aaa             ;加法調整,若有進位,會存在AH
        stosb           ;存入和內
        inc     ebx
        dec     n2
        jnz     u_a1
        cmp     n1,0
        jz      u_a3    ;假如被加數與加數位數相同時,n1會等於零

;計算只有較大位數部份,以上面為例,即計算
;  987654
;+ 000912
;---------
;  988566
;987+000的部份
u_a2:   lodsb
        add     al,ah    ;AH=計算重疊部份後的進位
        cbw
        aaa
        stosb
        dec     n1
        jnz     u_a2

;處理最大位數進位
u_a3:   sub     al,al   ;使AL等於零
        add     al,ah   ;AH=進位,加上進位
        or      al,al
        jz      u_a4
        stosb
u_a4:   mov     ecx,edi
        mov     eax,lpSum
        sub     ecx,eax
        ret
ubcd_add        ENDP
;-------------------------------------------------------------------------------
ubcd_mul        PROC    USES ebx edx esi edi lpLow1,lpLow2,Product,n1,n2
;計算兩非聚集BCD整數之乘積
;輸入:lpLow1:被乘數最低位址,以未聚集BCD方式儲存,大位數在高位址
;      lpLow2:乘數(multiplier)最低位址,以未聚集BCD方式儲存,大位數在高位址
;      Product:乘積最低位址
;      n1:被乘數位數
;      n2:乘數位數
;輸出:EAX:為乘積所儲存位址,以未聚集BCD方式儲存,大位數在高位址
;      ECX:乘積的位數
;      EBX、EDX、ESI、EDI均被保存起來
;原理:乘積位數,nProduct,可能是(n1+n2)或(n1+n2-1),先假設nProduct=(n1+n2),最後再
;   調整。作直式乘法需要配置記憶體作為暫存區,所配置記憶體的大小為
;   SizeTemp=乘積位數×(乘數位數+1)個位元組
;   以987654*912=900740448為例,nProduct為9,須配置記憶體(9×3+9)個位元組。
;         987654 被乘數,存於所配置記憶體位址的0∼(SizeTemp-1)處,也是乘積所存放處,共有n1位數
;       x    912 乘數,存於所配置記憶體位址的LARGEST∼(SizeTemp+3FH)處,共有n2位數,此例n2=3
;      ----------
;      zz1975308 暫存區,由所配置記憶體位址的(LARFEST+40H)處開始,對每一位乘數而言
;      z0987654z ,均佔據n1+n2位數,此例n1+n2共9位數,乘積也是9位數
;      8888886zz
;      ----------
;      900740448
;被乘數會被三位乘數乘三次,每一次都可分成三部份:最右邊要填入0,相乘,做左邊也要填入0
;底下的區域變數,lpHi1,lpHi2,nProduct分別表示被乘數最高位址、乘數最高位址、乘積位數
        LOCAL   lpHi1,lpHi2,nProduct:DWORD
        LOCAL   lpTemp:DWORD    ;配置記憶體位址
        clc
        mov     ecx,n1          ;計算乘積位數,並存於nProduct
        mov     eax,n2          ;乘數位數
        add     ecx,eax         ;ECX=乘積位數
        mov     nProduct,ecx
        inc     eax             ;乘數位數+1
        sub     edx,edx
        mul     ecx             ;EAX=乘積位數×(乘數位數+1)
        INVOKE  GlobalAlloc,GMEM_SHARE or GPTR,eax
        mov     lpTemp,eax

        mov     eax,lpLow1      ;計算被乘數、乘數最大位數所在位址,
        mov     ebx,lpLow2      ;並存於lpHi1、lpHi2
        add     eax,n1
        add     ebx,n2
        mov     lpHi1,eax
        mov     lpHi2,ebx

;第一階段,處理每一位乘數去乘被乘數
        mov     edi,lpTemp      ;EDI指向暫存區(存放每一位乘數相乘後的結果)
        mov     esi,lpLow2      ;每次計算乘數一位數乘被乘數結果之迴圈開始
m1:     sub     eax,eax         ;清除EAX、EDX使往後的AAM、AAA指令能正確運算
        mov     edx,eax

        mov     ecx,esi         ;小位數的填零部分,ECX為填零的個數
        sub     ecx,lpLow2
        mov     ebx,lpLow1      ;計算乘數每位數乘被乘數部分
        push    ecx
        jcxz    m3
m2:     stosb
        loop    m2

m3:     mov     al,[ebx]        ;BX指向被乘數
        mul     BYTE PTR [esi]
        aam
        add     al,dh           ;加上前一次的進位
        aaa
        mov     dh,ah           ;進位存於DH
        stosb
        inc     ebx
        cmp     ebx,lpHi1
        jne     m3

        mov     [edi],dh        ;處理進位部分
        inc     edi

        pop     eax             ;處理大位數填零部分
        mov     ecx,nProduct
        sub     ebx,lpLow1
        sub     ecx,eax
        sub     ecx,ebx
        dec     ecx             ;ECX為填零的個數
        jcxz    m5
m4:     mov     BYTE PTR [edi],0
        inc     edi
        loop    m4

m5:     inc     esi             ;指向乘數的下一位
        cmp     esi,lpHi2       ;檢查乘數是否都已算完
        jne     m1

;第二階段,處理在暫存區中每一位乘數乘積之和
        mov     edi,Product
        mov     ecx,nProduct
        xor     eax,eax
        inc     ecx
        rep     stosb           ;清除乘積的垃圾資料

        mov     esi,Product     ;ESI=乘積位址
        mov     ebx,lpTemp      ;EBX為暫存區位址
        mov     edx,nProduct
m6:     INVOKE  ubcd_add,esi,ebx,esi,edx,edx    ;ESI=ESI+EBX
        add     ebx,edx
        dec     n2              ;n2=乘數位數,即要相加次數
        jnz     m6
        INVOKE  GlobalFree,lpTemp
        mov     eax,Product
        mov     ecx,nProduct
        cmp     BYTE PTR [eax+ecx-1],0  ;檢查最高位數是否為零
        jnz     m7
        dec     ecx             ;若沒有進位,則乘積位數減一
m7:     ret
ubcd_mul        ENDP
;*******************************************************************************
                END     DLLEntry

第二步,撰寫底下的 UBCDDLL.DEF 檔:

EXPORTS
        UBCD_ADDS=ubcd_add
        UBCD_MULS=ubcd_mul

此處小木偶以 UBCD_ADDS、UBCD_MULS 取代原先的副程式 ubcd_add、ubcd_mul 名稱。

第三步,啟動『命令提示字元』,執行組譯、連結而產生三個檔案,UBCDDLL.DLL、UBCDDLL.LIB 及 UBCDDLL.EXP。開啟 XP 的命令提示字元,輸入下列指令:(「ml /c」中的「/c」是指示 ML.EXE 只組譯不連結 )

E:\HomePage\SOURCE\Win32\DLL>ml /c ubcddll.asm [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: ubcddll.asm

E:\HomePage\SOURCE\Win32\DLL>link /DLL /SUBSYSTEM:WINDOWS /DEF:ubcddll.def ubcddll.obj [Enter]
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

   Creating library ubcddll.lib and object ubcddll.exp

E:\HomePage\SOURCE\Win32\DLL>

第四步,也是最後一步,為剛建立好的動態連結程式庫撰寫包含檔 ( INC 檔 ) 及說明,在這個例子堙A包含檔,UDCDDLL.INC,內容如下:

UBCD_ADDS       PROTO   :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
UBCD_MULS       PROTO   :DWORD,:DWORD,:DWORD,:DWORD,:DWORD

如果想把自己的傑作發佈給其他同好使用,應該要包含 INC 檔、DLL 檔、LIB 檔及說明檔。說明檔可用純文字格式,儲存成 TXT 檔,或是 HTML 格式。如能附上原始碼,讓大家觀摩您的程式,也是不錯。

測試 DLL 檔

小木偶已完成一個動態連結庫,底下就來示範如何使用它。底下這個程式,TEST.ASM,執行後會在桌面上出現一個對話盒,可以做很大位數的加法及乘法,它會呼叫動態連結程式庫,UBCDDLL.DLL,實現大位數的加法及乘法。其原始碼如下:

;此程式碼是為了測試動態連結程式庫而製作的,包含兩個程式,計算大位整數的加法與計算大位整數的乘法
        .586
        .MODEL  FLAT,STDCALL
        OPTION  CASEMAP:NONE

INCLUDE         WINDOWS.INC
INCLUDE         USER32.INC
INCLUDE         KERNEL32.INC
INCLUDELIB      USER32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDE         UBCDDLL.INC     ;小木偶製作的UBCDDLL.INC包含檔
INCLUDELIB      UBCDDLL.LIB     ;小木偶製作的UBCDDLL.DLL動態連結程式庫

IDC_CALC        EQU             2001
IDC_EXIT        EQU             2002
IDC_N1          EQU             2003
IDC_N2          EQU             2004
IDC_ANSWER      EQU             2005
IDC_OP          EQU             2006

MAX_DIGIT       EQU             20

;*******************************************************************************
.DATA
hInstance       HINSTANCE       ?
index           DWORD           ?       ;被選定的運算子:加法:0,乘法:1
szDlgName       BYTE            'AddNMul',0
szAdd           BYTE            '+',0
szMul           BYTE            '×',0
ubcdN1          BYTE            MAX_DIGIT+1 DUP (?)
ubcdN2          BYTE            MAX_DIGIT+1 DUP (?)
buffer          BYTE            2*MAX_DIGIT+1 DUP (?)
nN1             DWORD           ?
nN2             DWORD           ?
szAnswer        BYTE            '='
answer          BYTE            2*MAX_DIGIT+1 DUP (?)
;*******************************************************************************
.CODE
;-------------------------------------------------------------------------------
get_number      PROC    USES esi edi hDia:HANDLE,ID:DWORD,adrOperand:DWORD
        LOCAL   szuBcd[MAX_DIGIT+2]:BYTE
        LOCAL   nBcd:DWORD
        INVOKE  GetDlgItemText,hDia,ID,ADDR szuBcd,MAX_DIGIT+1
        mov     nBcd,eax
        mov     ecx,eax
        jecxz   no_n
        lea     esi,szuBcd
        mov     edi,adrOperand
        add     esi,ecx
        dec     esi
        clc
next:   mov     al,[esi]
        and     al,0fh
        dec     esi
        stosb
        loop    next
        mov     eax,ecx
        stosb
        mov     eax,nBcd
no_n:   ret
get_number      ENDP
;-------------------------------------------------------------------------------
DlgProc PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
        LOCAL   hOperator:HANDLE
.IF uMsg==WM_INITDIALOG
        ;載入圖示
                INVOKE  LoadIcon,hInstance,OFFSET szDlgName
                INVOKE  SendMessage,hDlg,WM_SETICON,ICON_SMALL,eax
        ;把加、乘法運算子加入複合框
                INVOKE  GetDlgItem,hDlg,IDC_OP
                mov     hOperator,eax
                INVOKE  SendMessage,hOperator,CB_ADDSTRING,NULL,OFFSET szAdd
                INVOKE  SendMessage,hOperator,CB_ADDSTRING,NULL,OFFSET szMul
        ;設定編輯框最多輸入位數
                INVOKE  GetDlgItem,hDlg,IDC_N1
                INVOKE  SendMessage,eax,EM_SETLIMITTEXT,MAX_DIGIT,NULL
                INVOKE  GetDlgItem,hDlg,IDC_N2
                INVOKE  SendMessage,eax,EM_SETLIMITTEXT,MAX_DIGIT,NULL

.ELSEIF uMsg==WM_COMMAND
                mov     edx,wParam
                mov     eax,wParam
                shr     edx,10h         ;EDX=通知碼
                and     eax,0ffffh      ;EAX=控制元件識別碼
   .IF dx==BN_CLICKED
      .IF ax==IDC_EXIT
                jmp     exit
      .ELSEIF ax==IDC_CALC
                INVOKE  get_number,hDlg,IDC_N1,OFFSET ubcdN1
                mov     nN1,eax
                or      eax,eax
                jz      no_input
                INVOKE  get_number,hDlg,IDC_N2,OFFSET ubcdN2
                mov     nN2,eax
                or      eax,eax
                jz      no_input
         .IF index==0
                INVOKE  UBCD_ADDS,OFFSET ubcdN1,OFFSET ubcdN2,OFFSET buffer,nN1,nN2 ;呼叫UDCDDLL.DLL堛滾bcd_add副程式
         .ELSEIF index==1
                INVOKE  UBCD_MULS,OFFSET ubcdN1,OFFSET ubcdN2,OFFSET buffer,nN1,nN2 ;呼叫UDCDDLL.DLL堛滾bcd_mul副程式
         .ELSE
                jmp     no_input
         .ENDIF
                mov     edx,OFFSET buffer
                mov     edi,OFFSET answer
                add     edx,ecx
                dec     edx
@@:             mov     al,[edx]
                or      al,'0'
                dec     edx
                stosb
                dec     ecx
                jnz     @b
                mov     al,cl
                stosb
                INVOKE  SetDlgItemText,hDlg,IDC_ANSWER,OFFSET szAnswer
      .ENDIF
   .ELSEIF dx==CBN_SELENDOK
                INVOKE  SendDlgItemMessage,hDlg,IDC_OP,CB_GETCURSEL,0,0
                mov     index,eax
   .ENDIF
no_input:

.ELSEIF uMsg==WM_CLOSE
exit:   INVOKE  EndDialog,hDlg,NULL 

;其他訊息
.ELSE
        mov     eax,FALSE
        ret
.ENDIF
        mov     eax,TRUE
        ret
DlgProc ENDP
;-------------------------------------------------------------------------------
start:  INVOKE  GetModuleHandle,NULL
        mov     hInstance,eax
        INVOKE  DialogBoxParam,hInstance,OFFSET szDlgName,NULL,OFFSET DlgProc,NULL
        INVOKE  ExitProcess,eax
;*******************************************************************************
        END     start

把它存成 TEST.ASM 檔。然後撰寫資源描述檔,TEST.RC:

#include "c:\masm32\include\resource.h"

#define IDC_CALC    2001
#define IDC_EXIT    2002
#define IDC_N1      2003
#define IDC_N2      2004
#define IDC_ANSWER  2005
#define IDC_OP      2006

AddNMul         ICON    exclamation01.ico

AddNMul         DIALOG  6, 15, 185, 90
STYLE           DS_MODALFRAME|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
CAPTION         "計算大整數加法與乘法"
FONT            8,"MS Sans Serif"
{
 EDITTEXT       IDC_N1,      5, 13, 70, 12,WS_TABSTOP|ES_NUMBER|ES_RIGHT|ES_AUTOHSCROLL
 COMBOBOX       IDC_OP,     77, 13, 28, 40,CBS_DROPDOWNLIST|WS_VSCROLL
 EDITTEXT       IDC_N2,    105, 13, 70, 12,WS_TABSTOP|ES_NUMBER|ES_RIGHT|ES_AUTOHSCROLL
 LTEXT          "=",IDC_ANSWER,  5, 30,160, 12
 DEFPUSHBUTTON  "計算",IDC_CALC,10, 55, 80, 20
 PUSHBUTTON     "離開",IDC_EXIT,95, 55, 80, 20
}

像以前組譯、連結程式一樣,把圖示檔 ( exclamation01.ico )、原始碼 ( TEST.ASM ) 和資源描述檔 ( TEST.RC ) 放在同一資料夾堙A在命令提示字元中,下達下列指令:

E:\HomePage\SOURCE\DLL>rc test.rc [Enter]

E:\HomePage\SOURCE\DLL>ml test.asm /link test.res [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: test.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"test.obj" /DEBUG
"/OUT:test.exe"
"test.res"

E:\HomePage\SOURCE\DLL>test [Enter] →執行!

下圖是執行時的畫面,您可以在兩個編輯框媬擗J阿拉伯數字,在複合框中選取加法或減法運算子,按下『計算』按鈕得到結果。

在 TEST.ASM 堙A程式處理了兩個控制元件的通知碼,一個是 BN_CLICKED,另一個是 CBN_SELENDOK 。當使用者按下任何一個按鈕時,系統會發出 BN_CLICKED 通知碼給程式,然後在去檢查是哪一個按鈕被按下,如果是 IDC_EXIT 被按下,則離開程式;如果是 IDC_CALC 被按下,則計算結果。當使用者選定複合框清單內的選項時,系統發出 CBN_SELENDOK 通知碼,程式要取得使用者選擇加法還是乘法。這段程式都在處理 WM_COMMAND 訊息中實作出來了。


執行時動態連結 DLL

事實上,呼叫 DLL 檔中的副程式,有兩種方式,第一種方式就是前述所使用的方式,稱為『載入時動態連結』 ( load-time dynamic linking,又叫暗示性連結 );第二種方式則稱為『執行時動態連結 』( Run-Time Dynamic Linking,又叫明確性連結 )。執行時動態載入 DLL,不需要指定包含檔及匯入程式庫,那麼怎麼知道要載入哪一個程式庫呢?原來第一個步驟得在原始碼中呼叫 LoadLibrary 或 LoadLibraryEx API,由程式自己載入 DLL。假如載入成功,第二個步驟是呼叫 GetProcAddress 獲得動態程式庫中副程式的位址。第三個步驟,當要呼叫該副程式時,可用 80x86 的 push 指令把參數推入堆疊,再用 call 指令呼叫該副程式的位址。最後,如果不再需要動態連結程式庫時 ( 例如程式結束時 ),呼叫 FreeLibrary 卸載此程式庫。底下先看看所用到的 API。

LoadLibrary API

LoadLibrary 原型是

HINSTANCE LoadLibrary(
   LPCTSTR  lpLibFileName       // address of filename of executable module 
   );

參數 lpLibFileName 是指向 *.DLL 或 *.EXE 檔案名稱所在的字串位址,假如此字串包含路徑,須以『\』分隔每個子目錄名字,最後這個字串以 0 結尾。如果檔名只列出主檔名,會自動加上『.DLL』,但萬一動態連結程式庫真的沒有副檔名,那麼就要在檔名後加上『.』表示結尾沒副檔名。如果沒有路徑名稱,LoadLibrary 會依下面順序搜尋 DLL 檔:

  1. 主程式所在目錄。
  2. 現在的工作目錄。
  3. Windows 系統目錄。對 XP 而言,內定的系統目錄名是『C:\WINDOWS\SYSTEM32』。
  4. Windows 目錄。對 XP 而言,內定的 Windows 目錄名是『C:\WINDOWS』。
  5. 環境變數 PATH 所列舉的目錄。

假如 LoadLibrary 成功地找到並載入 DLL 檔,EAX 中會傳回模組代碼;如果失敗,EAX 會傳回 NULL。

LoadLibraryEx API

HINSTANCE LoadLibraryEx(
    LPCTSTR     lpLibFileName,  // points to name of executable module
    HANDLE      hFile,          // reserved, must be NULL 
    DWORD       dwFlags         // entry-point execution flag 
   );

LoadLibraryEx 的第一個參數與 LoadLibrary 相同。hFile 被保留給以後使用,必須為 NULL。參數 dwFlags 是用來載入動態連結程式庫之後發生的動作,可以是下面幾項

LoadLibraryEx 如果成功的載入 DLL 檔,會傳回模組代碼;如果失敗,EAX 會傳回 NULL。

GetProcAddress API

GetProcAddress 是用來取得動態連結程式庫中某個匯出副程式或變數的位址,其原型如下:

FARPROC GetProcAddress(
    HMODULE hModule,    // handle to DLL module  
    LPCSTR  lpProcName  // name of function 
   );

參數,hModule,是動態連結程式庫的模組代碼,也就是 LoadLibrary 或 LoadLibraryEx 的傳回值。lpProcName 是某個副程式或變數的名稱,須以零為結尾,其字母拼法及大小寫是依據模組定義檔中 EXPORT 段落中所描述的,lpProcName 也可以是序數。如果 GetProcAddress 執行成功,會傳回 DLL 的副程式進入點位址;如果失敗則傳回 NULL。然後我們把這個進入點的位址記錄在某個變數堙A例如 lpProc 變數,當主程式要呼叫動態連結程式庫中的副程式時,就以下面方式呼叫副程式:

                push    參數一
                push    參數二
                ……
                call    lpProc

如果要使用 INVOKE 假指令呼叫的話,得事先宣告,可以利用兩次 TYPEDEF 假指令:

UBCD_PROTO      TYPEDEF         PROTO   :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
UBCD_PROC       TYPEDEF         PTR UBCD_PROTO

第一句是用來宣告五個參數的原型定義成 UBCD_PROTO,第二句再把 UBCD_PROC 指定為 UBCD_PROTO 的指標,接著就可以在資料段定義副程式位址,如要呼叫時,就可用 INVOKE 呼叫了。大概的程式碼如下:

.DATA
lpMulProc       UBCD_PROC       ?
…………
.CDOE
…………
    INVOKE  GetProcAddress,hModule,OFFSET szBigMul
 .IF eax
    mov     lpMulProc,eax
…………
    INVOKE  lpMulProc,OFFSET ubcdN1,OFFSET ubcdN2,OFFSET buffer,nN1,nN2

FreeLibrary

FreeLibrary 的原型是

BOOL FreeLibrary(
    HMODULE hLibModule 	        // handle to loaded library module  
   );

hLibModele 是動態連結程式庫的模組代碼,亦即呼叫 LoadLibrary 後的傳回值。當程式結束或不再呼叫 DLL 時,就可以把它從自己的記憶體位址空間卸載,就可以呼叫 FreeLibrary。

當使用『執行時動態連結』方式呼叫動態連結程式庫中的副程式時,都應該檢查 LoadLibrary、GetProcAddress 是否成功的正確執行,並傳回正確值;否則傳回 NULL 而不檢查,很容易造成錯誤。因為如果載入動態連結程式庫錯誤或取得副程式位址錯誤,都會引發程式跳到錯誤位址執行,必定引發程式終止。正因為有機會檢查動態連結程式庫或其中的副程式是否正確,所以即使找不到動態連結程式庫或副程式,也不致使主程式完全不能執行,我們可以小心的撰寫缺少有關該動態連結程式庫的功能,而不會影響到的功能仍能執行。

飾演『執行時動態連結』

底下的 TEST2.ASM 是利用『執行時動態連結』呼叫 DLL,原始碼如下:

.586
        .MODEL  FLAT,STDCALL
        OPTION  CASEMAP:NONE

INCLUDE         WINDOWS.INC
INCLUDE         USER32.INC
INCLUDE         KERNEL32.INC
INCLUDELIB      USER32.LIB
INCLUDELIB      KERNEL32.LIB

IDC_CALC        EQU             2001
IDC_EXIT        EQU             2002
IDC_N1          EQU             2003
IDC_N2          EQU             2004
IDC_ANSWER      EQU             2005
IDC_OP          EQU             2006

MAX_DIGIT       EQU             40      ;可計算40位數的加法或乘法

;底下兩行可以在呼叫 DLL 副程式時使用 INVOKE,不過此程式中小木偶用 call 呼叫大數加法,用 INVOKE 呼叫乘法
UBCD_PROTO      TYPEDEF         PROTO   :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
UBCD_PROC       TYPEDEF PTR     UBCD_PROTO

;*******************************************************************************
.CONST
szUBCDDLL       BYTE            'UBCDDLL.DLL',0 ;定義 DLL 檔名以及副程式名稱
szBigAdd        BYTE            'UBCD_ADDS',0   ;定義副程式名稱,因為在模組定義檔,以UBCD_ADDS代
szBigMul        BYTE            'UBCD_MULS',0   ;替ubcd_add;以UBCD_MULS代替ubcd_mul
szDlgName       BYTE            'AddNMul',0
szAdd           BYTE            '+',0
szMul           BYTE            '×',0
szDllLoadFail   BYTE            'UBCDDLL.DLL載入失敗!',0dh,0ah,'某些功能無法執行。',0
szAddFail       BYTE            '加法功能失效',0
szMulFail       BYTE            '乘法功能失效',0
;*******************************************************************************
.DATA
hInstance       HINSTANCE       ?
hModule         HANDLE          0       ;動態連結程式庫(UBCDDLL.DLL)的模組代碼
lpAddProc       DWORD           ?       ;UBCD_ADDS副程式進入點位址
lpMulProc       UBCD_PROC       ?       ;UBCD_MULS副程式進入點位址
index           DWORD           ?       ;被選定的運算子:加法:+,乘法:×
ubcdN1          BYTE            MAX_DIGIT+1 DUP (?)
ubcdN2          BYTE            MAX_DIGIT+1 DUP (?)
buffer          BYTE            2*MAX_DIGIT+1 DUP (?)
nN1             DWORD           ?
nN2             DWORD           ?
szAnswer        BYTE            '='
answer          BYTE            2*MAX_DIGIT+1 DUP (?)
;*******************************************************************************
.CODE
;-------------------------------------------------------------------------------
get_number      PROC    USES esi edi hDia:HANDLE,ID:DWORD,adrOperand:DWORD
        LOCAL   szuBcd[MAX_DIGIT+2]:BYTE
        LOCAL   nBcd:DWORD
        INVOKE  GetDlgItemText,hDia,ID,ADDR szuBcd,MAX_DIGIT+1
        mov     nBcd,eax
        mov     ecx,eax
        jecxz   no_n
        lea     esi,szuBcd
        mov     edi,adrOperand
        add     esi,ecx
        dec     esi
        clc
next:   mov     al,[esi]
        and     al,0fh
        dec     esi
        stosb
        loop    next
        mov     eax,ecx
        stosb
        mov     eax,nBcd
no_n:   ret
get_number      ENDP
;-------------------------------------------------------------------------------
DlgProc PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
        LOCAL   hOperator:HANDLE
.IF uMsg==WM_INITDIALOG
        ;載入圖示
                INVOKE  LoadIcon,hInstance,OFFSET szDlgName
                INVOKE  SendMessage,hDlg,WM_SETICON,ICON_SMALL,eax
        ;設定編輯框最多輸入位數
                INVOKE  GetDlgItem,hDlg,IDC_N1
                INVOKE  SendMessage,eax,EM_SETLIMITTEXT,MAX_DIGIT,NULL
                INVOKE  GetDlgItem,hDlg,IDC_N2
                INVOKE  SendMessage,eax,EM_SETLIMITTEXT,MAX_DIGIT,NULL
        ;取得復合框代碼
                INVOKE  GetDlgItem,hDlg,IDC_OP
                mov     hOperator,eax
        ;使『計算』按鈕失效
                INVOKE  GetDlgItem,hDlg,IDC_CALC
                INVOKE  EnableWindow,eax,FALSE
        ;載入UBCDDLL.DLL檔
                INVOKE  LoadLibrary,OFFSET szUBCDDLL
   .IF eax
                mov     hModule,eax
                INVOKE  GetProcAddress,eax,OFFSET szBigAdd
         .IF eax
                mov     lpAddProc,eax
                INVOKE  SendMessage,hOperator,CB_ADDSTRING,NULL,OFFSET szAdd    ;把加法運算子加入複合框
                INVOKE  GetDlgItem,hDlg,IDC_CALC                                ;使『計算』按鈕正常
                INVOKE  EnableWindow,eax,TRUE
         .ELSE
                INVOKE  MessageBox,hDlg,OFFSET szAddFail,NULL,MB_OK or MB_ICONWARNING
         .ENDIF
                INVOKE  GetProcAddress,hModule,OFFSET szBigMul
         .IF eax
                mov     lpMulProc,eax
                INVOKE  SendMessage,hOperator,CB_ADDSTRING,NULL,OFFSET szMul    ;把加法運算子加入複合框
                INVOKE  GetDlgItem,hDlg,IDC_CALC                                ;使『計算』按鈕正常
                INVOKE  EnableWindow,eax,TRUE
         .ELSE
                INVOKE  MessageBox,hDlg,OFFSET szMulFail,NULL,MB_OK or MB_ICONWARNING
         .ENDIF
   .ELSE
                INVOKE  MessageBox,hDlg,OFFSET szDllLoadFail,NULL,MB_OK or MB_ICONWARNING
   .ENDIF

.ELSEIF uMsg==WM_COMMAND
                mov     edx,wParam
                mov     eax,wParam
                shr     edx,10h         ;EDX=通知碼
                and     eax,0ffffh      ;EAX=控制元件識別碼
   .IF dx==BN_CLICKED
      .IF ax==IDC_EXIT
                jmp     exit
      .ELSEIF ax==IDC_CALC
                INVOKE  get_number,hDlg,IDC_N1,OFFSET ubcdN1
                mov     nN1,eax
                or      eax,eax
                jz      no_input
                INVOKE  get_number,hDlg,IDC_N2,OFFSET ubcdN2
                mov     nN2,eax
                or      eax,eax
                jz      no_input
         .IF WORD PTR index==0cfa1h     ;0cfa1h='+'
                push    nN2
                push    nN1
                push    OFFSET buffer
                push    OFFSET ubcdN2
                push    OFFSET ubcdN1
                call    lpAddProc
         .ELSEIF WORD PTR index==0d1a1h ;0d1a1h='×'
                INVOKE  lpMulProc,OFFSET ubcdN1,OFFSET ubcdN2,OFFSET buffer,nN1,nN2
         .ELSE
                jmp     no_input
         .ENDIF
                mov     edx,OFFSET buffer
                mov     edi,OFFSET answer
                add     edx,ecx
                dec     edx
@@:             mov     al,[edx]
                or      al,'0'
                dec     edx
                stosb
                dec     ecx
                jnz     @b
                mov     al,cl
                stosb
                INVOKE  SetDlgItemText,hDlg,IDC_ANSWER,OFFSET szAnswer
      .ENDIF
   .ELSEIF dx==CBN_SELENDOK
                INVOKE  SendDlgItemMessage,hDlg,IDC_OP,CB_GETCURSEL,0,0
                INVOKE  SendDlgItemMessage,hDlg,IDC_OP,CB_GETLBTEXT,eax,OFFSET index
   .ENDIF
no_input:

.ELSEIF uMsg==WM_CLOSE
exit:    .IF hModule
                INVOKE  FreeLibrary,hModule
         .ENDIF
                INVOKE  EndDialog,hDlg,NULL 

;其他訊息
.ELSE
        mov     eax,FALSE
        ret
.ENDIF
        mov     eax,TRUE
        ret
DlgProc ENDP
;-------------------------------------------------------------------------------
start:  INVOKE  GetModuleHandle,NULL
        mov     hInstance,eax
        INVOKE  DialogBoxParam,hInstance,OFFSET szDlgName,NULL,OFFSET DlgProc,NULL
        INVOKE  ExitProcess,eax
;*******************************************************************************
        END     start

接著在『命令提示字元』中,輸入:

E:\HomePage\SOURCE\DLL>ml test2.asm /link test.res [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: test2.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"test2.obj" /DEBUG
"/OUT:test2.exe"
"test.res"

E:\HomePage\SOURCE\DLL>

因為是執行時才動態的連結 ubcddll.dll 程式庫,所以連結時不需要匯入程式庫提供資料,故原始程式中也不需要含入包含檔,所以把

INCLUDE         UBCDDLL.INC     ;小木偶製作的UBCDDLL.INC包含檔
INCLUDELIB      UBCDDLL.LIB     ;小木偶製作的UBCDDLL.DLL動態連結程式庫

兩行給刪掉了,退一步來說,即使磁碟堙A沒『UBCDDLL.INC』和『UBCDDLL.LIB』也沒關係。但是必須在資料段埵萓璈w義動態連結程式庫的檔名以及副程式名稱:

szUBCDDLL       BYTE            'UBCDDLL.DLL',0
szBigAdd        BYTE            'UBCD_ADDS',0
szBigMul        BYTE            'UBCD_MULS',0

當程式以 LoadLibrary 載入 UBCDDLL.DLL 後,小木偶檢查了傳回值是否為 NULL,如果是的話,會在螢幕上出現

但不會終止程式,還是能輸入數字。假如您把 UBCDDLL.DEF 中的匯出副程式刪除一個,變成:

EXPORTS
        UBCD_MULS=ubcd_mul

依前法組譯連結後,執行 TEST.EXE,會出現下面的對話盒,當按下確定後,程式立即終止。

但是如果執行 TEST2.EXE,也會出現一個對話盒,如下圖左,告訴使用者,僅僅加法失效,按下確定後,程式仍繼續執行,乘法功能還是有效,如下圖右。

 

在 TEST2.ASM 堙A因為必須考慮四種情形:可能加法功能失效而乘法功能正常,也可能反過來乘法功能失效而加法功能正常,或兩者都正常或都失效。所以小木偶不是以檢查 index 之數值來判斷使用者選擇加法還是乘法,而是檢查『字元』為『+』表示使用者選擇加法運算,『×』表示乘法運算。欲取得使用者在複合框中的編輯框所選定的內容字串方法是,在處理 CBN_SELENDOK 通知碼的時候,先獲得使用者選定的項目索引 ( 也就是使用者選定了清單內的第幾個項目 ( 由零開始 ) ),再由此項目索引傳回字串,方法如下:

   .ELSEIF dx==CBN_SELENDOK
                INVOKE  SendDlgItemMessage,hDlg,IDC_OP,CB_GETCURSEL,0,0
                INVOKE  SendDlgItemMessage,hDlg,IDC_OP,CB_GETLBTEXT,eax,OFFSET index

所以在 TEST2.ASM 堛 index,其實是一個雙位元組的字串,代表『+』或『×』,這點與 TEST.ASM 不同。


DUMPBIN.EXE

假如您從網際網路上下載了一個動態連結程式庫,想觀察它有哪些副程式可以被主程式呼叫,可以使用 DUMPBIN.EXE。它是一個二進位檔案傾印工具,可以觀察 COFF 格式的目的檔、COFF 物件的標準程式庫、可執行檔,和動態連結程式庫 (DLL)。DUMPBIN.EXE 可以在 MASM32\BIN 子目錄塈鋮魽C

DUMPBIN 只能在『命令提示字元』中執行,其用法是:

DUMPBIN 選項 檔名

上式中的『選項』可以不只使用一個,每一個選項都以『/』或『-』開始,假如要查看 DLL 檔堶戚些副程式可以被呼叫,可以用『/exports』選項。底下的例子是查看 UBCDDLL.DLL 埵陪些副程式可供其他程式呼叫:

E:\HomePage\SOURCE\DLL>c:\masm32\bin\dumpbin /exports ubcddll.dll [Enter]

E:\HomePage\SOURCE\DLL>Microsoft (R) COFF Binary File Dumper Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file ubcddll.dll

File Type: DLL

  Section contains the following exports for ubcddll.dll

           0 characteristics
    4C95C7A6 time date stamp Sun Sep 19 16:19:50 2010
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0000100C UBCD_ADDS
          2    1 00001074 UBCD_MULS

  Summary

        1000 .rdata
        1000 .reloc
        1000 .text

E:\HomePage\SOURCE\DLL>

上面的『ordinal hint RVA name』表格中的『name』欄位告訴我們這個 DLL 檔案包含了哪些副程式。『RVA』是『Relative Virtual Address』的縮寫,亦即相對虛擬位址,從 1000H 開始,因為 UBCDDLL.ASM 的 DLLEntry 用去了 0CH 位元組,故 UBCD_ADDS 是從 100CH 開始。而 DLLEntry 不被其他程式呼叫,並沒有列在模組定義檔的『EXPORTS』段落內,所以此處不會被列出來。『hint』是副程式在 name 欄位的位置,由零開始遞增。『ordinal』是序號,可以在模組定義檔的『EXPORTS』段落內的項目中指定,也可以不指定,可以以序號代替副程式名稱來呼叫副程式。

假如您想看看程式呼叫了哪些副程式,也可以用 DUMPBIN,此時使用『/IMPORTS』選項。底下是察看 TEST.EXE 呼叫了哪些副程式,而這些副程式又包含在哪些動態連結程式庫堙C

E:\HomePage\SOURCE\DLL>c:\masm32\bin\dumpbin /imports test.exe [Enter]

E:\HomePage\SOURCE\DLL>Microsoft (R) COFF Binary File Dumper Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file test.exe

File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    USER32.dll
                40513C Import Address Table
                405084 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                 19B  LoadIconA
                 100  GetDlgItem
                 210  SendMessageA
                 228  SetDlgItemTextA
                  B8  EndDialog
                 20B  SendDlgItemMessageA
                 102  GetDlgItemTextA
                  92  DialogBoxParamA

    KERNEL32.dll
                405108 Import Address Table
                405050 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                  75  ExitProcess
                 111  GetModuleHandleA

    ubcddll.dll
                40518C Import Address Table
                4050D4 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                   1  UBCD_MULS
                   0  UBCD_ADDS

  Summary

        1000 .data
        1000 .idata
        1000 .rdata
        1000 .reloc
        1000 .rsrc
        2000 .text

E:\HomePage\SOURCE\DLL>

到第十九章回到首頁到第二十一章