איך clipboard ו-drag-and-drop עובדים — טיפול נכון ב-OLE data transfer באפליקציות עסקיות

· עודכן בתאריך: · · Windows, clipboard, drag-and-drop, OLE, COM, פיתוח Windows, WinForms, WPF

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 21 Aug 2026)
פרסום ראשון
לצטט את המאמר הזה(DOI (ארכיון רשום): 10.5281/zenodo.22176601)

מזהי ה־DOI שלהלן מפנים לגרסאות שנשמרו בעבר בארכיון, ועשויים שלא להתאים לטקסט הנוכחי. להפניה לטקסט הנוכחי, השתמשו בכתובת של דף זה.

Go Komura (2026). איך clipboard ו-drag-and-drop עובדים — טיפול נכון ב-OLE data transfer באפליקציות עסקיות. KomuraSoft LLC. https://comcomponent.com/he/blog/windows-clipboard-drag-drop-ole-data-transfer/

DOI (ארכיון רשום)
10.5281/zenodo.22176601
DOI (הגרסה האחרונה שנרשמה)
10.5281/zenodo.22176602

“כשמדביקים טבלה מ-Excel העיצוב מתפרק.” “תוכן שהעתקנו באפליקציה שלנו לא יוצא כמו שתכננו כשמדביקים ב-Word.” “אנחנו רוצים לקבל קבצים ב-drag-and-drop.” — בקשות כאלה עולות לעיתים קרובות כשמעדכנים אפליקציות עסקיות.

אלה פעולות יומיומיות, אבל אם חושבים על ה-clipboard כעל “קופסה שמחזיקה נתון אחד”, מפספסים איך הוא באמת עובד. במציאות זה מנגנון ששם את אותו תוכן בכמה formats בבת אחת ונותן לצד ה-paste לבחור format שהוא מבין. לכן אותה העתקה מייצרת תוצאות שונות לפי מקום ה-paste.

הבעיה ש-paste נכשל אחרי שסוגרים את המקור קשורה ל-“delayed rendering”, שמייצר את הנתונים רק כשצריך אותם. ו-OLE drag-and-drop (D&D) מוסר IDataObject, אותו ייצוג נתונים שמשמש את OLE clipboard, דרך ממשקי COM. Copy-and-paste ו-D&D הם תכונות קשורות: הן חולקות את פורמט הנתונים ונבדלות בדרך שבה הוא נישא.

המאמר כתוב לאנשי IT בחברות קטנות ובינוניות ולמפתחי אפליקציות Windows. הוא מכסה קודם איך formats עובדים, אחר כך את המימושים בצד ה-paste, בצד ה-copy ובצד הניטור, ואז עובר לניהול history, סנכרון ו-RDP ולמלכודות של OLE D&D.

1. קודם כל, המסקנות

יש שלושה צירים לשמור בראש.

  1. צד ה-copy מציע כמה formats, וצד ה-paste בוחר מביניהם. הבסיס הוא CF_UNICODETEXT לטקסט, CF_HDROP לרשימת נתיבי קבצים, וה-registered format “HTML Format” לטקסט מעוצב.1234
  2. מתכננים לא רק את ה-format של הנתונים אלא גם את האימות ואת משך החיים שלהם. מאמתים נתונים מודבקים כקלט חיצוני לא מהימן. צד copy שמשתמש ב-delayed rendering מממש גם את שלב המימוש ביציאה. מנטרים עם AddClipboardFormatListener ו-WM_CLIPBOARDUPDATE, ונערכים לתחרות בקריאה עם retries.5678
  3. מבהירים עד כמה הנתונים נוסעים ומה קורה אחרי שנמסרו. History, סנכרון ענן והפניית RDP הם דברים לניהול. OLE D&D דורש אתחול STA דרך OleInitialize, ו-drop מהרשאה רגילה על אפליקציה elevated נחסם ב-UIPI. גם, Move הוא חוזה שבו הנתונים המקוריים נעלמים.910111213

אם רוצים לקרוא לפי מטרה, מתחילים מהפרק למטה.

בעיה או מטרה מה לבדוק קודם פרק
טבלת Excel מודבקת מתפרקת ה-formats שצד ה-copy מציע וה-format שצד ה-paste בוחר פרקים 2–4
לגרום להעתקה מהאפליקציה שלנו להיות שמישה באפליקציות אחרות הצעת כמה formats, ושמירת הנתונים אחרי יציאה פרק 5
לזהות העתקה ולייבא אותה אוטומטית רישום listener ו-retries בקריאה פרק 6
להשאיר סודות מחוץ ל-history, לסנכרון ול-RDP פורמטי ההחרגה בצד האפליקציה ומדיניות הארגון סעיף 6.3, פרק 7
לממש D&D; זה נכשל רק כש-elevated אתחול OLE, גבול ההרשאות, effects ואימות נתיבים פרקים 8–9

ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 20, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle

2. מה ה-clipboard באמת — לא “נתון אחד” אלא “אותו תוכן בכמה formats”

ה-clipboard הוא מנגנון לשיתוף נתונים בין אפליקציות. אפליקציות על אותו desktop יכולות להשתמש בו, אבל יחידת השיתוף המדויקת היא window station. Session משתמש אחר או session RDP יש לו clipboard נפרד משלו. Copy-and-paste עובד על RDP כי תכונת ה-redirection מגשרת בין השניים (פרק 7).

העיקרון העליון הוא שהשימוש מונע-משתמש. עמדת התכנון הרשמית היא שאפליקציה אינה שמה נתונים פנימה או מוציאה אותם בלי ידיעת המשתמש.1

אחרי ריקון ה-clipboard, צד ה-copy שם את אותו תוכן בכמה formats, מהמסוגל יותר כלפי מטה.6 באופן מושגי, העתקת טבלה בגיליון אלקטרוני נראית כך.

עדיפות Format תוכן
1 Format פרטי לאפליקציה ייצוג פנימי מלא כולל נוסחאות ועיצוב (ל-paste חזרה לאותה אפליקציה)
2 HTML Format קטע HTML ששומר את מבנה הטבלה והעיצוב
3 CSV טקסט מופרד-תאים
4 CF_UNICODETEXT Plain text מופרד-טאבים
5 Format תמונה Bitmap של איך הטבלה נראית

מה שצד ה-paste לוקח הוא ה-format שהוא מבין מהרשימה הזו. Word מקבל טבלה מעוצבת ו-Notepad מקבל טקסט מופרד-טאבים כי שניהם בחרו formats שונים.

במילים אחרות, “התוצאה תלויה ביעד ה-paste” אינו באג בפני עצמו. צריך להסתכל על ההצעה של צד ה-copy ועל הבחירה של צד ה-paste יחד.

איך אותה העתקה מייצרת תוצאות שונות לפי מקום ה-pasteצד ה-copy שם את אותו תוכן על ה-clipboard בכמה formats וצד ה-paste בוחר את ה-format שהוא מבין, כך ש-Word מקבל טבלה מעוצבת ו-Notepad מקבל טקסט מופרד-טאביםWord בוחר את זהNotepad בוחר את זהצד ה-copy: גיליון אלקטרוניClipboard (אותו תוכן בכמה formats)Format פרטי לאפליקציהHTML FormatCSVCF_UNICODETEXTטבלה מעוצבתטקסט מופרד-טאבים

בכיוון ההפוך, התלונות שבפתיחה — “העיצוב מתפרק”, “משהו מוזר מודבק” — כמעט כולן מצטמצמות לאיך אחד משני הצדדים בוחר או מציע formats. פרק 4 מכסה את צד ה-paste ופרק 5 את צד ה-copy.

3. Standard formats ו-registered formats — CF_UNICODETEXT, CF_HDROP, HTML Format

3.1. Standard formats — להשתמש בצד ה-Unicode לטקסט

Formats שה-OS מגדיר מראש נקראים standard formats. אלה אלה שמופיעים הכי הרבה באפליקציות עסקיות.2

Format ערך תוכן
CF_TEXT 1 טקסט ANSI (תלוי-code-page)
CF_UNICODETEXT 13 טקסט Unicode. זה ה-format הקנוני לטקסט
CF_HDROP 15 רשימת נתיבי קבצים (handle של HDROP)
CF_DIB 8 Bitmap בלתי תלוי במכשיר
CF_LOCALE 16 מזהה ה-locale שמשויך לטקסט

CF_TEXT ו-CF_UNICODETEXT הם “synthesized formats” שהמערכת ממירה ביניהם במרומז. ההמרה משתמשת ב-code page שמשויך ל-CF_LOCALE.2

עם זאת, תווים ש-ANSI אינו יכול לייצג אובדים בהמרה. אם מטפלים בסמלים שקיימים רק ב-Unicode, בתווים משלבים וכדומה, השארה להמרה מובילה ל-mojibake. הכלל הוא לקבע את הקריאות והכתיבות של האפליקציה על CF_UNICODETEXT, או DataFormats.UnicodeText ב-.NET.

המרה מרומזת בין CF_UNICODETEXT ל-CF_TEXTהאפליקציה קוראת וכותבת רק CF_UNICODETEXT; המערכת מסנתזת CF_TEXT בהמרה מרומזת לפי code page של CF_LOCALE. תווים ש-ANSI אינו יכול לייצג נופלים בהמרה הזוהמערכת ממירה במרומז (code page של CF_LOCALE)האפליקציה קוראת וכותבתCF_UNICODETEXT (קנוני)CF_TEXT (ANSI, תלוי-code-page)תווים שאי אפשר לייצג נופלים בהמרה (כר גידול ל-mojibake)

3.2. CF_HDROP — קבצים נוסעים כ”רשימת נתיבים”

CF_HDROP הוא מה ש-File Explorer משתמש בו להעתקות קבצים ול-D&D. הדבר הראשון לזכור הוא שמה שנוסע הוא רשימת נתיבים מלאים, לא הקבצים עצמם.

מבנה DROPFILES יושב בתחילת בלוק הזיכרון, ואחריו מחרוזות נתיב מופרדות בתווי NUL. מחרוזת ריקה מושמת בסוף, כך שהבלוק מסתיים ב-“double NUL”. בכותרת, pFiles הוא היסט ההתחלה של רשימת הנתיבים ו-fWide מציין אם המחרוזות הן Unicode.3

[כותרת DROPFILES: pFiles=היסט התחלת רשימת הנתיבים, fWide=1 (Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)

בקוד native שולפים אותם אחד-אחד עם DragQueryFile; ב-.NET מקבלים אותם כ-string[] דרך DataFormats.FileDrop. הנקודה ש”רק נתיבים נוסעים, לא הקבצים עצמם” חשובה שוב ל-D&D בפרקים 8 ו-9.

מבנה בלוק הזיכרון של CF_HDROPמבנה DROPFILES יושב בתחילת הזיכרון הגלובלי; pFiles נותן את היסט ההתחלה של רשימת הנתיבים ו-fWide מציין אם זה Unicode. נתיבים מלאים מופרדי-NUL באים אחר כך, ומסתיימים במחרוזת ריקה כמסיים double-NUL. רק נתיבים נוסעים, לא הקבצים עצמםמבנה DROPFILES (pFiles = היסט התחלת הרשימה / fWide = 1)C:\\data\\a.txt + NULC:\\data\\b.txt + NULמחרוזת ריקה מסיימת (double NUL)רק נתיבים נוסעים, לא הקבצים עצמם

3.3. Registered formats — RegisterClipboardFormat ו-“HTML Format”

נתונים ש-standard formats אינם יכולים לבטא אפשר לשתף כ-registered format תחת שם שאתם בוחרים. מעבירים שם ל-RegisterClipboardFormat ומקבלים מזהה format בחזרה. רישום אותו שם מאפליקציה אחרת מניב אותו ID, כך שהסכמה על שם בין אפליקציות היא נקודת המגע.1

כשמעבירים נתונים מובנים בין חבילת האפליקציות שלכם, משתמשים בשם שלא יתנגש, כמו KomuraSoft.Report.RowData.

HTML Format הוא “UTF-8 עם כותרת”

ה-registered format המייצג הוא “HTML Format”, פורמט טקסט מעוצב שעומד לצד RTF. הגוף הוא טקסט 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 על התחלה וסוף הקטע שהמשתמש בחר. היחידה היא בתים, לא תווים.

כשמייצרים אותו, מרכיבים בסדר הזה.

  1. שומרים את שדות ההיסט ברוחב קבוע (למשל 10 ספרות).
  2. בונים את גוף ה-HTML ומקודדים אותו כ-UTF-8.
  3. מודדים את מיקומי הבתים אחרי הקידוד וכותבים אותם חזרה לכותרת.

ב-UTF-8 שמכיל יפנית, ספירת התווים וספירת הבתים אינן תואמות. טועים בזה וההתחלה או הסוף נחתכים כשמדביקים באפליקציה אחרת.4

איך כותרת HTML Format קשורה להיסטיםבכותרת, StartHTML ו-EndHTML מצביעים על כל ה-HTML ו-StartFragment ו-EndFragment על הקטע שהמשתמש בחר, כולם כמיקומי בתים מתחילת הנתונים. כי ספירת תווים וספירת בתים מתפצלות ב-UTF-8, ממלאים אותם במיקומי בתים שנמדדו אחרי קידודכותרת (Version / StartHTML / EndHTML / StartFragment / EndFragment)כל ה-HTML (StartHTML עד EndHTML)הקטע שנבחר (StartFragment עד EndFragment)כל היסט = מיקום בתים מתחילת הנתונים (נמדד אחרי קידוד UTF-8 ונכתב חזרה)

מלבד אלה, CSV (DataFormats.CommaSeparatedValue ב-.NET) משמש גם הוא לעיתים קרובות לנתונים טבלאיים. לתאימות עם Excel, הצעת HTML Format (מעוצב), CSV (ערכים בלבד) ו-CF_UNICODETEXT (מופרד-טאבים) יחד עובדת לא משנה מה יעד ה-paste.

4. פרקטיקות בצד ה-paste — עדיפות format ואימות

4.1. לחפש מה-formats העשירים כלפי מטה

צד ה-paste מחפש את ה-formats שהוא יכול לטפל בהם, מתחיל מהעשיר ביותר במידע. אפשר להשתמש בסדר שבו צד ה-copy שם את ה-formats, מהמסוגל יותר כלפי מטה, או לציין סדר עדיפות משלכם.6

API של Win32 איך הוא בוחר
EnumClipboardFormats מונה בסדר שצד ה-copy שם אותם; משתמשים ב-format הראשון שמזהים
GetPriorityClipboardFormat בוחר format זמין מרשימת העדיפות שצד ה-paste מעביר

ב-.NET הפיצול נראה כך.

// הדבקת טבלה: מחפשים מעשיר לפשוט
var data = Clipboard.GetDataObject();
if (data is null) return;

// פרסום format אינו מבטיח שה-payload הוא 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))
{
    // מייבאים כטקסט מופרד בטאבים
}

זו התשובה לתלונה שבפתיחה ש”טבלת Excel מודבקת מתפרקת.” מבנה טבלה לעולם אינו מגיע לאפליקציה שקוראת רק plain text. עד כמה למטה ברשימת ה-formats מקבלים הוא החלטת תכנון של צד ה-paste.

פיצול paste שמחפש מ-formats עשירים כלפי מטהאם HTML Format נוכח וה-payload הוא מחרוזת, מייבאים כטבלה; אחרת CSV; ואם לא, טקסט מופרד-טאבים, יורדים מה-format המסוגל ביותר כלפי מטה. אם אין מועמד, מסרביםכןלאכןלאכןלאהתחלת pasteHTML Format נוכח וה-payload הוא מחרוזת?מאמתים את הכותרת ומייבאים כטבלהCSV נוכח?מייבאים כ-CSVUnicodeText נוכח?מייבאים כטקסט מופרד-טאביםסירוב

4.2. נתונים מודבקים הם קלט חיצוני

קל להתעלם מזה, אבל תוכן ה-clipboard הוא נתונים ממקור חיצוני, ואינכם יודעים איזו אפליקציה שמה אותם. Microsoft עצמה מזהירה בתיעוד OLE clipboard ש”נתוני clipboard אינם מהימנים; פרסו אותם בזהירות לפני השימוש באפליקציה.”5

נוכחות format לבדה אינה אומרת שאפשר לייבא את הנתונים. בודקים גם את סוג ה-payload בפועל, ומריצים את הבדיקות הבאות.

מה לאמת מה לבדוק
כותרת HTML Format האם ההיסטים מצביעים מחוץ לטווח. חלק מהאפליקציות פולטות כותרות שבורות
ערכים כמו מספרים, תאריכים וקודים האם הם עוברים את אותו אימות כמו קלט על המסך
גודל הנתונים האם הוא חורג ממגבלת הקבלה, כמו תמונות של מאות מגה-בתים או טקסט של מיליוני שורות

שימו לב שהמימוש מתחיל לפני שאפשר לבדוק את הגודל

להגנות מפני נתונים ענקיים, מבחינים את עומס איזה שלב אפשר למנוע. גם GetData של .NET מפעיל delayed rendering כשקוראים לו, ולטקסט הוא מממש עד מחרוזת מנוהלת. בדיקת הגודל רק אחרי השליפה אינה מונעת את עומס המימוש הזה.

אם בודקים את ה-HGLOBAL ש-Win32 GetClipboardData מחזיר עם GlobalSize, אפשר להגן מפני דחיפת נתונים ענקיים הלאה להמרה למחרוזת מנוהלת ולפרסור. ל-formats של delayed rendering, עם זאת, GetClipboardData עצמו מפעיל rendering. אי אפשר למנוע מאפליקציית המקור לממש את הנתונים עצמם.

כדי שה-UI לא יקפא, מעבירים את השליפה מחוץ ל-UI thread ומסרבים לנתונים שחורגים מהמגבלה. גם אז, Clipboard של .NET דורש STA. משתמשים ב-thread ייעודי שמוגדר ל-STA, לא ב-thread pool (MTA) של Task.Run (סעיף 5.1).

הרעיון ש”ערך שמגיע מבחוץ מאומת לפני שימוש, לא משנה הנתיב” הוא אותו רעיון שמפורט ב”לעולם אל תשתמשו בערך המפוענח של QR code כפי שהוא”. ההנחה ש-paste בטוח כי זו פעולת משתמש היא איך דברים משתבשים.

אימות נתונים מודבקים לפני השימושנתונים שנלקחים מה-clipboard עוברים בדיקות לנוכחות format, סוג payload, מגבלת גודל ותוכן בסדר הזה; אם בדיקה כלשהי נכשלת, מסרבים או נופלים ל-format המועמד הבאסוג שגויגדול מדילא תקיןבדיקה שה-format נוכח (GetDataPresent)בדיקת סוג ה-payload (is string / string[])בדיקת מגבלת הגודלאימות התוכן (כותרת, נתיבים, ערכים)ייבואסירוב / format מועמד הבא

5. פרקטיקות בצד ה-copy — הצעת כמה formats בבת אחת, ו-delayed rendering

5.1. לשים כמה formats בבת אחת

הפרקטיקה של צד ה-copy היא תמונת הראי של 4.1: להציע format עשיר ו-format פשוט באותו זמן. עם DataObject של WinForms/WPF זה כמה שורות.14

// WinForms (System.Windows.Forms). ל-WPF אותה צורה עם DataObject/Clipboard של System.Windows
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 = נשאר אחרי יציאת האפליקציה

כאן מסתכלים בנפרד על דרישת ה-thread ועל משך החיים אחרי יציאה.

דרישת thread: מחלקת Clipboard של .NET ניתנת לשימוש רק מ-thread STA.14 ה-UI thread של WinForms/WPF הוא STA בגלל [STAThread], כך שבדרך כלל זו אינה בעיה, אבל אי אפשר להשתמש בה מ-background thread שאינו STA. הרקע מוסבר ב”יסודות COM STA/MTA”.

משך חיים אחרי יציאה: copy: true פירושו “לשמור אחרי שהאפליקציה יוצאת.” הבנה יחד עם delayed rendering, בהמשך, מבהירה למה המפרט הזה נחוץ.

5.2. Delayed rendering — למה “סוגרים ואי אפשר להדביק”

לייצר כל אחד מה-formats הרבים בכל copy פירושו לעשות עבודה גם ל-formats שלעולם אינם בשימוש. המנגנון שנמנע מזה הוא delayed rendering.6

בזמן copy, מעבירים NULL כ-data handle ל-SetClipboardData, ורושמים לא את הנתונים עצמם אלא הבטחה “לייצר אותם כשיבקשו.” כשמבקשים את ה-format הזה, WM_RENDERFORMAT מגיע למקור ה-copy, ורק אז הנתונים נוצרים.

ביציאה, להפוך את ה”הבטחה” לנתונים

אם מקור ה-copy יוצא ומשאיר formats בלי render, צד ה-paste כבר לא יכול לקבל את הנתונים האלה. זו בעיית “סוגרים את המקור ואי אפשר להדביק.”

לפני יציאה, מקור ה-copy אחראי להגיב ל-WM_RENDERALLFORMATS ולממש כל format שעוד לא render. Formats שלא מומשו אובדים כשמקור ה-copy יוצא.6

זרימת delayed rendering ו-"סוגרים ואי אפשר להדביק"מקור ה-copy רושם רק הבטחה עם handle NULL ומממש את הנתונים עם WM_RENDERFORMAT כשמגיעה בקשה. ביציאה הוא אחראי לממש כל format עם WM_RENDERALLFORMATS; מזניחים זאת וה-format אובדמימוש עם WM_RENDERALLFORMATSהזנחת המימושמקור ה-copy: רישום הבטחה בלבד עם SetClipboardData(format, NULL)צד ה-paste מבקש את ה-format הזהWM_RENDERFORMAT → יצירת הנתונים במקוםמקור ה-copy עומד לצאתPaste עדיין עובד אחרי יציאהה-format הזה אובד (סוגרים ואי אפשר להדביק)

ב-OLE clipboard שמים IDataObject עם OleSetClipboard. בנקודה הזו, מה שה-clipboard מחזיק הוא מצביע לאובייקט הנתונים.

קריאה ל-OleFlushClipboard ביציאה מממשת את הנתונים על ה-clipboard, כך ש-paste עדיין עובד אחרי יציאה.7 Clipboard.SetDataObject(data, copy: true) של .NET מציין את התנהגות “לשמור אחרי יציאה” הזו.

כשמעתיקים טווח גדול ב-Excel ואז יוצאים, ההנחיה ששואלת אם לשמור את כמות המידע הגדולה על ה-clipboard היא אישור האם לבצע את המימוש הזה (ה-flush). גם באפליקציה שלכם, מתכננים delayed rendering ואת המימוש ביציאה כסט.

דחייה אינה מבטיחה שה-UI נשאר מגיב

Delayed rendering הוא אופטימיזציית ביצועים, אבל יצירת הנתונים המבוקשים רצה באופן סינכרוני בתוך עיבוד messages. הפשרה היא שה-UI קופא אם היצירה לוקחת זמן רב.6

6. פרקטיקות לניטור ה-clipboard — listener, retry והחרגה מ-history

6.1. להשתמש ב-AddClipboardFormatListener

דרישות כמו “לזהות ערך מקורא ברקוד או העתקה ממערכת הליבה ולייבא אוטומטית” קוראות לניטור שינויי clipboard. היסטורית יש שלוש שיטות, אבל היום יש תשובה נכונה אחת.8

שיטה הערכה
קריאה תקופתית על timer (polling) בזבזני, ויכול להחמיץ שינויים. לא להשתמש
SetClipboardViewer (שרשרת צופים) באג באפליקציה אחת בשרשרת שובר את הכול. נשמר רק לתאימות לאחור
AddClipboardFormatListener מומלץ. WM_CLIPBOARDUPDATE נמסר ל-window הרשום
זרימת ניטור clipboardרושמים עם AddClipboardFormatListener כשה-handle נוצר, ו-WM_CLIPBOARDUPDATE מגיע לא משנה איזו אפליקציה מעתיקה. קוראים עם retries, ומבטלים באופן סימטרי עם RemoveClipboardFormatListener כשה-handle נהרסביטול רישום סימטריOnHandleCreated: AddClipboardFormatListenerהמתנהאיזושהי אפליקציה מעתיקהWM_CLIPBOARDUPDATE מגיעקריאה עם retries (סעיף 6.2)OnHandleDestroyed: RemoveClipboardFormatListener
// מימוש 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)
    {
        // מבטלים את הרישום באופן סימטרי, בקצב עם השמדת ה-handle ויצירתו מחדש
        RemoveClipboardFormatListener(Handle);
        base.OnHandleDestroyed(e);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_CLIPBOARDUPDATE)
        {
            // כאן קוראים Clipboard.GetDataObject() ומייבאים אם ה-format נחוץ
        }
        base.WndProc(ref m);
    }
}

6.2. Retry כשאי אפשר לפתוח

רק window אחד בכל רגע יכול לפתוח את ה-clipboard. בזמן ש-process אחר מחזיק אותו פתוח, OpenClipboard נכשל.6

גם מיד אחרי WM_CLIPBOARDUPDATE, מקור ה-copy או אפליקציית ניטור אחרת עדיין יכולים לפעול עליו. מתייחסים לכישלון קריאה זמני כאל אירוע רגיל, ומוסיפים לוגיקה שמחכה כמה עשרות מילישניות ומנסה שוב כמה פעמים.

נקודה לשים לב אליה ב-.NET היא שה-overloads שמאפשרים לציין מספר retries ומרווח קיימים רק בצד הכתיבה, SetDataObject. בצד הקריאה, GetDataObject ודומים, אין כאלה.

צד הקריאה טיפול בתחרות
WinForms תופסים ExternalException, ממתינים, ומנסים שוב
WPF תופסים COMException, ממתינים, ומנסים שוב

ההמתנה וה-retry לקריאות ממומשים בצד האפליקציה.

זרימת retry לקריאת clipboardרק window אחד בכל רגע יכול לפתוח את ה-clipboard, כך שקריאה מיד אחרי הודעת השינוי יכולה להיכשל בתחרות עם process אחר. בחריגה, ממתינים כמה עשרות מילישניות ומנסים שוב; כשמגיעים למגבלה, מוותרים הפעם ומרימים בשינוי הבאהצלחהכישלון (window אחר משתמש בו)Retry עד כמה פעמיםהמגבלה הושגהWM_CLIPBOARDUPDATEניסיון הקריאהייבוא (אימות פרק 4)המתנה של כמה עשרות מילישניותויתור הפעם (הרמה בעדכון הבא)

6.3. להשאיר מחוץ ל-history ולסנכרון — שיקולים לתכונות copy שמטפלות בסודות

ל-Windows יש clipboard history (Win+V) וסנכרון בין מכשירים (cloud clipboard), ונתונים שאפליקציה שמה כפופים לשניהם כברירת מחדל. אפליקציה שתכונת ה-copy שלה נושאת סודות כמו סיסמאות או מספרי חשבון שמה גם registered format שמחריג את הנתונים מ-history ומסנכרון.1

Registered format מה הוא מדכא
ExcludeClipboardContentFromMonitorProcessing מחריג את כל התוכן שהועתק גם מ-history וגם מסנכרון בין מכשירים
CanIncludeInClipboardHistory (DWORD 0) History בלבד
CanUploadToCloudClipboard (DWORD 0) סנכרון בין מכשירים בלבד

מנהלי סיסמאות משאירים סיסמאות מועתקות מחוץ ל-Win+V דרך המנגנון הזה. כל מה שנדרש הוא להעביר את השם ל-RegisterClipboardFormat כדי לקבל מזהה format ולשים אותו לצד הנתונים הרגילים, כך שכדאי לממש זאת באפליקציות עסקיות שמטפלות בסודות.

7. ה-clipboard מנקודת מבט IT — שליטה ב-history, בסנכרון ענן וב-RDP

שליטה בתוכן מועתק בצד האפליקציה והחלטה מה הארגון בכללותו מתיר הן שאלות נפרדות. מה שמנהל מסתכל עליו הם שלושה דברים: history, סנכרון ענן, והפניית RDP.

Clipboard history צובר תוכן שהועתק לאחרונה. ה-cloud clipboard מסנכרן אותו בין מכשירים שמחוברים עם אותו חשבון Microsoft או חשבון Microsoft Entra.10

נוח ככל שזה, זה מוביל לשאריות ולדליפה: מידע אישי שהועתק ממערכת הליבה נשאר ב-history, או תוכן שהועתק במחשב עבודה מסתנכרן למחשב אישי.

7.1. שליטה ב-history ובסנכרון בין מכשירים

שתי המדיניות לשליטה ארגונית הן אלה.

מה נשלט GPO (Computer Configuration > Administrative Templates > System > OS Policies) Policy CSP (Intune) ברירת מחדל
Clipboard history Allow Clipboard History Experience/AllowClipboardHistory מותר
סנכרון בין מכשירים Allow Clipboard synchronization across devices Privacy/AllowCrossDeviceClipboard מותר

שתיהן זמינות מ-Windows 10 version 1809 ואילך; כשמושבתות, הפריטים המתאימים באפליקציית Settings מושבתים באפור, והמדיניות נכנסת לתוקף מיד.910

7.2. שליטה בהעברה בין sessions של RDP

הפניית clipboard ב-RDP (Remote Desktop) מגשרת בין המחשב המקומי ל-session המרוחק. כי copy-and-paste עובד כברירת מחדל, הוא יכול גם להפוך לנתיב להוצאת סודות משרת.

ההגדרה שחוסמת את שני הכיוונים היא מדיניות “Do not allow Clipboard redirection” (ערך Registry fDisableClip).11

מהדורות אחרונות של Windows Server ו-Windows 11 הוסיפו גם מדיניות לשליטה דקה יותר, כמו הגבלת כיוון השרת-ללקוח לטקסט בלבד. האם לאסור לגמרי או להגביל כיוון ו-format בשלבים הוא איזון בין תפעול לאבטחה.

נתיבים שבהם תוכן clipboard מתפשט, ונקודות השליטהתוכן שהועתק כפוף כברירת מחדל ל-history ולסנכרון ענן, ועל RDP הוא עובר ל-session אחר דרך redirection. כל אחד ניתן לשליטה במדיניות, וצד האפליקציה יכול להחריג תוכן מ-history ומסנכרון עם פורמטי ההחרגהClipboardHistory (Win+V)סנכרון ענן → מכשיר אחרהפניית RDP → session אחרשליטה: AllowClipboardHistoryשליטה: AllowCrossDeviceClipboardשליטה: fDisableClipצד האפליקציה: החרגה עם ExcludeClipboardContentFromMonitorProcessing ודומים (סעיף 6.3)

8. Drag-and-drop הוא COM — IDataObject + IDropSource + IDropTarget

8.1. אותם נתונים כמו ה-clipboard, נישאים אחרת

OLE drag-and-drop רץ על שלושת הצדדים הבאים.15

תפקיד ממומש על ידי עבודה
IDataObject מקור הגרירה הנתונים הנישאים. אותו אובייקט נתונים רב-format כמו ה-clipboard
IDropSource מקור הגרירה החלטה אם הגרירה ממשיכה או מבוטלת, ומשוב סמן
IDropTarget יעד ה-drop הכרזת קבלה וקבלת הנתונים ב-DragEnter/DragOver/DragLeave/Drop

הפעולה זורמת כך.

  1. מקור הגרירה קורא ל-DoDragDrop, ומתחיל את לולאת הגרירה.
  2. כשהעכבר נכנס ל-window של יעד drop, ה-IDropTarget מקבל הודעה.
  3. יעד ה-drop מכריז אם הוא מקבל, וב-drop שולף את ה-format שהוא צריך מ-IDataObject.

התיעוד הרשמי גם מסביר ש-D&D מספק את אותה פונקציונליות כמו copy-and-paste ב-clipboard, ושלאפליקציה שכבר מממשת copy-and-paste נדרשת רק תוספת קטנה.15 במילים אחרות, ה-DataObject הרב-format שהוכן בפרקים 2 עד 5 הוא גם הנתונים ש-D&D נושא.

זרימת OLE drag-and-dropמקור הגרירה קורא ל-DoDragDrop עם IDataObject כ-payload ולולאת הגרירה מתחילה; IDropTarget של יעד ה-drop מכריז על קבלה ב-DragEnter וב-DragOver, וב-Drop בוחר ושולף format מ-IDataObjectDoDragDropהעכבר נכנס ל-windowהכפתור משוחררמקור הגרירה: IDataObject + IDropSourceלולאת גרירהIDropTarget.DragEnter/DragOver (מכריזים על ה-Effect בכל פעם)IDropTarget.Dropבחירה ושליפה של format מ-IDataObject

