คลิปบอร์ดกับลากและวางทำงานอย่างไร — จัดการการถ่ายโอนข้อมูล OLE ให้ถูกต้องในแอปธุรกิจ

· · Windows, คลิปบอร์ด, ลากและวาง, OLE, COM, การพัฒนา Windows, WinForms, WPF

“เมื่อเราวางตารางที่คัดลอกจาก Excel การจัดรูปแบบพัง เราอยากให้มันวางเป็นตาราง” “เนื้อหาที่เราคัดลอกในแอปกลายเป็นสิ่งแปลกเมื่อเราวางลงใน Word” “เราอยากรับไฟล์ด้วยการลากและวางได้” — ในบทสนทนาให้คำปรึกษาเรื่องการเปลี่ยนแอปธุรกิจ คำขอรอบคัดลอกและวางกับลากและวาง (D&D) เป็นของประจำ

เพราะสิ่งเหล่านี้เป็น “ฟีเจอร์ที่ทุกคนถือเป็นเรื่องธรรมดา” พอดี ว่าพวกมันทำงานอย่างไรจริง ๆ จึงถูกรู้น้อยอย่างน่าแปลก หากคุณคิดคลิปบอร์ดเป็น “กล่องที่คุณใส่ข้อมูลชิ้นเดียว” คุณอธิบายไม่ได้ว่าทำไมสำเนาเดียวกันจึงให้ผลต่างกันขึ้นกับที่วาง หรือทำไมการวางหยุดทำงานหลังปิดแอปต้นทาง คลิปบอร์ดจริงคือกลไกที่วางเนื้อหาเดียวกันในหลายรูปแบบพร้อมกัน แล้วให้ฝั่งวางเลือกรูปแบบที่เข้าใจ

และลากและวาง ที่ก้น คือการถ่ายโอนข้อมูล OLE ที่ส่งผ่านการแทนข้อมูลเดียวกันพอดีกับคลิปบอร์ด (IDataObject) ผ่านอินเทอร์เฟซ COM กล่าวอีกนัย คัดลอกและวางกับ D&D เป็นพี่น้อง: เข้าใจอันหนึ่งถูกต้อง อีกอันอยู่ที่นั่นพอดี

บทความนี้มุ่งไปที่เจ้าหน้าที่ไอทีในบริษัทขนาดเล็กและกลาง และนักพัฒนาแอป Windows มันผูกเข้าด้วยกันในภาพเดียว ว่ารูปแบบคลิปบอร์ดทำงานอย่างไร แนวปฏิบัติฝั่งวางและฝั่งคัดลอก วิธีเฝ้าคลิปบอร์ดที่ถูกต้อง นโยบายการบริหารสำหรับประวัติคลิปบอร์ด การซิงค์คลาวด์ และ RDP และโครงสร้างกับกับดักของลากและวางแบบ OLE

1. สรุปก่อนเลย

  • คลิปบอร์ดคือพื้นที่เดียวที่แอปบนเดสก์ท็อปเดียวกัน (window station) ใช้ร่วมกัน และสิ่งที่นั่งอยู่ที่นั่นไม่ใช่ “ข้อมูลชิ้นเดียว” แต่เป็นเนื้อหาเดียวกันในหลายรูปแบบพร้อมกัน เซสชันอื่น เช่น RDP มีคลิปบอร์ดต่างกันแต่เดิม ฟีเจอร์การเปลี่ยนทิศทางคือสิ่งที่เชื่อมทั้งสอง เพราะปลายทางเลือกรูปแบบที่เข้าใจ สำเนาเดียวกันจึงให้ผลต่างกันขึ้นกับที่วาง12
  • สำหรับข้อความ ใช้ CF_UNICODETEXT CF_TEXT เป็น ANSI และขึ้นกับโค้ดเพจ และบนระบบญี่ปุ่นมันเป็นแหล่งเพาะอักขระเพี้ยน ระบบแปลงระหว่างทั้งสองโดยนัย แต่ฝั่งที่เป็นต้นแบบคือยูนิโค้ด3
  • ไฟล์เดินทางเป็น CF_HDROP (อาร์เรย์ของพาธที่จบด้วย NUL คู่) และข้อความที่จัดรูปแบบใช้รูปแบบที่ลงทะเบียน “HTML Format” HTML Format มีโครงสร้างแปลก: ข้อความ UTF-8 พร้อมส่วนหัวของออฟเซ็ตไบต์45
  • สาเหตุจริงของ “ฉันปิดแอปต้นทางแล้ววางไม่ได้อีก” คือ delayed rendering มันคือกลไกที่วางไม่ใช่เพย์โหลด แต่เพียงคำสัญญาว่าจะ “ผลิตเมื่อถูกขอ” หากคุณข้ามการทำให้เป็นรูปเป็นร่างตอนออก (การตอบ WM_RENDERALLFORMATS หรือ OleFlushClipboard สำหรับ OLE) การวางหยุดทำงาน26
  • ถือข้อมูลที่วางเป็นอินพุตที่ไม่น่าเชื่อถือจากภายนอก Microsoft เองระบุตรง ๆ ว่า “ข้อมูลคลิปบอร์ดไม่น่าเชื่อถือ จงแยกวิเคราะห์อย่างระมัดระวัง”7
  • สำหรับการเฝ้าคลิปบอร์ด AddClipboardFormatListener + WM_CLIPBOARDUPDATE คือตัวเลือกเดียว อย่าใช้การโพล และอย่าใช้ SetClipboardViewer เก่า (โซ่ผู้ชม) รูปแบบที่ลงทะเบียนที่กันความลับออกจากประวัติและการซิงค์ (ExcludeClipboardContentFromMonitorProcessing และพวก) ก็ถูกจัดให้เช่นกัน81
  • ประวัติคลิปบอร์ด (Win+V) และการซิงค์คลาวด์เป็นเรื่องการจัดการไอที คุณควบคุมได้ด้วย AllowClipboardHistory และ AllowCrossDeviceClipboard ผ่าน GPO / Intune (Policy CSP) และการเปลี่ยนทิศทางคลิปบอร์ดของ RDP มีนโยบายเฉพาะของตนเอง91011
  • ลากและวางคือ COM IDataObject เดียวกันกับคลิปบอร์ดถูกส่งระหว่าง IDropSource (ต้นทางลาก) กับ IDropTarget (เป้าหมายวาง) ผ่านลูป DoDragDrop RegisterDragDrop ต้องการการเริ่มต้นด้วย OleInitialize (STA)1213
  • คุณวางจาก File Explorer สิทธิ์ธรรมดาลงบนแอปที่ยกระดับไม่ได้ UIPI (การบล็อกข้อความตามระดับความสมบูรณ์) คือสาเหตุ และเป็นข้อจำกัดที่คุณควรรู้ตอนออกแบบ14

ด้านล่างเราเดินผ่านสิ่งนี้จากรากฐานคลิปบอร์ดขึ้นไป

2. คลิปบอร์ดคืออะไรจริง ๆ — ไม่ใช่ “ข้อมูลชิ้นเดียว” แต่เป็น “เนื้อหาเดียวกันในหลายรูปแบบ”

คลิปบอร์ดคือกลไกแบ่งปันข้อมูลร่วมที่ทุกแอปที่แชร์เดสก์ท็อปเดียวกันเข้าถึงได้ (ให้แม่นยำกว่า มันเป็นราย window station: เซสชันผู้ใช้ต่างกันหรือเซสชัน RDP แต่ละอันมีคลิปบอร์ดของตนเอง คัดลอกและวางทำงานข้าม RDP เพราะฟีเจอร์การเปลี่ยนทิศทางเชื่อมทั้งสอง — บทที่ 7) หลักการแรกคือมันขับด้วยผู้ใช้: ท่าทางการออกแบบอย่างเป็นทางการคือคุณไม่ใส่ข้อมูลเข้าหรือดึงข้อมูลออกลับหลังผู้ใช้1

จุดสำคัญคือการคัดลอกไม่วาง “ข้อมูลชิ้นเดียว” หน้าต่างที่คัดลอกเทคลิปบอร์ดแล้ววางหลายรูปแบบเรียงกัน แสดงเนื้อหาเดียวกันจากรูปแบบที่ทำได้มากกว่าลงไปยังรูปแบบที่ทำได้น้อยกว่า2 ตัวอย่างเช่น เมื่อคุณคัดลอกตารางในสเปรดชีต ในเชิงแนวคิดสิ่งต่อไปนี้อยู่บนคลิปบอร์ดพร้อมกัน

