本文用通俗易懂的語言介紹Linux平臺上共享對象庫(SO)的基本概念及主要優(yōu)點,通過剖析在Delphi for Linux中應用SO與在Delphi for Windows中應用DLL的異同,以編程實例講述了Linux平臺的SO庫文件的組成、SO庫文件的函數(shù)重載、特殊編譯指令、采用Delphi for Linux創(chuàng)建SO的編程規(guī)則、使用前的Linux系統(tǒng)設(shè)置,以及在Delphi for Linux中用隱式或顯式鏈接方法裝入和使用SO函數(shù)的基本方法、經(jīng)驗及技巧,并對應用SO可能出現(xiàn)的問題進行了探討和分析。
共享對象庫基本概念
Delphi for Linux是Borland公司推出的基于Linux平臺的、面向?qū)ο蟮目梢暬_發(fā)工具,是目前Linux平臺上很好的應用開發(fā)工具。Delphi for Linux也稱Kylix。大家用Kylix開發(fā)Linux應用程序時,可能使用過Linux操作系統(tǒng)本身帶的大量SO文件。SO是一種特殊的運行文件,包含若干方法、對象和資源,它不能直接運行,但可以被Kylix應用程序或其它可執(zhí)行文件動態(tài)調(diào)用。SO文件擴展名為.so,編譯前源文件擴展名為.dpr。本文所舉例子均在Red Hat Linux 7.3及Kylix 3.0環(huán)境下調(diào)試編譯通過,并可正常運行。
圖1是Kylix主程序與SO庫的層次關(guān)系圖。從中可看出使用SO庫有以下幾個優(yōu)點。
圖1 Kylix主程序與SO庫的層次關(guān)系圖
◆ 多個Kylix程序或它的多個單元文件可通過接口共用一個SO庫文件。另一方面,某一個Kylix程序,可通過多個接口使用多個SO庫文件。這樣,SO變成一種可共用的資源,實現(xiàn)真正的“資源共享”,大大縮小了Kylix應用程序的執(zhí)行代碼,增強了軟件的可重用性。
◆ 將SO文件作為Kylix應用程序的公共調(diào)用模塊設(shè)計時,由于其獨立于應用程序,軟件升級時只需修改SO庫文件及編譯SO,無需更改及重編譯Kylix應用主程序。
◆ 不僅可使用Kylix編寫SO庫,還可使用C或C++等常用語言來編寫,只要遵循特定的接口規(guī)范。
共享對象庫的創(chuàng)建
1.SO庫文件的構(gòu)成
SO庫文件和Kylix標準單元文件的內(nèi)部結(jié)構(gòu)基本相同,也有聲明、實現(xiàn)及初始化部分。區(qū)別之一在于SO庫只是其它程序可以調(diào)用的方法(包括函數(shù)及過程)集合。區(qū)別之二庫程序以library關(guān)鍵字而非project開頭啟動其項目文件;庫程序包含有exports語句,其列出要向外部提供的導出函數(shù)及過程。下面是SO庫文件代碼的簡單例子,用以說明其構(gòu)成。
library MyFirstSO; uses SysUtils, classes ; { Delphi for Windows 中引用類庫為Windows } function Add (A:Char;B:Char):Integer;cdecl;overload; begin Result := Ord (A) + Ord (B) ; end; function Add (A:Integer;B:Integer):Integer;cdecl;overload; begin Result := A + B ; end; function Double (N:Integer):Integer;cdecl begin Result := N * 2; end; exports Add (A:Integer;B:Integer), Add (A:Char;B:Char) name 'AddChar', Double;
2.SO庫文件中的函數(shù)重載
SO庫也可以使用重載函數(shù)(即多個函數(shù)使用相同名稱、不同參數(shù)),使用時需在重載的函數(shù)聲明后標上overload指令。Kylix可以用原名稱導出一個重載函數(shù),在exports從句中表示其參數(shù)表。若要導出多個重載函數(shù),則要在exports從句中用name字句指定不同名稱,以區(qū)別重載。這可從上面的例子MyFirstSO中看出,Add是重載函數(shù),為調(diào)用時區(qū)分,一個用原函數(shù)聲明Add導出,另一個用AddChar導出。
3.SO庫的特殊編譯指令
編譯后生成的SO庫運行文件使用lib前綴和.so擴展名。考慮到實際命名規(guī)則與版本和支持符號鏈,Kylix在Object Pascal語言中引入了幾個特殊編譯指令,這些在Delphi中沒有什么意義。庫源文件MyFirstSO.dpr編譯后產(chǎn)生的執(zhí)行文件為libMyFirstSO.so。
◆ $SOPREFIX 改變名稱前綴,默認為lib(正常庫)或bpl(Kylix包)。用前綴區(qū)別兩種庫是因為Linux的庫用單一擴展(.so)。
◆ $SOSUFFIX 在庫名與擴展名之間增加文本,指定版本或其它信息。
◆ $SOVERSION 在擴展名之后增加版本號。
◆ $SONAME 表示相關(guān)符號鏈名,由編譯器自動生成。
例如,下列代碼生成庫libsimple.so.2.0.1和符號鏈libsimple.so.2。
library simple ; uses SysUtils,Classes; //函數(shù)定義省略 {$SOVERSION '2.0.1'} {$SONAME 'libsimple.so.2'}
共享對象庫的使用
Kylix應用程序使用SO庫時,可以采用兩種方式:一種是隱式鏈接(Implicit linking),也稱靜態(tài)裝入;另一種是顯式鏈接(Explicit Linking),也稱動態(tài)裝入。下面分別介紹這兩種鏈接方式的使用方法、技巧及將窗體對象放入SO庫的技術(shù)。
1.使用前的系統(tǒng)設(shè)置
自定義SO庫建好后,Kylix應用程序調(diào)用時會報錯,這是因為Kylix找不到新建庫,必須對系統(tǒng)進行相關(guān)設(shè)置。這與在Delphi for Windows中使用DLL庫不同,DLL庫建好后只需將編譯后的DLL文件放到Delphi主程序目錄下即可使用。操作步驟如下:
◆ 將編譯好的SO庫文件放到Linux系統(tǒng)庫目錄/lib或/usr/lib下,或者在Linux系統(tǒng)庫路徑shell變量LD_LIBRARY_PATH中加入自定義SO庫文件所在路徑。
◆ 在根用戶(root)下,用ldconfig命令刷新庫緩沖區(qū)。
◆ 對Kylix執(zhí)行文件使用ldd命令,查看該程序所關(guān)聯(lián)的SO庫。
2.隱式鏈接
隱式鏈接是指在應用程序開始執(zhí)行時就將SO庫文件加載到應用程序中。實現(xiàn)隱式鏈接并不難,只需在應用程序中加入庫函數(shù)的聲明語句及庫的external定義從句,則庫函數(shù)可以和一般局部函數(shù)一樣使用。比如,要使用libMyFirstSO.so中的Add函數(shù),則只要在應用程序中增加下面語句:
function Add (A:Integer;B:Integer):Integer;cdecl ;
external 'libMyFirstSO.so';
3.顯式鏈接
顯式鏈接是應用程序在執(zhí)行過程中可根據(jù)實際需要隨時加載SO庫文件,也可以隨時卸載SO庫文件,還可在運行時進行SO庫的切換。而這些是隱式鏈接無法做到的。與隱式鏈接相比,顯式鏈接具有更大的靈活性。
在Kylix中,要動態(tài)裝入庫和調(diào)用導出函數(shù)可以用Delphi仿真代碼或自然Linux方法。下面分別介紹這兩種方法。
(1)用Delphi仿真代碼動態(tài)裝入
在Windows中動態(tài)裝入DLL是用Windows API函數(shù)—LoadLibrary或Delphi提供的SafeLoadLibrary函數(shù)完成的。找到庫后,程序調(diào)用Windows API函數(shù)—GetProcAddress搜索DLL導出函數(shù)。若找到匹配,則返回所請求函數(shù)指針,并將這個函數(shù)指針轉(zhuǎn)換成適當類型和調(diào)用。使用完后調(diào)用FreeLibrary,從內(nèi)存中釋放庫。
Kylix中使用Pascal RTL仿真函數(shù)實現(xiàn)SO庫動態(tài)裝入。下面的例子只列出Kylix應用程序中與動態(tài)鏈接相關(guān)部分,而非完整Kylix單元文件代碼。
unit DynaForm; interface uses SysUtils,Classes,Qcontrols,Qforms; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form1:TForm1; implementation {$R *.XFM} type TComputeInteger = function (x:Integer;y:Integer):Integer;cdecl; //調(diào)用庫函數(shù)接口類型定義 procedure TForm1.Button1Click(Sender:TObject); var Handle :Thandle ; Compute :TcomputeInteger; begin Handle:=LoadLibrary('libMyFirstSO.so');//動態(tài)裝入庫 if Handle<>0 then //找到庫 begin Compute:=TcomputeInteger(GetProcAddress(Handle,'Add'); //搜索庫函數(shù)Add,并返回函數(shù)指針 if Assigned(Compute) then ShowMessage(IntToStr(Compute(10,20));//使用庫函數(shù) FreeLibrary(Handle);//釋放庫 end else ShowMessage('Library not found'); end;
(2)用Linux自然代碼動態(tài)裝入
也可以使用Libc系統(tǒng)單元中的低級Linux函數(shù),這樣可使用更多參數(shù)、更好地控制系統(tǒng)。使用的Linux函數(shù)分別為dlopen(打開并裝入庫函數(shù))、dlsym(搜索庫函數(shù))、dlclose(釋放庫)。因此,上例中調(diào)用庫的代碼變?yōu)椋?
procedure TForm1.Button1Click(Sender:TObject); var Handle :Pointer ; Compute :TcomputeInteger; begin Handle:=dlopen('libMyFirstSO.so');//動態(tài)裝入庫 if Handle<>nil then //找到庫 begin Compute:=TcomputeInteger(dlsym(Handle,'Add'); //搜索庫函數(shù)Add,并返回函數(shù)指針 if Assigned(Compute) then ShowMessage(IntToStr(Compute(10,20));//使用庫函數(shù) dlclose(Handle);//釋放庫 end else ShowMessage('Library not found'); end;
(3)SO庫中窗體對象的使用
除了包含函數(shù)和過程的庫之外,還可以將Kylix建立的窗體放在共享對象中,這可以是對話框或其它窗體。
生成新的庫對象之后,只要在庫源文件的聲明部分增加對窗體單元文件的引用,然后在窗體單元文件中編寫生成和使用窗體的導出函數(shù)。下面的例子實現(xiàn)Kylix主程序通過調(diào)用SO庫窗體處理函數(shù),來激活模態(tài)對話框以選擇顏色,并更新應用主窗體顏色。步驟如下:
◆ 創(chuàng)建具有特定功能的窗體單元文件ScrollF,窗體對象為FormScroll。下面代碼僅用于說明,并非完整的程序。
unit ScrollF; interface uses SysUtils, Classes, QControls, QForms; type TFormScroll = class(TForm) //對象及方法定義省略 end; var FormScroll:TformScroll;
◆ 在窗體單元文件ScrollF的實現(xiàn)部分編寫使用窗體FormScroll的導出函數(shù)GetColor。其功能是激活對話框?qū)ο驠ormScroll以選擇顏色,并將顏色值返回。代碼如下:
function GetColor (Col: LongInt):LongInt;cdecl; var FormScroll:TformScroll; begin Result := Col; //函數(shù)返回缺省值 try FormScroll := TFormScroll.Create (Application); try FormScroll.SelectedColor := Col; //初始化顏色 if FormScroll.ShowModal = mrOK then //顯示對話框 Result := FormScroll.SelectedColor; //返回顏色值 finally FormScroll.Free; end; except on E: Exception do MessageDlg ('Error in FormDLL: ' +E.Message, mtError, [mbOK], 0); end; end;
◆ 在窗體文件ScrollF的定義部分增加導出函數(shù)GetColor的聲明。代碼如下:
function GetColor (Col:LongInt):LongInt;cdecl;
◆ 在庫源文件FormSO.dpr的定義部分增加對窗體單元ScrollF的引用。代碼如下:
library FormSO; uses ScrollF in 'ScrollF.pas' {FormScroll}; exports GetColor; end.
◆ 編譯庫文件FormSO.dpr,生成SO庫執(zhí)行文件libFormSO.so。
現(xiàn)在,就可以在Kylix應用程序中以隱式或動態(tài)方法來調(diào)用庫libFormSO.so中的窗體類函數(shù)GetColor。
應注意的問題
盡管SO庫為開發(fā)者帶來諸多好處,但由于其與一般Kylix程序的差異,如果把它當作后者一樣使用可能會帶來一些問題。下面列出使用中可能出現(xiàn)的幾個問題:
(1)在SO庫中大量引用CLX圖形類庫,將對SO庫帶來不良影響。
(2)如果編譯庫和主執(zhí)行文件而不運行庫,則會得到CLX代碼和數(shù)據(jù)的兩個拷貝。例如,可能得到兩個不同的全局Application對象,庫中的Application對象不能正確初始化。
(3)如果庫中需要大量調(diào)用CLX圖形類或CLX控件類對象,建議最好使用軟件包(Package)——一種特殊的共享庫;而若庫以非圖形類處理為主,如數(shù)值計算,則用SO庫更為方便。
掌握并能熟練地使用共享對象庫技術(shù),將有助于開發(fā)出功能更強、重用性更好、擴展更為靈活的應用程序。本文以實例方式,通過SO與DLL的比較介紹了共享對象庫(SO)的功能及使用方法。希望通過本文,讀者能使用自己創(chuàng)建的SO庫來開發(fā)基于Linux的應用程序。
|