8.2. OleInitialize (STA) נדרש

Window של יעד drop נרשם עם RegisterDragDrop. כתנאים מוקדמים, בודקים שני דברים: אתחול OLE ועיבוד messages.

משתמשים ב-OleInitialize לאתחול. אם משתמשים ב-CoInitialize / CoInitializeEx במקום, RegisterDragDrop נכשל עם E_OUTOFMEMORY. OleInitialize מאתחל COM כ-STA.12

ה-thread שרושם חייב להריץ message pump. OLE D&D הוא תכונה שמושרשת ב-windows ובעיבוד messages; מזניחים זאת ואפליקציות אחרות נתקעות במהלך גרירה.12 הרקע הוא אותו threading model כמו ב”יסודות COM STA/MTA”.

ב-WinForms/WPF, מקבלים דרך events

ב-WinForms/WPF, ה-framework מטפל באתחול OLE ובמימושי הממשקים. המפתח כותב את הכרזת הקבלה ואת הייבוא בפועל ב-events.

// 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, ה-payload
    // יכול להיות 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" על הרכיב ועם events DragOver / Drop, ושולפים את מערך הנתיבים עם e.Data.GetData(DataFormats.FileDrop).

הכרזת קבלה (ה-Effect) בכל פעם ב-DragEnter / DragOver היא מוסכמת IDropTarget. משמיטים אותה והסמן נשאר על “לא מותר.” בודקים לא רק אם ה-format נוכח אלא גם, כמו בדוגמת הקוד, אם מקור הגרירה מתיר Copy.

9. מלכודות D&D — elevation, Move, ואימות נתיבים

9.1. אי אפשר לעשות drop על אפליקציה elevated ל-Administrator

משחררים קובץ מ-File Explorer על אפליקציה שהופעלה עם “Run as administrator” וכלום לא קורה — זה אינו טעות מימוש אלא תכנון OS. UIPI (User Interface Privilege Isolation) חוסם כברירת מחדל messages מ-process שלמות נמוכה יותר ל-window שלמות גבוהה יותר, כך שהודעות drop מ-File Explorer שרץ בהרשאה רגילה (medium integrity) לעולם אינן מגיעות לאפליקציה elevated.13

איך UIPI חוסם drop על אפליקציה elevatedUIPI חוסם כברירת מחדל את הודעת ה-drop מ-File Explorer ב-medium integrity לאפליקציה elevated ב-high integrity, כך שהיא לעולם אינה מגיעה. משאירים את ה-UI בהרשאה רגילה ומבודדים עבודה מועדפת, וה-drop מגיעהודעת dropחסום (ברירת מחדל)עוברמסירה רק של עבודה מועדפתFile Explorer (medium integrity)UIPIאפליקציה elevated (high integrity): אין תגובהUI בהרשאה רגילה: ה-drop מגיעProcess נפרד שמבודד את העבודה שצריכה elevation

יש גם workaround שמתיר בנפרד WM_DROPFILES והודעות דומות עם ChangeWindowMessageFilterEx.13 עם זאת, זו תגובה להודעת drop ישנה דרך WM_DROPFILES, והיא אינה פותרת OLE D&D בכללותו.

ההנחיה היסודית היא לא להריץ את האפליקציה elevated כל הזמן. אם מבודדים רק את העבודה שצריכה elevation ל-process נפרד, ה-UI עצמו יכול להישאר בהרשאה רגילה ולקבל D&D. תכנון הבידוד מתואר בפירוט ב”הרשאות Administrator ו-broker processes באפליקציות Windows”.

9.2. מה DragDropEffects אומר — Move הוא חוזה ש”המקור נעלם”

Copy / Move / Link ב-DragDropEffects אינם קישוט סמן; הם חוזה בין מקור הגרירה ליעד ה-drop.

מקור הגרירה מכריז על קבוצת ה-effects שהוא מתיר ב-DoDragDrop, ויעד ה-drop בוחר את ה-effect בפועל. לפי המוסכמה, כשמוסכם Move, מקור הגרירה מוחק את הנתונים (הקובץ).

אם צד הקבלה מחזיר Move בלי לחשוב, מגיעים ל-“שחררתי וקובץ המקור נעלם.” לייבוא באפליקציות עסקיות, הופכים את צד הקבלה שמציין במפורש Copy לברירת המחדל הבטוחה.

חוזה DragDropEffects — עם Move, המקור נעלםמקור הגרירה מכריז על קבוצת ה-effects שהוא מתיר ב-DoDragDrop, ויעד ה-drop בוחר את ה-effect בפועל. כי המוסכמה היא שמקור הגרירה מוחק את הקובץ כשמוסכם Move, צד קבלה שמייבא צריך לציין Copy במפורשCopyMoveמקור הגרירה: הכרזת ה-effects המותרים (Copy | Move | Link)יעד ה-drop: בחירת ה-effect בפועלקובץ המקור נשאר (הצד הבטוח לייבוא)מקור הגרירה מוחק את הקובץ — מאיפה מגיע 'המקור נעלם'

9.3. אימות נתיבים שהושלכו

מה שנוסע ב-CF_HDROP/FileDrop הוא רק נתיבים (סעיף 3.2). לפני ייבוא, מעבירים אותם באותו אימות כמו קלט חיצוני, בדיוק כמו ב-paste.

  • קובץ או תיקייה: מחליטים כמפרט מה קורה כשמשחררים תיקייה שלמה (ייבוא רקורסיבי, או סירוב).
  • Placeholders של OneDrive: אם הנתיב קיים אבל גוף הקובץ אינו מקומי — קובץ on-demand — הורדה מתחילה ברגע שפותחים אותו, והיא נכשלת כשאין רשת. להתנהגות ולטיפול, ראו OneDrive Files On-Demand ואפליקציות עסקיות.
  • נתיבים ארוכים ומיוחדים: מקבלים נתיבים מעבר ל-MAX_PATH, נתיבי רשת (UNC), ונתיבים על מדיה נשלפת רק אחרי שאישרתם שעיבוד במורד הזרם יכול לטפל בהם.
  • מספר וגודל כולל: כדי ששחרור אלפי קבצים לא יקפיא את ה-UI, הופכים את הייבוא לאסינכרוני ומוסיפים מגבלה ותצוגת התקדמות.

10. סיכום

  • ה-clipboard הוא מנגנון ששם את אותו תוכן בכמה formats בבת אחת באזור יחיד שמשותף באותו desktop (window station). כי יעד ה-paste בוחר את ה-format, אותה העתקה מייצרת תוצאות שונות.
  • טקסט הוא CF_UNICODETEXT, קבצים הם CF_HDROP, וטקסט מעוצב הוא ה-registered format HTML Format (כותרת היסטי בתים + UTF-8).
  • צד ה-paste מחפש formats מעשיר לפשוט ומאמת את התוכן כקלט חיצוני. צד ה-copy מציע כמה formats בבת אחת, ואם הוא משתמש ב-delayed rendering, מממש גם את המימוש ביציאה (WM_RENDERALLFORMATS / OleFlushClipboard).
  • הניטור הוא AddClipboardFormatListener + WM_CLIPBOARDUPDATE. נערכים לתחרות OpenClipboard עם retries, ומחריגים סודות מ-history ומסנכרון עם ExcludeClipboardContentFromMonitorProcessing ודומים.
  • אנשי IT יכולים לשלוט ב-clipboard history, בסנכרון ענן ובהפניית RDP דרך GPO/Intune. כולם מותרים כברירת מחדל, לכן מחליטים במודע בסביבות שמטפלות בסודות.
  • D&D הוא COM: IDropSource/IDropTarget מוסרים את אותו IDataObject כמו ה-clipboard. RegisterDragDrop דורש OleInitialize (STA).
  • Drops על אפליקציה elevated נחסמים ב-UIPI. Move ב-DragDropEffects הוא חוזה ש”המקור נעלם”, ונתיבים שהושלכו מאומתים לפני ייבוא.

למשתמשים, copy-and-paste ו-D&D הם תכונות שקופות כמו אוויר. בדיוק לכן החוויה נפגעת כל כך כש-“אי אפשר להדביק”, “מתפרק” או “נעלם” קורים — ולהפך, אפליקציה שמציעה כמה formats ומטפלת ב-drops ביסודיות הופכת את התפעול היומיומי לחלק יותר בזה בלבד. אני מקווה שזה ישמש כקלט כששוקלים את עדיפות השינויים.

מאמרים קשורים

תחומי ייעוץ קשורים

KomuraSoft LLC מטפלת בתכנון ובמימוש של תמיכת copy-and-paste ו-drag-and-drop באפליקציות עסקיות (הצעת כמה formats, שילוב Excel, ייבוא קבצים שהושלכו), בחקירת שורש של פגמים כמו “זה מתפרק כשמדביקים” או “ההעתקה נעלמת”, באוטומציית קלט באמצעות ניטור clipboard, ובמימוש הגנות history וסנכרון למידע סודי. מקרים שמערבים את השכבות הנמוכות של COM ו-OLE מתקבלים גם אם הם מתחילים מבידוד התסמין.

קישורים

  1. Microsoft Learn, Clipboard Formats. על כך ש-window יכול לשים את אותו מידע בכמה clipboard formats; registered formats דרך RegisterClipboardFormat (רישום אותו שם מחזיר אותו ערך, כך שאפליקציות יכולות לשתף אותו); synthesized formats; והחרגת תוכן מ-clipboard history ומסנכרון ענן עם ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory ו-CanUploadToCloudClipboard. ↩ ↩2 ↩3 ↩4

  2. Microsoft Learn, Standard Clipboard Formats. על הגדרות standard formats כמו CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB ו-CF_LOCALE, ועל כך שהמערכת ממירה במרומז בין CF_TEXT ל-CF_UNICODETEXT באמצעות ה-code page שמשויך ל-CF_LOCALE. ↩ ↩2 ↩3

  3. Microsoft Learn, Shell Clipboard Formats. על כך ש-CF_HDROP מורכב ממבנה DROPFILES ומערך מחרוזות נתיב מלא שמסתיים ב-double-NUL; שליפת נתיבים בודדים עם DragQueryFile; ועל כך ש-shell formats מסוג CFSTR_ דורשים רישום דרך RegisterClipboardFormat. ↩ ↩2

  4. Microsoft Learn, HTML Clipboard Format. על כך שהשם הרשום הוא “HTML Format”; מבנה הכותרת עם היסטים (בבתים) כמו Version, StartHTML, EndHTML, StartFragment ו-EndFragment; על כך שהקידוד תמיד UTF-8; ועל מוסכמת ההערות StartFragment/EndFragment. ↩ ↩2 ↩3

  5. Microsoft Learn, OleGetClipboard function (ole2.h). על איך לקבל IDataObject מה-clipboard, ועל האזהרה שנתוני clipboard אינם מהימנים ויש לפרסר אותם בזהירות לפני שהאפליקציה משתמשת בהם. ↩ ↩2

  6. Microsoft Learn, Clipboard Operations. על כך שרק window אחד בכל רגע יכול לפתוח את ה-clipboard; הנחת formats מהמסוגל יותר כלפי מטה בזמן copy; בחירת format בזמן paste עם EnumClipboardFormats / GetPriorityClipboardFormat; delayed rendering בהעברת NULL ל-SetClipboardData ואחריות WM_RENDERFORMAT / WM_RENDERALLFORMATS; והפשרות של delayed rendering. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  7. Microsoft Learn, OleFlushClipboard function (ole2.h). על כך שאחרי OleSetClipboard ה-clipboard מחזיק רק מצביע לאובייקט הנתונים; OleFlushClipboard מממש את הנתונים על ה-clipboard כך ש-paste נשאר אפשרי אחרי יציאת האפליקציה; ועל ריקון ה-clipboard עם OleSetClipboard(NULL) כשאין צורך לשמור את הנתונים ביציאה. ↩ ↩2

  8. Microsoft Learn, Using the clipboard. על השוואת שלוש דרכי ניטור ה-clipboard (viewer windows, sequence numbers, ו-format listeners); על כך שתוכניות חדשות אמורות להשתמש ב-listener דרך AddClipboardFormatListener; על כך ששרשרת הצופים פגיעה לכשלים בשמירת השרשרת; ועל כך שאין לסרוק sequence numbers. ↩ ↩2

  9. Microsoft Learn, Policy CSP - Experience. על התרת או חסימת clipboard history עם מדיניות Experience/AllowClipboardHistory; זמינות מ-Windows 10 version 1809 ואילך; ברירת המחדל מותרת; ומיפוי GPO תחת “System > OS Policies” עם שינויים שנכנסים לתוקף מיד. ↩ ↩2

  10. Microsoft Learn, Policy CSP - Privacy. על התרת או חסימת סנכרון clipboard בין מכשירים עם מדיניות Privacy/AllowCrossDeviceClipboard; על כך שהסנכרון מתרחש בין מכשירים שמחוברים עם אותו חשבון Microsoft או חשבון Microsoft Entra; ועל כך שברירת המחדל מותרת. ↩ ↩2 ↩3

  11. Microsoft Learn, Policy CSP - ADMX_TerminalServer. על כך ש-TS_CLIENT_CLIPBOARD (“Do not allow Clipboard redirection,” ערך Registry fDisableClip) יכול לאסור שיתוף clipboard בין מקומי למרוחק ב-session Remote Desktop, ועל כך שה-redirection מותר כברירת מחדל. ↩ ↩2

  12. Microsoft Learn, RegisterDragDrop function (ole2.h). על איך לרשום window של יעד drop ואת ה-IDropTarget שלו; על כך שהקריאה תמיד נכשלת עם E_OUTOFMEMORY כש-COM אותחל עם CoInitialize/CoInitializeEx, כך ש-OleInitialize נדרש; ועל כך שאפליקציית מקור הגרירה נתקעת כשה-thread הקורא אינו מריץ message pump. ↩ ↩2 ↩3

  13. Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). על כך ש-UIPI הוא מנגנון אבטחה שחוסם כברירת מחדל קבלת messages משולח שלמות נמוכה יותר, ועל התרת messages ספציפיים (MSGFLT_ALLOW) עם מסנן messages לפי-window. ↩ ↩2 ↩3

  14. Microsoft Learn, How to add data to the Clipboard (Windows Forms). על הנחת נתונים בכמה formats בבת אחת עם DataObject ו-Clipboard.SetDataObject; הוספת נתונים בכמה formats כדי שאפליקציות אחרות יוכלו לזהות אותם; ועל כך שמחלקת Clipboard ניתנת לשימוש רק מ-thread STA, כך ש-[STAThread] נדרש. ↩ ↩2

  15. Microsoft Learn, Drag and Drop (COM). על כך ש-OLE drag-and-drop רץ על שלושת הצדדים IDropSource (מקור הגרירה), IDropTarget (יעד ה-drop) ו-DoDragDrop (הלולאה ש-OLE מספק); על כך שהוא מספק את אותה פונקציונליות כמו copy-and-paste ב-clipboard, כך שלאפליקציה שכבר מממשת copy-and-paste נדרשת רק תוספת קטנה; ועל סוגי המשוב. ↩ ↩2

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

למה העיצוב מתפרק כשאני מדביק טבלה שהועתקה מ-Excel לאפליקציה שלי?
כי ה-clipboard אינו מחזיק "נתון אחד": אותו תוכן מושם בכמה formats בבת אחת (ה-format הפרטי של האפליקציה, HTML Format, CSV, Unicode text וכן הלאה), ואפליקציית היעד בוחרת format שהיא מבינה ולוקחת אותו. כשהעיצוב מתפרק, הסיבה הטיפוסית היא שהיעד קורא רק plain text (CF_UNICODETEXT). אם רוצים גם את מבנה הטבלה, מממשים את צד ה-paste כך שיעדיף HTML Format או CSV. ולהפך, אם רוצים שאפליקציות אחרות ידביקו נכון מהעתקה שנעשתה באפליקציה שלכם, מציעים בזמן ה-copy גם format עשיר וגם format פשוט.
למה אי אפשר יותר להדביק אחרי שסגרתי את האפליקציה שממנה העתקתי?
כי המקור משתמש ב-delayed rendering. אפליקציות שמטפלות בנתונים גדולים אינן שמות את ה-payload בזמן ה-copy; הן רושמות על ה-clipboard רק הבטחה ש"ייצרו אותו כשיבקשו". אם המקור אז יוצא בלי לממש את הנתונים בתגובה ל-WM_RENDERALLFORMATS ב-shutdown, כל format שעוד לא render אובד. אפליקציה שמשתמשת ב-OLE clipboard (IDataObject) יכולה לשמור על paste אחרי יציאה בקריאה ל-OleFlushClipboard ב-shutdown כדי לממש את הנתונים.
איך האפליקציה שלי יכולה לצפות בשינויי ה-clipboard?
השיטה המומלצת כיום היא לרשום את ה-window כ-listener עם AddClipboardFormatListener ולטפל בהודעת WM_CLIPBOARDUPDATE שמגיעה בכל פעם שהתוכן משתנה. סקר תוכן על timer מבזבז עבודה ויכול להחמיץ עדכונים, ושרשרת הצופים הישנה שמבוססת על SetClipboardViewer נשמרת רק לתאימות לאחור, כי באג באפליקציה אחת בשרשרת שובר את כל השרשרת. שימו לב גם ש-OpenClipboard בקריאה יכול להיכשל כי process אחר מחזיק את ה-clipboard, לכן מממשים retry עם המתנה קצרה אם רוצים שהקריאה תהיה יציבה.
יש דרך להשאיר סודות כמו סיסמאות מחוץ ל-clipboard history (Win+V)?
יש שני מנופים, אחד בצד האפליקציה ואחד בצד המדיניות. בצד האפליקציה, אם שמים גם את ה-registered format ExcludeClipboardContentFromMonitorProcessing כשמעתיקים, התוכן הזה אינו נכלל לא ב-history ולא בסנכרון בין מכשירים. אפשר גם לשלוט בכל אחד בנפרד עם CanIncludeInClipboardHistory (history בלבד) ו-CanUploadToCloudClipboard (סנכרון בלבד). זה המנגנון שמנהלי סיסמאות משתמשים בו. אם רוצים לכבות זאת לכל הארגון, אפשר להשבית את ה-history ואת סנכרון הענן עצמם עם AllowClipboardHistory ו-AllowCrossDeviceClipboard דרך Group Policy או Intune (Policy CSP).
למה אי אפשר לגרור ולשחרר קובץ על אפליקציה שרצה כ-Administrator?
כי מנגנון אבטחה בשם UIPI (User Interface Privilege Isolation) חוסם מסירת messages מ-process שלמות נמוכה יותר ל-window שלמות גבוהה יותר. File Explorer רץ בהרשאה רגילה (medium integrity), כך שהודעות drag-and-drop לעולם אינן מגיעות ל-window של אפליקציה elevated. Workaround שמתיר בנפרד הודעות כמו WM_DROPFILES עם ChangeWindowMessageFilterEx ידוע, אבל הוא חל רק על הודעת ה-drop הישנה. התיקון האמיתי הוא להפסיק לתכנן את האפליקציה לרוץ elevated כל הזמן, ולבודד רק את העבודה שצריכה elevation ל-process נפרד.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג