深刻Delphi下的DLL編程

做者:岑心 html

該文已經收藏,寫得太好了,備份一份算法

做者原處:http://www.cnblogs.com/shangdawei/p/4058452.html數據庫

引 言
相信有些計算機知識的朋友都應該據說過「DLL」。尤爲是那些使用過windows操做系統的人,都應該有過屢次重裝系統的「悲慘」經歷——不管再怎樣當心,沒有驅動損壞,沒有病毒侵擾,仍然在使用(安裝)了一段時間軟件後,發現windows系統愈來愈龐大,操做愈來愈慢,還不時的出現曾經能使用的軟件沒法使用的狀況,致使最終不得不重裝系統。這種狀況經常是因爲dll文件的大量安裝和衝突形成的。這一方面說明DLL的不足,另外一方面也說明DLL的重要地位,以致咱們沒法杜絕它的使用。
DLL(動態連接庫,Dynamic Link Library)簡單來講是一種可經過調用執行的已編譯的代碼模塊。DLL是windows系統的早期產物。當時的主要目的是爲了減小應用程序對內存的使用。只有當某個函數或過程須要被使用時,才從硬盤調用它進入內存,一旦沒有程序再調用該DLL了,纔將其從內存中清除。光說整個windows系統,就包括了成百上千個dll文件,有些dll文件的功能是比較專業(好比網絡、數據庫驅動)甚至能夠不安裝的。假如這些功能所有要包括在一個應用程序(Application program)裏,windows將是一個數百M大小的exe文件。這個簡單的例子很容易解釋DLL的做用,而調用DLL帶來的性能損失則變得可被忽略不計。
多個應用程序調用同一個DLL,在內存裏只有一個代碼副本。而不會象靜態編譯的程序那樣每個都必須所有的被裝入。裝載DLL時,它將被映射到進程的地址空間,同時使用DLL的動態連接並不是將庫代碼拷貝,而僅僅記錄函數的入口點和接口。
同時DLL還能帶來的共享的好處。一家公司開發的不一樣軟件可能須要一些公用的函數/過程,這些函數/過程多是直接的使用一些內部開發的DLL;一些經常使用的功能則能夠直接使用windows的標準DLL,咱們常說的windows API就是包含在windows幾個公用DLL文件裏的函數/過程;理論上(若是不牽涉做者的版權),知道一個DLL的聲明及做用(函數定義的輸入參數及返回值),咱們徹底能夠在不清楚其實現(算法或編譯方式)的狀況下直接使用它。
假如一個DLL中函數/過程的算法獲得了更新,BUG獲得了修正,整個dll文件會獲得升級。通常來講爲了保證向下兼容,調用聲明與返回結果應該保持不變。但實際上,即便是同一家開發的DLL,隨着功能的改善,也很難保證某個調用執行徹底不變。在使用其餘人開發的DLL時這種糟糕狀況更加的嚴重。好比我在一個繪圖程序裏使用了某著名圖形軟件商舊版本的DLL包,我全部的調用都是根據他發佈的舊版的聲明來執行的。假設用戶安裝了該軟件商的一個新軟件,致使其中部分DLL被更新升級,假如這些DLL已經有過改動,直接後果將是個人軟件再也不穩定甚至沒法運行!不要輕視這種狀況,事實上它是很廣泛的,好比windows在修正BUG和升級過程當中,就不斷改動它包含的那些DLL。每每新版DLL不是簡單的增長新的函數/過程,而是更換甚至取消了原有的聲明,這時候咱們再也沒法保證全部程序都運行正常。
DLL除了上面提到的改善計算機資源利用率、增長開發效率、隱藏實現細節外,還能夠包含數據和各類資源。好比開發一個軟件的多國語言版,就可使用DLL將依賴於語言的函數和資源分離出來,而後讓各地的用戶安裝不一樣對應的DLL,以獲取本地字符集的支持。再好比一個軟件必須的圖形、圖標等資源,也能夠直接放在dll文件中統一安裝管理。 編程

建立一個DLL

