DLL 載入時會映射至各進程獨立位址空間,全域變數**不共享**。若需多進程即時共享資料,Windows 提供記憶體映射檔機制:在系統分頁檔保留命名共享記憶體區域,多進程透過同一名稱映射至各自位址空間,實現高效、無需實體檔案的跨進程資料交換。本文提供完整 DLL 實作與呼叫範例。
CreateFileMapping(INVALID_HANDLE_VALUE, ...):建立/開啟分頁檔支援的映射物件,指定名稱實現跨進程共享。MapViewOfFile(...):將映射物件視圖映射至呼叫進程位址空間,取得指標存取資料。UnmapViewOfFile/CloseHandle:釋放資源。
library ShareDataDLL;
uses
SysUtils, Windows, Classes;
const
MapName = 'Global\MyApp_SharedData'; // Global\ 前綴確保跨 Session 可見
type
PSharedData = ^TSharedData;
TSharedData = record
Status: array[0..49] of Char; // 固定長度字串,避免指標跨進程失效
Counter: Integer;
Timestamp: TDateTime;
end;
var
MapHandle: THandle = 0;
SharedMem: PSharedData = nil;
{ 供外部進程呼叫:取得共享記憶體指標 }
procedure GetSharedData(var AData: PSharedData); stdcall;
begin
AData := SharedMem;
end;
{ 初始化映射 }
procedure InitMapping;
var
Size: Integer;
begin
Size := SizeOf(TSharedData);
// 建立映射物件:分頁檔、讀寫、大小、名稱
MapHandle := CreateFileMapping(
THandle(-1), // INVALID_HANDLE_VALUE,使用分頁檔
nil, // 預設安全性
PAGE_READWRITE, // 保護屬性
0, Size, // 高/低 32 位大小
PChar(MapName) // 共享名稱
);
if MapHandle = 0 then RaiseLastOSError;
// 映射視圖至進程位址空間
SharedMem := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, Size);
if SharedMem = nil then begin
CloseHandle(MapHandle);
RaiseLastOSError;
end;
// 初始化預設值
StrPCopy(SharedMem.Status, 'INITIALIZED');
SharedMem.Counter := 0;
SharedMem.Timestamp := Now;
end;
{ 清理映射 }
procedure CleanupMapping;
begin
if SharedMem <> nil then UnmapViewOfFile(SharedMem);
if MapHandle <> 0 then CloseHandle(MapHandle);
SharedMem := nil;
MapHandle := 0;
end;
{ DLL 進程附加/分離入口 }
procedure DllEntryPoint(dwReason: DWORD);
begin
case dwReason of
DLL_PROCESS_ATTACH: InitMapping;
DLL_PROCESS_DETACH: CleanupMapping;
end;
end;
exports
GetSharedData index 1;
begin
DllProc := @DllEntryPoint;
DllEntryPoint(DLL_PROCESS_ATTACH); // 立即初始化
end.
unit MainUnit;
interface
uses Windows, SysUtils, Classes, Forms, Controls, StdCtrls;
type
TSharedData = record
Status: array[0..49] of Char;
Counter: Integer;
Timestamp: TDateTime;
end;
PSharedData = ^TSharedData;
TForm1 = class(TForm)
btnRead: TButton;
btnWrite: TButton;
edtStatus: TEdit;
edtCounter: TEdit;
procedure FormCreate(Sender: TObject);
procedure btnReadClick(Sender: TObject);
procedure btnWriteClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
SharedData: PSharedData;
procedure GetSharedData(var AData: PSharedData); stdcall; external 'ShareDataDLL.dll' index 1;
end;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
GetSharedData(SharedData); // 取得共享記憶體指標
end;
procedure TForm1.btnReadClick(Sender: TObject);
begin
if SharedData <> nil then begin
edtStatus.Text := StrPas(SharedData.Status);
edtCounter.Text := IntToStr(SharedData.Counter);
end;
end;
procedure TForm1.btnWriteClick(Sender: TObject);
begin
if SharedData <> nil then begin
StrPCopy(SharedData.Status, edtStatus.Text);
SharedData.Counter := SharedData.Counter + 1;
SharedData.Timestamp := Now;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// 無需釋放共享記憶體,DLL 會在進程分離時清理
end;
end.
| 項目 | 說明 |
|---|---|
名稱前綴 Global\ | 確保跨 Terminal Services Session 共享;本機單 Session 可省略。 |
| 固定長度陣列 | 共享結構體中**禁止使用動態字串、動態陣列、介面、物件參考**,因其內部指標指向進程私有堆疊,跨進程無效。改用 array[0..N] of Char、Integer、Double 等實值型別。 |
| 同步化 | 多進程並發讀寫需外部同步: • Mutex (跨進程互斥鎖)• Event (通知機制)• Critical Section 僅限進程內,不可跨進程。 |
| 安全性 | CreateFileMapping 可傳遞 SECURITY_ATTRIBUTES 限制存取權限 (ACL)。 |
| 大小限制 | 單一映射物件大小受限於虛擬記憶體空間 (32-bit 約 2GB),大數據建議分段映射或使用檔案映射。 |
// DLL 端新增
var
MutexHandle: THandle;
procedure InitMapping;
begin
// ... 原有代碼 ...
MutexHandle := CreateMutex(nil, False, PChar(MapName + '_Mutex'));
end;
procedure LockShared; stdcall;
begin
WaitForSingleObject(MutexHandle, INFINITE);
end;
procedure UnlockShared; stdcall;
begin
ReleaseMutex(MutexHandle);
end;
exports
GetSharedData index 1,
LockShared index 2,
UnlockShared index 3;
呼叫端寫入前呼叫 LockShared,寫入後 UnlockShared,確保資料一致性。
記憶體映射檔是 Windows 進程間共享資料的最高效方式:零拷貝、系統管理分頁、命名跨進程可見。封裝於 DLL 中,配合固定結構體、Mutex 同步、進程附加/分離自動管理,即構成穩健、可重用的跨進程共享元件。掌握此技術,可解決多進程架構下的即時資料同步、狀態共享、協同運算等核心需求。