ลำดับความสำคัญ รูปแบบ เนื้อหา
1 รูปแบบส่วนตัวของแอป การแทนภายในที่สมบูรณ์ รวมสูตรและการจัดรูปแบบ (สำหรับวางกลับเข้าแอปเดียวกัน)
2 HTML Format ชิ้น HTML ที่คงโครงสร้างตารางและการจัดรูปแบบ
3 CSV ข้อความที่คั่นด้วยเซลล์
4 CF_UNICODETEXT ข้อความธรรมดาที่คั่นด้วยแท็บ
5 รูปแบบภาพ บิตแมปของลักษณะตาราง

ฝั่งวางเลือกรูปแบบที่เข้าใจจากรายการนี้ แล้วดึงออก วางลงใน Word แล้วคุณได้ตารางที่จัดรูปแบบ วางลงใน Notepad แล้วคุณได้ข้อความที่คั่นด้วยแท็บ — เพราะทั้งสองเลือกรูปแบบต่างกัน “ผลขึ้นกับที่วาง” ไม่ใช่บั๊ก แต่เป็นผลปกติของการออกแบบนี้

ทำไมสำเนาเดียวกันจึงให้ผลต่างกันขึ้นกับที่วางฝั่งคัดลอกวางเนื้อหาเดียวกันบนคลิปบอร์ดในหลายรูปแบบ และฝั่งวางเลือกรูปแบบที่เข้าใจ ดังนั้น Word ได้ตารางที่จัดรูปแบบ และ Notepad ได้ข้อความที่คั่นด้วยแท็บWordNotepadคัดลอก: สเปรดชีตคลิปบอร์ด (หลายรูปแบบ)รูปแบบที่อุดมกว่ารูปแบบที่ธรรมดากว่าส่วนตัวของแอปHTML FormatCSVCF_UNICODETEXTตารางที่จัดรูปแบบข้อความที่คั่นด้วยแท็บ

พูดกลับกัน ข้อร้องเรียนตอนเปิด — “การจัดรูปแบบพัง” “สิ่งแปลกถูกวาง” — เกือบทั้งหมดลดเหลือปัญหาของวิธีที่ฝั่งหนึ่งเลือกรูปแบบ หรือวิธีที่อีกฝั่งเสนอพวกมัน บทที่ 4 ครอบฝั่งวาง บทที่ 5 ครอบฝั่งคัดลอก

3. รูปแบบมาตรฐานกับรูปแบบที่ลงทะเบียน — CF_UNICODETEXT, CF_HDROP, HTML Format

3.1. รูปแบบมาตรฐาน — ใช้ฝั่งยูนิโค้ดสำหรับข้อความ

รูปแบบที่ OS กำหนดล่วงหน้าเรียกว่ารูปแบบมาตรฐาน อันที่ปรากฏตลอดในแอปธุรกิจมีดังต่อไปนี้3

รูปแบบ ค่า เนื้อหา
CF_TEXT 1 ข้อความ ANSI (ขึ้นกับโค้ดเพจ)
CF_UNICODETEXT 13 ข้อความยูนิโค้ด นี่คือรูปแบบต้นแบบสำหรับข้อความ
CF_HDROP 15 รายการพาธไฟล์ (แฮนเดิล HDROP)
CF_DIB 8 บิตแมปที่ไม่ขึ้นกับอุปกรณ์
CF_LOCALE 16 ตัวระบุโลแคลที่เกี่ยวกับข้อความ

CF_TEXT และ CF_UNICODETEXT ถูกแปลงเข้าหากันโดยนัยโดยระบบ (รูปแบบสังเคราะห์) การแปลงรหัสอักขระใช้โค้ดเพจที่เกี่ยวกับ CF_LOCALE3 การพึ่งการแปลงนั้นทิ้งอักขระที่ ANSI แทนไม่ได้ (เช่น สัญลักษณ์เฉพาะยูนิโค้ดและอักขระรวม) ดังนั้นกฎคือรวมสิ่งที่แอปอ่านและเขียนบน CF_UNICODETEXT (DataFormats.UnicodeText ใน .NET)

การแปลงโดยนัยระหว่าง CF_UNICODETEXT กับ CF_TEXTแอปอ่านและเขียนเฉพาะ CF_UNICODETEXT ระบบสังเคราะห์ CF_TEXT ด้วยการแปลงโดยนัยด้วยโค้ดเพจของ CF_LOCALE อักขระที่ ANSI แทนไม่ได้ถูกทิ้งในการแปลงนั้นการแปลง CF_LOCALEแอปอ่านและเขียนCF_UNICODETEXTCF_TEXT (ANSI)อักขระที่แทนไม่ได้ถูกทิ้ง

3.2. CF_HDROP — ไฟล์เดินทางเป็น “รายการพาธ”

CF_HDROP คือสิ่งที่ใช้เมื่อคุณคัดลอกไฟล์ใน File Explorer หรือเมื่อคุณลากและวางไฟล์ เพย์โหลดไม่ใช่ไฟล์เอง แต่เป็นบล็อกหน่วยความจำที่วางอาร์เรย์ที่ “จบด้วย NUL คู่”: หลังส่วนหัวโครงสร้าง DROPFILES สตริงพาธเต็มที่คั่นด้วยอักขระ NUL และสตริงว่างที่ท้าย pFiles ของส่วนหัวคือออฟเซ็ตเริ่มต้นของรายการพาธ และ fWide บอกว่าสตริงเป็นยูนิโค้ดหรือไม่4

[DROPFILES header: pFiles=start offset of the path list, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)

ในโค้ดเนทีฟคุณดึงทีละอันด้วย DragQueryFile ใน .NET คุณรับเป็น string[] ผ่าน DataFormats.FileDrop ความจริงที่ว่า “สิ่งที่เดินทางคือพาธอย่างเดียว ไม่ใช่ไฟล์เอง” จะสำคัญอีกครั้งใน D&D ของบทที่ 8 และ 9

เค้าโครงบล็อกหน่วยความจำของ CF_HDROPโครงสร้าง DROPFILES นั่งที่ต้นของหน่วยความจำโกลบอล pFiles คือออฟเซ็ตเริ่มต้นของรายการพาธ และ fWide บอกว่าเป็นยูนิโค้ดหรือไม่ พาธเต็มตามมา คั่นด้วย NUL และบล็อกจบด้วยสตริงว่าง (NUL คู่) สิ่งที่เดินทางคือพาธอย่างเดียว ไม่ใช่ไฟล์เองDROPFILES (pFiles / fWide)C:\\data\\a.txt + NULC:\\data\\b.txt + NULสตริงว่าง (NUL คู่)พาธอย่างเดียวเดินทาง ไม่ใช่ไฟล์

3.3. รูปแบบที่ลงทะเบียน — RegisterClipboardFormat และ “HTML Format”

สำหรับข้อมูลที่รูปแบบมาตรฐานแสดงไม่ได้ แอปสามารถเลือกชื่อแล้วลงทะเบียนรูปแบบของตนเอง ส่งชื่อให้ RegisterClipboardFormat แล้วคุณได้รหัสรูปแบบกลับ การลงทะเบียนภายใต้ชื่อเดียวกันจากแอปอื่นคืนรหัสเดียวกัน ดังนั้นเมื่อคุณตกลงชื่อ คุณแบ่งปันข้อมูลระหว่างแอปได้1 เมื่อคุณส่งข้อมูลมีโครงสร้างในชุดแอปของตนเอง ใช้ชื่อที่จะไม่ชน เช่น KomuraSoft.Report.RowData

รูปแบบที่ลงทะเบียนตัวแทนคือ “HTML Format” สำหรับข้อความที่จัดรูปแบบ (คู่กับ RTF เป็นสองรูปแบบข้อความอุดมหลัก) เพย์โหลดคือข้อความ UTF-8 แต่มีโครงสร้างแปลก: ส่วนหัวที่แจกแจงออฟเซ็ตไบต์ ถูกแนบที่ด้านหน้า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>