在進行後面的講解以前,我想你們應該先清楚一個概念:例程聲明的是一個指針變量,調用函數/過程,實際上是經過指針轉入該函數/過程的執行代碼。
咱們先嚐試用Delphi來創建一個本身的DLL文件。這個DLL包含一個標準的目錄刪除(包含子目錄及文件)函數。
創建DLL
經過Delphi創建一個DLL是很容易的。New一個新Project,選擇DLL Wizard,而後會生成一個很是簡單的單元。該單元不象通常的工程文件以program開始,而是以library開始的。
該工程單元缺省引用了SysUtils、Classes兩個單元。能夠直接在該單元的uses以後,begin … end部分以前添加函數/過程代碼,也能夠在工程中添加包含代碼的單元,而後該單元將會被自動uses。
接下來是編寫DLL例程的代碼。若是是引用單元裏的例程,須要經過聲明時添加export後綴引出。假如是直接寫在library單元中的,則沒必要再寫export了。
最後一步是在library單元的begin語句之上,uses部分及函數定義之下添加exports部分,並列舉須要引出的例程名稱。注意僅僅是名稱,不包含procedure或function關鍵字,也不須要參數、返回值和後綴。
exports語句後的語法有三種形式(例程指具體的函數/過程):
exports例程名;
exports例程名 index 索引值;
exports例程名 name新名稱;
索引值和新名稱便於其餘程序肯定函數地址;也能夠不指定,若是沒有使用Index關鍵字,Delphi將按照exports後的順序從1開始自動分配索引號。Exports後可跟多個例程,之間以逗號分隔。
編譯,build最終的dll文件。
需注意的格式
爲了保證生成的DLL能正確與C++等語言兼容,須要注意如下幾點:
儘可能使用簡單類型或指針做爲參數及返回值的類型。這裏的簡單類型是指C++的簡單類型,因此string字符串類型最好轉換成Pchar字符指針。直接使用string的DLL例程在Delphi開發的程序中調用是沒有問題的(有資料指出需加入ShareMem作爲第一單元以確保正確),但若是使用C++或其餘語言開發的程序調用,則不能保證參數傳遞正確;
雖然過程是容許的,可是最好習慣所有寫成函數。過程則返回執行正確與否的true/false;
對於參數的指示字好比const(只讀)、out(只寫)等等,爲保證調用的兼容性,最好使用缺省方式(缺省var,便可讀寫的地址);
使用stdcall聲明後綴,以保證正確的異常處理。16位DLL沒法經過這種方式處理異常,因此還得在例程最外層用Try … Except將異常處理掉;
通常不使用far後綴,除非爲了保持與16位兼容。
範例代碼
DLL工程單元: windows

複製代碼

library FileOperate;

uses
  SysUtils,
  Classes,
  uDirectory in 'uDirectory.pas';
{$R *.res}

exports
  DeleteDir;

begin

end.函數功能實現單元 :
unit uDirectory;

interface

uses
  Classes, SysUtils;
function DeleteDir( DirName : Pchar ) : boolean; export; stdcall;

implementation

function DeleteDir( DirName : Pchar ) : boolean;
var
  FindFile : TSearchRec;
  s : string;
begin
  s := DirName;
  if copy( s, length( s ), 1 ) <> '/' then
    s := s + '/';
  if DirectoryExists( s ) then
  begin
    if FindFirst( s + '*.*', faAnyFile, FindFile ) = 0 then
    begin
      repeat
        if FindFile.Attr <> faDirectory then
        begin
          // 文件則刪除
          DeleteFile( s + FindFile.Name );
        end else begin
          // 目錄則嵌套自身
          if ( FindFile.Name <> '.' ) and ( FindFile.Name <> '..' ) then
            DeleteDir( Pchar( s + FindFile.Name ) );
        end;
      until FindNext( FindFile ) <> 0;
      FindCLose( FindFile );
    end;
  end;
  Result := RemoveDir( s );
end;

end.

複製代碼

初始化及釋放資源

Delphi中初始化有幾種方法。一種是利用Unit的Initalization與Finalization這兩個小節(不知道「單元小節」?你該先去惡補Delphi語法了)進行該單元中變量的初始化工做。注意,DLL雖然在內存中只有一個副本,可是例程隸屬於調用者的不一樣進程空間。若是想初始化公共變量來達到多進程共享是不可行的,同時也要注意公共變量帶來的衝突問題。 數組

