Comment fonctionnent le presse-papiers et le glisser-déposer — traiter correctement le transfert de données OLE dans les applications métier

· · Windows, Presse-papiers, Glisser-déposer, OLE, COM, Développement Windows, WinForms, WPF

« Lorsque nous collons un tableau copié depuis Excel, la mise en forme se défait. Nous voulons qu’il se colle comme un tableau. » « Le contenu que nous copions dans notre application se transforme en quelque chose de bizarre lorsque nous le collons dans Word. » « Nous voulons pouvoir recevoir des fichiers par glisser-déposer. » — Dans les conversations de conseil sur les changements d’applications métier, les demandes autour du copier-coller et du glisser-déposer (D&D) sont un classique.

Précisément parce que ce sont des « fonctionnalités que tout le monde tient pour acquises », comment elles fonctionnent réellement est étonnamment peu connu. Si vous pensez au presse-papiers comme « une boîte dans laquelle vous mettez un seul morceau de données », vous ne pouvez pas expliquer pourquoi la même copie produit des résultats différents selon l’endroit où vous collez, ni pourquoi le collage cesse de fonctionner après que vous fermez l’application source. Le vrai presse-papiers est un mécanisme qui place le même contenu dans plusieurs formats à la fois, et laisse le côté collage choisir un format qu’il comprend.

Et le glisser-déposer, au fond, est un transfert de données OLE qui transmet exactement la même représentation de données que le presse-papiers (IDataObject), à travers des interfaces COM. Autrement dit, le copier-coller et le D&D sont des frères : comprenez l’un correctement et l’autre est juste là.

Cet article s’adresse au personnel informatique des petites et moyennes entreprises et aux développeurs d’applications Windows. Il relie, en une seule image, comment fonctionnent les formats du presse-papiers, les pratiques côté collage et côté copie, la façon correcte de surveiller le presse-papiers, les stratégies administratives pour l’historique du presse-papiers, la synchronisation cloud et RDP, et la structure et les pièges du glisser-déposer OLE.

1. La conclusion, d’abord

  • Le presse-papiers est une zone unique partagée par les applications du même bureau (station de fenêtres), et ce qui s’y trouve n’est pas « un seul morceau de données » mais le même contenu dans plusieurs formats à la fois. Une session différente, telle que RDP, a à l’origine un presse-papiers différent ; la fonctionnalité de redirection est ce qui relie les deux. Parce que la destination choisit un format qu’elle comprend, la même copie produit des résultats différents selon l’endroit où vous collez.12
  • Pour le texte, utilisez CF_UNICODETEXT. CF_TEXT est ANSI et dépendant de la page de codes, et sur les systèmes japonais c’est un terreau pour le mojibake. Le système convertit entre les deux implicitement, mais le côté canonique est Unicode.3
  • Les fichiers voyagent comme CF_HDROP (un tableau de chemins terminé par un double NUL), et le texte mis en forme utilise le format enregistré « HTML Format ». HTML Format a une structure inhabituelle : du texte UTF-8 avec un en-tête de décalages d’octets.45
  • La vraie cause de « j’ai fermé l’application source et je ne pouvais plus coller » est le rendu différé. C’est un mécanisme qui place non pas la charge utile mais seulement une promesse de « la produire lorsqu’on le demandera » ; si vous sautez la matérialisation à la sortie (répondre à WM_RENDERALLFORMATS, ou OleFlushClipboard pour OLE), le collage cesse de fonctionner.26
  • Traitez les données collées comme une entrée non fiable provenant de l’extérieur. Microsoft elle-même indique clairement que « clipboard data is not trusted. Parse it carefully ».7
  • Pour surveiller le presse-papiers, AddClipboardFormatListener + WM_CLIPBOARDUPDATE est la seule option. N’utilisez pas l’interrogation, et n’utilisez pas l’ancien SetClipboardViewer (la chaîne de visionneuses). Des formats enregistrés qui gardent les secrets hors de l’historique et de la synchronisation (ExcludeClipboardContentFromMonitorProcessing et analogues) sont aussi fournis.81
  • L’historique du presse-papiers (Win+V) et la synchronisation cloud sont une préoccupation de gestion informatique. Vous pouvez les contrôler avec AllowClipboardHistory et AllowCrossDeviceClipboard via GPO / Intune (Policy CSP), et la redirection du presse-papiers RDP a une stratégie dédiée à elle.91011
  • Le glisser-déposer est COM. Le même IDataObject que le presse-papiers est transmis entre IDropSource (la source de glissement) et IDropTarget (la cible de dépôt) à travers la boucle DoDragDrop. RegisterDragDrop exige une initialisation avec OleInitialize (STA).1213
  • Vous ne pouvez pas déposer depuis l’Explorateur de fichiers à privilège ordinaire sur une application élevée. UIPI (blocage de messages par niveau d’intégrité) est la cause, et c’est une contrainte que vous devez connaître au moment de la conception.14

Ci-dessous nous parcourons cela depuis les fondations du presse-papiers.

2. Ce qu’est vraiment le presse-papiers — pas « un seul morceau de données » mais « le même contenu dans plusieurs formats »

Le presse-papiers est un mécanisme commun de partage de données que chaque application partageant le même bureau peut atteindre (plus précisément, il est par station de fenêtres : une session utilisateur différente ou une session RDP a chacune son propre presse-papiers. Le copier-coller fonctionne sur RDP parce que la fonctionnalité de redirection relie les deux — chapitre 7). Le premier principe est qu’il est piloté par l’utilisateur : la posture de conception officielle est que vous ne mettez pas de données ni n’en prenez à l’insu de l’utilisateur.1

Le point important est qu’une copie ne place pas « un seul morceau de données ». La fenêtre qui copie vide le presse-papiers puis place plusieurs formats à la suite, exprimant le même contenu du format le plus capable vers le moins capable.2 Par exemple, lorsque vous copiez un tableau dans un tableur, conceptuellement quelque chose comme ce qui suit est sur le presse-papiers en même temps.

Priorité Format Contenu
1 Format privé de l’application Une représentation interne complète, y compris formules et mise en forme (pour coller de nouveau dans la même application)
2 HTML Format Un fragment HTML qui conserve la structure de tableau et la mise en forme
3 CSV Texte délimité par cellules
4 CF_UNICODETEXT Texte brut séparé par des tabulations
5 Format image Un bitmap de l’apparence du tableau

Le côté collage choisit un format qu’il comprend dans cette liste et l’extrait. Collez dans Word et vous obtenez un tableau mis en forme ; collez dans le Bloc-notes et vous obtenez du texte séparé par des tabulations — parce que les deux ont choisi des formats différents. « Le résultat dépend de l’endroit où vous collez » n’est pas un bug ; c’est la conséquence normale de cette conception.

Pourquoi la même copie produit des résultats différents selon l'endroit où vous collezLe côté copie place le même contenu sur le presse-papiers dans plusieurs formats, et le côté collage choisit un format qu'il comprend, donc Word obtient un tableau mis en forme et le Bloc-notes du texte séparé par des tabulationsWordBloc-notesCopie : tableurPresse-papiers (plusieurs formats)Formats plus richesFormats plus brutsPrivé de l'applicationHTML FormatCSVCF_UNICODETEXTTableau mis en formeTexte séparé par des tabulations

Dit à l’envers, les plaintes d’ouverture — « la mise en forme se défait », « quelque chose de bizarre se colle » — se ramènent presque toutes à un problème de comment un côté choisit les formats, ou comment l’autre côté les offre. Le chapitre 4 couvre le côté collage ; le chapitre 5 couvre le côté copie.

3. Formats standard et formats enregistrés — CF_UNICODETEXT, CF_HDROP, HTML Format

3.1. Formats standard — utilisez le côté Unicode pour le texte

Les formats que l’OS définit d’emblée sont appelés formats standard. Ceux qui apparaissent constamment dans les applications métier sont les suivants.3

Format Valeur Contenu
CF_TEXT 1 Texte ANSI (dépendant de la page de codes)
CF_UNICODETEXT 13 Texte Unicode. C’est le format canonique pour le texte
CF_HDROP 15 Une liste de chemins de fichiers (un handle HDROP)
CF_DIB 8 Un bitmap indépendant du périphérique
CF_LOCALE 16 L’identifiant de locale associé au texte

CF_TEXT et CF_UNICODETEXT sont convertis l’un en l’autre implicitement par le système (formats synthétisés). La conversion de code de caractères utilise la page de codes associée à CF_LOCALE.3 S’appuyer sur cette conversion fait tomber les caractères que ANSI ne peut pas représenter (par exemple les symboles uniquement Unicode et les caractères combinants), donc la règle est unifier ce que l’application lit et écrit sur CF_UNICODETEXT (DataFormats.UnicodeText en .NET).

Conversion implicite entre CF_UNICODETEXT et CF_TEXTL'application lit et écrit seulement CF_UNICODETEXT ; le système synthétise CF_TEXT par conversion implicite avec la page de codes CF_LOCALE. Les caractères qu'ANSI ne peut pas représenter sont perdus dans cette conversionConversion CF_LOCALEL'application lit et écritCF_UNICODETEXTCF_TEXT (ANSI)Les caractères non représentables sont perdus

3.2. CF_HDROP — les fichiers voyagent comme « une liste de chemins »

CF_HDROP est ce qui est utilisé lorsque vous copiez des fichiers dans l’Explorateur de fichiers, ou lorsque vous glissez-déposez des fichiers. La charge utile n’est pas les fichiers eux-mêmes ; c’est un bloc mémoire qui dispose un tableau « terminé par un double NUL » : après un en-tête de structure DROPFILES, des chaînes de chemins complets séparées par des caractères NUL, et une chaîne vide à la fin. Le pFiles de l’en-tête est le décalage de début de la liste de chemins, et fWide dit si les chaînes sont Unicode.4

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

En code natif vous les extrairez une par une avec DragQueryFile ; en .NET vous les recevez comme un string[] via DataFormats.FileDrop. Le fait que « ce qui voyage n’est que les chemins, pas les fichiers eux-mêmes » importera à nouveau dans le D&D des chapitres 8 et 9.

Disposition du bloc mémoire de CF_HDROPUne structure DROPFILES siège au début de la mémoire globale ; pFiles est le décalage de début de la liste de chemins et fWide dit s'il s'agit d'Unicode. Les chemins complets suivent ensuite, séparés par NUL, et le bloc se termine par une chaîne vide (double NUL). Ce qui voyage n'est que les chemins, pas les fichiers eux-mêmesDROPFILES (pFiles / fWide)C:\\data\\a.txt + NULC:\\data\\b.txt + NULChaîne vide (double NUL)Seuls les chemins voyagent, pas les fichiers

3.3. Formats enregistrés — RegisterClipboardFormat et « HTML Format »

Pour des données que les formats standard ne peuvent pas exprimer, une application peut choisir un nom et enregistrer son propre format. Passez un nom à RegisterClipboardFormat et vous obtenez un ID de format en retour ; enregistrer sous le même nom depuis une application différente renvoie le même ID, donc une fois que vous êtes d’accord sur le nom vous pouvez partager des données entre applications.1 Lorsque vous transmettez des données structurées parmi votre propre suite d’applications, utilisez un nom qui n’entrera pas en collision, tel que KomuraSoft.Report.RowData.

Le format enregistré représentatif est « HTML Format », pour le texte mis en forme (avec RTF, l’un des deux grands formats de texte enrichi). La charge utile est du texte UTF-8, mais elle a une structure inhabituelle : un en-tête qui liste des décalages d’octets est attaché à l’avant.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>