แต่ละออฟเซ็ตคือตำแหน่งไบต์จากต้นของข้อมูล รวมส่วนหัวเอง แนวปฏิบัติทั่วไปคือจองความกว้างคงที่ (เช่น 10 หลัก) แล้วเขียนค่าที่วัดกลับหลังคุณสร้างตัวแล้ว StartFragment/EndFragment ทำเครื่องหมายต้นและท้ายของ “ชิ้นที่ผู้ใช้เลือกจริง” เป็นไบต์ (ไม่ใช่อักขระ) ใน UTF-8 ที่รวมภาษาญี่ปุ่น จำนวนอักขระกับจำนวนไบต์แยกจากกัน ดังนั้นหากคุณคำนวณออฟเซ็ตนี้ผิด การวางลงในแอปอื่นทิ้งต้นหรือท้าย หากคุณสร้าง HTML Format เอง คุณต้องเติมส่วนหัวด้วยตำแหน่งไบต์ที่วัดหลังเข้ารหัสเป็น UTF-85

ส่วนหัว HTML Format สัมพันธ์กับออฟเซ็ตอย่างไรStartHTML และ EndHTML ของส่วนหัวชี้ไปที่ HTML ทั้งก้อน และ StartFragment กับ EndFragment ชี้ไปที่ชิ้นที่ผู้ใช้เลือก ทั้งคู่เป็นตำแหน่งไบต์จากต้นของข้อมูล เพราะจำนวนอักขระกับจำนวนไบต์แยกใน UTF-8 ให้เติมส่วนหัวด้วยตำแหน่งไบต์ที่วัดหลังเข้ารหัสส่วนหัว (ออฟเซ็ตไบต์)HTML ทั้งก้อนชิ้นที่เลือกออฟเซ็ตคือไบต์หลัง UTF-8

CSV (DataFormats.CommaSeparatedValue ใน .NET) ก็ใช้กันทั่วไปสำหรับข้อมูลตาราง สำหรับการทำงานร่วมกับ Excel การเสนอ HTML Format (พร้อมการจัดรูปแบบ) CSV (ค่าอย่างเดียว) และ CF_UNICODETEXT (คั่นด้วยแท็บ) ด้วยกันหมายความว่าคุณไม่ต้องเลือกปลายทางการวางเดียว

4. แนวปฏิบัติฝั่งวาง — ลำดับความสำคัญของรูปแบบและการตรวจความถูกต้อง

4.1. มองจากรูปแบบที่อุดมลงมา

รูปแบบบนคลิปบอร์ดเรียงตามลำดับที่ฝั่งคัดลอกวางพวกมัน (นั่นคือจากที่แสดงได้มากกว่าไปยังที่น้อยกว่า) เส้นฐานของฝั่งวางคือมอง ในบรรดารูปแบบที่คุณจัดการได้ เริ่มจากอันที่มีข้อมูลมากที่สุด ใน Win32 คุณแจกแจงด้วย EnumClipboardFormats แล้วใช้รูปแบบแรกที่คุณรู้ หรือส่งรายการลำดับความสำคัญของตนเองให้ GetPriorityClipboardFormat แล้วให้มันเลือก2

ใน .NET กิ่งดูประมาณต่อไปนี้

// 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
}

นั่นคือคำตอบต่อข้อร้องเรียนตอนเปิด “การวางตาราง Excel พัง” แอปที่อ่านเฉพาะข้อความธรรมดาไม่เคยได้รับโครงสร้างตาราง คุณรับลงไปในรายการรูปแบบไกลแค่ไหนคือการตัดสินออกแบบฝั่งวาง

กิ่งการวางที่มองจากรูปแบบที่อุดมลงมาหาก HTML Format มีอยู่และเพย์โหลดก็เป็นสตริง ให้นำเข้าเป็นตาราง มิฉะนั้นลอง CSV หากนั่นก็ขาด ให้ตกลงสู่ข้อความที่คั่นด้วยแท็บ หากไม่มีผู้สมัครใด ให้ปฏิเสธใช่ไม่ใช่ไม่ใช่ไม่เริ่มวางHTML Format + สตริง?ตรวจส่วนหัว → ตารางมี CSV?นำเข้าเป็น CSVUnicodeText?ข้อความที่คั่นด้วยแท็บปฏิเสธ

4.2. ข้อมูลที่วางคืออินพุตภายนอก

พลาดง่าย แต่เนื้อหาคลิปบอร์ดคือข้อมูลจากภายนอก และคุณไม่รู้ว่าแอปใดวางพวกมัน Microsoft ยังเตือน ในเอกสารคลิปบอร์ด OLE ว่า “ข้อมูลคลิปบอร์ดไม่น่าเชื่อถือ จงแยกวิเคราะห์อย่างระมัดระวังก่อนใช้ในแอป”7

  • ตรวจว่าออฟเซ็ตส่วนหัว HTML Format ไม่ชี้ไปนอกบัฟเฟอร์ (แอปที่ส่งส่วนหัวพังมีอยู่จริง)
  • ค่าที่คุณนำเข้าเป็นตัวเลข วันที่ หรือรหัส ควรผ่านการตรวจความถูกต้องเดียวกันกับอินพุตบนหน้าจอ
  • ใส่การป้องกันต่อข้อมูลยักษ์ แม้ใครวางภาพหลายร้อยเมกะไบต์หรือข้อความหลายล้านบรรทัด อย่าบล็อก UI และปฏิเสธเมื่อเกินขีด ข้อควรระวัง: GetData ของ .NET ชั่วขณะที่คุณเรียก ทำให้เพย์โหลดทั้งก้อนเป็นรูปเป็นร่างเป็นสตริงแมเนจด์ (และ delayed rendering รันเป็นส่วนหนึ่งของสิ่งนั้น) ดังนั้นการวางการตรวจขนาดหลัง GetData ไม่ใช่การป้องกัน ใน Win32 การตรวจ GlobalSize บน HGLOBAL ที่ GetClipboardData คืนให้การป้องกันที่ขั้นของ “อย่าเดินต่อไปสู่การแปลงและการแยกวิเคราะห์เป็นสตริงแมเนจด์” แต่สำหรับรูปแบบ delayed rendering GetClipboardData เองจุดการเรนเดอร์ ดังนั้นคุณยังกันการทำให้เป็นรูปเป็นร่างฝั่งต้นทางคัดลอกไม่ได้ เพื่อไม่ให้ UI แข็ง ให้ย้ายการดึงออกจากเธรด UI (และแม้เช่นนั้น เพราะ Clipboard ของ .NET ต้องการ STA ทำบนเธรดเฉพาะที่ตั้งเป็น STA ไม่ใช่บนเธรดพูลของ Task.Run (MTA) — ตอนที่ 5.1)

แนวคิดที่ว่า “ค่าที่มาจากภายนอก ไม่ว่าเส้นทางใด ถูกตรวจก่อนที่คุณใช้” คือแนวคิดเดียวกันที่วางใน “อย่าใช้ค่าที่ถอดรหัสจาก QR ตามที่เป็น” สมมติฐานที่ว่าการวางปลอดภัยเพราะเป็นการกระทำของผู้ใช้ คือวิธีที่อุบัติเหตุเริ่ม

ตรวจข้อมูลที่วางก่อนที่คุณใช้ข้อมูลที่เอาจากคลิปบอร์ดผ่านการมีรูปแบบ ชนิดเพย์โหลด ขีดขนาด และการตรวจเนื้อหาตามลำดับนั้น ล้มเหลวอย่างใดอย่างหนึ่งแล้วคุณปฏิเสธหรือตกลงสู่รูปแบบผู้สมัครถัดไปชนิดผิดใหญ่เกินไปไม่ถูกต้องมีรูปแบบ?ชนิดเพย์โหลดถูก?ขนาดอยู่ในขีด?ตรวจเนื้อหานำเข้าปฏิเสธ / รูปแบบถัดไป

5. แนวปฏิบัติฝั่งคัดลอก — การเสนอหลายรูปแบบพร้อมกัน และ delayed rendering

5.1. วางหลายรูปแบบพร้อมกัน

แนวปฏิบัติฝั่งคัดลอกคือกลับของ 4.1: เสนอรูปแบบที่อุดมและรูปแบบธรรมดาในเวลาเดียวกัน ด้วย DataObject ของ WinForms/WPF คุณเขียนได้ไม่กี่บรรทัด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

สองหมายเหตุ ประการแรก คลาส Clipboard ของ .NET ใช้ได้เฉพาะจากเธรด STA15 เธรด UI ของ WinForms/WPF เป็น STA เพราะ [STAThread] ดังนั้นปกติไม่เป็นปัญหา แต่การแตะจากเธรดพื้นหลังล้มเหลว (พื้นฐาน STA/MTA อยู่ใน “พื้นฐาน COM STA/MTA”) ประการที่สอง ความหมายของ copy: true ผูกกับ delayed rendering ในตอนย่อยถัดไป

5.2. Delayed Rendering — ทำไม “ปิดต้นทางแล้ววางไม่ได้”

การสร้างเพย์โหลดใหญ่ในหลายรูปแบบทุกครั้งสิ้นเปลือง ดังนั้นคลิปบอร์ดมีกลไกชื่อ delayed rendering ส่ง NULL เป็นแฮนเดิลข้อมูลให้ SetClipboardData แล้ว แทนเพย์โหลด มีเพียงคำสัญญาว่าจะ “ผลิตเมื่อถูกขอ” ถูกลงทะเบียน เมื่อมีใครขอรูปแบบนั้น WM_RENDERFORMAT มาถึงต้นทางคัดลอก และเมื่อนั้นข้อมูลจึงถูกสร้าง2

ผลของการออกแบบนี้คือ “ฉันปิดแอปต้นทางแล้ววางไม่ได้อีก” ตอนเปิด ก่อนออก ต้นทางคัดลอกได้รับ WM_RENDERALLFORMATS และรับผิดชอบทำให้ทุกรูปแบบที่ยังไม่ถูกเรนเดอร์เป็นรูปเป็นร่าง ออกโดยไม่ทำสิ่งนั้นแล้วรูปแบบหายไป2

Delayed rendering และทำไมปิดแล้ววางจึงล้มเหลวต้นทางคัดลอกลงทะเบียนเพียงคำสัญญาด้วยแฮนเดิล NULL และทำให้เป็นรูปเป็นร่างตามคำขอผ่าน WM_RENDERFORMAT ตอนออกมันรับผิดชอบทำให้ทุกรูปแบบเป็นรูปเป็นร่างด้วย WM_RENDERALLFORMATS ข้ามสิ่งนั้นแล้วรูปแบบหายไปRENDERALLFORMATSข้ามการทำให้เป็นรูปเป็นร่างSetClipboardData NULL = คำสัญญาฝั่งวางขอWM_RENDERFORMAT → สร้างตอนนี้ต้นทางคัดลอกกำลังจะออกการวางทำงานหลังออกรูปแบบหายหลังปิด

บนคลิปบอร์ด OLE (สไตล์ที่วาง IDataObject ด้วย OleSetClipboard) ความสัมพันธ์นี้ชัดยิ่งกว่า สิ่งที่คลิปบอร์ดถือคือพอยน์เตอร์ไปยังออบเจ็กต์ข้อมูล และการเรียก OleFlushClipboard ตอนแอปออกทำให้ข้อมูลเป็นรูปเป็นร่างบนคลิปบอร์ด ดังนั้นการวางยังทำงานหลังออก6 Clipboard.SetDataObject(data, copy: true) ของ .NET คือสิ่งที่ระบุพฤติกรรม “คงไว้หลังออก” นี้

เมื่อคุณคัดลอกช่วงใหญ่ใน Excel แล้วพยายามออก พร้อมต์ “มีข้อมูลจำนวนมากบนคลิปบอร์ด คุณอยากวางข้อมูลนี้ลงในโปรแกรมอื่นในภายหลังได้หรือไม่” คือการยืนยันพอดีว่าจะรันการทำให้เป็นรูปเป็นร่างนี้ (flush) หรือไม่ หากคุณใช้ delayed rendering ในแอปของตนเอง จำไว้ว่าการทำให้เป็นรูปเป็นร่างตอนออกเป็นส่วนหนึ่งของชุดเดียวกัน delayed rendering คือการเพิ่มประสิทธิภาพ และเพราะคำขอเรนเดอร์รันแบบซิงโครนัสภายในประมวลผลข้อความ ข้อมูลที่ใช้เวลานานในการสร้างมีการแลกของการทำให้ UI แข็ง2

6. แนวปฏิบัติสำหรับการเฝ้าคลิปบอร์ด — ผู้ฟัง การลองใหม่ และการยกเว้นประวัติ

6.1. ใช้ AddClipboardFormatListener

ข้อกำหนด เช่น “เราอยากตรวจจับค่าจากเครื่องอ่านบาร์โค้ดหรือสำเนาจากระบบธุรกิจแล้วนำเข้าอัตโนมัติ” ต้องการให้คุณเฝ้าการเปลี่ยนของคลิปบอร์ด ในประวัติมีสามวิธี วันนี้คำตอบที่ถูกมีหนึ่ง8

วิธี การประเมิน
อ่านบนตัวตั้งเวลา (โพล) สิ้นเปลือง และคุณพลาดการอัปเดตได้ อย่าใช้
SetClipboardViewer (โซ่ผู้ชม) บั๊กในแอปหนึ่งในโซ่ทำลายทั้งโซ่ ถูกเก็บไว้เพื่อความเข้ากันได้ย้อนหลังเท่านั้น
AddClipboardFormatListener แนะนำ WM_CLIPBOARDUPDATE มาถึงหน้าต่างที่ลงทะเบียน
ลำดับของการเฝ้าคลิปบอร์ดลงทะเบียนด้วย AddClipboardFormatListener เมื่อแฮนเดิลถูกสร้าง และ WM_CLIPBOARDUPDATE มาถึงไม่ว่าแอปใดคัดลอก อ่านพร้อมการลองใหม่ และยกเลิกการลงทะเบียนสมมาตรด้วย RemoveClipboardFormatListener เมื่อแฮนเดิลถูกทำลายยกเลิกการลงทะเบียนAddClipboardFormatListenerรอแอปใดคัดลอกWM_CLIPBOARDUPDATEอ่านพร้อมการลองใหม่ (6.2)RemoveClipboardFormatListener
// 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. ลองใหม่เมื่อเปิดไม่ได้

มีเพียงหนึ่งหน้าต่างในแต่ละครั้ง ที่เปิดคลิปบอร์ดได้ ขณะที่โปรเซสอื่นเปิดอยู่ OpenClipboard ล้มเหลว2 ทันทีหลัง WM_CLIPBOARDUPDATE ต้นทางคัดลอกหรือผู้เฝ้าอื่นมักยังทำงานอยู่ ดังนั้นความล้มเหลวในการอ่านชั่วคราวคืออีเวนต์ปกติ ใส่การลองใหม่ไม่กี่ครั้งพร้อมรอสั้น (หลายสิบมิลลิวินาที) ระหว่างเสมอ โปรดทราบว่าโอเวอร์โหลด Clipboard ของ .NET ที่ให้คุณระบุจำนวนการลองใหม่และช่วงมีอยู่เฉพาะฝั่งเขียน SetDataObject ไม่มีเทียบเท่าฝั่งอ่าน (GetDataObject และพวก) ดังนั้นคุณเขียนจับ-รอ-ลองใหม่เอง — ExternalException บน WinForms, COMException บน WPF

ลำดับการลองใหม่อ่านคลิปบอร์ดมีเพียงหนึ่งหน้าต่างในแต่ละครั้งที่เปิดคลิปบอร์ดได้ ดังนั้นการอ่านทันทีหลังการแจ้งเปลี่ยนสามารถล้มเหลวด้วยการแข่งกับโปรเซสอื่น เมื่อมีข้อยกเว้น รอหลายสิบมิลลิวินาทีแล้วลองใหม่ หากถึงขีด ให้ยอมแพ้ครั้งนี้แล้วรับในการอัปเดตถัดไปสำเร็จกำลังถูกใช้ลองใหม่ขีดWM_CLIPBOARDUPDATEพยายามอ่านนำเข้า (การตรวจบทที่ 4)รอหลายสิบมิลลิวินาทียอมแพ้ครั้งนี้

6.3. กันออกจากประวัติและการซิงค์ — ดูแลฟีเจอร์คัดลอกที่จัดการความลับ

Windows มีประวัติคลิปบอร์ด (Win+V) และการซิงค์ข้ามอุปกรณ์ (คลิปบอร์ดคลาวด์) และข้อมูลที่แอปวางอยู่ในขอบเขตของทั้งคู่โดยค่าเริ่มต้น แอปที่ใส่ความลับ เช่น รหัสผ่านหรือหมายเลขบัญชี บนฟีเจอร์คัดลอกยังวางรูปแบบที่ลงทะเบียนที่ยกเว้นเนื้อหาจากประวัติและการซิงค์1

  • ExcludeClipboardContentFromMonitorProcessing: วางสิ่งนี้แล้วเนื้อหาของสำเนานั้นไม่ถูกรวมทั้งในประวัติและในการซิงค์
  • CanIncludeInClipboardHistory (DWORD 0): ยับยั้งประวัติอย่างเดียว
  • CanUploadToCloudClipboard (DWORD 0): ยับยั้งการซิงค์ข้ามอุปกรณ์อย่างเดียว

เหตุที่รหัสผ่านที่คัดลอกโดยตัวจัดการรหัสผ่านไม่เหลือบน Win+V คือกลไกนี้ คุณได้รหัสรูปแบบด้วยการส่งชื่อให้ RegisterClipboardFormat แล้วตั้งมันคู่กับข้อมูลธรรมดา ดังนั้นคุ้มที่จะอิมพลีเมนต์ในแอปธุรกิจใดที่จัดการความลับ

7. คลิปบอร์ดจากมุมไอที — การควบคุมประวัติ การซิงค์คลาวด์ และ RDP

ก้าวออกจากการพัฒนาเล็กน้อย นี่คือจุดที่สำคัญต่อผู้ดูแล ประวัติคลิปบอร์ดสะสมสำเนาล่าสุด และคลิปบอร์ดคลาวด์ซิงค์สำเนาข้ามอุปกรณ์ที่ลงชื่อเข้าด้วยบัญชี Microsoft / บัญชี Microsoft Entra เดียวกัน10 สะดวกเท่าที่เป็น มันยังผลิตของเหลือและการล้น: ข้อมูลส่วนบุคคลที่คัดลอกจากระบบธุรกิจสะสมในประวัติ และเนื้อหาที่คัดลอกบนพีซีงานซิงค์ไปพีซีส่วนตัว

นโยบายสองอย่างที่คุณใช้ควบคุมสิ่งนี้ในองค์กรมีดังต่อไปนี้

สิ่งที่คุณควบคุม GPO (Computer Configuration > Administrative Templates > System > OS Policies) Policy CSP (Intune) ค่าเริ่มต้น
ประวัติคลิปบอร์ด Allow Clipboard History Experience/AllowClipboardHistory อนุญาต
การซิงค์ข้ามอุปกรณ์ Allow Clipboard synchronization across devices Privacy/AllowCrossDeviceClipboard อนุญาต

ทั้งคู่ใช้ได้จาก Windows 10 เวอร์ชัน 1809 เป็นต้นไป ปิดพวกมันแล้วรายการที่ตรงกันในแอปการตั้งค่าถูกทำให้เป็นสีเทา และนโยบายมีผลทันที910

ของประจำอีกอย่างคือการเปลี่ยนทิศทางคลิปบอร์ดของ RDP (Remote Desktop) โดยค่าเริ่มต้น คัดลอกและวางทำงานระหว่างพีซีท้องถิ่นกับเซสชันระยะไกล จึงกลายเป็นเส้นทางสำหรับเอาความลับออกจากเซิร์ฟเวอร์ได้ นโยบาย “Do not allow clipboard redirection” (ค่าเรจิสทรี fDisableClip) บล็อกได้ทั้งสองทิศทาง11 Windows Server / Windows 11 รุ่นล่าสุดยังเพิ่มนโยบายที่ละเอียดกว่า เช่น จำกัดทิศทางเซิร์ฟเวอร์ไปไคลเอนต์เป็นข้อความอย่างเดียว ว่าคุณจะห้ามเด็ดขาดหรือจำกัดเป็นขั้นคือสมดุลของการปฏิบัติการและความปลอดภัย

เส้นทางที่เนื้อหาคลิปบอร์ดกระจายได้ และจุดควบคุมเนื้อหาที่คัดลอกอยู่ในขอบเขตของประวัติและการซิงค์คลาวด์โดยค่าเริ่มต้น และบน RDP พวกมันเดินทางไปอีกเซสชันผ่านการเปลี่ยนทิศทาง แต่ละเส้นทางควบคุมด้วยนโยบายได้ และฝั่งแอปยกเว้นตนเองจากประวัติและการซิงค์ด้วยรูปแบบยกเว้นได้คลิปบอร์ดประวัติ (Win+V)การซิงค์คลาวด์การเปลี่ยนทิศทาง RDPAllowClipboardHistoryAllowCrossDeviceClipboardfDisableClipรูปแบบยกเว้นของแอป (6.3)

8. ลากและวางคือ COM — IDataObject + IDropSource + IDropTarget

8.1. ข้อมูลเดียวกันกับคลิปบอร์ด วิธีถือที่ต่างกัน

ลากและวางแบบ OLE รันด้วยสามบทบาทต่อไปนี้12

บทบาท ใครอิมพลีเมนต์ งาน
IDataObject ต้นทางลาก เพย์โหลดที่กำลังถือ ออบเจ็กต์ข้อมูลหลายรูปแบบเดียวกันกับคลิปบอร์ด
IDropSource ต้นทางลาก การตัดสินว่าการลากดำเนินต่อหรือถูกยกเลิก และการตอบกลับเคอร์เซอร์
IDropTarget เป้าหมายวาง การประกาศรับ/ปฏิเสธใน DragEnter/DragOver/DragLeave/Drop และการรับการวาง

ต้นทางลากเรียก DoDragDrop ลูปลากเริ่ม และเมื่อเมาส์เข้าหน้าต่างเป้าหมายวาง IDropTarget นั้นถูกแจ้ง ตอนวาง IDataObject ถูกส่งต่อ เอกสารทางการยังกล่าวว่า “D&D ให้ฟังก์ชันการทำงานเดียวกันพอดีกับคัดลอกและวางของคลิปบอร์ด หากแอปอิมพลีเมนต์คัดลอกและวางอยู่แล้ว การเพิ่มมีน้อย”12 กล่าวอีกนัย DataObject หลายรูปแบบที่คุณสร้างในบทที่ 2 ถึง 5 กลายเป็นเพย์โหลด D&D ตามที่เป็น

ลำดับของลากและวางแบบ OLEต้นทางลากใส่ IDataObject ในเพย์โหลดแล้วเรียก DoDragDrop เพื่อเริ่มลูปลาก IDropTarget ของเป้าหมายวางประกาศรับ/ปฏิเสธใน DragEnter และ DragOver และตอน Drop เลือกรูปแบบจาก IDataObject แล้วดึงออกDoDragDropเมาส์เข้าปุ่มขึ้นIDataObject + IDropSourceลูปลากDragEnter/Over: EffectIDropTarget.Dropเลือกรูปแบบแล้วดึง

8.2. ต้องการ OleInitialize (STA)

หน้าต่างที่จะเป็นเป้าหมายวางลงทะเบียนด้วย RegisterDragDrop และมีกับดักคลาสสิกที่นี่ หากคุณเริ่มต้น COM ด้วย CoInitialize/CoInitializeEx RegisterDragDrop ล้มเหลวเสมอด้วย E_OUTOFMEMORY คุณต้องเริ่มต้นด้วย OleInitialize13 OleInitialize เริ่มต้น COM เป็น STA เพราะ D&D เป็นฟีเจอร์ที่รากอยู่ในโลก STA ของหน้าต่างและปั๊มข้อความ เธรดที่เรียกยังต้องรันปั๊มข้อความ ข้ามสิ่งนั้นแล้วแอปอื่นแฮงระหว่างการลาก13 พื้นหลังที่นี่คือการอภิปรายโมเดลเธรดพอดีใน “พื้นฐาน COM STA/MTA

ในแอป WinForms/WPF เฟรมเวิร์กรับการเริ่มต้น OLE และการอิมพลีเมนต์อินเทอร์เฟซ ดังนั้นนักพัฒนาเพียงเขียนอีเวนต์

// 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)
    }
};

รูปเหมือนกันใน WPF: คุณรับด้วย AllowDrop="True" และอีเวนต์ DragOver/Drop บนอิลิเมนต์ และคุณดึงอาร์เรย์พาธด้วย e.Data.GetData(DataFormats.FileDrop) การประกาศรับ/ปฏิเสธ (Effect) บนทุก DragEnter/DragOver คือธรรมเนียมของ IDropTarget ข้ามสิ่งนั้นแล้วคุณได้บั๊กที่เคอร์เซอร์ค้างที่ “ไม่อนุญาต” และไม่เคยเปลี่ยน

9. กับดักของ D&D — การยกระดับ Move และการตรวจพาธ

