DLL 載入時會映射到各進程獨立位址空間,全域變數各自獨立,一個進程修改不影響其他進程,無法直接共享資料。解決方案:記憶體映射檔。
在 Win32 位址空間保留一塊記憶體區域,實體儲存可用分頁檔(優於磁碟檔:無需建立/刪除暫存檔,系統自動管理分頁調度)。多進程映射同一映射物件即可共享資料。
library Project1;
uses
ShareMem, Windows, SysUtils, Classes;
const
MFileName: PChar = 'ShareData'; // 映射物件名稱
type
PGlobalDllData = ^TGlobalDllData;
TGlobalDllData = record
S: string[50];
I: Integer;
end;
var
GlobalData: PGlobalDllData; // 指向映射檔視圖的全域指標
MapHandle: THandle;
{ 供外部進程呼叫,取得共享資料指標 }
procedure GetDllData(var AGlobalData: PGlobalDllData); stdcall;
begin
AGlobalData := GlobalData;
end;
{ 開啟/建立映射檔並映射至進程位址空間 }
procedure OpenThisData;
var Size: Integer;
begin
Size := SizeOf(TGlobalDllData);
// 建立映射物件,使用分頁檔 (DWord(-1))
MapHandle := CreateFileMapping(DWord(-1), nil, PAGE_READWRITE, 0, Size, MFileName);
if MapHandle = 0 then RaiseLastWin32Error;
// 映射視圖至進程位址空間,傳回基址(各進程不同)
GlobalData := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
// 初始化測試資料
GlobalData^.S := 'TEST';
GlobalData^.I := 5;
if GlobalData = nil then begin
CloseHandle(MapHandle);
RaiseLastWin32Error;
end;
end;
{ 釋放資源 }
procedure CloseThisData;
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MapHandle);
end;
{ DLL 進入點 }
procedure DllEntryPoint(dwReason: DWORD);
begin
case dwReason of
DLL_PROCESS_ATTACH: OpenThisData; // 進程載入 DLL 時自動呼叫
DLL_PROCESS_DETACH: CloseThisData; // 進程卸載 DLL 時自動呼叫
end;
end;
{$R *.res}
exports GetDllData; // 導出供外部呼叫
begin
DllProc := @DllEntryPoint; // 指定 DLL 進入/離開處理程序
DllEntryPoint(DLL_PROCESS_ATTACH); // 手動觸發初始化(確保載入即建立映射)
end.
DllEntryPoint 收到 DLL_PROCESS_ATTACH → 呼叫 OpenThisData 建立映射物件「ShareData」並映射視圖,GlobalData 指向該視圖基址。GlobalData 指向各自基址(不同),但指向同一實體記憶體。GetDllData 取得指標,修改 GlobalData^.I := 10。GetDllData 取得指標,讀取 GlobalData^.I 即見 10,實現共享。DLL_PROCESS_DETACH → CloseThisData 解除映射、關閉控制代碼。CreateFileMapping 第一參數 DWord(-1) 表示使用系統分頁檔。PAGE_READWRITE 允許讀寫。MapViewOfFile 回傳各進程私有的虛擬位址,但映射同一實體頁框。