二是在library單元的begin … end部分進行DLL的初始化。假如想在DLL結束時有對應代碼,則能夠利用DLL自動建立的一個ExitProc過程變量,這是一個退出過程的指針。創建一個本身的過程,並將該過程的地址賦與ExitProc。由於ExitProc是DLL建立時就存在的,因此在begin … end部分就應該進行此步操做。同時創建一個臨時指針變量保存最初的ExitProc值,在本身的退出過程當中將ExitProc值賦回來。這樣作是爲了進行本身的退出操做後,能完成缺省的DLL退出操做(與在重載的Destory方法中inherated的意義是同樣的,完成了本身的destory,還須要進行缺省的父類destory才完整)。
示例以下: 網絡

複製代碼

library MyDLL;

var
  OldExitProc : pointer; // 公共變量,爲的保存最初的ExitProc指針以便賦回

procedure MyExitProc;
begin
  // 對應初始化的結束代碼
  ExitProc := OldExitProc; // 本身的退出過程當中要記住將ExitProc賦回
end;

begin
  // 初始化代碼
  OldExitProc := ExitProc;
  ExitProc := @MyExitProc;
end.

複製代碼

第三種方法和ExitProc相似,在System單元中預約義了一個指針變量DllProc(該方法須要引用 Windows單元)。在使用DLLProc時, 必須先寫好一個具備如下原型的程序: 數據結構

procedure DLLHandler(dwReason: DWORD); stdcall;

並在library的begin..end.之間, 將這個DLLHandler程序的執行地址賦給DLLProc中, 這時就能夠根據參數Reason的值分別做出相應的處理。示例以下: 函數

複製代碼

library MyDLL;

procedure MyDLLHandler( dwReason : DWORD );
begin
  case dwReason of
    DLL_Process_Attach :
      ; // 進程進入時
    DLL_Process_Detach :
      ; // 進程退出時
    DLL_Thread_Attach :
      ; // 線程進入時
    DLL_Thread_Detach :
      ; // 線程退出時
  end;
end;

begin
  // 初始化代碼
  DLLProc := @MyDLLHandler;
  MyDLLHandle( DLL_Process_Attach );
end.

複製代碼

可見,經過DLLProc 在處理多進程時比ExitProc更增強大和靈活。 工具

靜態(隱式)調用DLL

DLL已經有了,接下來咱們看如何調用並調試它。普通的DLL是不須要註冊的,可是要包含在windows搜索路徑中才能被找到。搜索路徑的順序是:當前目錄;Path路徑;windows目錄;widows系統目錄(system、system32)。
引入DLL例程的聲明方法
在須要使用外部例程(DLL函數/過程)的代碼以前預約義該函數/過程。即按DLL中的定義原樣聲明,且僅須要聲明。同時加上external後綴引入,與export引出相對應。根據exports的三種索引語法,也有三種肯定例程的方式(以函數聲明爲例):
function 函數名(參數表):返回值;external ’DLL文件名’;
function 函數名(參數表):返回值;external ’DLL文件名’ index 索引號;
function 函數名(參數表):返回值;external ’DLL文件名’ name 新名稱;
若是不肯定例程名稱,能夠用索引方式引入。若是按原名引入會發生衝突,則能夠用「新名稱」引入。
進行聲明後DLL函數的使用就和通常函數相同了。靜態調用方式簡單,但在啓動調用程序時即調入DLL做爲備用過程。若是此DLL文件不存在,那麼啓動時即會提示錯誤並馬上終止程序,無論定義是否使用。
快速查看DLL例程定義可使用Borland附帶的工具tdump.exe(在Delphi或BCB的bin目錄下),示例以下:
Tdump c:/windows/system/user32.dll > user32.txt
而後打開user32.txt文件,找到Exports from USER32.dll行,之下的部分就是DLL例程定義了,好比:

VA      Ord. Hint Name 
    -------- ---- ---- ---- 
    00001371    1 0000 ActivateKeyboardLayout 
    00005C20    2 0001 AdjustWindowRect 
    0000161B    3 0002 AdjustWindowRectEx

Name列就是例程的名稱,Ord就是該例程索引號。注意,該工具是不能獲得例程的參數表的。若是參數錯誤,調用DLL例程會引發堆棧錯誤而致使調用程序崩潰。

