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

Delphi 剪貼簿進階 (5):自定義格式與內存句柄操作(SetAsHandle/GetAsHandle)

     編輯:Delphi

場景:為什麼需要自定義剪貼簿格式?

標準格式(文本、圖片、組件)無法滿足結構化數據交換需求。例如:在應用程式間傳遞記錄、對象圖、加密數據塊。Delphi 提供 SetAsHandle/GetAsHandle 配合 RegisterClipboardFormat 實現任意二進制數據的剪貼簿存取。

核心 API 梳理

函數/方法用途關鍵參數
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.

關鍵技術細節深度解析

1. 為何必須用 GMEM_DDESHARE?

剪貼簿數據可能在進程間傳遞。GMEM_DDESHARE 標記告訴系統:此內存塊需支持 DDE/剪貼簿跨進程共享。缺少此標記會導致其他進程 GetAsHandle 失敗或數據損壞。

2. 記錄結構設計原則

  • 禁用托管類型:string、動態陣列、介面、Variant 等包含隱藏指針/引用計數,跨進程無效。
  • 固定長度:用 array[0..N] of Char 替代 string。
  • 對齊一致:packed record 或显式對齊,確保雙端結構佈局一致。
  • 版本號:格式名稱含版本(如 v1),升級時註冊新格式 ID,舊版兼容邏輯自行處理。

3. 內存所有權轉移規則

  • SetAsHandle:剪貼簿接管所有權,調用後不可再 GlobalFree,也不可再訪問該句柄。
  • GetAsHandle:調用者獲得所有權,必須成對 GlobalLock/Unlock 並最終 GlobalFree。
  • 若多次 GetAsHandle 同一格式,每次返回新複製的句柄,各自需釋放。

4. 原文代碼的遺漏修正

原示例缺少 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;

調試技巧

  1. 使用 Clipboard Viewer(Windows 內建 clipbrd.exe 或第三方工具 InsideClipboard)檢查格式 ID 與數據大小。
  2. 在 GlobalLock 後用斷點檢查內存佈局是否符合預期。
  3. 跨進程測試:編譯兩個獨立 EXE(複製端/粘貼端)驗證互操作。

總結

SetAsHandle/GetAsHandle + RegisterClipboardFormat 是 Delphi 實現高性能、類型安全、跨進程自定義數據交換的標準路徑。核心要點:固定結構、共享內存、所有權轉移、版本管理。掌握此技術,可構建插件系統、進程間通訊、拖放複雜對象等高級功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved