Clipboard và kéo thả hoạt động như thế nào — Xử lý đúng truyền dữ liệu OLE trong ứng dụng nghiệp vụ
· Go Komura · Windows, Clipboard, Kéo thả, OLE, COM, Phát triển Windows, WinForms, WPF
“Khi chúng tôi dán bảng sao chép từ Excel, định dạng tan. Chúng tôi muốn nó dán thành bảng.” “Nội dung chúng tôi sao chép trong ứng dụng hóa thành thứ kỳ lạ khi dán vào Word.” “Chúng tôi muốn nhận tệp bằng kéo thả.” — Trong các cuộc trò chuyện tư vấn về thay đổi ứng dụng nghiệp vụ, yêu cầu quanh sao chép-và-dán và kéo thả (D&D) là món chủ lực.
Chính vì đây là “tính năng mọi người coi là đương nhiên”, cách chúng thực sự hoạt động ít được biết đến một cách đáng ngạc nhiên. Nếu bạn nghĩ clipboard như “một hộp bạn bỏ một mảnh dữ liệu vào”, bạn không thể giải thích vì sao cùng lần sao chép cho kết quả khác tùy nơi bạn dán, hoặc vì sao dán ngừng hoạt động sau khi đóng ứng dụng nguồn. Clipboard thật là cơ chế đặt cùng nội dung ở nhiều định dạng cùng lúc, và để phía dán chọn một định dạng nó hiểu.
Và kéo thả, ở đáy, là truyền dữ liệu OLE giao đúng cùng biểu diễn dữ liệu với clipboard (IDataObject), qua giao diện COM. Nói cách khác, sao chép-và-dán và D&D là anh em: hiểu đúng một cái thì cái kia ở ngay đó.
Bài viết này hướng tới nhân viên IT tại công ty vừa và nhỏ và nhà phát triển ứng dụng Windows. Nó buộc lại, trong một bức tranh, cách định dạng clipboard hoạt động, các thực hành phía dán và phía sao chép, cách đúng để theo dõi clipboard, các chính sách quản trị cho lịch sử clipboard, đồng bộ đám mây, và RDP, cùng cấu trúc và cái bẫy của kéo thả OLE.
1. Kết luận trước
- Clipboard là một vùng duy nhất được các ứng dụng trên cùng desktop (window station) dùng chung, và thứ ngồi đó không phải “một mảnh dữ liệu” mà là cùng nội dung ở nhiều định dạng cùng lúc. Một phiên khác, như RDP, vốn có clipboard khác; tính năng chuyển hướng mới là thứ bắc cầu hai bên. Vì đích chọn một định dạng nó hiểu, cùng lần sao chép cho kết quả khác tùy nơi bạn dán.12
- Với văn bản, dùng CF_UNICODETEXT. CF_TEXT là ANSI và phụ thuộc trang mã, và trên hệ thống tiếng Nhật nó là ổ nuôi mojibake. Hệ thống chuyển giữa hai cái ngầm, nhưng phía chuẩn tắc là Unicode.3
- Tệp đi như CF_HDROP (mảng đường dẫn kết thúc bằng NUL kép), và văn bản đã định dạng dùng định dạng đã đăng ký “HTML Format”. HTML Format có cấu trúc khác thường: văn bản UTF-8 với đầu đề các offset byte.45
- Nguyên nhân thật của “tôi đóng ứng dụng nguồn rồi không còn dán được” là kết xuất trì hoãn. Đó là cơ chế đặt không phải payload mà chỉ lời hứa “tạo khi được hỏi”; nếu bạn bỏ qua hiện thực hóa lúc thoát (đáp WM_RENDERALLFORMATS, hoặc OleFlushClipboard với OLE), dán ngừng hoạt động.26
- Coi dữ liệu đã dán như đầu vào không tin cậy từ bên ngoài. Chính Microsoft nói thẳng rằng “dữ liệu clipboard không được tin. Phân tích nó cẩn thận”.7
- Để theo dõi clipboard, AddClipboardFormatListener + WM_CLIPBOARDUPDATE là lựa chọn duy nhất. Đừng dùng thăm dò, và đừng dùng SetClipboardViewer cũ (chuỗi viewer). Các định dạng đã đăng ký giữ bí mật khỏi lịch sử và đồng bộ (ExcludeClipboardContentFromMonitorProcessing và bạn) cũng được cung cấp.81
- Lịch sử clipboard (Win+V) và đồng bộ đám mây là mối quan tâm quản trị IT. Bạn có thể điều khiển chúng bằng AllowClipboardHistory và AllowCrossDeviceClipboard qua GPO / Intune (Policy CSP), và chuyển hướng clipboard RDP có chính sách riêng.91011
- Kéo thả là COM. Cùng IDataObject với clipboard được giao giữa IDropSource (nguồn kéo) và IDropTarget (đích thả) qua vòng DoDragDrop. RegisterDragDrop đòi khởi tạo bằng OleInitialize (STA).1213
- Bạn không thể thả từ File Explorer đặc quyền thông thường lên ứng dụng đã nâng quyền. UIPI (chặn thông điệp theo mức toàn vẹn) là nguyên nhân, và đó là ràng buộc bạn nên biết lúc thiết kế.14
Dưới đây chúng ta lần từ nền tảng clipboard lên.
2. Clipboard thực sự là gì — Không phải “một mảnh dữ liệu” mà là “cùng nội dung ở nhiều định dạng”
Clipboard là cơ chế chia sẻ dữ liệu chung mà mọi ứng dụng chia cùng desktop đều với tới được (chính xác hơn, nó theo từng window station: phiên người dùng khác hoặc phiên RDP mỗi cái có clipboard riêng. Sao chép-và-dán hoạt động qua RDP vì tính năng chuyển hướng bắc cầu hai bên — Chương 7). Nguyên tắc đầu tiên là nó do người dùng dẫn: lập trường thiết kế chính thức là bạn không bỏ dữ liệu vào hay lấy dữ liệu ra sau lưng người dùng.1
Điểm quan trọng là một lần sao chép không đặt “một mảnh dữ liệu”. Cửa sổ sao chép làm trống clipboard rồi đặt vài định dạng nối tiếp, biểu đạt cùng nội dung từ định dạng mạnh hơn xuống định dạng yếu hơn.2 Ví dụ, khi bạn sao chép một bảng trong bảng tính, về mặt khái niệm thứ như sau nằm trên clipboard cùng lúc.
| Ưu tiên | Định dạng | Nội dung |
|---|---|---|
| 1 | Định dạng riêng ứng dụng | Biểu diễn nội bộ đầy đủ, gồm công thức và định dạng (để dán lại vào cùng ứng dụng) |
| 2 | HTML Format | Mảnh HTML giữ cấu trúc bảng và định dạng |
| 3 | CSV | Văn bản phân cách ô |
| 4 | CF_UNICODETEXT | Văn bản thuần phân cách bằng tab |
| 5 | Định dạng ảnh | Bitmap về cách bảng trông |
Phía dán chọn một định dạng nó hiểu từ danh sách này rồi trích. Dán vào Word thì bạn được bảng đã định dạng; dán vào Notepad thì bạn được văn bản phân cách bằng tab — vì hai bên chọn định dạng khác. “Kết quả tùy nơi bạn dán” không phải lỗi; đó là hệ quả bình thường của thiết kế này.
flowchart TB
accTitle: Vì sao cùng lần sao chép cho kết quả khác tùy nơi bạn dán
accDescr: Phía sao chép đặt cùng nội dung lên clipboard ở nhiều định dạng, và phía dán chọn một định dạng nó hiểu, nên Word được bảng đã định dạng và Notepad được văn bản phân cách bằng tab
copy["Sao chép: bảng tính"] --> cb["Clipboard(nhiều định dạng)"]
cb --> rich["Định dạng giàu hơn"]
cb --> plain["Định dạng thuần hơn"]
rich --> f1["Riêng ứng dụng"]
rich --> f2["HTML Format"]
plain --> f3["CSV"]
plain --> f4["CF_UNICODETEXT"]
f2 -->|"Word"| word["Bảng đã định dạng"]
f4 -->|"Notepad"| notepad["Văn bản phân cách bằng tab"]
Nói ngược lại, các khiếu nại mở đầu — “định dạng tan”, “thứ kỳ lạ được dán” — gần như đều quy về vấn đề cách một phía chọn định dạng, hoặc cách phía kia cung cấp chúng. Chương 4 trình bày phía dán; Chương 5 trình bày phía sao chép.
3. Định dạng chuẩn và định dạng đã đăng ký — CF_UNICODETEXT, CF_HDROP, HTML Format
3.1. Định dạng chuẩn — Dùng phía Unicode cho văn bản
Các định dạng hệ điều hành định nghĩa sẵn được gọi là định dạng chuẩn. Những cái hiện liên tục trong ứng dụng nghiệp vụ là các cái sau.3
| Định dạng | Giá trị | Nội dung |
|---|---|---|
| CF_TEXT | 1 | Văn bản ANSI (phụ thuộc trang mã) |
| CF_UNICODETEXT | 13 | Văn bản Unicode. Đây là định dạng chuẩn tắc cho văn bản |
| CF_HDROP | 15 | Danh sách đường dẫn tệp (handle HDROP) |
| CF_DIB | 8 | Bitmap độc lập thiết bị |
| CF_LOCALE | 16 | Định danh locale gắn với văn bản |
CF_TEXT và CF_UNICODETEXT được hệ thống chuyển thành nhau ngầm (định dạng tổng hợp). Chuyển mã ký tự dùng trang mã gắn với CF_LOCALE.3 Dựa vào lần chuyển đó làm rơi các ký tự ANSI không biểu diễn được (ví dụ ký hiệu chỉ Unicode và ký tự kết hợp), nên quy tắc là thống nhất những gì ứng dụng đọc và ghi trên CF_UNICODETEXT (DataFormats.UnicodeText trong .NET).
flowchart TB
accTitle: Chuyển ngầm giữa CF_UNICODETEXT và CF_TEXT
accDescr: Ứng dụng chỉ đọc và ghi CF_UNICODETEXT; hệ thống tổng hợp CF_TEXT bằng chuyển ngầm với trang mã CF_LOCALE. Các ký tự ANSI không biểu diễn được bị rơi trong lần chuyển đó
apprw["Ứng dụng đọc và ghi"] --> uni["CF_UNICODETEXT"]
uni <-->|"Chuyển CF_LOCALE"| ansi["CF_TEXT(ANSI)"]
ansi -.-> loss["Ký tự không biểu diễn được bị rơi"]
3.2. CF_HDROP — Tệp đi như “danh sách đường dẫn”
CF_HDROP là thứ được dùng khi bạn sao chép tệp trong File Explorer, hoặc khi bạn kéo thả tệp. Payload không phải bản thân các tệp; đó là khối bộ nhớ trải một mảng “kết thúc bằng NUL kép”: sau đầu đề cấu trúc DROPFILES, các chuỗi đường dẫn đầy đủ phân cách bằng ký tự NUL, và một chuỗi rỗng ở cuối. pFiles của đầu đề là offset bắt đầu của danh sách đường dẫn, và fWide nói các chuỗi có phải Unicode không.4
[DROPFILES header: pFiles=start offset of the path list, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)
Trong mã native bạn kéo chúng ra từng cái bằng DragQueryFile; trong .NET bạn nhận chúng như string[] qua DataFormats.FileDrop. Sự thật rằng “thứ đi chỉ là đường dẫn, không phải bản thân các tệp” sẽ lại quan trọng trong D&D của Chương 8 và 9.
flowchart TB
accTitle: Bố cục khối bộ nhớ của CF_HDROP
accDescr: Cấu trúc DROPFILES ngồi ở đầu bộ nhớ toàn cục; pFiles là offset bắt đầu của danh sách đường dẫn và fWide nói nó có phải Unicode. Các đường dẫn đầy đủ rồi theo, phân cách bằng NUL, và khối kết thúc bằng chuỗi rỗng(NUL kép). Thứ đi chỉ là đường dẫn, không phải tệp
hdr["DROPFILES(pFiles / fWide)"] --> p1["C:\\data\\a.txt + NUL"]
p1 --> p2["C:\\data\\b.txt + NUL"]
p2 --> tail["Chuỗi rỗng(NUL kép)"]
hdr -.-> note["Chỉ đường dẫn đi, không phải tệp"]
3.3. Định dạng đã đăng ký — RegisterClipboardFormat và “HTML Format”
Với dữ liệu mà định dạng chuẩn không biểu đạt được, ứng dụng có thể chọn một tên và đăng ký định dạng riêng. Truyền một tên cho RegisterClipboardFormat thì bạn nhận lại ID định dạng; đăng ký dưới cùng tên từ ứng dụng khác trả về cùng ID, nên một khi bạn thống nhất tên bạn có thể chia dữ liệu giữa các ứng dụng.1 Khi bạn truyền dữ liệu có cấu trúc giữa bộ ứng dụng của riêng bạn, dùng tên sẽ không va, như KomuraSoft.Report.RowData.
Định dạng đã đăng ký đại diện là “HTML Format”, cho văn bản đã định dạng (cùng với RTF, một trong hai định dạng văn bản giàu lớn). Payload là văn bản UTF-8, nhưng nó có cấu trúc khác thường: một đầu đề liệt kê các offset byte được gắn ở phía trước.5
Version:0.9
StartHTML:<byte offset of the start of the whole HTML>
EndHTML:<byte offset of the end of the whole HTML>
StartFragment:<byte offset of the start of the fragment>
EndFragment:<byte offset of the end of the fragment>
<html><body>
<!--StartFragment--><b>bold</b> fragment text<!--EndFragment-->
</body></html>
Mỗi offset là vị trí byte từ đầu dữ liệu, gồm cả bản thân đầu đề; thực hành thông thường là dành sẵn chiều rộng cố định (ví dụ 10 chữ số) rồi viết các giá trị đã đo lại sau khi bạn đã xây thân. StartFragment/EndFragment đánh dấu đầu và cuối của “mảnh người dùng thực sự đã chọn” theo byte (không theo ký tự). Trong UTF-8 gồm tiếng Nhật, số ký tự và số byte lệch nhau, nên nếu bạn tính sai offset này, dán vào ứng dụng khác làm rơi đầu hoặc cuối. Nếu bạn tự tạo HTML Format, bạn phải điền đầu đề bằng vị trí byte đo sau khi mã hóa sang UTF-8.5
flowchart TB
accTitle: Cách đầu đề HTML Format liên hệ với các offset
accDescr: StartHTML và EndHTML của đầu đề trỏ toàn bộ HTML, và StartFragment cùng EndFragment trỏ mảnh người dùng đã chọn, cả hai như vị trí byte từ đầu dữ liệu. Vì số ký tự và số byte lệch trong UTF-8, hãy điền đầu đề bằng vị trí byte đo sau khi mã hóa
header["Đầu đề(offset byte)"] --> html["Toàn bộ HTML"]
html --> frag["Mảnh đã chọn"]
header -.-> byte["Offset là byte sau UTF-8"]
CSV (DataFormats.CommaSeparatedValue trong .NET) cũng thường được dùng cho dữ liệu dạng bảng. Để tương tác với Excel, cung cấp HTML Format (có định dạng), CSV (chỉ giá trị), và CF_UNICODETEXT (phân cách bằng tab) cùng lúc nghĩa là bạn không phải chọn một đích dán duy nhất.
4. Thực hành phía dán — Ưu tiên định dạng và kiểm tra
4.1. Nhìn từ định dạng giàu xuống
Các định dạng trên clipboard được xếp theo thứ tự phía sao chép đã đặt chúng (tức là từ biểu đạt nhiều hơn xuống ít hơn). Đường cơ sở của phía dán là nhìn, trong các định dạng bạn xử lý được, bắt đầu từ cái có nhiều thông tin nhất. Trong Win32 bạn hoặc liệt kê bằng EnumClipboardFormats rồi dùng định dạng đầu tiên bạn nhận ra, hoặc truyền danh sách ưu tiên riêng cho GetPriorityClipboardFormat và để nó chọn.2
Trong .NET nhánh trông đại khái như sau.
// Pasting a table: look from rich to plain
var data = Clipboard.GetDataObject();
if (data is null) return;
// Advertising a format does not guarantee the payload is a string. Use this
// branch only when the type also checks out; otherwise fall through to the next candidate
if (data.GetDataPresent(DataFormats.Html)
&& data.GetData(DataFormats.Html) is string html)
{
// Validate the HTML Format header, then import as a table
}
else if (data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
// Import as CSV
}
else if (data.GetDataPresent(DataFormats.UnicodeText))
{
// Import as tab-separated text
}
Đó là câu trả lời cho khiếu nại mở đầu, “dán bảng Excel thì tan”. Ứng dụng chỉ đọc văn bản thuần không bao giờ nhận cấu trúc bảng. Bạn chấp nhận xuống bao xa trong danh sách định dạng là quyết định thiết kế phía dán.
flowchart TB
accTitle: Nhánh dán nhìn từ định dạng giàu xuống
accDescr: Nếu HTML Format có mặt và payload cũng là chuỗi, nhập như bảng; nếu không thử CSV; nếu cái đó cũng thiếu, rơi xuống văn bản phân cách bằng tab. Nếu không ứng viên nào có mặt, từ chối
startsel["Bắt đầu dán"] --> h{"HTML Format + chuỗi?"}
h -->|"có"| useh["Kiểm tra đầu đề → bảng"]
h -->|"không"| c{"CSV có mặt?"}
c -->|"có"| usec["Nhập như CSV"]
c -->|"không"| t{"UnicodeText?"}
t -->|"có"| uset["Văn bản phân cách bằng tab"]
t -->|"không"| giveup["Từ chối"]
4.2. Dữ liệu đã dán là đầu vào bên ngoài
Dễ bỏ lỡ, nhưng nội dung clipboard là dữ liệu từ bên ngoài, và bạn không biết ứng dụng nào đã đặt chúng. Microsoft cũng cảnh báo, trong tài liệu clipboard OLE, rằng “dữ liệu clipboard không được tin. Phân tích nó cẩn thận trước khi dùng trong ứng dụng”.7
- Kiểm tra rằng các offset đầu đề HTML Format không trỏ ra ngoài bộ đệm (ứng dụng phát đầu đề hỏng có tồn tại).
- Giá trị bạn nhập như số, ngày, hoặc mã nên đi qua cùng kiểm tra như đầu vào trên màn hình.
- Đặt phòng thủ chống dữ liệu khổng lồ. Ngay cả nếu ai đó dán ảnh hàng trăm megabyte hoặc hàng triệu dòng văn bản, đừng chặn UI, và từ chối khi vượt giới hạn. Một lưu ý: GetData của .NET, đúng lúc bạn gọi, hiện thực hóa toàn bộ payload thành chuỗi managed (và kết xuất trì hoãn chạy như một phần của điều đó), nên đặt kiểm tra kích thước sau GetData không phải phòng thủ. Trong Win32, kiểm tra GlobalSize trên HGLOBAL mà GetClipboardData trả về cho bạn phòng thủ ở giai đoạn “đừng đi tiếp vào chuyển và phân tích như chuỗi managed”, nhưng với định dạng kết xuất trì hoãn bản thân GetClipboardData kích hoạt kết xuất, nên bạn vẫn không ngăn được hiện thực hóa phía nguồn sao chép. Để UI không đóng băng, chuyển lần lấy khỏi luồng UI (và ngay cả vậy, vì Clipboard của .NET đòi STA, hãy làm trên luồng chuyên dụng đặt thành STA, không trên luồng thread-pool của Task.Run (MTA) — Mục 5.1).
Ý tưởng rằng “giá trị tới từ bên ngoài, dù đường nào, được kiểm tra trước khi bạn dùng” là cùng ý được trình bày trong “Không bao giờ dùng nguyên giá trị giải mã từ mã QR”. Giả định rằng dán an toàn vì đó là hành động của người dùng là cách tai nạn bắt đầu.
flowchart TB
accTitle: Kiểm tra dữ liệu đã dán trước khi dùng
accDescr: Dữ liệu lấy từ clipboard đi qua có-mặt-định-dạng, kiểu-payload, giới-hạn-kích-thước, và kiểm tra nội dung theo thứ tự đó; thất bại cái nào thì bạn từ chối hoặc rơi xuống ứng viên định dạng tiếp
present["Định dạng có mặt?"] --> type["Kiểu payload ổn?"]
type --> size["Kích thước trong giới hạn?"]
size --> content["Kiểm tra nội dung"]
content --> ok["Nhập"]
type -.->|"sai kiểu"| rej["Từ chối / định dạng tiếp"]
size -.->|"quá lớn"| rej
content -.->|"không hợp lệ"| rej
5. Thực hành phía sao chép — Cung cấp nhiều định dạng cùng lúc, và kết xuất trì hoãn
5.1. Đặt nhiều định dạng cùng lúc
Thực hành phía sao chép là ngược của 4.1: cung cấp định dạng giàu và định dạng thuần cùng lúc. Với DataObject của WinForms/WPF bạn có thể viết trong vài dòng.15
// WinForms (System.Windows.Forms). WPF is the same shape with System.Windows DataObject/Clipboard
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText); // HTML Format string including the header
data.SetData(DataFormats.CommaSeparatedValue, csv); // CSV
data.SetData(DataFormats.UnicodeText, plainText); // Plain text
Clipboard.SetDataObject(data, copy: true); // copy:true = keep after the app exits
Hai lưu ý. Thứ nhất, lớp Clipboard của .NET chỉ dùng được từ luồng STA.15 Luồng UI WinForms/WPF là STA vì [STAThread], nên điều này thường không thành vấn đề, nhưng chạm nó từ luồng nền thì thất bại (nền tảng STA/MTA nằm trong “Nền tảng COM STA/MTA”). Thứ hai, copy: true nghĩa là gì gắn với kết xuất trì hoãn ở mục tiếp.
5.2. Kết xuất trì hoãn — Vì sao “đóng nguồn thì không dán được”
Xây payload lớn ở nhiều định dạng mỗi lần thì lãng phí, nên clipboard có cơ chế gọi là kết xuất trì hoãn. Truyền NULL làm handle dữ liệu cho SetClipboardData và, thay vì payload, chỉ một lời hứa “tạo khi được hỏi” được đăng ký; khi ai đó yêu cầu định dạng đó, WM_RENDERFORMAT tới nguồn sao chép, và chỉ khi đó dữ liệu mới được tạo.2
Hệ quả của thiết kế này là câu mở đầu “tôi đóng ứng dụng nguồn rồi không còn dán được”. Trước khi thoát, nguồn sao chép nhận WM_RENDERALLFORMATS và chịu trách nhiệm hiện thực hóa mọi định dạng chưa được kết xuất; thoát mà không làm điều đó thì định dạng mất.2
flowchart TB
accTitle: Kết xuất trì hoãn và vì sao đóng-rồi-dán thất bại
accDescr: Nguồn sao chép chỉ đăng ký lời hứa bằng handle NULL, và hiện thực hóa theo yêu cầu qua WM_RENDERFORMAT. Lúc thoát nó chịu trách nhiệm hiện thực hóa mọi định dạng bằng WM_RENDERALLFORMATS; bỏ qua thì định dạng mất
promise["SetClipboardData NULL = lời hứa"] --> req["Phía dán yêu cầu"]
req --> render["WM_RENDERFORMAT → xây ngay"]
promise --> quit["Nguồn sao chép sắp thoát"]
quit -->|"RENDERALLFORMATS"| ok["Dán vẫn được sau khi thoát"]
quit -->|"Bỏ hiện thực hóa"| lost["Định dạng mất sau khi đóng"]
Trên clipboard OLE (kiểu đặt IDataObject bằng OleSetClipboard), quan hệ này còn rõ hơn. Tất cả clipboard giữ là con trỏ tới đối tượng dữ liệu, và gọi OleFlushClipboard lúc ứng dụng thoát hiện thực hóa dữ liệu lên clipboard, nên dán vẫn hoạt động sau khi thoát.6 Clipboard.SetDataObject(data, copy: true) của .NET chính là thứ chỉ định hành vi “giữ sau khi thoát” này.
Khi bạn sao chép một vùng lớn trong Excel rồi cố thoát, lời nhắc “There is a large amount of information on the Clipboard. Do you want to be able to paste this information into another program later?” chính là xác nhận có chạy lần hiện thực hóa này (flush) không. Nếu bạn dùng kết xuất trì hoãn trong ứng dụng của mình, hãy nhớ rằng hiện thực hóa lúc thoát là một phần của cùng bộ. Kết xuất trì hoãn là tối ưu hiệu năng, và vì yêu cầu kết xuất chạy đồng bộ bên trong xử lý thông điệp, dữ liệu mất nhiều thời gian để tạo có sự đánh đổi đóng băng UI.2
6. Thực hành theo dõi clipboard — Listener, thử lại, và loại khỏi lịch sử
6.1. Dùng AddClipboardFormatListener
Các yêu cầu như “chúng tôi muốn phát hiện giá trị đầu đọc mã vạch hoặc lần sao chép từ hệ thống nghiệp vụ rồi nhập tự động” cần bạn theo dõi thay đổi clipboard. Về lịch sử có ba phương pháp; hôm nay câu trả lời đúng là một.8
| Phương pháp | Đánh giá |
|---|---|
| Đọc trên bộ hẹn giờ (thăm dò) | Lãng phí, và bạn có thể bỏ lỡ cập nhật. Đừng dùng |
| SetClipboardViewer (chuỗi viewer) | Lỗi ở một ứng dụng trong chuỗi làm hỏng cả chuỗi. Chỉ được giữ vì tương thích ngược |
| AddClipboardFormatListener | Được khuyến nghị. WM_CLIPBOARDUPDATE tới cửa sổ đã đăng ký |
flowchart TB
accTitle: Luồng theo dõi clipboard
accDescr: Đăng ký bằng AddClipboardFormatListener khi handle được tạo, và WM_CLIPBOARDUPDATE tới dù ứng dụng nào đã sao chép. Đọc với thử lại, và hủy đăng ký đối xứng bằng RemoveClipboardFormatListener khi handle bị hủy
created["AddClipboardFormatListener"] --> wait["Chờ"]
anyapp["Một ứng dụng nào đó sao chép"] --> notify["WM_CLIPBOARDUPDATE"]
wait --> notify
notify --> readtry["Đọc với thử lại(6.2)"]
readtry --> wait
destroyed["RemoveClipboardFormatListener"] -.->|"hủy đăng ký"| created
// Minimal WinForms implementation
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)
{
// Unregister symmetrically to match handle destruction / recreation
RemoveClipboardFormatListener(Handle);
base.OnHandleDestroyed(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CLIPBOARDUPDATE)
{
// Read Clipboard.GetDataObject() here and import if the format is one you need
}
base.WndProc(ref m);
}
}
6.2. Thử lại khi bạn không mở được
Chỉ một cửa sổ tại một thời điểm có thể mở clipboard; trong khi tiến trình khác đang mở, OpenClipboard thất bại.2 Ngay sau WM_CLIPBOARDUPDATE, nguồn sao chép hoặc watcher khác thường vẫn đang thao tác, nên lần đọc thất bại tạm thời là sự kiện bình thường. Luôn đặt vài lần thử lại với chờ ngắn (vài chục mili giây) ở giữa. Lưu ý rằng các overload Clipboard của .NET cho phép bạn chỉ định số lần thử lại và khoảng chỉ tồn tại ở phía ghi, SetDataObject. Không có tương đương phía đọc (GetDataObject và bạn), nên bạn tự viết bắt-chờ-thử lại — ExternalException trên WinForms, COMException trên WPF.
flowchart TB
accTitle: Luồng thử lại đọc clipboard
accDescr: Chỉ một cửa sổ tại một thời điểm có thể mở clipboard, nên lần đọc ngay sau thông báo thay đổi có thể thất bại vì đua với tiến trình khác. Khi ngoại lệ, chờ vài chục mili giây rồi thử lại; nếu chạm giới hạn, bỏ lần này và nhặt ở cập nhật tiếp
upd["WM_CLIPBOARDUPDATE"] --> tryread["Thử đọc"]
tryread -->|"thành công"| useok["Nhập(kiểm tra Ch. 4)"]
tryread -->|"đang dùng"| waitretry["Chờ vài chục ms"]
waitretry -->|"thử lại"| tryread
waitretry -->|"giới hạn"| giveup2["Bỏ lần này"]
6.3. Giữ khỏi lịch sử và đồng bộ — Chăm sóc tính năng sao chép xử lý bí mật
Windows có lịch sử clipboard (Win+V) và đồng bộ xuyên thiết bị (clipboard đám mây), và dữ liệu ứng dụng đặt mặc định nằm trong phạm vi cả hai. Ứng dụng đặt bí mật như mật khẩu hoặc số tài khoản trên tính năng sao chép cũng đặt một định dạng đã đăng ký loại nội dung khỏi lịch sử và đồng bộ.1
- ExcludeClipboardContentFromMonitorProcessing: Đặt cái này và nội dung lần sao chép đó không được gồm vào lịch sử lẫn đồng bộ.
- CanIncludeInClipboardHistory (DWORD 0): Chỉ ức chế lịch sử.
- CanUploadToCloudClipboard (DWORD 0): Chỉ ức chế đồng bộ xuyên thiết bị.
Lý do mật khẩu do trình quản lý mật khẩu sao chép không ở lại trên Win+V là cơ chế này. Bạn nhận ID định dạng bằng cách truyền tên cho RegisterClipboardFormat rồi đặt nó cạnh dữ liệu thông thường, nên đáng triển khai trong mọi ứng dụng nghiệp vụ xử lý bí mật.
7. Clipboard từ góc độ IT — Lịch sử, đồng bộ đám mây, và điều khiển RDP
Bước ra một chút khỏi phát triển, đây là các điểm quan trọng với quản trị viên. Lịch sử clipboard tích các lần sao chép gần, và clipboard đám mây đồng bộ các lần sao chép giữa các thiết bị đăng nhập cùng tài khoản Microsoft / tài khoản Microsoft Entra.10 Tiện như vậy, nó cũng tạo cặn và tràn: thông tin cá nhân sao chép từ hệ thống nghiệp vụ tích trong lịch sử, và nội dung sao chép trên PC công việc đồng bộ sang PC cá nhân.
Hai chính sách bạn dùng để điều khiển điều này trong tổ chức là các cái sau.
| Thứ bạn điều khiển | GPO (Computer Configuration > Administrative Templates > System > OS Policies) | Policy CSP (Intune) | Mặc định |
|---|---|---|---|
| Lịch sử clipboard | Allow Clipboard History | Experience/AllowClipboardHistory | Được phép |
| Đồng bộ xuyên thiết bị | Allow Clipboard synchronization across devices | Privacy/AllowCrossDeviceClipboard | Được phép |
Cả hai có từ Windows 10 phiên bản 1809 trở đi; tắt chúng thì các mục tương ứng trong ứng dụng Settings bị xám, và chính sách có hiệu lực ngay.910
Món chủ lực kia là chuyển hướng clipboard RDP (Remote Desktop). Mặc định, sao chép-và-dán hoạt động giữa PC cục bộ và phiên từ xa, nên nó có thể trở thành đường lấy bí mật ra khỏi máy chủ. Chính sách “Do not allow clipboard redirection” (giá trị registry fDisableClip) có thể chặn cả hai hướng.11 Các bản Windows Server / Windows 11 gần đây cũng đã thêm chính sách mịn hơn, như hạn chế hướng máy chủ-sang-client chỉ văn bản. Liệu bạn cấm hẳn hay hạn chế theo giai đoạn là sự cân bằng giữa vận hành và bảo mật.
flowchart TB
accTitle: Các đường nội dung clipboard có thể lan, và các điểm điều khiển
accDescr: Nội dung đã sao chép mặc định nằm trong phạm vi lịch sử và đồng bộ đám mây, và trên RDP chúng đi sang phiên khác qua chuyển hướng. Mỗi đường có thể được điều khiển bằng chính sách, và phía ứng dụng có thể tự loại khỏi lịch sử và đồng bộ bằng các định dạng loại trừ
cb["Clipboard"] --> hist["Lịch sử(Win+V)"]
cb --> cloud["Đồng bộ đám mây"]
cb --> rdp["Chuyển hướng RDP"]
hist -.-> p1["AllowClipboardHistory"]
cloud -.-> p2["AllowCrossDeviceClipboard"]
rdp -.-> p3["fDisableClip"]
cb -.-> p4["Định dạng loại trừ ứng dụng(6.3)"]
8. Kéo thả là COM — IDataObject + IDropSource + IDropTarget
8.1. Cùng dữ liệu với clipboard, cách mang khác
Kéo thả OLE chạy với ba vai sau.12
| Vai | Ai triển khai | Việc |
|---|---|---|
| IDataObject | Nguồn kéo | Payload đang được mang. Cùng đối tượng dữ liệu đa định dạng với clipboard |
| IDropSource | Nguồn kéo | Quyết liệu lần kéo tiếp tục hay bị hủy, và phản hồi con trỏ |
| IDropTarget | Đích thả | Tuyên bố chấp nhận/từ chối trong DragEnter/DragOver/DragLeave/Drop, và nhận lần thả |
Nguồn kéo gọi DoDragDrop, vòng kéo bắt đầu, và khi chuột vào cửa sổ đích thả thì IDropTarget đó được thông báo; khi thả, IDataObject được giao. Tài liệu chính thức cũng nói rằng “D&D cung cấp đúng cùng chức năng với sao chép-và-dán clipboard. Nếu ứng dụng đã triển khai sao chép-và-dán, phần thêm là nhỏ”.12 Nói cách khác, DataObject đa định dạng bạn đã xây ở Chương 2 tới 5 trở thành payload D&D nguyên như vậy.
flowchart TB
accTitle: Luồng kéo thả OLE
accDescr: Nguồn kéo đặt IDataObject vào payload rồi gọi DoDragDrop để bắt đầu vòng kéo; IDropTarget của đích thả tuyên bố chấp nhận/từ chối trong DragEnter và DragOver, và khi Drop chọn một định dạng từ IDataObject rồi trích
src["IDataObject + IDropSource"] -->|"DoDragDrop"| loop["Vòng kéo"]
loop -->|"chuột vào"| enter["DragEnter/Over: Effect"]
enter -->|"nhả nút"| drop["IDropTarget.Drop"]
drop --> data["Chọn định dạng rồi trích"]
8.2. OleInitialize (STA) là bắt buộc
Cửa sổ sẽ là đích thả đăng ký bằng RegisterDragDrop, và có một cái bẫy kinh điển ở đây. Nếu bạn khởi tạo COM bằng CoInitialize/CoInitializeEx, RegisterDragDrop luôn thất bại với E_OUTOFMEMORY; bạn phải khởi tạo bằng OleInitialize.13 OleInitialize khởi tạo COM như STA, vì D&D là tính năng bám thế giới STA của cửa sổ và bơm thông điệp. Luồng gọi cũng phải đang chạy bơm thông điệp; bỏ qua thì ứng dụng khác treo trong lúc kéo.13 Bối cảnh ở đây chính là thảo luận mô hình luồng trong “Nền tảng COM STA/MTA”.
Trong ứng dụng WinForms/WPF khung lo việc khởi tạo OLE và các triển khai giao diện, nên nhà phát triển chỉ phải viết các sự kiện.
// WinForms: accept dropped files
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
// Also check that the source allows Copy (some sources only allow Move/Link)
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
&& (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
? DragDropEffects.Copy // Accept: receive as a copy
: DragDropEffects.None; // Do not accept
};
listView1.DragDrop += (s, e) =>
{
// Drag data is also untrusted input. Even if it advertises FileDrop, the payload
// can be null or a different type, and GetData itself can fail
object data;
try { data = e.Data.GetData(DataFormats.FileDrop); }
catch (COMException) { return; }
if (data is not string[] paths) return;
foreach (var path in paths)
{
// Validate the path before importing (Section 9.3)
}
};
Hình dạng giống nhau trong WPF: bạn nhận bằng AllowDrop="True" và các sự kiện DragOver/Drop trên phần tử, và bạn trích mảng đường dẫn bằng e.Data.GetData(DataFormats.FileDrop). Tuyên bố chấp nhận/từ chối (Effect) ở mọi DragEnter/DragOver là quy ước IDropTarget; bỏ qua thì bạn gặp lỗi con trỏ ở lại “không được phép” và không bao giờ đổi.
9. Những cái bẫy D&D — Nâng quyền, Move, và kiểm tra đường dẫn
9.1. Bạn không thể thả lên ứng dụng đã nâng quyền quản trị
Thả tệp từ File Explorer lên ứng dụng khởi chạy bằng “Run as administrator” thì không có gì xảy ra — đây không phải lỗi triển khai, đó là hành vi hệ điều hành. UIPI (User Interface Privilege Isolation) mặc định chặn thông điệp từ tiến trình toàn vẹn thấp hơn tới cửa sổ toàn vẹn cao hơn, nên thông báo thả từ File Explorer đặc quyền thông thường (toàn vẹn trung bình) không bao giờ tới ứng dụng đã nâng quyền.14
flowchart TB
accTitle: Cách UIPI chặn thả lên ứng dụng đã nâng quyền
accDescr: Thông báo thả từ File Explorer toàn vẹn trung bình tới ứng dụng đã nâng quyền toàn vẹn cao mặc định bị UIPI chặn và không bao giờ tới. Giữ UI ở đặc quyền thông thường rồi cô lập việc đặc quyền, và lần thả tới
explorer["Explorer(trung bình)"] -->|"thông báo thả"| uipi{"UIPI"}
uipi -->|"bị chặn"| elevated["Ứng dụng đã nâng quyền: không thả"]
uipi -->|"đi qua"| normal["UI thông thường: thả tới"]
normal -.->|"ủy thác việc đặc quyền"| broker["Tiến trình nâng quyền đã cô lập"]
Cách vòng cho phép riêng các thông điệp cụ thể như WM_DROPFILES bằng ChangeWindowMessageFilterEx được biết rõ,14 nhưng thứ điều đó cho qua là thông báo thả cũ hơn (WM_DROPFILES); nó không giải D&D OLE như một khối. Hướng dẫn thực tiễn rõ: thôi thiết kế ứng dụng chạy nâng quyền suốt. Cô lập chỉ việc cần nâng quyền vào tiến trình riêng, và bản thân UI có thể ở lại đặc quyền thông thường rồi nhận D&D (thiết kế cô lập được trình bày chi tiết trong “Cách cô lập cụ thể “chỉ những thao tác cần đặc quyền quản trị” trong ứng dụng Windows”).
9.2. DragDropEffects nghĩa là gì — Move là hợp đồng rằng “bản gốc biến mất”
Copy/Move/Link trên DragDropEffects không phải trang trí; chúng là hợp đồng giữa nguồn kéo và đích thả. Nguồn kéo tuyên bố tập hiệu ứng nó cho phép trong DoDragDrop, đích thả chọn hiệu ứng thật, và khi Move thành công, nguồn kéo xóa dữ liệu (tệp) — đó là quy ước. Nếu phía nhận vô tư trả về Move, bạn gặp tai nạn “tôi thả rồi tệp gốc biến mất”. Với mục đích nhập của ứng dụng nghiệp vụ, phía nhận tuyên bố Copy là mặc định an toàn.
flowchart TB
accTitle: Hợp đồng DragDropEffects — Move xóa bản gốc
accDescr: Nguồn kéo tuyên bố tập hiệu ứng được phép trong DoDragDrop, và đích thả chọn hiệu ứng thật. Khi Move thành công nguồn kéo xóa tệp, nên với nhập phía nhận nên tuyên bố Copy
srcdecl["Nguồn: hiệu ứng được phép"] --> tgtsel["Đích: chọn Effect"]
tgtsel -->|"Copy"| copyok["Bản gốc còn(nhập)"]
tgtsel -->|"Move"| moveact["Nguồn xóa tệp"]
9.3. Kiểm tra đường dẫn đã thả
Thứ đi trong CF_HDROP/FileDrop chỉ là đường dẫn (Mục 3.2). Trước khi nhập, hãy đưa nó qua cùng kiểm tra đầu vào không tin cậy như dán.
- Tệp hay thư mục: Quyết như đặc tả điều gì xảy ra khi cả thư mục được thả (đệ quy rồi nhập, hoặc từ chối).
- Placeholder OneDrive: Đường dẫn có thể tồn tại trong khi thân tệp không cục bộ — tệp theo yêu cầu. Khoảnh khắc bạn mở nó một lần tải bắt đầu, và khi ngoại tuyến thì thất bại. Hành vi và đối sách nằm trong “OneDrive “Files On-Demand” và ứng dụng nghiệp vụ”.
- Đường dẫn dài và đường dẫn khác thường: Đường dẫn vượt MAX_PATH, đường dẫn mạng (UNC), và đường dẫn trên phương tiện tháo được chỉ nên được chấp nhận sau khi bạn đã xác nhận xử lý phía sau xử lý được chúng.
- Số lượng và tổng kích thước: Để thả hàng nghìn tệp không đóng băng UI, hãy làm việc nhập bất đồng bộ và đặt giới hạn cùng hiển thị tiến độ.
10. Tóm tắt
- Clipboard là cơ chế đặt cùng nội dung ở nhiều định dạng cùng lúc trong một vùng dùng chung trong cùng desktop (window station). Phía dán chọn định dạng, nên cùng lần sao chép cho kết quả khác.
- Văn bản là CF_UNICODETEXT, tệp là CF_HDROP, và văn bản đã định dạng là định dạng đã đăng ký HTML Format (đầu đề offset byte + UTF-8).
- Phía dán nhìn từ giàu xuống thuần và coi payload như đầu vào bên ngoài. Phía sao chép cung cấp nhiều định dạng cùng lúc, và nếu dùng kết xuất trì hoãn thì cũng triển khai hiện thực hóa lúc thoát (WM_RENDERALLFORMATS / OleFlushClipboard).
- Theo dõi là AddClipboardFormatListener + WM_CLIPBOARDUPDATE. Chuẩn bị cho đua OpenClipboard bằng thử lại, và giữ bí mật khỏi lịch sử và đồng bộ bằng ExcludeClipboardContentFromMonitorProcessing và bạn.
- IT có thể điều khiển lịch sử clipboard, đồng bộ đám mây, và chuyển hướng RDP bằng GPO / Intune. Mặc định là được phép cho tất cả, nên hãy quyết có chủ ý trong môi trường xử lý bí mật.
- D&D là COM: IDropSource/IDropTarget giao cùng IDataObject với clipboard. RegisterDragDrop đòi OleInitialize (STA).
- Lần thả lên ứng dụng đã nâng quyền bị UIPI chặn. Move trên DragDropEffects là hợp đồng rằng “bản gốc biến mất”; kiểm tra đường dẫn đã thả trước khi nhập.
Sao chép-và-dán và D&D, với người dùng, là tính năng nên cảm thấy như không khí. Chính vì vậy “tôi không dán được”, “nó tan”, và “nó biến mất” làm tổn thương trải nghiệm nhiều đến thế — và vì sao ứng dụng cung cấp nhiều định dạng và xử lý thả đúng tự nó làm thao tác hàng ngày mượt hơn. Tôi hy vọng đây là tư liệu hữu ích khi bạn quyết sửa gì trước.
Bài viết liên quan
- COM / ActiveX / OCX là gì? — Giải thích khác biệt và mối quan hệ
- Nền tảng COM STA/MTA — Mô hình luồng và cách tránh treo
- Tích hợp Windows Shell hiện nay — Menu ngữ cảnh, liên kết tệp, và những gì đã đổi trên Windows 11
- Vì sao tiến trình EXCEL.EXE còn lại sau tự động hóa Excel COM bằng C# — Mẫu giải phóng tham chiếu và quyết định thay thế
- Thiết kế UX ứng dụng Windows — Ưu tiên theo môi trường sử dụng
- OneDrive “Files On-Demand” và ứng dụng nghiệp vụ — Những giả định mà placeholder phá vỡ và cách đối phó
Lĩnh vực tư vấn liên quan
KomuraSoft LLC đảm nhận thiết kế và triển khai hỗ trợ sao chép-và-dán cùng kéo thả trong ứng dụng nghiệp vụ (cung cấp nhiều định dạng, tương tác Excel, nhập tệp đã thả), điều tra nguyên nhân gốc rễ của vấn đề như “nó tan khi tôi dán” hoặc “bản sao biến mất”, tự động hóa đầu vào theo dõi clipboard, và triển khai giữ dữ liệu mật khỏi lịch sử và đồng bộ. Các vụ liên quan tầng thấp hơn của COM và OLE được chào đón ngay cả nếu bạn bắt đầu từ cô lập triệu chứng.
- Phát triển ứng dụng Windows
- Phát triển thành phần COM
- Tư vấn kỹ thuật và rà soát thiết kế
- Liên hệ với chúng tôi
Liên kết tham khảo
-
Microsoft Learn, Clipboard Formats. Về việc một cửa sổ có thể đặt cùng thông tin ở nhiều định dạng clipboard; định dạng đã đăng ký qua RegisterClipboardFormat (đăng ký cùng tên trả về cùng giá trị, nên các ứng dụng có thể chia); định dạng tổng hợp; và loại nội dung khỏi lịch sử clipboard / đồng bộ đám mây bằng ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory và CanUploadToCloudClipboard. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Clipboard Operations. Về chỉ một cửa sổ tại một thời điểm có thể mở clipboard; đặt định dạng từ biểu đạt nhiều hơn xuống ít hơn lúc sao chép; chọn định dạng lúc dán bằng EnumClipboardFormats / GetPriorityClipboardFormat; kết xuất trì hoãn bằng cách truyền NULL cho SetClipboardData và trách nhiệm WM_RENDERFORMAT / WM_RENDERALLFORMATS; và các sự đánh đổi của kết xuất trì hoãn. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Standard Clipboard Formats. Về định nghĩa các định dạng chuẩn CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB và CF_LOCALE, và về hệ thống chuyển ngầm CF_TEXT và CF_UNICODETEXT dùng trang mã gắn với CF_LOCALE. ↩ ↩2 ↩3
-
Microsoft Learn, Shell Clipboard Formats. Về CF_HDROP gồm cấu trúc DROPFILES cộng mảng chuỗi đường dẫn đầy đủ kết thúc bằng NUL kép; lấy từng đường dẫn bằng DragQueryFile; và các định dạng shell CFSTR_ đòi đăng ký qua RegisterClipboardFormat. ↩ ↩2
-
Microsoft Learn, HTML Clipboard Format. Về tên đã đăng ký là “HTML Format”; cấu trúc đầu đề với offset byte như Version, StartHTML, EndHTML, StartFragment và EndFragment; mã hóa luôn là UTF-8; và quy ước chú thích StartFragment/EndFragment. ↩ ↩2 ↩3
-
Microsoft Learn, OleFlushClipboard function (ole2.h). Về OleSetClipboard làm clipboard chỉ giữ con trỏ tới đối tượng dữ liệu; OleFlushClipboard hiện thực hóa dữ liệu lên clipboard nên dán vẫn hoạt động sau khi ứng dụng thoát; và làm trống clipboard bằng OleSetClipboard(NULL) khi bạn không cần giữ lúc thoát. ↩ ↩2
-
Microsoft Learn, OleGetClipboard function (ole2.h). Về cách lấy IDataObject từ clipboard, và cảnh báo rằng dữ liệu clipboard không được tin và nên được phân tích cẩn thận trước khi ứng dụng dùng. ↩ ↩2
-
Microsoft Learn, Using the clipboard. Về so sánh ba cách theo dõi clipboard (cửa sổ viewer, số thứ tự, và listener định dạng); chương trình mới được kỳ vọng dùng listener qua AddClipboardFormatListener; chuỗi viewer mong manh khi bảo trì chuỗi không đầy đủ; và số thứ tự không phải thứ bạn nên thăm dò. ↩ ↩2
-
Microsoft Learn, Policy CSP - Experience. Về cho phép hoặc từ chối lịch sử clipboard bằng chính sách Experience/AllowClipboardHistory; có từ Windows 10 phiên bản 1809 trở đi; mặc định là được phép; và ánh xạ GPO dưới “System > OS Policies” với thay đổi có hiệu lực ngay. ↩ ↩2
-
Microsoft Learn, Policy CSP - Privacy. Về cho phép hoặc từ chối đồng bộ clipboard xuyên thiết bị bằng chính sách Privacy/AllowCrossDeviceClipboard; đồng bộ xảy ra giữa các thiết bị đăng nhập cùng tài khoản Microsoft / tài khoản Microsoft Entra; và mặc định là được phép. ↩ ↩2 ↩3
-
Microsoft Learn, Policy CSP - ADMX_TerminalServer. Về TS_CLIENT_CLIPBOARD (“Do not allow clipboard redirection”, giá trị registry fDisableClip) có thể cấm chia sẻ clipboard giữa cục bộ và từ xa trong phiên Remote Desktop, và về chuyển hướng được phép mặc định. ↩ ↩2
-
Microsoft Learn, Drag and Drop (COM). Về kéo thả OLE chạy với ba thứ IDropSource (nguồn kéo), IDropTarget (đích thả) và DoDragDrop (vòng OLE cung cấp); cung cấp cùng chức năng với sao chép-và-dán clipboard, nên ứng dụng đã triển khai sao chép-và-dán chỉ cần phần thêm nhỏ; và các loại phản hồi. ↩ ↩2 ↩3
-
Microsoft Learn, RegisterDragDrop function (ole2.h). Về đăng ký cửa sổ đích thả bằng IDropTarget; luôn thất bại với E_OUTOFMEMORY nếu COM được khởi tạo bằng CoInitialize/CoInitializeEx, nên OleInitialize là bắt buộc; và ứng dụng nguồn kéo treo nếu luồng gọi không đang chạy bơm thông điệp. ↩ ↩2 ↩3
-
Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). Về UIPI là cơ chế bảo mật mặc định chặn nhận thông điệp từ bên gửi toàn vẹn thấp hơn, và về cho phép thông điệp cụ thể theo từng cửa sổ bằng bộ lọc thông điệp (MSGFLT_ALLOW). ↩ ↩2 ↩3
-
Microsoft Learn, How to add data to the Clipboard (Windows Forms). Về đặt dữ liệu ở nhiều định dạng cùng lúc bằng DataObject và Clipboard.SetDataObject; thêm ở nhiều định dạng để ứng dụng khác nhận ra; và lớp Clipboard chỉ dùng được từ luồng STA, nên [STAThread] là bắt buộc. ↩ ↩2
Bài viết liên quan
Các bài viết gần đây có cùng thẻ để tìm hiểu sâu hơn những chủ đề lân cận.
"Không phản hồi" thực sự là gì — Cách Windows quyết định ứng dụng đã treo, và cách thiết kế ứng dụng không treo
"Không phản hồi" của Windows là cơ chế trong đó hệ điều hành phán rằng một cửa sổ chưa lấy thông điệp trong 5 giây và thay nó bằng cửa sổ...
API thread pool Win32 — Đồng thời mà không tạo luồng, với CreateThreadpoolWork
Bạn đang rải các lời gọi CreateThread khắp mã native? Bài viết này giải thích API thread pool Win32 được thiết kế lại từ Vista — bốn đối ...
Named pipe trong thực tiễn — IPC chuẩn của Windows, từ thiết kế đến bảo mật
Hướng dẫn thực tiễn về named pipe, cơ chế giao tiếp giữa các tiến trình chuẩn của Windows. Bài viết sắp xếp, từ nguồn gốc, việc chọn giữa...
Ứng dụng hỏng khi thức dậy từ ngủ — Sự kiện nguồn Windows và cách viết ứng dụng nghiệp vụ sống sót
Bạn mở laptop và kết nối của ứng dụng nghiệp vụ đã chết — nguyên nhân là thiết kế chưa tính đến ngủ. Bài viết này trình bày luồng thông b...
DllMain và Loader Lock — Lý do thật sự bạn bị bảo "đừng làm gì trong khởi tạo DLL"
Vì sao bạn không được gọi LoadLibrary hoặc đồng bộ với luồng khác từ DllMain. Dựa trên nguồn gốc, bài viết giải thích cách loader lock tu...
Chủ đề liên quan
Các trang này đặt chủ đề trong bối cảnh rộng hơn của dịch vụ và quyết định.
Chủ đề kỹ thuật Windows
Cổng vào phát triển Windows, điều tra lỗi và khai thác tài sản hiện có.
Di chuyển ActiveX
Quyết định giữ, bọc hay thay thế các thành phần COM / ActiveX / OCX.
Luồng UI và bộ định thời
Luồng UI của WPF / WinForms, luồng bất đồng bộ, Dispatcher và thiết kế bộ định thời.
Dịch vụ liên quan đến chủ đề này
Bài viết liên quan trực tiếp đến các dịch vụ sau.
Phát triển ứng dụng Windows
Ứng dụng nghiệp vụ, tích hợp thiết bị và công cụ liên lạc, từ yêu cầu đến phát triển.
Tái sử dụng và di chuyển tài sản hiện có
Tái sử dụng và di chuyển tài sản COM / ActiveX / OCX cùng các phụ thuộc 32/64-bit.
Câu hỏi thường gặp
Các câu hỏi thường gặp khi tư vấn về chủ đề của bài viết.
- Vì sao định dạng bảng sao chép từ Excel tan khi tôi dán vào ứng dụng của mình?
- Clipboard không giữ "một mảnh dữ liệu". Cùng nội dung được đặt ở vài định dạng cùng lúc (định dạng riêng của ứng dụng nguồn, HTML Format, CSV, văn bản Unicode, và tương tự), và ứng dụng đích chọn một định dạng nó hiểu rồi trích cái đó. Khi định dạng tan, nguyên nhân điển hình là đích chỉ đọc văn bản thuần (CF_UNICODETEXT). Nếu bạn muốn cả cấu trúc bảng, hãy triển khai phía dán sao cho nó ưu tiên HTML Format hoặc CSV. Ngược lại, nếu bạn muốn ứng dụng khác dán đúng từ lần sao chép trong ứng dụng của bạn, hãy cung cấp cả định dạng giàu lẫn định dạng thuần lúc sao chép.
- Vì sao tôi không còn dán được sau khi đóng ứng dụng tôi đã sao chép từ đó?
- Vì nguồn đang dùng kết xuất trì hoãn. Ứng dụng xử lý dữ liệu lớn không đặt payload lúc sao chép; chúng chỉ đăng ký trên clipboard một lời hứa rằng chúng sẽ "tạo khi được hỏi". Nếu nguồn rồi thoát mà không hiện thực hóa dữ liệu để đáp WM_RENDERALLFORMATS lúc tắt, mọi định dạng chưa được kết xuất đều mất. Ứng dụng dùng clipboard OLE (IDataObject) có thể giữ dán hoạt động sau khi thoát bằng cách gọi OleFlushClipboard lúc tắt để hiện thực hóa dữ liệu.
- Ứng dụng của tôi theo dõi thay đổi clipboard bằng cách nào?
- Phương pháp hiện được khuyến nghị là đăng ký cửa sổ của bạn làm listener bằng AddClipboardFormatListener và xử lý thông điệp WM_CLIPBOARDUPDATE tới mỗi khi nội dung đổi. Thăm dò nội dung trên bộ hẹn giờ lãng phí việc và có thể bỏ lỡ cập nhật, và chuỗi viewer cũ dựa trên SetClipboardViewer chỉ được giữ vì tương thích ngược, vì lỗi ở một ứng dụng trong chuỗi làm hỏng cả chuỗi. Cũng lưu ý rằng OpenClipboard lúc đọc có thể thất bại vì tiến trình khác đang giữ clipboard, nên hãy triển khai thử lại với chờ ngắn nếu bạn muốn lần đọc ổn định.
- Có cách nào giữ bí mật như mật khẩu khỏi lịch sử clipboard (Win+V) không?
- Có hai đòn bẩy, một phía ứng dụng và một phía chính sách. Phía ứng dụng, nếu bạn cũng đặt định dạng đã đăng ký ExcludeClipboardContentFromMonitorProcessing khi sao chép, nội dung đó không được gồm vào lịch sử lẫn đồng bộ xuyên thiết bị. Bạn cũng có thể điều khiển từng cái độc lập bằng CanIncludeInClipboardHistory (chỉ lịch sử) và CanUploadToCloudClipboard (chỉ đồng bộ). Đây là cơ chế trình quản lý mật khẩu dùng. Nếu bạn muốn tắt cho cả tổ chức, bạn có thể tắt bản thân lịch sử và đồng bộ đám mây bằng AllowClipboardHistory và AllowCrossDeviceClipboard qua Group Policy hoặc Intune (Policy CSP).
- Vì sao tôi không thể kéo thả tệp lên ứng dụng đang chạy với quyền quản trị?
- Vì cơ chế bảo mật gọi là UIPI (User Interface Privilege Isolation) chặn gửi thông điệp từ tiến trình toàn vẹn thấp hơn tới cửa sổ toàn vẹn cao hơn. File Explorer chạy ở đặc quyền thông thường (toàn vẹn trung bình), nên thông báo kéo thả không bao giờ tới cửa sổ của ứng dụng đã nâng quyền. Cách vòng được biết rõ là cho phép riêng các thông điệp như WM_DROPFILES bằng ChangeWindowMessageFilterEx, nhưng nó chỉ áp dụng cho thông báo thả cũ hơn. Sửa thật sự là thôi thiết kế ứng dụng chạy nâng quyền suốt, và cô lập chỉ việc cần nâng quyền vào tiến trình riêng.