9.1. คุณวางลงบนแอปที่ยกระดับเป็นผู้ดูแลระบบไม่ได้

วางไฟล์จาก File Explorer ลงบนแอปที่เปิดด้วย “รันในฐานะผู้ดูแลระบบ” แล้วไม่มีอะไรเกิด — นี่ไม่ใช่บั๊กการอิมพลีเมนต์ แต่เป็นพฤติกรรมของ OS UIPI (User Interface Privilege Isolation) บล็อกข้อความจากโปรเซสความสมบูรณ์ต่ำไปยังหน้าต่างความสมบูรณ์สูงโดยค่าเริ่มต้น ดังนั้นการแจ้งวางจาก File Explorer สิทธิ์ธรรมดา (ความสมบูรณ์กลาง) ไม่ถึงแอปที่ยกระดับ14

UIPI บล็อกการวางลงบนแอปที่ยกระดับอย่างไรการแจ้งวางจาก File Explorer ความสมบูรณ์กลางไปยังแอปที่ยกระดับความสมบูรณ์สูงถูกบล็อกโดย UIPI โดยค่าเริ่มต้นและไม่มาถึง คง UI ที่สิทธิ์ธรรมดาแล้วแยกงานที่มีสิทธิ์ และการวางมาถึงแจ้งวางถูกบล็อกผ่านมอบหมายงานที่มีสิทธิ์Explorer (กลาง)UIPIแอปที่ยกระดับ: ไม่มีการวางUI ธรรมดา: การวางมาถึงโปรเซสที่ยกระดับที่แยก

ทางอ้อมที่อนุญาตข้อความเฉพาะ เช่น WM_DROPFILES เป็นรายตัวด้วย ChangeWindowMessageFilterEx เป็นที่รู้จักดี14 แต่สิ่งที่สิ่งนั้นปล่อยผ่านคือการแจ้งวางแบบเก่า (WM_DROPFILES) มันไม่แก้ OLE D&D ทั้งก้อน คำแนะนำเชิงปฏิบัติชัดเจน: หยุดออกแบบแอปให้รันยกระดับตลอดเวลา แยกเฉพาะงานที่ต้องการการยกระดับไปอีกโปรเซส และ UI เองอยู่ที่สิทธิ์ธรรมดาแล้วรับ D&D ได้ (การออกแบบการแยกมีรายละเอียดใน “วิธีแยก "เฉพาะงานที่ต้องใช้สิทธิ์ผู้ดูแลระบบ" ในแอป Windows อย่างเป็นรูปธรรม”)

9.2. DragDropEffects หมายความว่าอะไร — Move คือสัญญาว่า “ของเดิมหายไป”

Copy/Move/Link บน DragDropEffects ไม่ใช่การตกแต่ง พวกมันคือสัญญาระหว่างต้นทางลากกับเป้าหมายวาง ต้นทางลากประกาศชุดเอฟเฟกต์ที่อนุญาตใน DoDragDrop เป้าหมายวางเลือกเอฟเฟกต์จริง และเมื่อ Move สำเร็จ ต้นทางลากลบข้อมูล (ไฟล์) — นั่นคือธรรมเนียม หากฝั่งรับคืน Move โดยไม่คิด คุณได้อุบัติเหตุ “ฉันวางแล้วไฟล์เดิมหาย” สำหรับการนำเข้าของแอปธุรกิจ ฝั่งรับระบุ Copy คือค่าเริ่มต้นที่ปลอดภัย

สัญญา DragDropEffects — Move ลบของเดิมต้นทางลากประกาศชุดเอฟเฟกต์ที่อนุญาตใน DoDragDrop และเป้าหมายวางเลือกเอฟเฟกต์จริง เมื่อ Move สำเร็จ ต้นทางลากลบไฟล์ ดังนั้นสำหรับการนำเข้า ฝั่งรับควรระบุ CopyCopyMoveต้นทาง: เอฟเฟกต์ที่อนุญาตเป้าหมาย: เลือก Effectของเดิมเหลือ (นำเข้า)ต้นทางลบไฟล์

9.3. การตรวจพาธที่วาง

สิ่งที่เดินทางใน CF_HDROP/FileDrop คือพาธอย่างเดียว (ตอนที่ 3.2) ก่อนนำเข้า ให้ผ่านการตรวจอินพุตที่ไม่น่าเชื่อถือเดียวกันกับการวาง

  • ไฟล์หรือโฟลเดอร์: ตัดสินเป็นสเปกว่าเกิดอะไรเมื่อโฟลเดอร์ทั้งก้อนถูกวาง (ไล่ลงแล้วนำเข้า หรือปฏิเสธ)
  • ตัวยึดตำแหน่ง OneDrive: พาธอาจมีอยู่ขณะตัวไฟล์ไม่ได้อยู่ท้องถิ่น — ไฟล์ตามคำขอ ชั่วขณะที่คุณเปิด การดาวน์โหลดเริ่ม และออฟไลน์มันล้มเหลว พฤติกรรมและมาตรการอยู่ใน “OneDrive "Files On-Demand" กับแอปธุรกิจ
  • พาธยาวและพาธแปลก: พาธเกิน MAX_PATH พาธเครือข่าย (UNC) และพาธบนสื่อถอดได้ควรถูกรับเฉพาะหลังคุณยืนยันว่าการประมวลผลปลายน้ำจัดการได้
  • จำนวนและขนาดรวม: เพื่อไม่ให้การวางไฟล์หลายพันไฟล์ทำให้ UI แข็ง ให้นำเข้าแบบอะซิงโครนัสแล้วใส่ขีดกับการแสดงความคืบหน้า

10. สรุป

  • คลิปบอร์ดคือกลไกที่วางเนื้อหาเดียวกันในหลายรูปแบบพร้อมกันในพื้นที่เดียวที่ใช้ร่วมภายในเดสก์ท็อปเดียวกัน (window station) ฝั่งวางเลือกรูปแบบ ดังนั้นสำเนาเดียวกันให้ผลต่างกัน
  • ข้อความคือ CF_UNICODETEXT ไฟล์คือ CF_HDROP และข้อความที่จัดรูปแบบคือรูปแบบที่ลงทะเบียน HTML Format (ส่วนหัวออฟเซ็ตไบต์ + UTF-8)
  • ฝั่งวางมองจากที่อุดมไปยังที่ธรรมดาและถือเพย์โหลดเป็นอินพุตภายนอก ฝั่งคัดลอกเสนอหลายรูปแบบพร้อมกัน และหากใช้ delayed rendering มันอิมพลีเมนต์การทำให้เป็นรูปเป็นร่างตอนออก (WM_RENDERALLFORMATS / OleFlushClipboard) ด้วย
  • การเฝ้าคือ AddClipboardFormatListener + WM_CLIPBOARDUPDATE เตรียมสำหรับการแข่งของ OpenClipboard ด้วยการลองใหม่ และกันความลับออกจากประวัติและการซิงค์ด้วย ExcludeClipboardContentFromMonitorProcessing และพวก
  • ไอทีควบคุมประวัติคลิปบอร์ด การซิงค์คลาวด์ และการเปลี่ยนทิศทาง RDP ได้ด้วย GPO / Intune ค่าเริ่มต้นคืออนุญาตทั้งหมด ดังนั้นตัดสินโดยเจตนาในสภาพแวดล้อมที่จัดการความลับ
  • D&D คือ COM: IDropSource/IDropTarget ส่งผ่าน IDataObject เดียวกันกับคลิปบอร์ด RegisterDragDrop ต้องการ OleInitialize (STA)
  • การวางลงบนแอปที่ยกระดับถูกบล็อกโดย UIPI Move บน DragDropEffects คือสัญญาว่า “ของเดิมหายไป” ตรวจพาธที่วางก่อนที่คุณนำเข้า

คัดลอกและวางกับ D&D สำหรับผู้ใช้คือฟีเจอร์ที่ควรรู้สึกเหมือนอากาศ นั่นพอดีคือเหตุที่ “ฉันวางไม่ได้” “มันพัง” และ “มันหาย” ทำร้ายประสบการณ์มากนัก — และเหตุที่แอปที่เสนอหลายรูปแบบและจัดการการวางอย่างถูกทำให้การปฏิบัติการประจำวันลื่นขึ้นด้วยตนเอง ฉันหวังว่านี่เป็นวัสดุที่มีประโยชน์เมื่อคุณตัดสินว่าจะแก้สิ่งใดก่อน

