首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi DLL 函數靜態與動態加載完整指南

     編輯:Delphi

外部 DLL 函數調用兩大模式:Static Load (靜態) vs Dynamic Load (動態)

Delphi 調用外部 DLL 函數有兩種根本不同的方式:靜態加載 (編譯期綁定,聲明簡單) 和 動態加載 (運行期綁定,靈活控制)。理解它們的語法、生命週期管理、錯誤處理差異,能根據場景選擇最適合的方案。

模式 1:靜態加載 (Static Load)


// 最簡單聲明:直接在單元級聲明
procedure DoSomething; external 'MYLIB.DLL';

// 帶調用慣例與重命名
function MyFunc(X, Y: Integer): Integer; stdcall; external 'MYLIB.DLL' name 'RealFuncName';

// 帶參數的完整聲明
procedure ProcessData(const Input: PChar; var Output: PChar; Len: Integer); cdecl; external 'DATA.DLL';

特點

  • 優點:語法簡潔、像呼叫普通函數、編譯器檢查參數類型
  • 缺點:啟動時必須找到 DLL、缺失即報錯無法啟動、無法延遲加載、無法根據條件選擇 DLL 版本
  • 適用場景:核心依賴、必須存在的系統 DLL、簡單工具函數

模式 2:動態加載 (Dynamic Load)


uses Windows;

type
  // 1. 定義函數指標類型 (必須匹配 DLL 導出簽名)
  TTimeRec = record
    Second: Integer;
    Minute: Integer;
    Hour: Integer;
  end;
  TGetTime = procedure(var Time: TTimeRec);  // 函數指標類型

var
  Time: TTimeRec;
  Handle: THandle;      // HMODULE
  GetTime: TGetTime;    // 函數指標變數

begin
  // 2. 載入 DLL
  Handle := LoadLibrary('MYLIB.DLL');  // 返回 0 表示失敗
  if Handle <> 0 then
  try
    // 3. 獲取函數地址
    @GetTime := GetProcAddress(Handle, 'GetTime');
    if @GetTime <> nil then
    begin
      // 4. 調用函數
      GetTime(Time);
      WriteLn('The time is ', Time.Hour, ':', Time.Minute, ':', Time.Second);
    end
    else
      ShowMessage('函數 GetTime 未找到');
  finally
    // 5. 釋放 DLL (引用計數歸零時真正卸載)
    FreeLibrary(Handle);
  end
  else
    ShowMessage('無法載入 MYLIB.DLL: ' + SysErrorMessage(GetLastError));
end;

特點

  • 優點:完全控制載入時機、失敗可優雅處理、支援插件架構、可根據條件載入不同版本、減少啟動依賴
  • 缺點:代碼較繁瑣、需手動定義函數指標類型、參數類型安全靠開發者保證
  • 適用場景:插件系統、可選功能模組、多版本 DLL 兼容、延遲加載優化啟動速度

關鍵 API 對照表

動態加載完整封裝模式 (推薦)


type
  TDynamicDLL = class
  private
    FHandle: THandle;
    function GetProc(const Name: string): Pointer;
  public
    constructor Create(const DLLName: string);
    destructor Destroy; override;
    function LoadFunction(const Name: string; var ProcPtr): Boolean;
    property Handle: THandle read FHandle;
  end;

constructor TDynamicDLL.Create(const DLLName: string);
begin
  FHandle := LoadLibrary(PChar(DLLName));
  if FHandle = 0 then
    RaiseLastOSError;
end;

destructor TDynamicDLL.Destroy;
begin
  if FHandle <> 0 then FreeLibrary(FHandle);
  inherited;
end;

function TDynamicDLL.GetProc(const Name: string): Pointer;
begin
  Result := GetProcAddress(FHandle, PAnsiChar(Name));
  if Result = nil then RaiseLastOSError;
end;

function TDynamicDLL.LoadFunction(const Name: string; var ProcPtr): Boolean;
begin
  Pointer(ProcPtr) := GetProc(Name);
  Result := ProcPtr <> nil;
end;

// 使用
var
  DLL: TDynamicDLL;
  MyFunc: function(X: Integer): Integer; stdcall;
begin
  DLL := TDynamicDLL.Create('MYLIB.DLL');
  try
    if DLL.LoadFunction('MyFunc', @MyFunc) then
      ShowMessage(IntToStr(MyFunc(123)));
  finally
    DLL.Free;
  end;
end;

調用慣例與參數傳遞對照

操作API說明
載入 DLLLoadLibrary / LoadLibraryEx返回 HMODULE (THandle),失敗返回 0
獲取函數地址GetProcAddress返回 FARPROC,需轉型為函數指標類型
釋放 DLLFreeLibrary引用計數 -1,歸零時卸載
獲取錯誤碼GetLastErrorLoadLibrary/GetProcAddress 失敗後調用

常見陷阱與避坑

  1. 靜態加載 DLL 缺失導致應用無法啟動:改用動態加載或安裝檢查
  2. 動態加載函數指標類型不匹配:導致堆棧破壞、隨機崩潰,必須精確對應 DLL 導出簽名
  3. 調用慣例不匹配:stdcall vs cdecl 導致參數錯位、堆棧不平衡
  4. 字串參數傳遞:DLL 為 ANSI 還是 Unicode,Delphi 版本差異,建議用 PAnsiChar/PWideChar 顯式指定
  5. FreeLibrary 過早調用:仍在使用函數指標時釋放 DLL → 訪問違規
  6. 線程安全:DLL 內部全局狀態非線程安全,需同步或每線程載入

選型決策樹

DLL 是系統核心/必須存在?
  ├─ 是 → 靜態加載 (簡單、類型安全)
  └─ 否 → 需要插件/可選/多版本/延遲加載?
          ├─ 是 → 動態加載 (靈活、可控)
          └─ 否 → 靜態加載 (開發效率優先)

掌握靜態與動態加載的語法細節、生命週期管理、錯誤處理模式,能讓 Delphi 與外部 DLL 互操作既安全又靈活。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
慣例參數順序堆棧清理寄存器傳參Delphi 指令字
register左→右被調用者是 (EAX,EDX,ECX)預設
pascal左→右被調用者否pascal
cdecl右→左調用者否cdecl
stdcall右→左被調用者否stdcall (WinAPI 標準)
safecall右→左被調用者否safecall (COM/自動異常)