標準格式(文本、圖片、組件)無法滿足結構化數據交換需求。例如:在應用程式間傳遞記錄、對象圖、加密數據塊。Delphi 提供 SetAsHandle/GetAsHandle 配合 RegisterClipboardFormat 實現任意二進制數據的剪貼簿存取。
| 函數/方法 | 用途 | 關鍵參數 |
|---|---|---|
RegisterClipboardFormat(lpszFormat) | 註冊全局唯一格式 ID | 格式名稱字串(如 'MyApp.Record.v1') |
GlobalAlloc(GMEM_DDESHARE, Size) | 分配可跨進程共享的可移動內存塊 | 返回 HGLOBAL 句柄(非指針) |
GlobalLock(hMem) | 鎖定內存塊獲取線性指針 | 返回 Pointer,操作完成需 GlobalUnlock |
Clipboard.SetAsHandle(fmt, hMem) | 將內存句柄放入剪貼簿 | 剪貼簿接管內存所有權,無需手動釋放 |
Clipboard.GetAsHandle(fmt) | 從剪貼簿取出內存句柄 | 調用者負責 GlobalLock/Unlock/GlobalFree |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Clipbrd;
type
TForm1 = class(TForm)
btnCopy: TButton;
btnPaste: TButton;
procedure FormCreate(Sender: TObject);
procedure btnCopyClick(Sender: TObject);
procedure btnPasteClick(Sender: TObject);
end;
// 自定義數據結構:僅包含定長字段,避免托管類型
TMyRecord = record
Name: array[0..15] of AnsiChar; // 固定長度,便於跨進程
Age: Byte;
Score: Single;
end;
var
Form1: TForm1;
CF_MYFORMAT: Word; // 全局格式 ID
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// 程式啟動時註冊一次,全系統唯一
CF_MYFORMAT := RegisterClipboardFormat('MyApp.TMyRecord.v1');
btnCopy.Caption := '複製記錄';
btnPaste.Caption := '粘貼顯示';
end;
procedure TForm1.btnCopyClick(Sender: TObject);
var
hMem: HGLOBAL;
PRec: ^TMyRecord;
begin
// 1. 分配共享內存
hMem := GlobalAlloc(GMEM_DDESHARE or GMEM_ZEROINIT, SizeOf(TMyRecord));
if hMem = 0 then RaiseLastOSError;
// 2. 鎖定並寫入數據
PRec := GlobalLock(hMem);
try
StrLCopy(PRec.Name, '張三', Length(PRec.Name));
PRec.Age := 28;
PRec.Score := 98.5;
finally
GlobalUnlock(hMem);
end;
// 3. 放入剪貼簿(所有權轉移)
Clipboard.SetAsHandle(CF_MYFORMAT, hMem);
ShowMessage('自定義記錄已放入剪貼簿');
end;
procedure TForm1.btnPasteClick(Sender: TObject);
var
hMem: HGLOBAL;
PRec: ^TMyRecord;
begin
if not Clipboard.HasFormat(CF_MYFORMAT) then
begin
ShowMessage('剪貼簿中無自定義格式數據');
Exit;
end;
hMem := Clipboard.GetAsHandle(CF_MYFORMAT);
if hMem = 0 then Exit;
PRec := GlobalLock(hMem);
try
ShowMessageFmt('姓名: %s'#13#10'年齡: %d'#13#10'分數: %.1f',
[PRec.Name, PRec.Age, PRec.Score]);
finally
GlobalUnlock(hMem);
// 關鍵:GetAsHandle 後調用者負責釋放!
GlobalFree(hMem);
end;
end;
end.
GMEM_DDESHARE?剪貼簿數據可能在進程間傳遞。GMEM_DDESHARE 標記告訴系統:此內存塊需支持 DDE/剪貼簿跨進程共享。缺少此標記會導致其他進程 GetAsHandle 失敗或數據損壞。
string、動態陣列、介面、Variant 等包含隱藏指針/引用計數,跨進程無效。array[0..N] of Char 替代 string。packed record 或显式對齊,確保雙端結構佈局一致。v1),升級時註冊新格式 ID,舊版兼容邏輯自行處理。SetAsHandle:剪貼簿接管所有權,調用後不可再 GlobalFree,也不可再訪問該句柄。GetAsHandle:調用者獲得所有權,必須成對 GlobalLock/Unlock 並最終 GlobalFree。GetAsHandle 同一格式,每次返回新複製的句柄,各自需釋放。原示例缺少 GlobalFree,會導致內存洩漏。本文在 btnPasteClick 的 finally 區塊補全釋放邏輯。
type
TVarDataHeader = record
DataSize: Cardinal; // 緊隨其後的實際數據長度
// 實際數據緊跟在後:Byte[DataSize]
end;
procedure CopyVarData(const AData: TBytes);
var
hMem: HGLOBAL;
P: PByte;
Header: ^TVarDataHeader;
begin
hMem := GlobalAlloc(GMEM_DDESHARE, SizeOf(TVarDataHeader) + Length(AData));
P := GlobalLock(hMem);
try
Header := PTVarDataHeader(P);
Header.DataSize := Length(AData);
Move(AData[0], P[SizeOf(TVarDataHeader)], Length(AData));
finally
GlobalUnlock(hMem);
end;
Clipboard.SetAsHandle(CF_MYFORMAT, hMem);
end;
clipbrd.exe 或第三方工具 InsideClipboard)檢查格式 ID 與數據大小。GlobalLock 後用斷點檢查內存佈局是否符合預期。SetAsHandle/GetAsHandle + RegisterClipboardFormat 是 Delphi 實現高性能、類型安全、跨進程自定義數據交換的標準路徑。核心要點:固定結構、共享內存、所有權轉移、版本管理。掌握此技術,可構建插件系統、進程間通訊、拖放複雜對象等高級功能。