บทความที่เกี่ยวข้อง

ด้านให้คำปรึกษาที่เกี่ยวข้อง

KomuraSoft LLC รับการออกแบบและการอิมพลีเมนต์ของการรองรับคัดลอกและวางกับลากและวางในแอปธุรกิจ (การเสนอหลายรูปแบบ การทำงานร่วมกับ Excel การนำเข้าไฟล์ที่วาง) การสอบสวนหาสาเหตุของปัญหา เช่น “มันพังเมื่อฉันวาง” หรือ “สำเนาหาย” ระบบอัตโนมัติของอินพุตที่เฝ้าคลิปบอร์ด และการอิมพลีเมนต์ที่กันข้อมูลลับออกจากประวัติและการซิงค์ กรณีที่เกี่ยวข้องกับชั้นล่างของ COM และ OLE ยินดีแม้คุณเริ่มจากการแยกอาการ

ลิงก์อ้างอิง

  1. Microsoft Learn, Clipboard Formats. ว่าด้วยหน้าต่างสามารถวางข้อมูลเดียวกันในหลายรูปแบบคลิปบอร์ด รูปแบบที่ลงทะเบียนผ่าน RegisterClipboardFormat (การลงทะเบียนชื่อเดียวกันคืนค่าเดียวกัน จึงแอปแบ่งปันได้) รูปแบบสังเคราะห์ และการยกเว้นเนื้อหาจากประวัติคลิปบอร์ด / การซิงค์คลาวด์ด้วย ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory และ CanUploadToCloudClipboard  2 3 4 5

  2. Microsoft Learn, Clipboard Operations. ว่าด้วยมีเพียงหนึ่งหน้าต่างในแต่ละครั้งที่เปิดคลิปบอร์ดได้ การวางรูปแบบจากที่แสดงได้มากกว่าไปยังที่น้อยกว่าตอนคัดลอก การเลือกรูปแบบตอนวางด้วย EnumClipboardFormats / GetPriorityClipboardFormat delayed rendering ด้วยการส่ง NULL ให้ SetClipboardData และความรับผิดชอบของ WM_RENDERFORMAT / WM_RENDERALLFORMATS และการแลกของ delayed rendering  2 3 4 5 6 7 8

  3. Microsoft Learn, Standard Clipboard Formats. ว่าด้วยนิยามของรูปแบบมาตรฐาน CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB และ CF_LOCALE และว่าระบบแปลง CF_TEXT กับ CF_UNICODETEXT โดยนัยโดยใช้โค้ดเพจที่เกี่ยวกับ CF_LOCALE  2 3

  4. Microsoft Learn, Shell Clipboard Formats. ว่าด้วย CF_HDROP ประกอบด้วยโครงสร้าง DROPFILES บวกอาร์เรย์ของสตริงพาธเต็มที่จบด้วย NUL คู่ การดึงพาธรายตัวด้วย DragQueryFile และรูปแบบเชลล์ CFSTR_ ต้องการการลงทะเบียนผ่าน RegisterClipboardFormat  2

  5. Microsoft Learn, HTML Clipboard Format. ว่าด้วยชื่อที่ลงทะเบียนคือ “HTML Format” โครงสร้างส่วนหัวที่มีออฟเซ็ตไบต์ เช่น Version, StartHTML, EndHTML, StartFragment และ EndFragment การเข้ารหัสเป็น UTF-8 เสมอ และธรรมเนียมความเห็น StartFragment/EndFragment  2 3

  6. Microsoft Learn, OleFlushClipboard function (ole2.h). ว่าด้วย OleSetClipboard ทำให้คลิปบอร์ดถือเพียงพอยน์เตอร์ไปยังออบเจ็กต์ข้อมูล OleFlushClipboard ทำให้ข้อมูลเป็นรูปเป็นร่างบนคลิปบอร์ดจึงการวางยังทำงานหลังแอปออก และการเทคลิปบอร์ดด้วย OleSetClipboard(NULL) เมื่อคุณไม่ต้องคงไว้ตอนออก  2

  7. Microsoft Learn, OleGetClipboard function (ole2.h). ว่าด้วยวิธีได้ IDataObject จากคลิปบอร์ด และคำเตือนว่าข้อมูลคลิปบอร์ดไม่น่าเชื่อถือและควรแยกวิเคราะห์อย่างระมัดระวังก่อนแอปใช้  2

  8. Microsoft Learn, Using the clipboard. ว่าด้วยการเปรียบเทียบสามวิธีเฝ้าคลิปบอร์ด (หน้าต่างผู้ชม หมายเลขลำดับ และผู้ฟังรูปแบบ) โปรแกรมใหม่ควรถูกคาดหวังให้ใช้ผู้ฟังผ่าน AddClipboardFormatListener โซ่ผู้ชมเปราะเมื่อการบำรุงโซ่ไม่สมบูรณ์ และหมายเลขลำดับไม่ใช่สิ่งที่คุณควรโพล  2

  9. Microsoft Learn, Policy CSP - Experience. ว่าด้วยการอนุญาตหรือปฏิเสธประวัติคลิปบอร์ดด้วยนโยบาย Experience/AllowClipboardHistory ความพร้อมใช้จาก Windows 10 เวอร์ชัน 1809 เป็นต้นไป ค่าเริ่มต้นคืออนุญาต และการแมป GPO ภายใต้ “System > OS Policies” โดยการเปลี่ยนมีผลทันที  2

  10. Microsoft Learn, Policy CSP - Privacy. ว่าด้วยการอนุญาตหรือปฏิเสธการซิงค์คลิปบอร์ดข้ามอุปกรณ์ด้วยนโยบาย Privacy/AllowCrossDeviceClipboard การซิงค์เกิดระหว่างอุปกรณ์ที่ลงชื่อเข้าด้วยบัญชี Microsoft / บัญชี Microsoft Entra เดียวกัน และค่าเริ่มต้นคืออนุญาต  2 3

  11. Microsoft Learn, Policy CSP - ADMX_TerminalServer. ว่าด้วย TS_CLIENT_CLIPBOARD (“Do not allow clipboard redirection” ค่าเรจิสทรี fDisableClip) สามารถห้ามการแชร์คลิปบอร์ดระหว่างท้องถิ่นกับระยะไกลในเซสชัน Remote Desktop และการเปลี่ยนทิศทางถูกอนุญาตโดยค่าเริ่มต้น  2

  12. Microsoft Learn, Drag and Drop (COM). ว่าด้วยลากและวางแบบ OLE รันด้วยสามอย่างคือ IDropSource (ต้นทางลาก) IDropTarget (เป้าหมายวาง) และ DoDragDrop (ลูปที่ OLE จัดให้) การให้ฟังก์ชันการทำงานเดียวกันกับคัดลอกและวางของคลิปบอร์ด จึงแอปที่อิมพลีเมนต์คัดลอกและวางอยู่แล้วต้องการการเพิ่มน้อย และชนิดของการตอบกลับ  2 3

  13. Microsoft Learn, RegisterDragDrop function (ole2.h). ว่าด้วยการลงทะเบียนหน้าต่างเป้าหมายวางด้วย IDropTarget การล้มเหลวเสมอด้วย E_OUTOFMEMORY หาก COM ถูกเริ่มต้นด้วย CoInitialize/CoInitializeEx จึงต้องการ OleInitialize และการที่แอปต้นทางลากแฮงหากเธรดที่เรียกไม่รันปั๊มข้อความ  2 3

  14. Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). ว่าด้วย UIPI เป็นกลไกความปลอดภัยที่โดยค่าเริ่มต้นบล็อกการรับข้อความจากผู้ส่งความสมบูรณ์ต่ำ และการอนุญาตข้อความเฉพาะรายหน้าต่างด้วยตัวกรองข้อความ (MSGFLT_ALLOW)  2 3

  15. Microsoft Learn, How to add data to the Clipboard (Windows Forms). ว่าด้วยการวางข้อมูลในหลายรูปแบบพร้อมกันด้วย DataObject และ Clipboard.SetDataObject การเพิ่มในหลายรูปแบบเพื่อให้แอปอื่นรู้จักได้ และคลาส Clipboard ใช้ได้เฉพาะจากเธรด STA จึงต้องการ [STAThread]  2

บทความล่าสุดที่มีแท็กเดียวกัน เพื่อเจาะลึกหัวข้อใกล้เคียง

"ไม่ตอบสนอง" คืออะไรจริง ๆ — Windows ตัดสินว่าแอปแฮงอย่างไร และออกแบบอย่างไรให้ไม่แฮง

"ไม่ตอบสนอง" ของ Windows คือกลไกที่ OS ตัดสินว่าหน้าต่างไม่ได้ดึงข้อความเป็นเวลา 5 วินาทีแล้วแทนที่ด้วยหน้าต่างโกสต์ บทความนี้ครอบคลุมภาย...

Win32 Thread Pool API — คอนเคอร์เรนซีโดยไม่สร้างเธรดเอง ด้วย CreateThreadpoolWork

โค้ดเนทีฟของคุณกระจายการเรียก CreateThread ไปทั่วหรือไม่ บทความนี้อธิบาย Win32 thread pool API ที่ออกแบบใหม่ใน Vista — ออบเจ็กต์สี่ชนิด w...

Named Pipe ในทางปฏิบัติ — IPC มาตรฐานของ Windows ตั้งแต่การออกแบบถึงความปลอดภัย

คู่มือเชิงปฏิบัติเรื่อง named pipe ซึ่งเป็นการสื่อสารระหว่างโปรเซสมาตรฐานของ Windows บทความนี้จัดระเบียบจากแหล่งปฐมภูมิ ทั้งการเลือกระหว่...

แอปที่พังเมื่อรีซูมจากสลีป — อีเวนต์พลังงานของ Windows ทำงานอย่างไร และสร้างแอปธุรกิจที่รอดได้อย่างไร

เปิดแล็ปท็อปแล้วการเชื่อมต่อของแอปธุรกิจตาย — สาเหตุคือการออกแบบที่ไม่เคยคิดถึงสลีป บทความนี้ครอบคลุมลำดับการแจ้ง WM_POWERBROADCAST พฤติก...

DllMain และ Loader Lock — เหตุผลจริงที่ถูกบอกให้ "อย่าทำอะไรในการเริ่มต้น DLL"

ทำไมคุณต้องไม่เรียก LoadLibrary หรือซิงโครไนซ์กับเธรดอื่นจาก DllMain บทความนี้อธิบายจากแหล่งปฐมภูมิว่า loader lock ทำให้การแจ้ง DLL ทุกคร...

หน้าเหล่านี้วางหัวข้อไว้ในบริบทที่กว้างขึ้นของบริการและการตัดสินใจ

บทความนี้เกี่ยวข้องโดยตรงกับบริการต่อไปนี้

คำถามที่พบบ่อย

คำถามที่มักพบในการปรึกษาเกี่ยวกับหัวข้อของบทความนี้

ทำไมการจัดรูปแบบของตารางที่คัดลอกจาก Excel จึงพังเมื่อฉันวางลงในแอป
คลิปบอร์ดไม่ได้ถือ "ข้อมูลชิ้นเดียว" เนื้อหาเดียวกันถูกวางในหลายรูปแบบพร้อมกัน (รูปแบบส่วนตัวของแอปต้นทาง HTML Format CSV ข้อความยูนิโค้ด เป็นต้น) และแอปปลายทางเลือกรูปแบบที่เข้าใจแล้วดึงสิ่งนั้น เมื่อการจัดรูปแบบพัง สาเหตุทั่วไปคือปลายทางอ่านเฉพาะข้อความธรรมดา (CF_UNICODETEXT) หากคุณอยากได้โครงสร้างตารางด้วย ให้อิมพลีเมนต์ฝั่งวางให้ชอบ HTML Format หรือ CSV ก่อน ในทางกลับกัน หากอยากให้แอปอื่นวางจากสำเนาที่ทำในแอปของคุณได้อย่างถูกต้อง ให้เสนอทั้งรูปแบบที่อุดมและรูปแบบธรรมดาตอนคัดลอก
ทำไมฉันวางไม่ได้อีกหลังปิดแอปที่คัดลอกมา
เพราะต้นทางใช้ delayed rendering แอปที่จัดการข้อมูลใหญ่ไม่วางเพย์โหลดตอนคัดลอก พวกมันลงทะเบียนเพียงคำสัญญาบนคลิปบอร์ดว่าจะ "ผลิตเมื่อถูกขอ" หากต้นทางจากนั้นออกโดยไม่ทำให้ข้อมูลเป็นรูปเป็นร่างเพื่อตอบ WM_RENDERALLFORMATS ตอนปิดระบบ รูปแบบใดที่ยังไม่ถูกเรนเดอร์จะหายไป แอปที่ใช้คลิปบอร์ด OLE (IDataObject) คงการวางให้ทำงานหลังออกได้ด้วยการเรียก OleFlushClipboard ตอนปิดระบบเพื่อทำให้ข้อมูลเป็นรูปเป็นร่าง
แอปของฉันเฝ้าการเปลี่ยนของคลิปบอร์ดได้อย่างไร
วิธีที่แนะนำในปัจจุบันคือลงทะเบียนหน้าต่างของคุณเป็นผู้ฟังด้วย AddClipboardFormatListener แล้วจัดการข้อความ WM_CLIPBOARDUPDATE ที่มาถึงทุกครั้งที่เนื้อหาเปลี่ยน การโพลเนื้อหาบนตัวตั้งเวลาสิ้นเปลืองงานและพลาดการอัปเดตได้ และโซ่ผู้ชมเก่าที่อิง SetClipboardViewer ถูกเก็บไว้เพื่อความเข้ากันได้ย้อนหลังเท่านั้น เพราะบั๊กในแอปหนึ่งในโซ่ทำลายทั้งโซ่ โปรดทราบด้วยว่า OpenClipboard ตอนอ่านสามารถล้มเหลวเพราะโปรเซสอื่นถือคลิปบอร์ด ดังนั้นอิมพลีเมนต์การลองใหม่พร้อมรอสั้นหากอยากให้อ่านเสถียร
มีวิธีกันความลับ เช่น รหัสผ่าน ออกจากประวัติคลิปบอร์ด (Win+V) หรือไม่
มีสองคันโยก หนึ่งฝั่งแอปและหนึ่งฝั่งนโยบาย ฝั่งแอป หากคุณยังวางรูปแบบที่ลงทะเบียน ExcludeClipboardContentFromMonitorProcessing เมื่อคัดลอก เนื้อหานั้นไม่ถูกรวมทั้งในประวัติและในการซิงค์ข้ามอุปกรณ์ คุณยังควบคุมแต่ละอย่างอิสระได้ด้วย CanIncludeInClipboardHistory (ประวัติอย่างเดียว) และ CanUploadToCloudClipboard (ซิงค์อย่างเดียว) นี่คือกลไกที่ตัวจัดการรหัสผ่านใช้ หากอยากปิดทั้งองค์กร คุณปิดประวัติและการซิงค์คลาวด์เองได้ด้วย AllowClipboardHistory และ AllowCrossDeviceClipboard ผ่าน Group Policy หรือ Intune (Policy CSP)
ทำไมฉันลากและวางไฟล์ลงบนแอปที่รันในฐานะผู้ดูแลระบบไม่ได้
เพราะกลไกความปลอดภัยชื่อ UIPI (User Interface Privilege Isolation) บล็อกการส่งข้อความจากโปรเซสความสมบูรณ์ต่ำไปยังหน้าต่างความสมบูรณ์สูง File Explorer รันที่สิทธิ์ธรรมดา (ความสมบูรณ์กลาง) ดังนั้นการแจ้งลากและวางไม่ถึงหน้าต่างของแอปที่ยกระดับ ทางอ้อมที่อนุญาตข้อความ เช่น WM_DROPFILES เป็นรายตัวด้วย ChangeWindowMessageFilterEx เป็นที่รู้จักดี แต่ใช้ได้เฉพาะการแจ้งวางแบบเก่า การแก้จริงคือหยุดออกแบบแอปให้รันยกระดับตลอดเวลา และแยกเฉพาะงานที่ต้องการการยกระดับไปอีกโปรเซส

โปรไฟล์ผู้เขียน

หน้าแนะนำผู้เขียนบทความ

Go Komura

ผู้แทนของ KomuraSoft LLC

เชี่ยวชาญด้านการพัฒนาซอฟต์แวร์ Windows ที่ปรึกษาเทคนิค และการตรวจสอบบั๊ก โดยเฉพาะโปรเจกต์ที่มีระบบเดิมและบั๊กที่ทำซ้ำได้ยาก

ลิงก์สาธารณะ

กลับไปยังบล็อก