調用代碼

創建一個普通工程,在Main窗體上放置一個TShellTreeView控件(Samples頁),再放置一個按鈕,添加代碼以下:

複製代碼

function DeleteDir( DirName : Pchar ) : boolean; stdcall;
  external 'FileOperate.dll';

procedure TForm1.Button1Click( Sender : TObject );
begin
  if DirectoryExists( ShellTreeView.Path ) then
    if Application.MessageBox( Pchar( '肯定刪除目錄' + QuotedStr( ShellTreeView.Path )
      + '嗎?' ), 'Information', MB_YESNO ) = IDYes then
      if DeleteDir( Pchar( ShellTreeView.Path ) ) then
        showmessage( '刪除成功' );
end;

複製代碼

該範例調用的就是前面創建的DLL。
注意,聲明時要包括stdcall後綴,這樣才能保證調用Delphi開發的DLL的例程中相似PChar這樣的參數值傳遞正確。你們有興趣能夠試驗一下,不加入stdcall或者safecall後綴執行上面代碼,將不能保證成功傳遞字符串參數給DLL函數。

調試方法

在Delphi主菜單Run項目中選擇Parameters,打開「Run Parameters」對話框。

在Host Application中填入一個宿主程序(該程序調用了將要調試的DLL),還能夠在Parameters中輸入參數。保存內容,而後就能夠在DLL工程中設置斷點、跟蹤/單步執行了。
Run該DLL工程,而後將運行宿主程序。執行會調用DLL的操做,而後就能跟蹤進入該DLL的代碼,接下來的調試操做和普通程序是同樣的。
由於操做系統或其餘軟件影響的緣由,可能會出現進行了上述步驟仍然沒法正常跟蹤/中斷DLL代碼的狀況。

這時能夠試試在菜單Project |Options 對話框的 Linker 頁面裏將 EXE and DLL Options 中的Include TD32 debug info及include remote debug symbols兩個選項選中。
假如仍是不能中斷 那隻好另外創建一個引用執行代碼單元的應用程序,寫代碼調用例程調試完成後再編譯DLL了(其實該方法有時候蠻方便的,但有時候亦很是麻煩)。

引入文件
DLL比較複雜時,能夠爲它的聲明專門建立一個引入單元,這會使該DLL變得更加容易維護和查看。引入單元的格式以下:

複製代碼

unit MyDllImport; { Import unit for MyDll.dll }

interface

procedure MyDllProc;

implementation

procedure MyDllProc; external 'MyDll' index 1;

end.

複製代碼

這樣之後想要使用MyDll中的例程時,只要簡單的在程序模塊中的uses子句中加上MyDllImport便可。其實這僅僅是種方便開發的技巧,你們打開Windows等引入windows API的單元,能夠看到相似的作法。


動態(顯式)調用DLL

前面講述靜態調用DLL時提到,DLL會在啓動調用程序時即被調入。因此這樣的作法只能起到公用DLL以及減少運行文件大小的做用,並且DLL裝載出錯會馬上致使整個啓動過程終止,哪怕該DLL在運行中只起到微不足道的做用。
使用動態調用DLL的方式,僅在調用外部例程時纔將DLL裝載內存(引用記數爲0時自動將該DLL從內存中清除),從而節約了內存空間。並且能夠判斷裝載是否正確以免調用程序崩潰的狀況,最多損失該例程功能而已。
動態調用雖然有上述優勢,可是對於頻繁使用的例程,因DLL的調入和釋放會有額外的性能損耗,因此這樣的例程則適合使用靜態引入。
調用範例
DLL動態調用的原理是首先聲明一個函數/過程類型並建立一個指針變量。爲了保證該指針與外部例程指針一致以確保賦值正確,函數/過程的聲明必須和外部例程的原始聲明兼容(兼容的意思是一、參數名稱能夠不同;二、參數/返回值類型至少保持能夠相互賦值,好比原始類型聲明爲Word,新的聲明能夠爲Integer,假如傳遞的實參老是在Word的範圍內,就不會出錯)。
接下來經過windows API函數LoadLibrary引入指定的庫文件,LoadLibrary的參數是DLL文件名,返回一個THandle。若是該步驟成功,再經過另外一個API函數GetProcAddress得到例程的入口地址,參數分別爲LoadLibrary的指針和例程名,最終返回例程的入口指針。將該指針賦值給咱們預先定義好的函數/過程指針,而後就可使用這個函數/過程了。記住最後還要使用API函數FreeLibrary來減小DLL引用記數,以保證DLL使用結束後能夠清除出內存。這三個API函數的Delphi聲明以下:

