Delphi探索 |
|
conundrum
尊榮會員 ![]() ![]() ![]() ![]() ![]() ![]() 發表:893 回覆:1272 積分:643 註冊:2004-01-06 發送簡訊給我 |
http://download.pchome.net/article/2002/12/14/142.htm
Delphi探索 (十)
注:本文由方圓(上海)翻譯兼創作,文中如有不當之處歡迎給作者來信共同探討(E-mail:ilovejhx@163.net),您的反饋是我寫作的最大的動力!謝謝! 上一章我把object Pascal程式的類類型及類引用全部講完了。 這一章我將要講述動態連結程式庫的內容。 ■本文的{生得到“Macula工作組”的大力支持!在此表示感謝! 概要介紹: 所謂動態連結程式庫(Dynamic-Link Libraries,簡稱DLLs),簡單地說就是一個可執行模組,其副檔名d.DLL,模組中包含了可以被其他應用程式或其他DLLs使用的常式和資源。跟 一般的的可執行程式副檔名d.EXE不同的是,DLLs沒有通常的主程序,但它有多個執行入口。 DLLs的特點在於它的代碼是在運行期動態地鏈結到調用它的程式中的,DLLs一旦載入,就能被多個應用程式或其他DLLs共用。 用Object Pascal編寫的程式可以使用用其他語言寫的DLLs,反之,用其他語言寫的程式,也可以調用Object Pascal寫的DLLs。 一:怎樣訪問DLL中的常式 1:靜態引入方式 在Object Pascal中,要訪問DLL中的常式最簡單的方式是在程式的開始處用External指示字列出要從DLL中調用的常式,這稱d靜態引入或隱式載入。這種方式下,DLL中的常式在程式執行前被裝載,如果指定的DLL出錯,程式也就不能運行。 External指示字除了用於從DLL中引入常式之外,還用於從單獨編譯的用彙編寫的程式中引入常式。 External的語法是這樣的: External 字串常量 [ name 字串常量] [index 整型常量] External指示字後可以什l都不跟,也可以跟一個字串或字串常量,這通常是一個DLL的檔案名,或者Name子句和Index子句。 如果External後面什l也不跟,這時候必須用編譯指令{$L}指定一個DLL的檔案名。程式示例如下: {$L MyDLLs.DLL} Procedure SetStr(Var Str:String);External; 注意:儘管Object Pascal本身是大小寫不敏感的,但在列出要引入的常式時,常式的識別字必須與DLL中相應輸出的常式的識別字完全一致包括大小寫也要一致,這是因此,DLL可能是用其他語言編寫的,其他語言可能是區分大小寫的。 External指示字後可以帶一個字串常量,用於指定DLL的檔案名,帶Name的子句給DLL中的常式換名,程式示例如下: Function GetStr:StdCall;External 'MyDlls.DLL' Name "MyGetStr'; 上例中,External指示字後跟了一個字串,指定從MyDlls.dll文件中引入函數GetStr,函數的調用約定方式是StdCall,Name子句指定把函數名GetStr改dMyGetStr,以後程式中調用這個常式時,是使用MyGetStr這個名字。 External和Name後跟的也可以是先前已聲明過的字串常量。 如果Dll是通過索引號輸出常式的,您就可以通過索引號來引入常式,方法是在External指示字後帶一個index子句,程式示例如下: Function GetStr:String;External 'testlib.dll' index 5; 通過索引號引入常式可以減少DLL的載入時間,因dWindows不必去檢索DLL的輸出常式表。 靜態引入方式雖然在使用上比較簡單,但也有缺點,就是如果DLL出錯,比如沒有找到DLL或者DLL中沒有指定要引入的常式,這時候程式就能再運行了。 所以調用DLL還有另一種方式,也稱顯式載入。即動態引入方式。 2:動態引入方式 動態引入方式就是使用Windows的兩個API即LoadLibrary和GetProcAddress,前者用於獲得DLL的控制碼,後者用於獲得DLL中常式的位元址,這種方式之所以被稱d動態的,是因d它不需要在程式的開始處把要引入的常式全部列出,只要在調用前引入,並且LoadLibrary可以指定不同的DLL,GetProcAddress可以指定不同的常式,最重要的是如果指定的DLL出錯,最多是API調用失敗,但不會導致程式終止,因此我們應該在程式中監視DLL的返回值,根據返回值作出相應的處理。程式示例如下: Var LibHandel:Thandlel; LibHandle:=LoadLibrary(Pchar(DLLname)); 以上首先聲明一個DLL的控制碼,然後用LoadLibrary獲得DLL的控制碼,其中DLLName是DLL的檔案名,由於LoadLibrary是Windows的API,因此調用時要強制轉換成Pchar類型。 Var Getcount:Function(Index:integer):Integer;StdCall; @Getcount:=GetProcAddress(LibHandle,'GetCount'); 以上聲明瞭一個常式指標,指向一個函數,有一個Integer類型的參數,返回類型也是Integer,調用約定是StdCall方式,然後用GetProcAddress獲得DLL中GetCount常式的位址。 Var MyCount:Integer; MyCount:=GetCount(1); 以上是調用DLL中常式GetCount的例子。 DLL只能輸出常式,雖然DLL中也含有變數(總體變數),但程式不能直接引入這些變數,只能通過介面常式間接地訪問這些變數。DLL也不能直接訪問調用DLL的程式中的變數。 注意,不管那種方式,在編譯期,編譯器不檢查DLL是否存在以及要引入的常式是否存在等問題,因此這類錯誤在編譯期是檢查不出來的。 3:引入整個單元 您可以把程式中要引入的常式的聲明入在一個單獨的單元中,然後把這個單元加到你程式的USES部分,這種方式仍然是靜態引入方式,不同的是把要引入的常式的聲明跟調用常式的單元分開,典型的例子是Delphi的Windows單元,這個單元集中聲明瞭一些要引入的常式,主要是Windows的API,這樣做的好處是有利於維護工作,例如當DLL發生變化時,一般您只需要修改聲明常式的單元並重新編譯。 二:怎樣寫自己的DLL 1:概述 使用Delphi和Object Pascal語言編寫自己的DLL,跟編寫一般的應用程式幾乎沒有什l大的區別,您可以首先設計應用程式的方法來設計您的DLL,當程式編制並且調試好以後,再對程式作一些修改。 首先是修改工程文件,一般的應用程式,它的工程文件是以Program打頭,而DLL的工程文件必須以Library打頭。編譯器根據這個區別,來決定是把程式編譯成EXE還是DLL文件。 編寫DLL的目的就是d了輸出常式供其他程式使用,因此在工程文件中把要輸出的常式用Exports引出。 要引出的常式最好採用StdCall調用約寫,這是d了讓用其他語言編寫的程式能夠調用DLL中的常式。 其實,最簡單的辦法是在FILE功能表下選擇新建命令時,從Delphi的模板中選擇建立一個DLL,這樣,這些工作它已經d你做好了。 一個典型的DLL工程文件示例如下: library Project2; { Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. } uses Sharemem, SysUtils, Classes; Exports InitExport name ExportEntryPoint; {$R *.RES} begin {初始化代碼} end. 2:在工程文件中導出常式 要導出常式是使用Exports保留字,其語法如下: Exports 識別字 [index 整型常量] [Name 字串常量] [resident] 注意:用Exports導出的常式必須是先前已聲明過的。 輸出的常式後可帶Index 子句,表示常式是用索引號導出的。相應地其他程式就可以用索引號導出DLL中的常式,用索引號導入常式要比用常式名導入的速度快。 Index子句中,在Index指示字後面必須跟一個整數常量,其值只能在1到32767之間。 程式示例如下: Exports Routines1 Index 1; Routines2; 上例中,輸出的Rountines1常式,其索引號d1,輸出的另外一個常式Routines2沒有帶Index子句。 輸出的常式後也可以帶一個Name子句,表示該常式用Name後指定的名稱導出。例如: Exports Routines1 name 'MyRoutines'; Name子句中,後面必須帶一個字串或字串常量。 如果沒有NAME子句,就還以常式原先的名稱輸出,不過識別字全部改d大寫。 輸出的常式後可以帶Resident指示字,表示當DLL被載入後,輸出資訊就駐留在記憶體中,這樣可以減少查找某個常式入口的時間特別是通過名字來查找常式入口的時間。 3:聲明要輸出的常式 當編寫自己的DLL時,程式中要輸出的常式(也稱介面常式)必須在單元的Interface部分的最後聲明,程式示例如下: Unit PassForm; Interface Ueses... Type... Var... Procedure Routines(Param1:integer);StdCall; Implementation 當DLL要被用基它語言編寫的程式調用時,常式的參數類型和返回類型必須是其他語言中合法的類型,最典型的例子就是Object Pascal一般使用Pascal風格的字串,而其他語言像C語言使用的是以NULL字元結束的字串。 聲明瞭介面常式後,就可以在程式的Implementation部分定義介面常式,這跟定義一般的常式沒有什l區別。 4:初始化代碼 在DLL的工程文件中,您可以在Begin和End 之間加入初始化代碼,正如可執行文件的工程文件中也有初始化代碼一樣,初始化代碼在DLL被載入時執行。 初始化代碼主要用於對DLL的總體變數初始化,還可以用ExitProc變數指定一個DLL卸載時要執行的過程。ExitProc變數在System單元中聲明,其聲明如下: Var ExitProc:Pointer; ExitProc指定的過程不能有參數。 在初始化代碼中,您還可以設置ExitCode變數,這個變數也是在System單元中聲明,缺省值d0,如果您在初始化代碼中把ExitCode設d非0值,DLL將被卸載並且通知調用DLL的程式DLL載入失敗。 當DLL卸載時,Delphi將自動執行ExitProc變數指定的過程(如果有的話)。 5:DLL的共用記憶體機制 如果DLL輸出的過程或函數帶有長字串類型的參數,或者函數的返回類型是長字串或帶有長字串元素的構造類型,Object Pascal規定無論是DLL本身還是調用它的程式必須把ShareMem單元加到Uses部分。這個規定還適用於這l一種情況,就是如果在一個程式或DLL中用New或GetMem分配記憶體,而釋放是在另一個程式或DLL中用Dispose或FreeMem。 ShareMem單元實際上是一個從DelphiMM.DLL中引入常式的介面單元。而DelphiMM.DLL提供的就是共用記憶體的管理功能。 如果需要把ShareMEM單元加到uses部分,ShareMEM單元必須加在最前面,也就是說在所有其他單元之前。 6:System單元中與DLL有關的幾個變數 實際上,我們已經介紹過System單元中的兩個變數ExitProc和ExitCode,還有幾個變數是與DLL有關的。 IsLibrary是個布林類型的變數,在可執行程式中訪問,這個變數的值是FALSE,而在DLL中訪問,這個變數的值是TRUE。 在DLL載入期間,Hinstance變數表示DLL的實例控制碼,而cmdLine 變數在DLL中總是nil。 本章把“動態連結程式庫的內容”全部講述完畢了。 下一章我將講述程式中的“異常處理”。 附錄: 我收集的一些API函數。 一、高級編程介面函數Advapi32.dll 函數原型 說明 AbortSystemShutDown 終止使用InitiatezSystemShutdown函數吭r
動系統 AccessCheck 伺服器應用程式用該函數來控制驗證客戶對 某物件的訪問許可權 AccessCheckAndAuditAlarm 執行一個訪問驗證並{生相應的查核消息, 或確定某客戶過程是否具有必要的特權 AddAccessAllowedAce 把一個允許訪問的訪問控制項ACE添加到訪 問控制表ACL上,並允許訪問指定的安全標 識符SID AddAccessDeniedAce 向訪問控制表添ACL加一個被拒絕的訪問控 制項ACE AddAce 向一個指定的訪問控制表ACL增加一個或多 個訪問控制項ACE AddAuditAccessAce 向系統訪問控制表ACL添加一個系統查核訪 問控制項ACE,訪問的指定識別字SID將被查 核 AdjustTokenGroups 調整指定訪問標記中的組 AdjustTokenPrivileges 允許或禁止指定訪問標記中的特權 AllocateAndInitializeSid 用多達8個子許可權分配和初始化一個安全標 識符 AllocateLocallyUniqueId 分配一個本地唯一的識別字LUID AreAllAccessesgranted 檢查一個允許訪問的遮罩中是否出現了訪問 遮罩中所要求的全部訪問特權 AreAnyAccessesGranted 測試一個所要求的訪問許可權集中是否有任何 許可權被一個訪問遮罩所允許 BackupEventLog 向一個備份檔案保存給定的事件記錄 ChangServiceConfig 改變指定伺服器的配置參數 ClearEventLog 清除給定的事件記錄,並有選擇的將此記錄 文件保存在一個備份檔案中 CloseEventLog 關閉給定的事件記錄 CloseServiceHandle 關閉由OpenSCManager返回的服務控制管理 程式控制碼,或由OpenService或 CreateService返回的服務物件控制碼 ControlService 向指定的伺服器發送一個控制碼 CopySid 向指定的緩衝區拷貝安全識別字SID CreatePrivateObjectSecurity d一個新的、被保護的伺服器物件分配和初 始化一個自相關的安全描述符 CreateProcessAsUser 創建一個新進程和它的主線程,以執行指定 的可執行文件 CreateService 創建一個服務物件,並將它添加到指定的服 務控制管理程式的資料庫中 DeleteAce 從一個訪問的控制表ACL中刪除指定的訪問 控制項ACE DeleteSwrvice 從服務控制管理資料庫中刪除指定的服務 DEregisterEventSource 關閉由RegisterEventSource函數返回的句 柄 Destroy PrivateObject Security 刪除一個被保護的服務物件的安全描述符 DeplicateToken 通過複製一個已存在的標記來創建一個新的 訪問標記 EnumDependentServices 枚舉依賴於另一個指定服務的服務 EnumServicesStatus 枚舉指定的服務控制程式資料庫中每個服務 的名稱和狀態 EqualPrefixSid 測試兩個安全識別字SID的字首值是否相等 EqualSid 比較兩個安全識別字SID值是否相等 FindFirstfreeAce 在訪問控制表ACL中檢取一個指向第一個自 由位元組的指標 FreeSid 釋放由AllocteAndInitializSid函數分配的 安全識別字 GetAce 獲得指向訪問控制表中的指定訪問控制項的 指標 GetAclInformation 檢取有關訪問控制表的資訊 GetFileSecurity 獲取一個文件或目錄安全性的指定資訊 GetKernelObjectSecurity 檢取保護一個記憶體物件的安全描述符 GetLengthSid 返回一個合法安全識別字結構的位元組長度 GetNumberOfEventLogRecords 檢取給定事件記錄中的記錄數 GetOldestEventLogRecord 檢取給定事件記錄中最老記錄的絕對記錄號 GetprivateObjectSecurity 從一個被保護伺服器物件的安全描述符中檢 取資訊 GetSecurityDescriptorControl 檢取一個安全描述符的控制與修正資訊 GetSecurityDescriptorDacl 檢取指向安全描述符的自由訪問控制表的指 針 GetSecurityDescriptorGroup 檢取一個安全描述符的基本資訊 GetSecurityDescriptorLength 返回一個結構合法的SECURITY_DESCRIPTOR 結構的長度 GetSecurityDescriptorOwner 檢取安全描述符的擁有者資訊 GetSecurityDescriptorSacl 檢取指定安全描述符中系統訪問控制表的指 針 GetSeviceDisplayName 檢取和指定服務名相關的可顯示名 GetSeviceKeyName 返回和指定顯示名相關的服務名 GetSidIdentifierAuthority 返回指定安全識別字中 SID_IDENTIFIER_AUTHORITY結構的位址 GetSidLengthRequired 返回存儲帶有指定數目子許可權的安全識別字 結構所需的緩衝區長度 GetSidAuthority 返回指定安全識別字結構中指定的子許可權的 地址 GetSidSubAuthorityCount 返回包含子許可權計數的安全識別字結構的域 地址 GetTokenInformation 檢取有關存取標記指定的類型的資訊 GetUserName 返回當前線程的用戶名 ImpersonateLoggedOnUser 讓調用線程類比一個用戶 ImpersonateNamedPipeClient 類比一個有名管道的客戶應用程式 ImpersonateSelf 獲得一個類比調用進程安全描述表的存取標 記 InitializeAcl 創建一個新的訪問控制表 InitializeSecurityDescriptor 初始化一個新的安全描述符 InitializeSid 初始化一個安全識別字 InitiateSystemShoutdown 啟動指定電腦的一次關機和重妍佢r
IsTextUnicode 驗證指定的緩衝區是否包含Unicode文本形 式 IsValidAcl 使一個訪問控制表失效 IsValidSecurityDescriptor 驗證一個SECURITY_DESCRIPTOR結構是否合 法 IsValidsid 通過驗證修正數目是否在已知的範圍內,以 及子許可權數目是否小於最大值來檢驗一個安 全識別字是否合法 LockServiceDatabase 加鎖指定的服務控制管理程式的資料庫 LogonUser 登錄一個新用戶 LookupAccountName 接受一個系統和帳戶名稱作d輸入 LookupAccountSid 接受一個安全識別字作d輸入 LookupPrivilegeDisplayName 檢取一個表示特權的可顯示名稱 LookupPrivilegeName 檢取一個在特定系統上以指定的局部唯一標 識符表示的特權所對應的名稱 LookupPrivilegeValue 檢取一個指定系統上使用的局部唯一識別字 MakeAbsoluteSD 利用一個自相關格式的安全描述符作d模板 ,創建一個絕對格式的安全描述符 MakeSelfRelativeSD 利用一個絕對格式的安全描述符作d模板, 創建一個自相關格式的安全描述符 MapGenericMask 將指定的存取遮罩中的一般存取映射d特定 和標準的存取 NotifyBootConfigStatus 通知服務控制管理器,妍囧t統以接受配置 NotifyChangeEventLog 當一個事件被寫進登錄文件時,應用程式接 受通知 ObjectCloseAuditAlarm 當刪除一個物件的控制碼時該函數{生查核消 息 ObjectOpenAuditAlarm 當客戶應用程式試圖獲准對一個物件進行訪 問,或創建一個新的物件時該函數{生查核 消息 ObjectPrivilegeAuditAlarm 當客戶應用程式試圖通過使用控制碼對伺服器 應用程式對該物件進行帶特權的操作時,該 函數{生查核消息 OpenBackupeventLog 打開一個備份事件記錄的控制碼 OpenEventLog 打開一個事件記錄的控制碼 OpenprocessToken 打開一個與過程相聯繫的訪問標記 OpenSCManager 在指定機器上創建與服務控制管理程式的聯 系,並打開指定的資料庫 OpenService 打開一個已存在的服務 OpenThreadToken 打開與指定線程相聯繫的訪問標記 PrivilegeCheck 測試指定訪問標記所代表的安全描述符,以 確定該安全描述符是否包含了指定的許可權 PrivilegedSeviceAuditAlarm 當客戶試圖進行許可權系統服務操作時,該函 數{生查核消息 QueryServiceConfig 檢取指定服務的配置參數 QueryServiceLockStatus 檢取指定服務控制管理程式資料庫的封鎖狀 態 QueryServiceObjectSecurity 檢取服務物件的安全描述符 QueryServiceStatus 檢取指定服務的當前狀態 ReadEventLog 從給定的事件記錄中讀取全部入口 RegCloseKey 釋放給定關鍵字的控制碼 RegConnectRegistry 建立與另一電腦上預定義控制碼的聯繫 RegCreateKey 創建給定的關鍵字,若登錄時該關鍵字已存 在,則打開該關鍵字 RegCreateKeyEx 創建給定的關鍵字,若登錄時該關鍵字已存 在,則打開該關鍵字 RegDeleteKey 刪除指定的關鍵字,但不能刪除具有子關鍵 字的關鍵字 RegDeleteValue 把一個已命名的值從給定的登記關鍵字中刪 除 RegEnumKey 枚舉給定的已打開關鍵字的子關鍵字 RegEnumKeyEx 枚舉給定的已打開關鍵字的子關鍵字 RegEnumValue 枚舉給定的已打開關鍵字的子關鍵字的值 RegFlushKey 把給定的已打開關鍵字所有屬性寫入登記中 RegGetKeySecurity 檢取保護給定的已打開關鍵字的安全描述符 RegisterEventSource 返回源名稱所表示的事件控制碼以便使用該句 柄記錄事件 RegisterServiceCtrlHandler d指定的服務登記一個函數以處理服務控制 請求 RegLoadKey 創建一個在HKEY_USER或 HKEY_LOCAL_MACHINE下的子關鍵字並把指定 文件中的登記資訊存儲到這個關鍵字中 RegNotifyChangeKeyValue 指出一個關鍵字或它的任何子關鍵字所發生 的變化 RegOpenKey 打開指定的關鍵字 RegOpenKeyEx 打開指定的關鍵字 RegQueryInfoKey 檢取指定的登記關鍵字的資訊 RegQueryMultipleValues 檢取一個已打開的登記關鍵字值名列表的類 型和資料 RegQueryValue 在登記中檢取與指定關鍵字未命名的值相聯 系的值 RegQueryValueEx 檢取與一個已打開登記關鍵字相聯繫的指定 值名稱的類型和資料 RegReplaceKey 用另一個文件代替支援一個關鍵字和它的子 關鍵字的文件 RegRestoreKey 讀取指定文件中的登記資訊,並把它裝入給 定的關鍵字中 RegSaveKey 把給定關鍵字和它的子關鍵字以及值存放到 一個新的文件中 RegSetKeySecurity d一個已打開的登記關鍵字設置安全描述符 RegSetValue 把一個值和給定關鍵字聯繫起來 RegSetValueEx 把資料存放到一個已打開的登記關鍵字的值 域中 RegUnloadKey 從登記中退出指定的關鍵字和子關鍵字 ReportEvent 在給定記錄的末端寫一個入口 RevertToSelf 終止類比的客戶應用程式 SetAclInformation 設置指定的訪問控制表的資訊 SetFileSecurity 設置一個文件或目錄物件的安全性 SetKernelObjectSecurity 設置一個內部物件的安全性 SetprivateObjectSecurity 修改一個私有物件的安全性 SetSecurityDescriptorDacl 設置一個隨機訪問控制表中的資訊 SetSecurityDescriptorGroup 設置一個絕對格式安全描述符的原始組資訊 SetSecurityDescriptorOwner 設置一個絕對格式安全描述符的擁有者資訊 SetSecurityDescriptorSacl 設置一個系統訪問控制表中的資訊 SetServiceBits 使用服務控制管理器和伺服器的服務登記一 服務的服務類型 SetServiceObjectSecurity 設置調用服務的服務物件的安全描述符 SetServiceStatus d調用服務更新服務控制管理程式的狀態信 息 SetThreadToken 分配一個類比標誌給線程,或讓線程停止使 用一個類比標誌 SetTokenInformation d一個特定的訪問標記設置各種類型的資訊 StartService 妍吨@個服務 StartServiceCtrlDispatcher 把一個服務過程的主線程連接到服務控制管 理程式中 UnlockServiceDatabase 解鎖一個服務控制管理程式的資料庫 (舊文章有空也是要看看)
|
adonis
高階會員 ![]() ![]() ![]() ![]() 發表:140 回覆:258 積分:159 註冊:2002-04-15 發送簡訊給我 |
針對
引言: 如果External後面什麼也不跟,這時候必須用編譯指令{$L}指定一個DLL的檔案名。程式示例如下: {$L MyDLLs.DLL} Procedure SetStr(Var Str:String);External;為什麼我依其{$L XXX.dll} (XXX.dll是我在使用的DLL檔)方式卻告訴我找不到 XXX.dll檔,若以一般的靜態宣告的方式,也就是在 External 後接 XXX.dll就可以正常並且得以使用其內部的函數,那表示我的XXX.dll是存在的是沒問題的,但更特別的是以編譯指令時若直接指定我的 XXX.dll 的絕對位址時,卻會告訴我那是一個 Bad 的檔案,不知有哪位大大可以告知我,謝謝。
------
我也在努力學習中,若有錯謬請見諒。 |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |