更新紀錄(僅初版,2026年08月21日 發布)
- 初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22176621)
以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。
Go Komura(2026)。〈剪貼簿與拖放的運作方式 ── 在業務應用程式中正確處理 OLE 資料傳輸〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/windows-clipboard-drag-drop-ole-data-transfer/
- DOI(已登錄存檔)
- 10.5281/zenodo.22176621
- DOI(上次登錄版本)
- 10.5281/zenodo.22176622
「貼上 Excel 的表格,格式就散掉」「自家應用程式複製的內容貼進 Word,變不成預期的樣子」「希望能用拖放把檔案收進來」── 在業務應用程式的改版案子裡,這類諮詢相當常見。
這些都是很日常的操作,但只要把剪貼簿想成「放進一份資料的箱子」,就會看錯它的運作方式。實際上,它是把同一份內容同時以多種格式放上,再由貼上端挑選自己看得懂的格式的機制。同一次複製之所以會因貼上位置而得到不同結果,原因就在這裡。
「關掉複製來源就貼不上」的問題,牽涉到需要時才產生資料的「延遲轉譯」。而 OLE 拖放(D&D)也是把與 OLE 剪貼簿相同的資料表示 IDataObject,透過 COM 的介面交遞出去。複製貼上與 D&D,是把資料格式共用、只換了搬運方式的一組相連功能。
本文是寫給中小企業的資訊人員與 Windows 應用程式開發者的說明。先掌握格式的運作方式,再看貼上端、複製端、監看端的實作,然後進入剪貼簿歷程、同步、RDP 的管理,以及 OLE D&D 的注意事項。
1. 先講結論
要掌握的主軸有以下三個。
- 複製端提供多種格式,貼上端從中挑選。文字是
CF_UNICODETEXT,檔案路徑清單是CF_HDROP,帶格式的文字則以已註冊格式「HTML Format」為基本。1234 - 要設計的不只是資料的格式,還有驗證與存活期間。貼上的資料要當成不可信的外部輸入來驗證。使用延遲轉譯的複製端,必須連結束時的定案處理一起實作。監看要用
AddClipboardFormatListener與WM_CLIPBOARDUPDATE,讀取時的競用則用重試來因應。5678 - 要明確界定資料會流到哪裡,以及交出去之後會發生什麼。剪貼簿歷程、雲端同步、RDP 重新導向都是管理對象。OLE D&D 需要以
OleInitialize做 STA 初始化,而從一般權限放到提升權限的應用程式上會被 UIPI 擋住。另外,Move是「來源資料會消失」的契約。910111213
如果想依目的閱讀,請從下表的章節開始。
| 困擾與目的 | 先確認什麼 | 要讀的章節 |
|---|---|---|
| 貼上 Excel 的表格就散掉 | 複製端提供的格式,以及貼上端挑選的格式 | 第 2〜4 章 |
| 想讓自家應用程式的複製也能用在其他應用程式 | 多種格式的提供,以及結束後仍保留的處理 | 第 5 章 |
| 想偵測複製並自動收進來 | 接聽者的註冊與讀取的重試 | 第 6 章 |
| 不想讓機密經由歷程、同步、RDP 留下 | 應用程式端的排除格式與組織的原則 | 第 6.3 節與第 7 章 |
| 想實作 D&D、只有提升權限時不會動 | OLE 初始化、權限邊界、效果與路徑的驗證 | 第 8〜9 章 |
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 20 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
2. 剪貼簿的真面目 ── 不是「一份資料」,而是「同一份內容的多種格式」
剪貼簿是應用程式之間共享資料的機制。同一個桌面上的應用程式都能使用,但精確的共享單位是視窗站(window station)。不同的使用者工作階段或 RDP 工作階段,各自擁有不同的剪貼簿。RDP 之所以能複製貼上,是因為重新導向功能把兩邊接了起來(第 7 章)。
使用上的大原則是由使用者主導。不在使用者不知情的狀況下擅自存取資料,這是官方的設計方針。1
複製端會先清空剪貼簿,然後把同一份內容以多種格式、由表達力高的排到低依序放上。6 把試算表軟體複製表格的場面用概念方式排出來,大致如下。
| 優先順序 | 格式 | 內容 |
|---|---|---|
| 1 | 應用程式私有格式 | 連公式與格式都完整的內部表示(供貼回同一個應用程式) |
| 2 | HTML Format | 保留表格結構與格式的 HTML 片段 |
| 3 | CSV | 以分隔字元區隔儲存格的文字 |
| 4 | CF_UNICODETEXT | 以 Tab 分隔的純文字 |
| 5 | 影像格式 | 表格外觀的點陣圖 |
貼上端從這份清單中取出的,是自己看得懂的格式。同一次複製在 Word 裡變成帶格式的表格、在記事本裡變成 Tab 分隔的文字,是因為兩者挑的格式不同。
也就是說,「結果隨貼上位置而不同」這件事本身並不是錯誤。必須把複製端的提供與貼上端的挑選合起來看。
flowchart TB
accTitle: 同一次複製為何依貼上位置得到不同結果
accDescr: 複製端把同一份內容以多種格式放上剪貼簿,貼上端挑選自己看得懂的格式,因此 Word 得到帶格式的表格,記事本得到 Tab 分隔的文字
copy["複製端:試算表軟體"] --> cb["剪貼簿(同一份內容的多種格式)"]
cb --> f1["應用程式私有格式"]
cb --> f2["HTML Format"]
cb --> f3["CSV"]
cb --> f4["CF_UNICODETEXT"]
f2 -->|"Word 挑選"| word["帶格式的表格"]
f4 -->|"記事本挑選"| notepad["Tab 分隔的文字"]
反過來說,開頭提到的「格式散掉」「貼出奇怪的東西」這類諮詢,幾乎都能歸結為其中一端在格式的挑選方式或提供方式上的問題。貼上端的部分在第 4 章談,複製端的部分在第 5 章談。
3. 標準格式與已註冊格式 ── CF_UNICODETEXT、CF_HDROP、HTML Format
3.1. 標準格式 ── 文字要用 Unicode 這一邊
作業系統事先定義好的格式稱為標準格式。在業務應用程式裡最常出現的是以下這幾個。2
| 格式 | 值 | 內容 |
|---|---|---|
| CF_TEXT | 1 | ANSI 文字(依賴字碼頁) |
| CF_UNICODETEXT | 13 | Unicode 文字。文字要以這個為準 |
| CF_HDROP | 15 | 檔案路徑清單(HDROP 控制代碼) |
| CF_DIB | 8 | 裝置無關點陣圖 |
| CF_LOCALE | 16 | 與文字關聯的地區設定識別碼 |
CF_TEXT 與 CF_UNICODETEXT 是系統會隱含互相轉換的「合成格式」。轉換時使用與 CF_LOCALE 關聯的字碼頁。2
不過,ANSI 那一邊表達不了的字元會在轉換中遺失。處理 Unicode 特有的符號或組合字元時,交給轉換處理就會導致亂碼。應用程式的讀寫統一使用 CF_UNICODETEXT,在 .NET 則統一用 DataFormats.UnicodeText,這是原則。
flowchart LR
accTitle: CF_UNICODETEXT 與 CF_TEXT 的隱含轉換
accDescr: 應用程式只讀寫 CF_UNICODETEXT,CF_TEXT 由系統用 CF_LOCALE 的字碼頁隱含轉換合成。ANSI 那一邊表達不了的字元會在這個轉換中掉落
apprw["應用程式的讀寫"] --> uni["CF_UNICODETEXT(正規)"]
uni <-->|"系統隱含轉換(CF_LOCALE 的字碼頁)"| ansi["CF_TEXT(ANSI,依賴字碼頁)"]
ansi -.-> loss["表達不了的字元會在轉換中掉落(亂碼的溫床)"]
3.2. CF_HDROP ── 檔案是以「路徑清單」傳遞
檔案總管的檔案複製與 D&D 所使用的就是 CF_HDROP。首先要記住的是,傳過去的不是檔案本體,而是完整路徑的清單。
記憶體區塊的開頭是 DROPFILES 結構,後面排著以 NUL 字元分隔的路徑字串。最後會放一個空字串,所以結尾是「雙 NUL」。標頭的 pFiles 表示路徑清單的起始位移,fWide 表示字串是否為 Unicode。3
[DROPFILES標頭: pFiles=路徑清單的起始位移, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)
原生程式碼可用 DragQueryFile 逐一取出,.NET 則能以 DataFormats.FileDrop 取得 string[]。「傳過去的只有路徑,不是檔案本體」這一點,在第 8〜9 章的 D&D 也同樣重要。
flowchart TB
accTitle: CF_HDROP 的記憶體區塊結構
accDescr: 全域記憶體的開頭是 DROPFILES 結構,pFiles 指出路徑清單的起始位移,fWide 表示是否為 Unicode。後面排著以 NUL 分隔的完整路徑,最後以空字串形成的雙 NUL 結尾。傳過去的只有路徑,不是檔案本體
hdr["DROPFILES 結構(pFiles = 清單起始位移 / fWide = 1)"] --> p1["C:\\data\\a.txt + NUL"]
p1 --> p2["C:\\data\\b.txt + NUL"]
p2 --> tail["結尾的空字串(雙 NUL)"]
hdr -.-> note["傳過去的只有路徑,不是檔案本體"]
3.3. 已註冊格式 ── RegisterClipboardFormat 與「HTML Format」
標準格式表達不了的資料,可以自己決定名稱後當成已註冊格式來共享。把名稱傳給 RegisterClipboardFormat 就會回傳格式 ID。只要用同一個名稱註冊,別的應用程式也能取得相同的 ID,所以在應用程式之間先講好名稱,就成了彼此的接點。1
若要在自家應用程式群之間交換結構化資料,就取一個像 KomuraSoft.Report.RowData 這樣不會撞名的名稱。
HTML Format 是「帶標頭的 UTF-8」
已註冊格式的代表,是與 RTF 並列的帶格式文字格式「HTML Format」。內容是 UTF-8 文字,但開頭附了一個列出位元組位移的標頭。4
Version:0.9
StartHTML:<整份HTML的起始位元組位置>
EndHTML:<整份HTML的結束位元組位置>
StartFragment:<片段的起始位元組位置>
EndFragment:<片段的結束位元組位置>
<html><body>
<!--StartFragment--><b>粗體的</b>片段文字<!--EndFragment-->
</body></html>
各位移的基準是包含標頭本身在內的資料開頭。StartHTML / EndHTML 指整份 HTML,StartFragment / EndFragment 指使用者選取的片段的起點與終點。單位不是字元數,而是位元組數。
產生時,依下列順序組出來。
- 以固定位數(例如 10 位)先保留位移欄位。
- 組出 HTML 本文,並編碼成 UTF-8。
- 實際量出編碼後的位元組位置,寫回標頭。
在含中文等非 ASCII 字元的 UTF-8 裡,字元數與位元組數並不一致。這裡弄錯,貼到其他應用程式時開頭或結尾就會缺字。4
flowchart TB
accTitle: HTML Format 的標頭與位移的關係
accDescr: 標頭的 StartHTML 與 EndHTML 指出整份 HTML,StartFragment 與 EndFragment 指出使用者選取的片段,兩者都以資料開頭起算的位元組位置表示。UTF-8 的字元數與位元組數不一致,所以要用編碼後實際量出的位元組位置填入
header["標頭(Version / StartHTML / EndHTML / StartFragment / EndFragment)"] --> html["整份 HTML(StartHTML〜EndHTML)"]
html --> frag["選取的片段(StartFragment〜EndFragment)"]
header -.-> byte["各位移 = 從資料開頭起算的位元組位置(UTF-8 編碼後實測再寫回)"]
此外,表格型資料也常用 CSV(.NET 的 DataFormats.CommaSeparatedValue)。要與 Excel 互通時,把 HTML Format(帶格式)、CSV(只有值)與 CF_UNICODETEXT(Tab 分隔)一起提供,就不會挑貼上的對象。
4. 貼上端的做法 ── 格式的優先順序與驗證
4.1. 從豐富的格式開始依序尋找
貼上端要在自己能處理的格式中,從資訊量最多的開始找。做法有兩種:使用複製端依表達力高低排好的格式順序,或是自己指定優先順序。6
| Win32 API | 挑選方式 |
|---|---|
EnumClipboardFormats |
依複製端放上的順序列舉,使用最先認得的格式 |
GetPriorityClipboardFormat |
從貼上端傳入的優先順序清單中,挑出可用的格式 |
在 .NET 會寫成下面這樣的分支。
// 表格的貼上: 依豐富→純文字的順序尋找
var data = Clipboard.GetDataObject();
if (data is null) return;
// 就算宣告了格式,實體也未必是 string。只有連型別都確認過時才走
// 這一支,不行就落到下一個候選
if (data.GetDataPresent(DataFormats.Html)
&& data.GetData(DataFormats.Html) is string html)
{
// 先驗證 HTML Format 標頭,再當成表格收進來
}
else if (data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
// 當成 CSV 收進來
}
else if (data.GetDataPresent(DataFormats.UnicodeText))
{
// 當成 Tab 分隔的文字收進來
}
開頭那則「貼上 Excel 的表格就散掉」的諮詢,答案就在這裡。只讀純文字的應用程式拿不到表格的結構。要收到哪一種格式為止,是貼上端的設計決策。
flowchart TB
accTitle: 從豐富格式依序往下找的貼上分支
accDescr: 有 HTML Format 且實體也是字串就當成表格收進來,沒有就試 CSV,再沒有就落到 Tab 分隔的文字,從表達力高的格式依序往下。所有候選都沒有就不接受
startsel["開始貼上"] --> h{"有 HTML Format 且實體是 string?"}
h -->|"是"| useh["驗證標頭後當成表格收進來"]
h -->|"否"| c{"有 CSV?"}
c -->|"是"| usec["當成 CSV 收進來"]
c -->|"否"| t{"有 UnicodeText?"}
t -->|"是"| uset["當成 Tab 分隔的文字收進來"]
t -->|"否"| giveup["不接受"]
4.2. 貼上的資料就是外部輸入
常被忽略的是,剪貼簿的內容是不知道由哪個應用程式放上的、來自外部的資料。Microsoft 在 OLE 剪貼簿的文件裡也警告「剪貼簿的資料不可信。在應用程式使用之前要仔細剖析」。5
光是格式存在,還不能判定可以收進來。實體的型別也要確認,並通過下列驗證。
| 要驗證的東西 | 確認內容 |
|---|---|
| HTML Format 的標頭 | 位移有沒有指到範圍外。也有應用程式會送出壞掉的標頭 |
| 數值、日期、代碼等值 | 是否通得過與畫面輸入相同的驗證 |
| 資料的大小 | 幾百 MB 的影像或幾百萬列的文字等,是否超過可接受的上限 |
注意在查大小之前,具體化就已經開始了
在防範巨量資料時,要分清楚擋得住的是哪一個階段的負擔。.NET 的 GetData 在呼叫時也會觸發延遲轉譯,是文字的話會一路具體化成 Managed 字串。取得之後才確認大小,擋不住那次具體化的負擔。
用 GlobalSize 去查 Win32 的 GetClipboardData 回傳的 HGLOBAL,確實能防止巨量資料進入轉成 Managed 字串與剖析的階段。不過,對延遲轉譯的格式而言,GetClipboardData 本身就會觸發轉譯。要連「在複製來源把資料具體化」這件事本身都擋掉是做不到的。
為了不讓 UI 凍住,要把取得處理移出 UI 執行緒,並拒絕超過上限的資料。即使如此,.NET 的 Clipboard 仍然必須在 STA。不要用 Task.Run 的執行緒集區(MTA),而要用設定成 STA 的專用執行緒(第 5.1 節)。
「從外部進來的值,不管走哪條路徑,都要驗證過再用」這個想法,與「不能直接使用 QR Code 的讀取值」整理過的一樣。以為貼上是使用者操作所以安全,正是出問題的起點。
flowchart LR
accTitle: 先驗證再使用貼上資料的流程
accDescr: 從剪貼簿取出的資料,依序通過格式存在確認、實體型別確認、大小上限、內容驗證,只要有一關不合格就拒絕,或落到下一個候選格式
present["確認格式存在(GetDataPresent)"] --> type["確認實體型別(is string / string[])"]
type --> size["確認大小上限"]
size --> content["驗證內容(標頭、路徑、值)"]
content --> ok["收進來"]
type -.->|"型別不符"| rej["拒絕 / 換下一個候選格式"]
size -.->|"太大"| rej
content -.->|"不正確"| rej
5. 複製端的做法 ── 同時提供多種格式與延遲轉譯
5.1. 同時放上多種格式
複製端的做法就是 4.1 的反面,也就是同時提供豐富格式與純文字格式。用 WinForms/WPF 的 DataObject,幾行就能寫完。14
// WinForms(System.Windows.Forms)。WPF 也是用 System.Windows 的 DataObject/Clipboard,寫法相同
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText); // 帶標頭的 HTML Format 字串
data.SetData(DataFormats.CommaSeparatedValue, csv); // CSV
data.SetData(DataFormats.UnicodeText, plainText); // 純文字
Clipboard.SetDataObject(data, copy: true); // copy:true=應用程式結束後仍保留
這裡把執行緒的條件與結束後的存活期間分開來確認。
執行緒的條件:.NET 的 Clipboard 類別只能從 STA 執行緒使用。14 WinForms/WPF 的 UI 執行緒因為有 [STAThread] 而是 STA,通常不成問題,但不是 STA 的背景執行緒就不能用。背景知識在「COM STA/MTA 基礎知識」中說明。
結束後的存活期間:copy: true 是「應用程式結束後仍保留」的指定。與下一節的延遲轉譯一起理解,就會明白為什麼需要這個指定。
5.2. 延遲轉譯 ── 「一關掉就貼不上」的真面目
如果每次複製都把所有格式全部產生出來,連用不到的格式也要花處理成本。避開這件事的機制就是延遲轉譯(delayed rendering)。6
複製時把 SetClipboardData 的資料控制代碼傳成 NULL,登記的不是資料本體,而是「被要求時再產生」的承諾。當該格式被要求時,WM_RENDERFORMAT 會送到複製來源,這時才產生資料。
結束時要把「承諾」換成資料
複製來源若在還留著未轉譯格式的狀態下結束,貼上端就收不到那份資料。這就是「關掉複製來源就貼不上」的問題。
複製來源有責任回應結束前的 WM_RENDERALLFORMATS,把所有未轉譯的格式具體化。沒有定案的格式,會隨著複製來源結束而消失。6
flowchart TB
accTitle: 延遲轉譯的流程與「一關掉就貼不上」
accDescr: 複製來源以 NULL 控制代碼只放上承諾,被要求時才用 WM_RENDERFORMAT 具體化。結束時有責任以 WM_RENDERALLFORMATS 把所有格式具體化,疏忽的話該格式就會遺失
promise["複製來源:以 SetClipboardData(format, NULL) 只登記「承諾」"] --> req["貼上端要求該格式"]
req --> render["WM_RENDERFORMAT → 當場產生資料"]
promise --> quit["複製來源準備結束"]
quit -->|"以 WM_RENDERALLFORMATS 具體化"| ok["結束後仍可貼上"]
quit -->|"疏於具體化"| lost["該格式遺失(「一關掉就貼不上」)"]
在 OLE 剪貼簿裡,是用 OleSetClipboard 放上 IDataObject。此時剪貼簿持有的,是指向資料物件的指標。
結束時呼叫 OleFlushClipboard,資料就會在剪貼簿上具體化,結束後仍能貼上。7 .NET 的 Clipboard.SetDataObject(data, copy: true) 指定的就是這種「結束後仍保留」的行為。
在 Excel 複製大範圍後結束時,之所以會被問「剪貼簿上有大量資訊。要保留嗎?」,就是在確認要不要做這個定案處理(flush)。自己寫的應用程式也一樣,要把延遲轉譯與結束時的定案處理當成一組來設計。
延遲了不代表 UI 就不會凍住
延遲轉譯是效能最佳化,但被要求的資料的產生,是在訊息處理過程中同步執行的。產生若耗時,UI 就會凍住,這是一種取捨。6
6. 監看剪貼簿的做法 ── 接聽者、重試與排除歷程
6.1. 使用 AddClipboardFormatListener
碰到「想偵測條碼掃描器的值或核心系統的複製,然後自動收進來」這類需求時,就需要監看剪貼簿的變更。做法在歷史上有三種,但現在的正解只有一個。8
| 做法 | 評價 |
|---|---|
| 用計時器定期讀取(輪詢) | 浪費多,也會漏偵測。不要用 |
| SetClipboardViewer(檢視器鏈) | 鏈上一個應用程式出問題就毀掉整條鏈。只為向後相容而留存 |
| AddClipboardFormatListener | 建議做法。WM_CLIPBOARDUPDATE 會送到已註冊的視窗 |
flowchart LR
accTitle: 監看剪貼簿的流程
accDescr: 在建立控制代碼時以 AddClipboardFormatListener 註冊,之後不論哪個應用程式複製都會收到 WM_CLIPBOARDUPDATE。讀取要帶重試,並在銷毀控制代碼時以 RemoveClipboardFormatListener 對稱解除
created["OnHandleCreated:AddClipboardFormatListener"] --> wait["等待"]
anyapp["某個應用程式複製"] --> notify["收到 WM_CLIPBOARDUPDATE"]
wait --> notify
notify --> readtry["帶重試地讀取(第 6.2 節)"]
readtry --> wait
destroyed["OnHandleDestroyed:RemoveClipboardFormatListener"] -.->|"對稱解除"| created
// WinForms 的最小實作
public partial class MainForm : Form
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
const int WM_CLIPBOARDUPDATE = 0x031D;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
AddClipboardFormatListener(Handle);
}
protected override void OnHandleDestroyed(EventArgs e)
{
// 配合控制代碼的銷毀與重建,對稱地解除
RemoveClipboardFormatListener(Handle);
base.OnHandleDestroyed(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CLIPBOARDUPDATE)
{
// 在這裡讀 Clipboard.GetDataObject(),是需要的格式就收進來
}
base.WndProc(ref m);
}
}
6.2. 打不開時就重試
能開啟剪貼簿的一次只有一個視窗。其他處理程序開著的期間,OpenClipboard 會失敗。6
在 WM_CLIPBOARDUPDATE 的緊接之後,複製來源或別的監看程式也可能正在操作。要把暫時讀不到當成正常事件處理,加入夾著數十毫秒短暫等待、重試數次的處理。
在 .NET 要注意的是,可指定重試次數與間隔的多載,只存在於寫入端的 SetDataObject。讀取端的 GetDataObject 等並沒有。
| 讀取端 | 競用時的因應 |
|---|---|
| WinForms | catch ExternalException,等待後重試 |
| WPF | catch COMException,等待後重試 |
讀取的等待與重試,要由應用程式自己實作。
flowchart LR
accTitle: 剪貼簿讀取重試的流程
accDescr: 能開啟剪貼簿的一次只有一個視窗,因此變更通知緊接之後的讀取可能與其他處理程序競用而失敗。收到例外就等待數十毫秒再重試,達到上限就這次放棄,等下次更新再取
upd["WM_CLIPBOARDUPDATE"] --> tryread["嘗試讀取"]
tryread -->|"成功"| useok["進入收取(第 4 章的驗證)"]
tryread -->|"失敗(其他視窗使用中)"| waitretry["等待數十毫秒"]
waitretry -->|"重試至多數次"| tryread
waitretry -->|"達到上限"| giveup2["這次放棄(等下次更新再取)"]
6.3. 不讓它進入歷程與同步 ── 處理機密的複製功能該有的顧慮
Windows 有剪貼簿歷程(Win+V)與跨裝置同步(雲端剪貼簿),應用程式放上的資料預設就是它們的對象。會把密碼或銀行帳號這類機密放進複製功能的應用程式,要一併放上用來排除歷程與同步的已註冊格式。1
| 已註冊格式 | 抑制的對象 |
|---|---|
ExcludeClipboardContentFromMonitorProcessing |
把該次複製的整份內容同時排除於歷程與跨裝置同步之外 |
CanIncludeInClipboardHistory (DWORD 0) |
只排除歷程 |
CanUploadToCloudClipboard (DWORD 0) |
只排除跨裝置同步 |
密碼管理員複製的密碼之所以不會留在 Win+V,靠的就是這個機制。只要把名稱傳給 RegisterClipboardFormat 取得格式 ID,再和一般資料並排設定就好,因此在處理機密的業務應用程式裡值得實作。
7. 資訊部門視角的剪貼簿 ── 歷程、雲端同步與 RDP 的控制
應用程式端控制複製內容,和組織整體要允許到什麼範圍,是兩件要分開想的事。管理者要看的對象有三個:剪貼簿歷程、雲端同步、RDP 重新導向。
剪貼簿歷程會累積最近的複製內容。雲端剪貼簿則會在以同一個 Microsoft 帳戶/Microsoft Entra 帳戶登入的裝置之間同步。10
方便的另一面,是從核心系統複製的個資留在歷程裡、公司電腦的複製內容同步到私人電腦這類殘留與越界。
7.1. 控制歷程與跨裝置同步
在組織裡要控制的原則有以下兩個。
| 控制對象 | GPO(電腦設定 > 系統管理範本 > 系統 > OS 原則) | Policy CSP(Intune) | 預設 |
|---|---|---|---|
| 剪貼簿歷程 | 允許剪貼簿歷程記錄 | Experience/AllowClipboardHistory | 允許 |
| 跨裝置同步 | 允許裝置之間的剪貼簿同步 | Privacy/AllowCrossDeviceClipboard | 允許 |
兩者都能在 Windows 10 版本 1809 以後使用,停用後「設定」應用程式中對應的項目會變成灰色,而且原則會立即生效。910
7.2. 控制 RDP 的跨工作階段傳輸
RDP(遠端桌面)的剪貼簿重新導向,會在本機電腦與遠端工作階段之間搭橋。預設可以複製貼上,因此也會成為把伺服器上的機密帶出去的路徑。
要雙向都阻擋,設定是「不允許剪貼簿重新導向」原則(登錄值 fDisableClip)。11
近年的 Windows Server/Windows 11 還新增了更細的控制原則,例如只把伺服器到用戶端的方向限定為文字。要全面禁止,還是依方向與格式分階段限制,要在維運與安全性之間權衡決定。
flowchart LR
accTitle: 剪貼簿內容擴散的路徑與控制點
accDescr: 複製的內容預設會成為歷程與雲端同步的對象,在 RDP 則會經由重新導向送到別的工作階段。每一條都能用原則控制,應用程式端也能用排除格式讓內容不進歷程與同步
cb["剪貼簿"] --> hist["歷程(Win+V)"]
cb --> cloud["雲端同步 → 其他裝置"]
cb --> rdp["RDP 重新導向 → 其他工作階段"]
hist -.-> p1["控制:AllowClipboardHistory"]
cloud -.-> p2["控制:AllowCrossDeviceClipboard"]
rdp -.-> p3["控制:fDisableClip"]
cb -.-> p4["應用程式端:用 ExcludeClipboardContentFromMonitorProcessing 等排除(第 6.3 節)"]
8. 拖放的真面目是 COM ── IDataObject + IDropSource + IDropTarget
8.1. 與剪貼簿相同的資料,不同的搬運方式
OLE 拖放由以下三者運作。15
| 角色 | 實作者 | 工作 |
|---|---|---|
| IDataObject | 拖曳來源 | 被搬運的資料本體。與剪貼簿相同的多格式資料物件 |
| IDropSource | 拖曳來源 | 判斷拖曳要繼續或中止,以及游標回饋 |
| IDropTarget | 放置目標 | 以 DragEnter/DragOver/DragLeave/Drop 表明是否接受並接收 |
操作的流程如下。
- 拖曳來源呼叫
DoDragDrop,開始拖曳的迴圈。 - 滑鼠進入放置目標視窗時,通知會送到
IDropTarget。 - 放置目標表明是否接受,並在放下時從
IDataObject取出需要的格式。
官方文件也說明,D&D 提供與剪貼簿複製貼上相同的功能,已經實作複製貼上的應用程式只需要加一點點東西。15 也就是說,第 2〜5 章準備好的多格式 DataObject,在 D&D 也就是要搬運的資料。
flowchart LR
accTitle: OLE 拖放的流程
accDescr: 拖曳來源以 IDataObject 為貨物呼叫 DoDragDrop,開始拖曳的迴圈,放置目標的 IDropTarget 以 DragEnter 與 DragOver 表明是否接受,並在 Drop 時從 IDataObject 挑選格式取出
src["拖曳來源:IDataObject + IDropSource"] -->|"DoDragDrop"| loop["拖曳的迴圈"]
loop -->|"滑鼠進入視窗"| enter["IDropTarget.DragEnter/DragOver(每次都表明 Effect)"]
enter -->|"放開按鍵"| drop["IDropTarget.Drop"]
drop --> data["從 IDataObject 挑選格式取出"]
8.2. 必須有 OleInitialize(STA)
放置目標視窗要用 RegisterDragDrop 註冊。在那之前,請先確認 OLE 的初始化與訊息處理這兩點。
初始化要用 OleInitialize。只用 CoInitialize / CoInitializeEx 代替的話,RegisterDragDrop 會以 E_OUTOFMEMORY 失敗。OleInitialize 會把 COM 初始化成 STA。12
註冊的執行緒必須跑訊息幫浦。OLE D&D 是扎根於視窗與訊息處理的功能,疏忽這點會讓拖曳期間的其他應用程式停止回應。12 背景與「COM STA/MTA 基礎知識」的執行緒模型相同。
在 WinForms/WPF 用事件接收
在 WinForms/WPF 裡,框架會代勞 OLE 初始化與介面實作。開發者只要把是否接受的表明與實際的收取寫在事件裡。
// WinForms: 接收檔案的放下
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
// 也要確認來源端允許 Copy(有些來源只允許 Move/Link)
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
&& (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
? DragDropEffects.Copy // 可接受: 以複製接收
: DragDropEffects.None; // 不接受
};
listView1.DragDrop += (s, e) =>
{
// 拖曳資料也是外部輸入。就算宣告 FileDrop,實體也可能是 null 或
// 別的型別,取得本身也可能失敗
object data;
try { data = e.Data.GetData(DataFormats.FileDrop); }
catch (COMException) { return; }
if (data is not string[] paths) return;
foreach (var path in paths)
{
// 驗證路徑後再收進來(第 9.3 節)
}
};
WPF 的構圖也一樣。用元素的 AllowDrop="True" 與 DragOver / Drop 事件接收,再以 e.Data.GetData(DataFormats.FileDrop) 取出路徑陣列。
在 DragEnter / DragOver 每次都表明是否接受(Effect),是 IDropTarget 的規矩。省掉這裡,游標就會一直維持「禁止」不變。除了格式在不在,也要像程式碼範例那樣確認拖曳來源是否允許 Copy。
9. D&D 的陷阱 ── 提升權限、Move 與路徑的驗證
9.1. 無法放到提升為系統管理員的應用程式上
從檔案總管把檔案放到「以系統管理員身分執行」的應用程式上卻沒有反應 ── 這不是實作出錯,而是作業系統的規格。因為 UIPI(User Interface Privilege Isolation,使用者介面權限隔離)預設會阻擋從較低完整性等級的處理程序送往較高完整性等級視窗的訊息,所以從一般權限(中完整性)的檔案總管送往提升權限應用程式的放下通知不會送達。13
flowchart LR
accTitle: 放到提升權限應用程式上被 UIPI 阻擋的機制
accDescr: 從中完整性的檔案總管送往高完整性提升權限應用程式的放下通知,預設會被 UIPI 阻擋而送不到。把 UI 保持在一般權限並分離特權處理,放下就送得到
explorer["檔案總管(中完整性)"] -->|"放下通知"| uipi{"UIPI"}
uipi -->|"阻擋(預設)"| elevated["提升權限應用程式(高完整性):沒有反應"]
uipi -->|"通過"| normal["一般權限的 UI:放下送得到"]
normal -.->|"只委託特權處理"| broker["把需要提升權限的處理分離出去的另一個處理程序"]
也有用 ChangeWindowMessageFilterEx 個別放行 WM_DROPFILES 等訊息的變通做法。13 不過,這是針對舊式 WM_DROPFILES 放下通知的對策,並不能解決整個 OLE D&D。
根本的方針是不要讓應用程式一直以提升權限執行。只要把需要提升權限的處理分離到另一個處理程序,UI 本體就能維持一般權限並接收 D&D。分離的設計在「在 Windows 應用程式中只分離「需要系統管理員權限的處理」的具體寫法」有詳述。
9.2. DragDropEffects 的意義 ── Move 是「來源會消失」的契約
DragDropEffects 的 Copy / Move / Link 不是游標的裝飾,而是拖曳來源與放置目標之間的契約。
拖曳來源以 DoDragDrop 宣告允許的效果集合,放置目標挑選實際的效果。Move 一旦成立,拖曳來源就會刪除資料(檔案),這是規範。
接收端不假思索回傳 Move,就會變成「一放下原本的檔案就不見了」的問題。業務應用程式的收取用途,接收端要明示 Copy,把它當成安全側的預設。
flowchart LR
accTitle: DragDropEffects 的契約 ── Move 會讓來源消失
accDescr: 拖曳來源以 DoDragDrop 宣告允許的效果集合,放置目標挑選實際的效果。Move 成立時拖曳來源會刪除檔案,因此收取用途的接收端明示 Copy 才安全
srcdecl["拖曳來源:宣告允許的效果(Copy | Move | Link)"] --> tgtsel["放置目標:挑選實際的效果"]
tgtsel -->|"Copy"| copyok["原始檔案留著(收取用途的安全側)"]
tgtsel -->|"Move"| moveact["拖曳來源刪除檔案 ──「來源不見了」的問題根源"]
9.3. 驗證放下的路徑
CF_HDROP/FileDrop 傳過來的只有路徑(第 3.2 節)。收進來之前,要和貼上一樣,通過當成外部輸入的驗證。
- 是檔案還是資料夾:把整個資料夾被放下的情況當成規格先決定好(要遞迴收取,還是拒絕)。
- OneDrive 的預留位置:路徑存在但檔案本體不在本機的「隨選檔案」,一打開就會開始下載,離線時會失敗。行為與對策請參考「OneDrive「隨選檔案」與業務應用 ── 預留位置打破的前提與對策」。
- 長路徑與特殊路徑:超過 MAX_PATH 的路徑、網路(UNC)路徑、卸除式媒體上的路徑,要先確認後續處理應付得來再接受。
- 數量與總大小:為了不讓數千個檔案的放下把 UI 凍住,收取要非同步化,並設上限與進度顯示。
10. 總結
- 剪貼簿是在同一個桌面(視窗站)內共享的一塊區域,把同一份內容以多種格式同時放上的機制。因為由貼上端挑選格式,同一次複製也會有不同結果。
- 文字是 CF_UNICODETEXT,檔案是 CF_HDROP,帶格式的文字是已註冊格式 HTML Format(位元組位移標頭 + UTF-8)。
- 貼上端依豐富→純文字的順序尋找格式,並把內容當成外部輸入驗證。複製端同時提供多種格式,若使用延遲轉譯,就要連結束時的定案(WM_RENDERALLFORMATS/OleFlushClipboard)一起實作。
- 監看用 AddClipboardFormatListener + WM_CLIPBOARDUPDATE。OpenClipboard 的競用用重試因應,機密則用 ExcludeClipboardContentFromMonitorProcessing 等排除於歷程與同步之外。
- 資訊部門可以用 GPO/Intune 控制剪貼簿歷程、雲端同步與 RDP 重新導向。預設全都是允許,因此在處理機密的環境要有意識地判斷。
- D&D 的真面目是 COM,IDropSource/IDropTarget 交遞的是與剪貼簿相同的 IDataObject。RegisterDragDrop 必須有 OleInitialize(STA)。
- 放到提升權限應用程式上會被 UIPI 阻擋。DragDropEffects 的 Move 是「來源會消失」的契約,放下的路徑要驗證後再收進來。
複製貼上與 D&D,對使用者來說是像空氣一樣的功能。正因如此,「貼不上」「散掉」「不見了」發生時體驗的惡化特別大;反過來說,多格式提供與放置支援都做到位的應用程式,光是這樣就能讓日常操作更順手。希望這些能成為你排改版優先順序時的判斷材料。
相關文章
- COM / ActiveX / OCX 是什麼 - 差異與關係一次整理
- COM STA/MTA 基礎知識 - 執行緒模型與避免停止回應的思考方式
- 今日的 Windows 殼層整合 ── 右鍵選單、檔案關聯,以及 Windows 11 改了什麼
- C# 操作 Excel 時 EXCEL.EXE 殘留的問題 ── COM 參照釋放模式與替換判斷
- Windows 應用程式 UX 設計 - 依使用環境決定優先順序
- OneDrive「隨選檔案」與業務應用 ── 預留位置打破的前提與對策
相關諮詢領域
合同會社小村軟體承接業務應用程式的複製貼上與拖放支援的設計與實作(多格式提供、Excel 互通、檔案放下的收取)、「一貼上就散掉」「複製不見了」這類問題的原因調查、使用剪貼簿監看的輸入自動化,以及機密資訊的歷程與同步對策實作。牽涉 COM 與 OLE 低層的案子,從釐清現象開始也沒問題。
參考連結
-
Microsoft Learn, Clipboard Formats. 關於視窗能把同一份資訊以多種剪貼簿格式放上;經由 RegisterClipboardFormat 的已註冊格式(以同名註冊會傳回同一個值,因此能在應用程式之間共享);合成格式;以及用 ExcludeClipboardContentFromMonitorProcessing、CanIncludeInClipboardHistory 與 CanUploadToCloudClipboard 把內容排除於剪貼簿歷程/雲端同步之外。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Standard Clipboard Formats. 關於 CF_TEXT(ANSI)、CF_UNICODETEXT、CF_HDROP、CF_DIB 與 CF_LOCALE 等標準格式的定義,以及系統會使用與 CF_LOCALE 關聯的字碼頁,在 CF_TEXT 與 CF_UNICODETEXT 之間隱含轉換。 ↩ ↩2 ↩3
-
Microsoft Learn, Shell Clipboard Formats. 關於 CF_HDROP 由 DROPFILES 結構與雙 NUL 結尾的完整路徑字串陣列構成;用 DragQueryFile 取出個別路徑;以及 CFSTR_ 系列的殼層格式需要經由 RegisterClipboardFormat 註冊。 ↩ ↩2
-
Microsoft Learn, HTML Clipboard Format. 關於註冊名稱是「HTML Format」;帶有 Version、StartHTML、EndHTML、StartFragment、EndFragment 等位移(以位元組為單位)的標頭結構;編碼一律是 UTF-8;以及 StartFragment/EndFragment 註解的慣例。 ↩ ↩2 ↩3
-
Microsoft Learn, OleGetClipboard function (ole2.h). 關於如何從剪貼簿取得 IDataObject,以及「剪貼簿的資料不可信,應用程式使用前應仔細剖析」的警告。 ↩ ↩2
-
Microsoft Learn, Clipboard Operations. 關於一次只有一個視窗能開啟剪貼簿;複製時要從表達力高的格式依序放上;貼上時用 EnumClipboardFormats/GetPriorityClipboardFormat 挑選格式;把 NULL 傳給 SetClipboardData 的延遲轉譯與 WM_RENDERFORMAT/WM_RENDERALLFORMATS 的責任;以及延遲轉譯的取捨。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, OleFlushClipboard function (ole2.h). 關於 OleSetClipboard 只讓剪貼簿持有指向資料物件的指標;OleFlushClipboard 把資料具體化到剪貼簿上,使應用程式結束後仍能貼上;以及結束時若不需要保留,應以 OleSetClipboard(NULL) 清空。 ↩ ↩2
-
Microsoft Learn, Using the clipboard. 關於比較監看剪貼簿的三種方式(檢視器視窗、序號、格式接聽者);新程式應該使用 AddClipboardFormatListener 的接聽者;檢視器鏈在鏈的維護不完整時很脆弱;以及序號不該拿來輪詢。 ↩ ↩2
-
Microsoft Learn, Policy CSP - Experience. 關於用 Experience/AllowClipboardHistory 原則允許或禁止剪貼簿歷程;從 Windows 10 版本 1809 起可用;預設為允許;以及在 GPO 對應到「系統 > OS 原則」之下,變更會立即生效。 ↩ ↩2
-
Microsoft Learn, Policy CSP - Privacy. 關於用 Privacy/AllowCrossDeviceClipboard 原則允許或禁止跨裝置剪貼簿同步;同步發生在以同一個 Microsoft 帳戶/Microsoft Entra 帳戶登入的裝置之間;以及預設為允許。 ↩ ↩2 ↩3
-
Microsoft Learn, Policy CSP - ADMX_TerminalServer. 關於 TS_CLIENT_CLIPBOARD(「不允許剪貼簿重新導向」,登錄值 fDisableClip)能禁止遠端桌面工作階段中本機與遠端之間的剪貼簿共享,以及預設允許重新導向。 ↩ ↩2
-
Microsoft Learn, RegisterDragDrop function (ole2.h). 關於放置目標視窗與 IDropTarget 的註冊方式;若用 CoInitialize/CoInitializeEx 初始化 COM 會一律以 E_OUTOFMEMORY 失敗,因此需要 OleInitialize;以及呼叫的執行緒沒有跑訊息幫浦時,拖曳來源應用程式會停止回應。 ↩ ↩2 ↩3
-
Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). 關於 UIPI 是預設阻擋接收來自較低完整性等級傳送端訊息的安全性機制,以及能用視窗層級的訊息篩選允許(MSGFLT_ALLOW)特定訊息。 ↩ ↩2 ↩3
-
Microsoft Learn, How to add data to the Clipboard (Windows Forms). 關於用 DataObject 與 Clipboard.SetDataObject 同時放上多種格式的資料;應以多種格式加入,好讓其他應用程式認得;以及 Clipboard 類別只能從 STA 執行緒使用,因此需要 [STAThread]。 ↩ ↩2
-
Microsoft Learn, Drag and Drop (COM). 關於 OLE 拖放由 IDropSource(拖曳來源)、IDropTarget(放置目標)與 DoDragDrop(OLE 提供的迴圈)三者運作;提供與剪貼簿複製貼上相同的功能,已實作複製貼上的應用程式只需少量追加;以及回饋的種類。 ↩ ↩2
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
Windows 列印驅動程式停止提供 ── 業務應用程式的報表與標籤列印如何因應
Microsoft 正分階段推動 v3/v4 列印驅動程式的停止提供,從 2026 年 7 月起會優先選用 IPP 類別驅動程式。本文整理 Windows protected print mode 之下會消失哪些東西,並以判斷表梳理業務應用程式的報表、標籤列印中相依處的盤點...
什麼是 OLE 物件 —— 內嵌與連結的機制以及業務文件中的陷阱
在 Word 中內嵌 Excel 表格的功能,本質就是 OLE 物件。本文從內嵌與連結的差異、複合檔案與結構化儲存體、In-Place Activation 的機制,一路談到連結中斷、檔案膨脹與資安對策,全部立足於實務視角。
「沒有回應」的真面目 ── Windows 如何判定應用程式卡住,以及不卡住的設計
Windows 的「沒有回應」,是視窗 5 秒沒有取出訊息時由作業系統判定、並換成幽靈視窗的機制。本文從判定的內部運作,講到卡住的經典原因、把重工作移出 UI 執行緒的設計,以及無回應的調查程序。
Windows 應用程式的深色模式與對比佈景主題支援 ── DWM 的深色標題列、WinForms/WPF 跟隨系統佈景主題、高對比時的繪製
說明如何讓 WinForms/WPF 應用程式跟隨 Windows 11 的深色模式與對比佈景主題。整理 DWM 的深色標題列、.NET 9/10 的 SetColorMode 與 ThemeMode、佈景主題切換的偵測,以及高對比時以系統色彩繪製的做法。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
UI 執行緒 & 計時器
整理 WPF / WinForms UI 執行緒、非同步流程、Dispatcher 使用、計時器判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
既有資產活用 & 遷移支援
在持續活用 COM / ActiveX / OCX 資產、原生程式碼與 32 位元相依的同時,協助規劃階段性的遷移。
常見問題
整理諮詢這個主題時常見的問題。
- 為什麼把從 Excel 複製的表格貼進應用程式後格式會散掉?
- 因為剪貼簿裡放的不是「一份資料」,而是同一份內容同時以多種格式(應用程式私有格式、HTML Format、CSV、Unicode 文字等)存在,貼上端的應用程式會挑自己看得懂的格式取出。格式散掉時,典型情況是貼上端只讀純文字(CF_UNICODETEXT)。若希望連表格結構一起收下,貼上端就要實作成優先取出 HTML Format 或 CSV。反過來,若希望自家應用程式複製的內容能被其他應用程式正確貼上,複製時就要同時提供豐富格式與純文字格式。
- 為什麼關掉複製來源的應用程式後就貼不上了?
- 因為複製來源使用了延遲轉譯(delayed rendering)。處理大型資料的應用程式在複製時不放上資料本體,只在剪貼簿登記「被要求時再產生」的承諾。在這個狀態下,複製來源若沒有回應結束時的 WM_RENDERALLFORMATS 把資料定案就結束,尚未轉譯的格式就會遺失。使用 OLE 剪貼簿(IDataObject)的應用程式,只要在結束時呼叫 OleFlushClipboard 把資料具體化,就能在結束後仍維持可貼上的狀態。
- 要怎麼從自己寫的應用程式監看剪貼簿的變更?
- 目前建議的做法是用 AddClipboardFormatListener 把自己的視窗註冊為接聽者,並處理每次內容變更就會送達的 WM_CLIPBOARDUPDATE 訊息。用計時器定期讀取內容的輪詢浪費很多資源,而以 SetClipboardViewer 為基礎的舊式檢視器鏈,只要鏈上一個應用程式出問題整條鏈就壞掉,因此只為了向後相容而保留。另外,讀取時的 OpenClipboard 可能與其他處理程序競用而失敗,所以實作成短暫等待後重試會比較穩定。
- 有沒有辦法讓密碼這類機密資訊不要留在剪貼簿歷程(Win+V)裡?
- 應用程式端與原則端各有一個手段。應用程式端在複製時一併放上名為 ExcludeClipboardContentFromMonitorProcessing 的已註冊格式,該內容就不會進入剪貼簿歷程,也不會進入跨裝置同步。也可以用 CanIncludeInClipboardHistory(僅歷程)或 CanUploadToCloudClipboard(僅同步)個別控制。密碼管理員用的正是這個機制。若想在整個組織關掉,可以用群組原則或 Intune(Policy CSP)的 AllowClipboardHistory 與 AllowCrossDeviceClipboard 直接停用剪貼簿歷程與雲端同步本身。
- 為什麼無法把檔案拖放到以系統管理員身分執行的應用程式上?
- 因為名為 UIPI(User Interface Privilege Isolation,使用者介面權限隔離)的安全性機制,會阻擋從較低完整性等級的處理程序送往較高完整性等級視窗的訊息。檔案總管以一般權限(中完整性)執行,所以拖放的通知不會送到提升權限後的應用程式視窗。用 ChangeWindowMessageFilterEx 個別放行 WM_DROPFILES 等訊息的變通做法也廣為人知,但那只適用於舊式的放置通知。根本的做法是不要把應用程式設計成一直以提升權限執行,而是把需要提升權限的處理分離到另一個處理程序。