將檔案路徑放入剪貼板,讓使用者能在總管中 Ctrl+V 貼上實體檔案,需使用 CF_HDROP 格式配合 DROPFILES 結構。本文提供完整的 C++Builder 實作函式 CopyToClipboard,支援單一檔案路徑複製至剪貼板。
bool CopyToClipboard(AnsiString file)
{
if (file.IsEmpty()) return false;
// 1. 計算緩衝區大小:DROPFILES 結構(20 bytes) + 路徑字串 + 雙 null 結尾(2 bytes)
int nLen = 20 + file.Length() + 2;
// 2. 分配可移動、共享、零初始化的全域記憶體
HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, nLen);
if (hMem == NULL) return false;
// 3. 鎖定記憶體取得指標
char* Data = (char*)GlobalLock(hMem);
if (!Data)
{
GlobalFree(hMem);
return false;
}
// 4. 填寫 DROPFILES 結構
// 結構定義:
// typedef struct _DROPFILES {
// DWORD pFiles; // 檔案清單相對結構起始的位移(固定 20)
// POINT pt; // 點擊座標(拖放用,此處為 0)
// BOOL fNC; // 非客戶區旗標
// BOOL fWide; // 是否為 Unicode 路徑(FALSE=ANSI)
// } DROPFILES;
*(DWORD*)Data = 20; // pFiles = 20 (sizeof(DROPFILES))
// pt.x, pt.y, fNC, fWide 已由 GMEM_ZEROINIT 歸零
// 5. 複製檔案路徑至緩衝區(位移 20 之後)
strcpy(Data + 20, file.c_str());
// 結尾已有雙 null(配置時 +2,且 GlobalAlloc 零初始化)
GlobalUnlock(hMem);
// 6. 開啟剪貼板、清空、設定資料
bool success = false;
if (OpenClipboard(NULL))
{
EmptyClipboard();
if (SetClipboardData(CF_HDROP, hMem) != NULL)
{
hMem = NULL; // 所有權轉移給剪貼板,不需自行釋放
success = true;
}
CloseClipboard();
}
// 7. 若設定失敗,釋放記憶體
if (hMem) GlobalFree(hMem);
return success;
}
// 使用範例
void __fastcall TForm1::Button1Click(TObject *Sender)
{
CopyToClipboard("C:\\scandisk.log");
}
Offset Size Field 0 4 pFiles = 20 (檔案清單起始位移) 4 4 pt.x = 0 8 4 pt.y = 0 12 4 fNC = 0 (FALSE) 16 4 fWide = 0 (FALSE=ANSI, TRUE=Unicode) 20 ... 檔案路徑字串(以單一 null 分隔多檔,末尾雙 null)
總大小 = 20 + Σ(路徑長度+1) + 1(額外結尾 null)。
| 旗標 | 用途 |
|---|---|
| GMEM_MOVEABLE | 允許記憶體管理器移動區塊(剪貼板要求) |
| GMEM_DDESHARE | 允許 DDE/剪貼板共享(舊版相容) |
| GMEM_ZEROINIT | 自動零初始化,省去 memset |
SetClipboardData 成功(返回非 NULL)→ 記憶體所有權轉移給剪貼板,**不可**呼叫 GlobalFree。SetClipboardData 失敗(返回 NULL)→ 記憶體仍屬呼叫者,**必須**呼叫 GlobalFree 釋放。
bool CopyFilesToClipboard(const std::vector& files, bool useUnicode = false)
{
if (files.empty()) return false;
// 計算總大小
size_t total = 20; // DROPFILES
for (auto& f : files) total += f.Length() + 1;
total += 1; // 額外結尾 null
HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, total);
if (!hMem) return false;
char* Data = (char*)GlobalLock(hMem);
*(DWORD*)Data = 20;
// fWide 在 ANSI 版保持 0;Unicode 版需設為 1 並用 wchar_t 複製路徑
char* ptr = Data + 20;
for (auto& f : files)
{
strcpy(ptr, f.c_str());
ptr += f.Length() + 1;
}
*ptr = '\0'; // 雙 null 結尾
GlobalUnlock(hMem);
bool success = false;
if (OpenClipboard(NULL))
{
EmptyClipboard();
UINT fmt = useUnicode ? CF_HDROP : CF_HDROP; // 實際上 CF_HDROP 兩者通用,fWide 決定解讀方式
if (SetClipboardData(fmt, hMem) != NULL)
{
hMem = NULL;
success = true;
}
CloseClipboard();
}
if (hMem) GlobalFree(hMem);
return success;
}
CopyToClipboard("C:\\test.txt")。test.txt 被貼上(複製/移動視目的資料夾權限),即成功。| 現象 | 原因 | 修正 |
|---|---|---|
| 貼上無反應 | pFiles 位移錯誤(非 20) | 確認 *(DWORD*)Data = 20 |
| 路徑截斷/亂碼 | 緩衝區大小不足或缺雙 null | 配置 +2 位元組,確保零初始化 |
| OpenClipboard 失敗 | 其他程式佔用剪貼板 | 重試機制或延遲執行 |
| Unicode 路徑失效 | fWide=1 但資料為 ANSI | fWide=1 時必須寫入 wchar_t (UTF-16) |
掌握 CF_HDROP 格式,即可讓你的應用程式與 Windows Shell 完美整合,提供原生的檔案拖放/複製體驗。