Function LoadLibrary(LibFileName:PChar):THandle; 
Function GetProcAddress(Module:THandle;ProcName:PChar):TfarProc; 
Procedure FreeLibrary(LibModule:THandle);

將前面靜態調用DLL例程的代碼更改成動態調用,以下所示:

複製代碼

type
  TDllProc = function( PathName : Pchar ) : boolean; stdcall;

var
  LibHandle : THandle;
  DelPath : TDllProc;

begin
  LibHandle := LoadLibrary( Pchar( 'FileOperate.dll' ) );
  if LibHandle >= 32 then
  begin
    try
      DelPath := GetProcAddress( LibHandle, Pchar( 'DeleteDir' ) );
      if DirectoryExists( ShellTreeView.Path ) then
        if Application.MessageBox
          ( Pchar( '肯定刪除目錄' + QuotedStr( ShellTreeView.Path ) + '嗎?' ),
          'Information', MB_YESNO ) = IDYes then
          if DelPath( Pchar( ShellTreeView.Path ) ) then
            showmessage( '刪除成功' );
    finally
      FreeLibrary( LibHandle );
    end;
  end;
end;

複製代碼

Delphi開發DLL常見問題

字符串參數
前面曾提到過,爲了保證DLL參數/返回值傳遞的正確性,尤爲是爲C++等其餘語言開發的宿主程序使用時,應儘可能使用指針或基本類型,由於其餘語言與Delphi的變量存儲分配方法多是不同的。C++中字符纔是基本類型,串則是字符型的線形鏈表。因此最好將string強制轉換爲Pchar。
若是DLL和宿主程序都用Delphi開發,且使用string(還有動態數組,它們的數據結構相似)做爲導出例程的參數/返回值,那麼添加ShareMem爲工程文件uses語句的第一個引用單元。ShareMem是Borland共享的內存管理器Borlndmm.dll的接口單元。引用該單元的DLL的發佈須要包括Borlndmm.dll,不然就得避免使用string。
在DLL中創建及顯示窗體
凡是基於窗體的Delphi應用程序都自動包含了一個全局對象Application,這點你們是很熟悉的。值得注意的是Delphi建立的DLL一樣有一個獨立的Application。因此如果在DLL中建立的窗體要成爲應用程序的模式窗體的話,就必須將該Application替換爲應用程序的,不然結果難以預料(該窗體建立後,對它的操做好比最小化將不會隸屬於任何主窗體)。在DLL中要避免使用ShowMessage而用MessageBox。
建立DLL中的模式窗體比較簡單,把Application.Handle屬性做爲參數傳遞給DLL例程,將該句柄賦與Dll的Application.Handle,而後再用Application建立窗體就能夠了。
無模式窗體則要複雜一些,除了建立顯示窗體例程,還必須有一個對應的釋放窗體例程。對於無模式窗體須要十分當心,建立和釋放例程的調用都需在調用程序中獲得控制。這有兩層意思:一要防止同一個窗體實例的屢次建立;二由應用程序建立一個無模式窗體必須保證由應用程序釋放,不然假如DLL中有另外一處代碼先行釋放,再調用釋放例程將會失敗。
下面是DLL窗體的代碼:

複製代碼

unit uSampleForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TSampleForm = class( TForm )
    Panel : TPanel;
  end;

procedure CreateAndShowModalForm( AppHandle : THandle; Caption : PChar );
  export; stdcall;
function CreateAndShowForm( AppHandle : THandle ) : LongInt; export; stdcall;
procedure CloseShowForm( AFormRef : LongInt ); export; stdcall;

implementation

{$R *.dfm}

// 模式窗體
procedure CreateAndShowModalForm( AppHandle : THandle; Caption : PChar );
var
  Form : TSampleForm;
  str : string;
begin
  Application.Handle := AppHandle;
  Form := TSampleForm.Create( Application );
  try
    str := Caption;
    Form.Caption := str;
    Form.ShowModal;
  finally
    Form.Free;
  end;
end;

// 非模式窗體
function CreateAndShowForm( AppHandle : THandle ) : LongInt;
var
  Form : TSampleForm;
begin
  Application.Handle := AppHandle;
  Form := TSampleForm.Create( Application );
  Result := LongInt( Form );
  Form.Show;
end;

procedure CloseShowForm( AFormRef : LongInt );
begin
  if AFormRef > 0 then
    TSampleForm( AFormRef ).Release;
end;

end.

複製代碼

DLL工程單元的引出聲明:

exports 
  CloseShowForm, 
  CreateAndShowForm, 
  CreateAndShowModalForm;

應用程序調用聲明: 

procedure CreateAndShowModalForm(Handle : THandle;Caption : PChar);stdcall;external 'FileOperate.dll'; 
function  CreateAndShowForm(AppHandle : THandle):LongInt;stdcall;external 'FileOperate.dll'; 
procedure CloseShowForm(AFormRef : LongInt);stdcall;external 'FileOperate.dll';

除了普通窗體外,怎麼在DLL中建立TMDIChildForm呢?

其實與建立普通窗體相似,不過此次須要傳遞調用程序的Application.MainForm做爲參數:

複製代碼

function ShowForm( mainForm : TForm ) : integer;
stdcall
var
  Form1 : TForm1;
  ptr : PLongInt;
begin
  ptr := @( Application.mainForm ); // 先把DLL的MainForm句柄保存起來,也無須釋放,只不過是替換一下
  ptr^ := LongInt( mainForm ); // 用調用程序的mainForm替換DLL的MainForm
  Form1 := TForm1.Create( mainForm ); // 用參數創建
end;

複製代碼

代碼中用了一個臨時指針的緣由在Application.MainForm是隻讀屬性。MDI窗體的FormStyle不用設爲fmMDIChild。
初始化COM庫
若是在DLL中使用了TADOConnection之類的COM組件,或者ActiveX控件,調用時會提示 「標記沒有引用存儲」等錯誤,這是由於沒有初始化COM。DLL中不會調用CoInitilizeEx,初始化COM庫被認爲是應用程序的責任,這是Borland的實現策略。
你須要作的是一、引用Activex單元,保證CoInitilizeEx函數被正確調用了

二、在單元級加入初始化和退出代碼:

initialization 
   Coinitialize(nil); 
finalization 
   CoUninitialize; 
end.

三、 在結束時記住將鏈接和數據集關閉,不然也會報地址錯誤。
引出DLL中的對象
從DLL窗體的例子中能夠發現,將句柄作爲參數傳遞給DLL,DLL能指向這個句柄的實例。一樣的道理,從DLL中引出對象,基本思路是經過函數返回DLL中對象的指針,將該指針賦值到宿主程序的變量,使該變量指向內存中某對象的地址。對該變量的操做即對DLL中的對象的操做。
本文再也不詳解代碼,僅說明須要注意的幾點規則:
應用程序只能訪問對象中的虛擬方法,因此要引用的對象方法必須聲明爲虛方法;
DLL和應用程序中都須要相同的對象及方法定義,且方法定義順序必須一致;
DLL中的對象沒法繼承;
對象實例只能在DLL中建立。
聲明虛方法的目的不是爲了重載,而是爲了將該方法加入虛擬方法表中。對象的方法與普通例程是不一樣的,這樣作才能讓應用程序獲得方法的指針。
DLL畢竟是結構化編程時代的產物,基於函數級的代碼共享,實現對象化已經力不從心。如今相似DLL功能,但對對象提供強大支持的新方式已經獲得廣泛應用,象接口(COM/DCOM/COM+)之類的技術。進程內的服務端程序從外表看就是一個dll文件,但它不經過外部例程引出應用,而是經過註冊發佈一系列接口來提供支持。它與DLL從使用上有兩個較大區別:須要註冊,經過建立接口對象調用服務。能夠看出,DLL雖然經過一些技巧也能夠引出對象,可是使用不便,並且經常將對象化強制轉爲過程化的方式,這種狀況下最好考慮新的實現方法。
注:本文代碼在Delphi六、7中調試經過。
附:本文參考了「Delphi5開發人員指南」等書及資料。

delphi編寫dll心得,感恩前輩的總結(外一篇)

1。每一個函數體(包括exports和非exports函數)後面加 'stdcall;', 以編寫出通用的dll
2。exports函數後面必須加'export;'(放在'stdcall;'前面)
3。對於非exports函數可使用string類型,並且建議使用string類型進行參數傳遞
4。對於exports函數請使用PChar類型作參數傳遞
5。若是exports調用其餘函數,建議在exports函數體內使用變量過渡,而後再調用其餘函數;  也就說:儘可能不要把exports的參數再做爲參數調用其餘函數。
6。exports函數中若是有回傳參數:若是是非地址型的(如integer,boolean等基本類型)請  使用var前綴,若是是地址型的請不要使用var前綴(如PChar或數組等)。  對不使用var前綴要回傳的參數請使用內存拷貝類函數,如StrPCopy,CopyMemory,Move等。  緣由:dll和主應用程序並不能很好的共用一塊內存,因此必須進行內存拷貝才能正確將dll  中的內容回傳(拷貝)到主應用程序中。也所以對回傳的地址標識類參數,在調用dll以前必須  進行內存分配,例如Delphi中:AllocMem(n integer),Pb中:Space(nlong)。  注意在調用dll處dll函數聲明時,如果delphi參數聲明同dll中的參數聲明(回傳地址型的參數無需加  var),如果pb回傳參數必須加ref 前綴。
7。非exports函數的參數必須遵循規則:回傳參數加前綴var,你徹底能夠對待非exports函數同在Delphi應用  裏寫函數同樣
8。非exports函數中若是有數組參數,不管是否回傳,請加var前綴,它是地址調用
9。在dll中布爾型請注意bool和boolean的區別,在調用方環境中將可能引發不一樣的結果
10。在dll函數中儘可能避免使用delphi特有的數據類型或類,如TStringList等
11。減小use列表中沒必要要單元的引用,以減小dll的大小
12。dll的調試:可使用showmessage(需use dialogs)來調試,也能夠[run]->[Parameters]中配置宿主  程序來單步跟蹤dll的執行狀況
13。請注意dll中申請的全部內存必須正確釋放,不然dll可能在被調用n次以後會出現地址引用錯誤
14。在調用dll時候:
     1)運行環境:能夠直接放在應用程序同目錄下,也能夠放在一個文件夾下,若是放在一個文件夾下
你必須將此文件夾路徑設置到環境變量中,你能夠在應用程序中設置,也能夠在dll中設置:

複製代碼

var
  PathBuf : array [ 0 .. 2048 ] of Char;
  Pathstr : string;

begin
  FillChar( PathBuf, 2048, ' ' );
  windows.GetEnvironmentVariable( 'PATH', PathBuf, 2048 );
  Pathstr := string( PathBuf );
  Pathstr := Trim( Pathstr );
  if Pos( lowerCase( AppPath + 'tuxedo/dll' ), lowerCase( Pathstr ) ) <= 0 then
  begin
    Pathstr := Pathstr + ';' + AppPath + 'tuxedo/dll';
    SetEnvironmentVariable( 'PATH', PAnsiChar( Pathstr ) );
  end;
end;

複製代碼

2)開發環境:若delphi同運行環境沒什麼區別,它是直接編譯生成應用程序,並運行應用程序;    
若PB,必須將dll的路徑相對PB的開發工具的應用程序來設置,如放到pb9.0.exe同目錄下,固然你能夠
設置[HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/App Paths/]下面對應你的開
發工具的應用程序名稱目錄下設置dll所在的路徑(分號隔開添加既可,不要將原來的路徑覆蓋)
在dll中獲取dll的路徑:

複製代碼

var 
    Buffer:array [0..255] of char; 
    tmpstr:String; 
begin 
    GetModuleFileName(HInstance, Buffer, SizeOf(Buffer)); 
    tmpstr:=ExtractFilePath(Buffer); 
    //... 
end;

複製代碼

提示信息儘可能不要在dll中showmessage,最好是做爲信息參數傳回,宿主程序再根據結果來進行信息提示,  這樣也能夠不引用Dialogs單元

相關文章
相關標籤/搜索