Chaque décalage est une position en octets depuis le début des données, y compris l’en-tête lui-même ; la pratique habituelle est de réserver une largeur fixe (par exemple 10 chiffres) et d’écrire les valeurs mesurées après avoir construit le corps. StartFragment/EndFragment marquent le début et la fin de « le fragment que l’utilisateur a réellement sélectionné » en octets (pas en caractères). En UTF-8 qui inclut le japonais, le nombre de caractères et le nombre d’octets divergent, donc si vous vous trompez dans ce calcul de décalage, coller dans une autre application fait tomber le début ou la fin. Si vous générez HTML Format vous-même, vous devez remplir l’en-tête avec des positions d’octets mesurées après encodage en UTF-8.5

Comment l'en-tête HTML Format se rapporte aux décalagesStartHTML et EndHTML de l'en-tête pointent vers tout le HTML, et StartFragment et EndFragment pointent vers le fragment que l'utilisateur a sélectionné, tous deux comme positions d'octets depuis le début des données. Parce que le nombre de caractères et le nombre d'octets divergent en UTF-8, remplissez l'en-tête avec des positions d'octets mesurées après encodageEn-tête (décalages d'octets)Tout le HTMLFragment sélectionnéLes décalages sont des octets après UTF-8

CSV (DataFormats.CommaSeparatedValue en .NET) est aussi couramment utilisé pour les données tabulaires. Pour l’interopérabilité avec Excel, offrir HTML Format (avec mise en forme), CSV (valeurs seulement) et CF_UNICODETEXT (séparé par des tabulations) ensemble signifie que vous n’avez pas à choisir une seule destination de collage.

4. Pratiques côté collage — priorité des formats et validation

4.1. Regardez depuis les formats riches vers le bas

Les formats sur le presse-papiers sont alignés dans l’ordre où le côté copie les a placés (c’est-à-dire du plus expressif au moins). La ligne de base du côté collage est de regarder, parmi les formats que vous pouvez traiter, en commençant par celui qui a le plus d’information. En Win32 vous énumérez soit avec EnumClipboardFormats et utilisez le premier format que vous reconnaissez, soit vous passez votre propre liste de priorités à GetPriorityClipboardFormat et le laissez choisir.2

En .NET la branche ressemble à quelque chose comme ce qui suit.

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

C’est la réponse à la plainte d’ouverture, « coller un tableau Excel se défait ». Une application qui ne lit que le texte brut ne reçoit jamais la structure de tableau. Jusqu’où vous descendez dans la liste de formats que vous acceptez est une décision de conception du côté collage.

Branchement de collage qui regarde depuis les formats riches vers le basSi HTML Format est présent et que la charge utile est aussi une chaîne, importer comme un tableau ; sinon essayer CSV ; si celui-ci manque aussi, tomber vers le texte séparé par des tabulations. Si aucun des candidats n'est présent, refuserouinonouinonouinonCommencer le collageHTML Format + chaîne ?Valider l'en-tête → tableauCSV présent ?Importer comme CSVUnicodeText ?Texte séparé par des tabulationsRefuser

4.2. Les données collées sont une entrée externe

C’est facile à manquer, mais le contenu du presse-papiers est des données provenant de l’extérieur, et vous ne savez pas quelle application les a placées. Microsoft met aussi en garde, dans la documentation du presse-papiers OLE, que « clipboard data is not trusted. Parse it carefully before you use it in the app ».7

  • Validez que les décalages de l’en-tête HTML Format ne pointent pas hors du tampon (des applications qui émettent des en-têtes cassés existent).
  • Les valeurs que vous importez comme nombres, dates ou codes doivent passer par la même validation que la saisie à l’écran.
  • Mettez une défense contre les données énormes. Même si quelqu’un colle une image de centaines de mégaoctets ou des millions de lignes de texte, ne bloquez pas l’interface, et refusez une fois qu’une limite est dépassée. Une mise en garde : GetData de .NET, au moment où vous l’appelez, matérialise toute la charge utile en une chaîne gérée (et le rendu différé s’exécute comme une partie de cela), donc placer une vérification de taille après GetData n’est pas une défense. En Win32, vérifier GlobalSize sur le HGLOBAL que GetClipboardData renvoie vous donne bien une défense au stade de « ne pas poursuivre vers la conversion et l’analyse comme une chaîne gérée », mais pour les formats à rendu différé GetClipboardData lui-même déclenche le rendu, donc vous ne pouvez toujours pas empêcher la matérialisation du côté source de copie. Pour empêcher l’interface de geler, déplacez la récupération hors du thread d’interface (et même alors, parce que le Clipboard de .NET exige STA, faites-le sur un thread dédié réglé en STA, pas sur le thread du pool de threads de Task.Run (MTA) — section 5.1).

L’idée qu’« une valeur qui arrive de l’extérieur, quel que soit le chemin, est validée avant que vous l’utilisiez » est la même que celle exposée dans « Ne jamais utiliser telle quelle la valeur décodée d’un QR code ». L’hypothèse que coller est sûr parce que c’est une action de l’utilisateur est comment les accidents commencent.

Validez les données collées avant de les utiliserLes données prises depuis le presse-papiers passent par présence du format, type de charge utile, limite de taille et validation de contenu dans cet ordre ; échouer l'un d'eux et vous refusez ou tombez vers le format candidat suivantmauvais typetrop grandinvalideFormat présent ?Type de charge utile OK ?Taille dans la limite ?Valider le contenuImporterRefuser / format suivant

5. Pratiques côté copie — offrir plusieurs formats à la fois, et le rendu différé

5.1. Placez plusieurs formats à la fois

La pratique du côté copie est l’inverse de 4.1 : offrez un format riche et un format brut en même temps. Avec le DataObject de WinForms/WPF vous pouvez l’écrire en quelques lignes.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

Deux notes. Première, la classe Clipboard de .NET ne peut être utilisée que depuis un thread STA.15 Le thread d’interface WinForms/WPF est STA à cause de [STAThread], donc ce n’est normalement pas un problème, mais le toucher depuis un thread d’arrière-plan échoue (les fondamentaux STA/MTA sont dans « Les fondamentaux STA/MTA de COM »). Deuxième, ce que copy: true signifie est lié au rendu différé de la sous-section suivante.

5.2. Rendu différé — pourquoi « fermer la source et vous ne pouvez plus coller »

Construire une grande charge utile dans de nombreux formats à chaque fois est gaspilleur, donc le presse-papiers a un mécanisme appelé rendu différé. Passez NULL comme handle de données à SetClipboardData et, au lieu de la charge utile, seule une promesse de « la produire lorsqu’on le demandera » est enregistrée ; lorsqu’un quelqu’un demande ce format, WM_RENDERFORMAT arrive à la source de copie, et seulement alors les données sont générées.2

La conséquence de cette conception est le « j’ai fermé l’application source et je ne pouvais plus coller » d’ouverture. Avant de se terminer, la source de copie reçoit WM_RENDERALLFORMATS et est responsable de matérialiser chaque format qui n’a pas encore été rendu ; se terminer sans le faire et le format est perdu.2

Le rendu différé et pourquoi fermer puis coller échoueLa source de copie n'enregistre qu'une promesse avec un handle NULL, et matérialise à la demande via WM_RENDERFORMAT. À la sortie elle est responsable de matérialiser chaque format avec WM_RENDERALLFORMATS ; sauter cela et le format est perduRENDERALLFORMATSSauter la matérialisationSetClipboardData NULL = promesseLe côté collage le demandeWM_RENDERFORMAT → construire maintenantLa source de copie sur le point de sortirLe collage fonctionne après la sortieFormat perdu après fermeture

Sur le presse-papiers OLE (le style qui place un IDataObject avec OleSetClipboard), cette relation est encore plus claire. Tout ce que le presse-papiers détient est un pointeur vers l’objet de données, et appeler OleFlushClipboard à la sortie de l’application matérialise les données sur le presse-papiers, donc le collage fonctionne encore après la sortie.6 Le Clipboard.SetDataObject(data, copy: true) de .NET est ce qui spécifie ce comportement « le garder après la sortie ».

Lorsque vous copiez une grande plage dans Excel et essayez de quitter, l’invite « Il y a une grande quantité d’informations dans le Presse-papiers. Voulez-vous pouvoir coller ces informations dans un autre programme plus tard ? » est exactement la confirmation de s’il faut exécuter cette matérialisation (le flush). Si vous utilisez le rendu différé dans votre propre application, souvenez-vous que la matérialisation à la sortie fait partie du même ensemble. Le rendu différé est une optimisation de performance, et parce que la demande de rendu s’exécute de façon synchrone à l’intérieur du traitement de messages, des données qui prennent longtemps à générer ont le compromis de geler l’interface.2

6. Pratiques pour surveiller le presse-papiers — écouteur, nouvelle tentative et exclusion de l’historique

6.1. Utilisez AddClipboardFormatListener

Des exigences telles que « nous voulons détecter une valeur de lecteur de codes-barres ou une copie depuis le système métier et l’importer automatiquement » ont besoin que vous surveilliez les changements du presse-papiers. Historiquement il y a trois méthodes ; aujourd’hui la bonne réponse en est une.8

Méthode Évaluation
Lire sur un minuteur (interrogation) Gaspilleur, et vous pouvez manquer des mises à jour. Ne pas utiliser
SetClipboardViewer (chaîne de visionneuses) Un bug dans une application de la chaîne casse toute la chaîne. Conservé seulement pour la compatibilité ascendante
AddClipboardFormatListener Recommandé. WM_CLIPBOARDUPDATE arrive à la fenêtre enregistrée
Flux de surveillance du presse-papiersEnregistrez avec AddClipboardFormatListener lorsque le handle est créé, et WM_CLIPBOARDUPDATE arrive peu importe quelle application a copié. Lisez avec une nouvelle tentative, et désenregistrez de façon symétrique avec RemoveClipboardFormatListener lorsque le handle est détruitdésenregistrerAddClipboardFormatListenerAttendreUne application copieWM_CLIPBOARDUPDATELire avec nouvelle tentative (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. Nouvelle tentative lorsque vous ne pouvez pas l’ouvrir

Une seule fenêtre à la fois peut ouvrir le presse-papiers ; pendant qu’un autre processus l’a ouvert, OpenClipboard échoue.2 Juste après WM_CLIPBOARDUPDATE, la source de copie ou un autre surveillant est souvent encore en train d’opérer, donc un échec de lecture temporaire est un événement normal. Mettez toujours quelques nouvelles tentatives avec une courte attente (dizaines de millisecondes) entre elles. Notez que les surcharges Clipboard de .NET qui vous laissent spécifier un nombre de tentatives et un intervalle existent seulement du côté écriture, SetDataObject. Il n’y a pas d’équivalent du côté lecture (GetDataObject et analogues), donc vous écrivez le catch-attendre-réessayer vous-même — ExternalException sur WinForms, COMException sur WPF.

Flux de nouvelle tentative de lecture du presse-papiersUne seule fenêtre à la fois peut ouvrir le presse-papiers, donc une lecture juste après la notification de changement peut échouer en courant avec un autre processus. Sur une exception, attendez des dizaines de millisecondes et réessayez ; si vous atteignez la limite, abandonnez cette fois et reprenez à la mise à jour suivantesuccèsen cours d'utilisationréessayerlimiteWM_CLIPBOARDUPDATETenter une lectureImporter (contrôles du ch. 4)Attendre des dizaines de msAbandonner cette fois

6.3. Le garder hors de l’historique et de la synchronisation — soin pour les fonctionnalités de copie qui traitent des secrets

Windows a l’historique du presse-papiers (Win+V) et la synchronisation inter-appareils (le presse-papiers cloud), et les données qu’une application place sont dans le périmètre des deux par défaut. Une application qui met des secrets tels que des mots de passe ou des numéros de compte sur une fonctionnalité de copie place aussi un format enregistré qui exclut le contenu de l’historique et de la synchronisation.1

  • ExcludeClipboardContentFromMonitorProcessing : Placez ceci et le contenu de cette copie n’est inclus ni dans l’historique ni dans la synchronisation.
  • CanIncludeInClipboardHistory (DWORD 0) : Supprimer l’historique seulement.
  • CanUploadToCloudClipboard (DWORD 0) : Supprimer la synchronisation inter-appareils seulement.

La raison pour laquelle un mot de passe copié par un gestionnaire de mots de passe ne reste pas sur Win+V est ce mécanisme. Vous obtenez un ID de format en passant le nom à RegisterClipboardFormat et le définissez aux côtés des données ordinaires, donc cela vaut d’être implémenté dans toute application métier qui traite des secrets.

7. Le presse-papiers du point de vue informatique — historique, synchronisation cloud et contrôles RDP

En s’écartant un peu du développement, voici les points qui importent à un administrateur. L’historique du presse-papiers accumule les copies récentes, et le presse-papiers cloud synchronise les copies entre appareils connectés avec le même compte Microsoft / compte Microsoft Entra.10 Aussi commode que cela soit, cela produit aussi des résidus et des débordements : des informations personnelles copiées depuis un système métier s’accumulent dans l’historique, et le contenu copié sur un PC professionnel se synchronise vers un PC personnel.

Les deux stratégies que vous utilisez pour contrôler cela dans une organisation sont les suivantes.

Ce que vous contrôlez GPO (Configuration ordinateur > Modèles d’administration > Système > Stratégies de l’OS) Policy CSP (Intune) Défaut
Historique du presse-papiers Allow Clipboard History Experience/AllowClipboardHistory Autorisé
Synchronisation inter-appareils Allow Clipboard synchronization across devices Privacy/AllowCrossDeviceClipboard Autorisé

Les deux sont disponibles à partir de Windows 10 version 1809 ; désactivez-les et les éléments correspondants dans l’application Paramètres sont grisés, et la stratégie prend effet immédiatement.910

L’autre classique est la redirection du presse-papiers RDP (Bureau à distance). Par défaut, le copier-coller fonctionne entre le PC local et la session distante, donc cela peut devenir un chemin pour emporter des secrets hors d’un serveur. La stratégie « Do not allow clipboard redirection » (valeur de registre fDisableClip) peut bloquer les deux directions.11 Les versions récentes de Windows Server / Windows 11 ont aussi ajouté des stratégies plus fines, telles que restreindre la direction serveur-vers-client au texte seulement. Que vous l’interdisiez carrément ou le restreignez par étapes est un équilibre d’exploitation et de sécurité.

Chemins le long desquels le contenu du presse-papiers peut se répandre, et les points de contrôleLe contenu copié est dans le périmètre de l'historique et de la synchronisation cloud par défaut, et sur RDP il voyage vers une autre session via la redirection. Chaque chemin peut être contrôlé par stratégie, et le côté application peut s'exclure de l'historique et de la synchronisation avec les formats d'exclusionPresse-papiersHistorique (Win+V)Synchronisation cloudRedirection RDPAllowClipboardHistoryAllowCrossDeviceClipboardfDisableClipFormats d'exclusion de l'application (6.3)

8. Le glisser-déposer est COM — IDataObject + IDropSource + IDropTarget

8.1. Les mêmes données que le presse-papiers, une façon différente de les porter

Le glisser-déposer OLE s’exécute avec les trois rôles suivants.12

Rôle Qui l’implémente Travail
IDataObject Source de glissement La charge utile transportée. Le même objet de données multi-formats que le presse-papiers
IDropSource Source de glissement Décider si le glissement continue ou est annulé, et le retour visuel du curseur
IDropTarget Cible de dépôt Déclarer accepter/refuser dans DragEnter/DragOver/DragLeave/Drop, et recevoir le dépôt

La source de glissement appelle DoDragDrop, la boucle de glissement démarre, et lorsque la souris entre dans une fenêtre cible de dépôt cet IDropTarget est notifié ; au dépôt, l’IDataObject est transmis. La documentation officielle dit aussi que « le D&D fournit exactement la même fonctionnalité que le copier-coller du presse-papiers. Si une application implémente déjà le copier-coller, l’ajout est petit ».12 Autrement dit, le DataObject multi-formats que vous avez construit dans les chapitres 2 à 5 devient la charge utile D&D telle quelle.

Flux du glisser-déposer OLELa source de glissement met un IDataObject dans la charge utile et appelle DoDragDrop pour démarrer la boucle de glissement ; l'IDropTarget de la cible de dépôt déclare accepter/refuser dans DragEnter et DragOver, et au Drop choisit un format depuis l'IDataObject et l'extraitDoDragDropla souris entrebouton relâchéIDataObject + IDropSourceBoucle de glissementDragEnter/Over : EffectIDropTarget.DropChoisir un format et extraire

8.2. OleInitialize (STA) est requis

Une fenêtre qui sera une cible de dépôt s’enregistre avec RegisterDragDrop, et il y a un piège classique ici. Si vous avez initialisé COM avec CoInitialize/CoInitializeEx, RegisterDragDrop échoue toujours avec E_OUTOFMEMORY ; vous devez initialiser avec OleInitialize.13 OleInitialize initialise COM comme STA, parce que le D&D est une fonctionnalité enracinée dans le monde STA des fenêtres et d’une pompe de messages. Le thread appelant doit aussi exécuter une pompe de messages ; sautez cela et d’autres applications se bloquent pendant le glissement.13 Le contexte ici est exactement la discussion des modèles de threads dans « Les fondamentaux STA/MTA de COM ».

Dans une application WinForms/WPF le framework s’occupe de l’initialisation OLE et des implémentations d’interface, donc le développeur n’a qu’à écrire les événements.

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

La forme est la même en WPF : vous recevez avec AllowDrop="True" et les événements DragOver/Drop sur l’élément, et vous extrayez le tableau de chemins avec e.Data.GetData(DataFormats.FileDrop). Déclarer accepter/refuser (Effect) à chaque DragEnter/DragOver est la convention IDropTarget ; sautez-la et vous obtenez le bug où le curseur reste sur « non autorisé » et ne change jamais.

9. Pièges du D&D — élévation, Move et validation de chemin

9.1. Vous ne pouvez pas déposer sur une application élevée en tant qu’administrateur

Déposez un fichier depuis l’Explorateur de fichiers sur une application lancée avec « Exécuter en tant qu’administrateur » et rien ne se passe — ce n’est pas un bug d’implémentation, c’est le comportement de l’OS. UIPI (User Interface Privilege Isolation) bloque par défaut les messages depuis un processus de plus basse intégrité vers une fenêtre de plus haute intégrité, donc les notifications de dépôt depuis l’Explorateur de fichiers à privilège ordinaire (intégrité moyenne) n’atteignent jamais une application élevée.14

Comment UIPI bloque les dépôts sur une application élevéeLes notifications de dépôt depuis l'Explorateur à intégrité moyenne vers une application élevée à haute intégrité sont bloquées par UIPI par défaut et n'arrivent jamais. Gardez l'interface à privilège ordinaire et isolez le travail privilégié, et le dépôt arrivenotification de dépôtbloquépassedéléguer le travail privilégiéExplorateur (moyenne)UIPIApplication élevée : pas de dépôtInterface ordinaire : le dépôt arriveProcessus élevé isolé

Une solution de contournement qui autorise individuellement des messages spécifiques tels que WM_DROPFILES avec ChangeWindowMessageFilterEx est bien connue,14 mais ce qu’elle laisse passer est l’ancienne notification de dépôt (WM_DROPFILES) ; elle ne résout pas le D&D OLE dans son ensemble. Le guidage pratique est clair : arrêtez de concevoir l’application pour qu’elle s’exécute élevée en permanence. Isolez seulement le travail qui a besoin d’élévation dans un processus séparé, et l’interface elle-même peut rester à privilège ordinaire et recevoir le D&D (la conception d’isolation est couverte en détail dans « Comment isoler concrètement, dans une application Windows, « uniquement les opérations nécessitant des privilèges administrateur » »).

9.2. Ce que DragDropEffects signifie — Move est un contrat que « l’original disparaît »

Copy/Move/Link sur DragDropEffects ne sont pas de la décoration ; ils sont un contrat entre la source de glissement et la cible de dépôt. La source de glissement déclare l’ensemble des effets qu’elle autorise dans DoDragDrop, la cible de dépôt choisit l’effet réel, et lorsque Move réussit, la source de glissement supprime les données (le fichier) — c’est la convention. Si le côté réception renvoie Move sans y penser, vous obtenez l’accident « je l’ai déposé et le fichier original a disparu ». Pour un usage d’import d’une application métier, le côté réception déclarant Copy est le défaut sûr.

Le contrat DragDropEffects — Move supprime l'originalLa source de glissement déclare l'ensemble des effets autorisés dans DoDragDrop, et la cible de dépôt choisit l'effet réel. Lorsque Move réussit la source de glissement supprime le fichier, donc pour l'import le côté réception doit déclarer CopyCopyMoveSource : effets autorisésCible : choisir EffectL'original reste (import)La source supprime le fichier

9.3. Valider un chemin déposé

Ce qui voyage dans CF_HDROP/FileDrop n’est que le chemin (section 3.2). Avant d’importer, passez-le par la même validation d’entrée non fiable que le collage.

  • Fichier ou dossier : Décidez comme spécification ce qui se passe lorsqu’un dossier entier est déposé (récursif et importer, ou refuser).
  • Fichiers fictifs OneDrive : Le chemin peut exister alors que le corps du fichier n’est pas local — un fichier à la demande. Le moment où vous l’ouvrez un téléchargement démarre, et hors ligne cela échoue. Le comportement et les contre-mesures sont dans « OneDrive « Fichiers à la demande » et applications métier ».
  • Chemins longs et chemins inhabituels : Les chemins au-delà de MAX_PATH, les chemins réseau (UNC) et les chemins sur supports amovibles ne doivent être acceptés qu’après avoir confirmé que le traitement en aval peut les gérer.
  • Nombre et taille totale : Afin que déposer des milliers de fichiers ne gèle pas l’interface, rendez l’import asynchrone et mettez une limite et un affichage de progression.

10. Résumé

  • Le presse-papiers est un mécanisme qui place le même contenu dans plusieurs formats à la fois dans une zone unique partagée à l’intérieur du même bureau (station de fenêtres). Le côté collage choisit le format, donc la même copie produit des résultats différents.
  • Le texte est CF_UNICODETEXT, les fichiers sont CF_HDROP, et le texte mis en forme est le format enregistré HTML Format (un en-tête de décalages d’octets + UTF-8).
  • Le côté collage regarde du riche vers le brut et traite la charge utile comme une entrée externe. Le côté copie offre plusieurs formats à la fois, et s’il utilise le rendu différé il implémente aussi la matérialisation à la sortie (WM_RENDERALLFORMATS / OleFlushClipboard).
  • La surveillance est AddClipboardFormatListener + WM_CLIPBOARDUPDATE. Préparez-vous aux courses OpenClipboard avec une nouvelle tentative, et gardez les secrets hors de l’historique et de la synchronisation avec ExcludeClipboardContentFromMonitorProcessing et analogues.
  • L’informatique peut contrôler l’historique du presse-papiers, la synchronisation cloud et la redirection RDP avec GPO / Intune. Le défaut est autorisé pour tous, donc décidez délibérément dans les environnements qui traitent des secrets.
  • Le D&D est COM : IDropSource/IDropTarget transmettent le même IDataObject que le presse-papiers. RegisterDragDrop exige OleInitialize (STA).
  • Les dépôts sur une application élevée sont bloqués par UIPI. Move sur DragDropEffects est un contrat que « l’original disparaît » ; validez un chemin déposé avant de l’importer.

Le copier-coller et le D&D sont, pour l’utilisateur, des fonctionnalités qui devraient sembler comme de l’air. C’est exactement pourquoi « je ne peux pas coller », « ça se défait » et « ça a disparu » blessent tant l’expérience — et pourquoi une application qui offre plusieurs formats et gère correctement les dépôts rend les opérations quotidiennes plus fluides à elle seule. J’espère que ceci est un matériau utile lorsque vous décidez quoi corriger en premier.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge la conception et l’implémentation du copier-coller et du glisser-déposer dans les applications métier (offrir plusieurs formats, interopérabilité Excel, importer des fichiers déposés), l’investigation des causes profondes de problèmes tels que « ça se défait lorsque je colle » ou « la copie disparaît », l’automatisation de saisie qui surveille le presse-papiers, et les implémentations qui gardent les données confidentielles hors de l’historique et de la synchronisation. Les cas qui impliquent les couches inférieures de COM et OLE sont les bienvenus même si vous commencez par isoler le symptôme.

Références

  1. Microsoft Learn, Clipboard Formats. Sur le fait qu’une fenêtre peut placer la même information dans plusieurs formats de presse-papiers ; les formats enregistrés via RegisterClipboardFormat (enregistrer le même nom renvoie la même valeur, donc les applications peuvent le partager) ; les formats synthétisés ; et exclure le contenu de l’historique du presse-papiers / de la synchronisation cloud avec ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory et CanUploadToCloudClipboard.  2 3 4 5

  2. Microsoft Learn, Clipboard Operations. Sur le fait qu’une seule fenêtre à la fois peut ouvrir le presse-papiers ; placer les formats du plus expressif au moins expressif au moment de la copie ; la sélection de format au moment du collage avec EnumClipboardFormats / GetPriorityClipboardFormat ; le rendu différé en passant NULL à SetClipboardData et les responsabilités WM_RENDERFORMAT / WM_RENDERALLFORMATS ; et les compromis du rendu différé.  2 3 4 5 6 7 8

  3. Microsoft Learn, Standard Clipboard Formats. Sur les définitions des formats standard CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB et CF_LOCALE, et sur le système qui convertit implicitement CF_TEXT et CF_UNICODETEXT en utilisant la page de codes associée à CF_LOCALE.  2 3

  4. Microsoft Learn, Shell Clipboard Formats. Sur le fait que CF_HDROP est composé d’une structure DROPFILES plus un tableau de chaînes de chemins complets terminé par un double NUL ; récupérer les chemins individuels avec DragQueryFile ; et les formats shell CFSTR_ exigeant un enregistrement via RegisterClipboardFormat.  2

  5. Microsoft Learn, HTML Clipboard Format. Sur le fait que le nom enregistré est « HTML Format » ; la structure d’en-tête avec des décalages d’octets tels que Version, StartHTML, EndHTML, StartFragment et EndFragment ; l’encodage étant toujours UTF-8 ; et la convention de commentaires StartFragment/EndFragment.  2 3

  6. Microsoft Learn, OleFlushClipboard function (ole2.h). Sur le fait qu’OleSetClipboard fait que le presse-papiers ne détient qu’un pointeur vers l’objet de données ; OleFlushClipboard matérialisant les données sur le presse-papiers afin que le collage fonctionne encore après que l’application se termine ; et vider le presse-papiers avec OleSetClipboard(NULL) lorsque vous n’avez pas besoin de le garder à la sortie.  2

  7. Microsoft Learn, OleGetClipboard function (ole2.h). Sur la façon d’obtenir un IDataObject depuis le presse-papiers, et l’avertissement que les données du presse-papiers ne sont pas fiables et doivent être analysées soigneusement avant que l’application ne les utilise.  2

  8. Microsoft Learn, Using the clipboard. Sur la comparaison des trois façons de surveiller le presse-papiers (fenêtres visionneuses, numéros de séquence et écouteurs de format) ; les nouveaux programmes étant censés utiliser un écouteur via AddClipboardFormatListener ; la chaîne de visionneuses étant fragile lorsque la maintenance de la chaîne est incomplète ; et les numéros de séquence n’étant pas quelque chose que vous devez interroger.  2

  9. Microsoft Learn, Policy CSP - Experience. Sur le fait d’autoriser ou de refuser l’historique du presse-papiers avec la stratégie Experience/AllowClipboardHistory ; la disponibilité à partir de Windows 10 version 1809 ; le défaut étant autorisé ; et le mappage GPO sous « System > OS Policies » avec des changements prenant effet immédiatement.  2

  10. Microsoft Learn, Policy CSP - Privacy. Sur le fait d’autoriser ou de refuser la synchronisation inter-appareils du presse-papiers avec la stratégie Privacy/AllowCrossDeviceClipboard ; la synchronisation se produisant entre appareils connectés avec le même compte Microsoft / compte Microsoft Entra ; et le défaut étant autorisé.  2 3

  11. Microsoft Learn, Policy CSP - ADMX_TerminalServer. Sur le fait que TS_CLIENT_CLIPBOARD (« Do not allow clipboard redirection », valeur de registre fDisableClip) peut interdire le partage du presse-papiers entre local et distant dans une session Bureau à distance, et sur le fait que la redirection est autorisée par défaut.  2

  12. Microsoft Learn, Drag and Drop (COM). Sur le fait que le glisser-déposer OLE s’exécute avec les trois de IDropSource (source de glissement), IDropTarget (cible de dépôt) et DoDragDrop (la boucle que OLE fournit) ; fournissant la même fonctionnalité que le copier-coller du presse-papiers, de sorte qu’une application qui implémente déjà le copier-coller n’a besoin que d’un petit ajout ; et les types de retour visuel.  2 3

  13. Microsoft Learn, RegisterDragDrop function (ole2.h). Sur l’enregistrement d’une fenêtre cible de dépôt avec un IDropTarget ; échouer toujours avec E_OUTOFMEMORY si COM a été initialisé avec CoInitialize/CoInitializeEx, de sorte qu’OleInitialize est requis ; et l’application source de glissement se bloquant si le thread appelant n’exécute pas une pompe de messages.  2 3

  14. Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). Sur le fait qu’UIPI est un mécanisme de sécurité qui par défaut bloque la réception de messages depuis un émetteur de plus basse intégrité, et sur l’autorisation de messages spécifiques par fenêtre avec un filtre de messages (MSGFLT_ALLOW).  2 3

  15. Microsoft Learn, How to add data to the Clipboard (Windows Forms). Sur le fait de placer des données dans plusieurs formats à la fois avec DataObject et Clipboard.SetDataObject ; ajouter dans plusieurs formats afin que d’autres applications puissent le reconnaître ; et la classe Clipboard n’étant utilisable que depuis un thread STA, de sorte que [STAThread] est requis.  2

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Pourquoi la mise en forme d'un tableau copié depuis Excel se défait-elle lorsque je le colle dans mon application ?
Le presse-papiers ne détient pas « un seul morceau de données ». Le même contenu est placé dans plusieurs formats à la fois (le format privé de l'application source, HTML Format, CSV, texte Unicode, etc.), et l'application de destination choisit un format qu'elle comprend et l'extrait. Lorsque la mise en forme se défait, la cause typique est que la destination ne lit que le texte brut (CF_UNICODETEXT). Si vous voulez aussi la structure de tableau, implémentez le côté collage de sorte qu'il préfère HTML Format ou CSV. Inversement, si vous voulez que d'autres applications collent correctement depuis une copie faite dans votre propre application, offrez à la fois un format riche et un format brut au moment de la copie.
Pourquoi ne puis-je plus coller après avoir fermé l'application depuis laquelle j'ai copié ?
Parce que la source utilise le rendu différé. Les applications qui traitent de grandes données ne placent pas la charge utile au moment de la copie ; elles n'enregistrent sur le presse-papiers qu'une promesse qu'elles « la produiront lorsqu'on le demandera ». Si la source se termine ensuite sans matérialiser les données en réponse à WM_RENDERALLFORMATS à l'arrêt, tout format qui n'a pas encore été rendu est perdu. Une application qui utilise le presse-papiers OLE (IDataObject) peut garder le collage fonctionnel après la sortie en appelant OleFlushClipboard à l'arrêt pour matérialiser les données.
Comment mon application peut-elle surveiller les changements du presse-papiers ?
La méthode actuellement recommandée est d'enregistrer votre fenêtre comme écouteur avec AddClipboardFormatListener et de gérer le message WM_CLIPBOARDUPDATE qui arrive chaque fois que le contenu change. Interroger le contenu sur un minuteur gaspille du travail et peut manquer des mises à jour, et l'ancienne chaîne de visionneuses basée sur SetClipboardViewer n'est conservée que pour la compatibilité ascendante, parce qu'un bug dans une application de la chaîne casse toute la chaîne. Notez aussi que OpenClipboard à la lecture peut échouer parce qu'un autre processus détient le presse-papiers, donc implémentez une nouvelle tentative avec une courte attente si vous voulez que la lecture soit stable.
Y a-t-il un moyen de garder des secrets tels que des mots de passe hors de l'historique du presse-papiers (Win+V) ?
Il y a deux leviers, un côté application et un côté stratégie. Côté application, si vous placez aussi le format enregistré ExcludeClipboardContentFromMonitorProcessing lorsque vous copiez, ce contenu n'est inclus ni dans l'historique ni dans la synchronisation inter-appareils. Vous pouvez aussi contrôler chacun indépendamment avec CanIncludeInClipboardHistory (historique seulement) et CanUploadToCloudClipboard (synchronisation seulement). C'est le mécanisme qu'utilisent les gestionnaires de mots de passe. Si vous voulez le désactiver pour toute l'organisation, vous pouvez désactiver l'historique et la synchronisation cloud eux-mêmes avec AllowClipboardHistory et AllowCrossDeviceClipboard via la Stratégie de groupe ou Intune (Policy CSP).
Pourquoi ne puis-je pas glisser-déposer un fichier sur une application qui s'exécute en tant qu'administrateur ?
Parce qu'un mécanisme de sécurité appelé UIPI (User Interface Privilege Isolation) bloque la livraison de messages depuis un processus de plus basse intégrité vers une fenêtre de plus haute intégrité. L'Explorateur de fichiers s'exécute à privilège ordinaire (intégrité moyenne), donc les notifications de glisser-déposer n'atteignent jamais la fenêtre d'une application élevée. Une solution de contournement qui autorise individuellement des messages tels que WM_DROPFILES avec ChangeWindowMessageFilterEx est bien connue, mais elle ne s'applique qu'à l'ancienne notification de dépôt. Le vrai correctif est d'arrêter de concevoir l'application pour qu'elle s'exécute élevée en permanence, et d'isoler seulement le travail qui a besoin d'élévation dans un processus séparé.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog