История изменений (первая версия, опубликована 21 Aug 2026)
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.22176613)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Го Комура (2026). Буфер обмена и перетаскивание: OLE-передача данных в бизнес-приложениях. KomuraSoft LLC. https://comcomponent.com/ru/blog/windows-clipboard-drag-drop-ole-data-transfer/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.22176613
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.22176614
«Когда вставляем таблицу из Excel, оформление разваливается.» «То, что копируем в своём приложении, в Word выходит не так, как задумано.» «Хотим принимать файлы перетаскиванием.» — При доработке бизнес-приложений такие запросы встречаются постоянно.
Всё это повседневные операции, но если считать буфер обмена «ящиком, куда кладут один объект данных», устройство будет прочитано неверно. На деле это механизм, который кладёт одно и то же содержимое сразу в нескольких форматах, а сторона вставки выбирает формат, который понимает. Поэтому одна и та же копия даёт разный результат в зависимости от того, куда вставляют.
Проблема «закрыл источник — и вставить уже нельзя» связана с «отложенным рендерингом», который строит данные только когда они нужны. А OLE-перетаскивание (D&D) передаёт IDataObject — то же представление данных, что и у OLE-буфера обмена, — через интерфейсы COM. Копирование с вставкой и D&D — родственные функции: формат данных общий, отличается способ доставки.
Статья рассчитана на сотрудников ИТ малых и средних компаний и разработчиков Windows-приложений. Сначала разбираем устройство форматов, затем реализацию стороны вставки, стороны копирования и мониторинга, затем управление журналом, синхронизацией и RDP и оговорки OLE D&D.
1. Сначала выводы
Держите в уме три оси.
- Сторона копирования предлагает несколько форматов, сторона вставки выбирает среди них. Для текста база —
CF_UNICODETEXT, для списка путей файлов —CF_HDROP, для форматированного текста — зарегистрированный формат «HTML Format».1234 - Проектируйте не только формат данных, но и проверку и срок жизни. Данные при вставке проверяйте как недоверенный ввод извне. Сторона копирования, которая использует отложенный рендеринг, реализует и фиксацию при выходе. Мониторьте через
AddClipboardFormatListenerиWM_CLIPBOARDUPDATE, а конкуренцию при чтении закрывайте повторами.5678 - Ясно обозначьте, как далеко уходят данные и что происходит после передачи. Журнал, облачная синхронизация и перенаправление RDP — это то, чем управляют. Для OLE D&D нужна инициализация STA через
OleInitialize, а сброс с обычных прав на повышенное приложение блокирует UIPI. Кроме того,Move— договор, по которому исходные данные исчезают.910111213
Если читать по цели, начинайте с главы ниже.
| Проблема или цель | Что проверять сначала | Глава |
|---|---|---|
| Вставленная таблица Excel разваливается | Форматы, которые предлагает сторона копирования, и формат, который выбирает сторона вставки | Главы 2–4 |
| Сделать копию из своего приложения пригодной для других | Предложение нескольких форматов и сохранение данных после выхода | Глава 5 |
| Обнаружить копирование и импортировать автоматически | Регистрация слушателя и повторы чтения | Глава 6 |
| Не пускать секреты в журнал, синхронизацию и RDP | Форматы исключения на стороне приложения и организационная политика | Раздел 6.3, глава 7 |
| Реализовать D&D; не работает только при повышении | Инициализация OLE, граница прав, эффекты и проверка путей | Главы 8–9 |
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 20, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Что на деле представляет буфер обмена — не «один объект данных», а «одно и то же содержимое в нескольких форматах»
Буфер обмена — механизм обмена данными между приложениями. Им могут пользоваться приложения на одном рабочем столе, но точная единица совместного использования — станция окон. У другого сеанса пользователя или сеанса RDP свой отдельный буфер. Копирование и вставка через RDP работают потому, что функция перенаправления связывает два буфера (глава 7).
Главный принцип — использование по инициативе пользователя. Официальная позиция проектирования: приложение не кладёт данные и не забирает их без ведома пользователя.1
Опустошив буфер, сторона копирования кладёт одно и то же содержимое в нескольких форматах, от самого выразительного вниз.6 Концептуально копирование таблицы в электронной таблице выстраивается так.
| Приоритет | Формат | Содержимое |
|---|---|---|
| 1 | Собственный формат приложения | Полное внутреннее представление, включая формулы и оформление (для вставки обратно в то же приложение) |
| 2 | HTML Format | Фрагмент HTML, который сохраняет структуру таблицы и оформление |
| 3 | CSV | Текст, разделённый по ячейкам |
| 4 | CF_UNICODETEXT | Простой текст, разделённый табуляцией |
| 5 | Формат изображения | Растровое изображение того, как выглядит таблица |
Сторона вставки забирает формат, который понимает, из этого списка. Word получает оформленную таблицу, а Блокнот — текст с табуляцией, потому что они выбрали разные форматы.
Иначе говоря, «результат зависит от цели вставки» само по себе не дефект. Нужно смотреть на предложение стороны копирования и выбор стороны вставки вместе.
flowchart TB
accTitle: Как одна и та же копия даёт разный результат в зависимости от места вставки
accDescr: Сторона копирования кладёт одно и то же содержимое в буфер обмена в нескольких форматах, а сторона вставки выбирает формат, который понимает, поэтому Word получает оформленную таблицу, а Блокнот — текст с табуляцией
copy["Сторона копирования: электронная таблица"] --> cb["Буфер обмена (одно содержимое в нескольких форматах)"]
cb --> f1["Собственный формат приложения"]
cb --> f2["HTML Format"]
cb --> f3["CSV"]
cb --> f4["CF_UNICODETEXT"]
f2 -->|"Word выбирает это"| word["Оформленная таблица"]
f4 -->|"Блокнот выбирает это"| notepad["Текст с табуляцией"]
С другой стороны, жалобы из начала — «оформление разваливается», «вставляется что-то странное» — почти все сводятся к тому, как одна из двух сторон выбирает или предлагает форматы. Глава 4 — сторона вставки, глава 5 — сторона копирования.
3. Стандартные и зарегистрированные форматы — CF_UNICODETEXT, CF_HDROP, HTML Format
3.1. Стандартные форматы — для текста используйте сторону Unicode
Форматы, которые ОС определяет заранее, называют стандартными. Именно они чаще всего встречаются в бизнес-приложениях.2
| Формат | Значение | Содержимое |
|---|---|---|
| CF_TEXT | 1 | Текст ANSI (зависит от кодовой страницы) |
| CF_UNICODETEXT | 13 | Текст Unicode. Это канонический формат для текста |
| CF_HDROP | 15 | Список путей файлов (дескриптор HDROP) |
| CF_DIB | 8 | Независимый от устройства растр |
| CF_LOCALE | 16 | Идентификатор локали, связанный с текстом |
CF_TEXT и CF_UNICODETEXT — «синтезируемые форматы»: система неявно преобразует одно в другое. Преобразование использует кодовую страницу, связанную с CF_LOCALE.2
Однако символы, которые ANSI не может представить, при преобразовании теряются. Если вы обрабатываете символы только Unicode, комбинирующие знаки и подобное, оставлять это преобразованию — путь к порче текста. Правило: чтение и запись приложения стандартизуйте на CF_UNICODETEXT или, в .NET, на DataFormats.UnicodeText.
flowchart LR
accTitle: Неявное преобразование между CF_UNICODETEXT и CF_TEXT
accDescr: Приложение читает и пишет только CF_UNICODETEXT; система синтезирует CF_TEXT неявным преобразованием по кодовой странице CF_LOCALE. Символы, которые ANSI не может представить, в этом преобразовании отбрасываются
apprw["Приложение читает и пишет"] --> uni["CF_UNICODETEXT (канонический)"]
uni <-->|"Система преобразует неявно (кодовая страница CF_LOCALE)"| ansi["CF_TEXT (ANSI, зависит от кодовой страницы)"]
ansi -.-> loss["Непредставимые символы отбрасываются при преобразовании (рассадник порчи текста)"]
3.2. CF_HDROP — файлы передаются как «список путей»
CF_HDROP — то, что Проводник использует для копирования файлов и D&D. Первое, что нужно запомнить: передаётся список полных путей, а не сами файлы.
В начале блока памяти стоит структура DROPFILES, затем строки путей, разделённые символами NUL. В конце кладётся пустая строка, поэтому блок заканчивается «двойным NUL». В заголовке pFiles — смещение начала списка путей, fWide указывает, Unicode ли строки.3
[Заголовок DROPFILES: pFiles=смещение начала списка путей, fWide=1 (Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)
В нативном коде их забирают по одному через DragQueryFile; в .NET получают как string[] через DataFormats.FileDrop. Точка «передаются только пути, а не сами файлы» снова важна для D&D в главах 8 и 9.
flowchart TB
accTitle: Структура блока памяти CF_HDROP
accDescr: В начале глобальной памяти стоит структура DROPFILES; pFiles даёт смещение начала списка путей, fWide указывает, Unicode ли это. Дальше идут полные пути, разделённые NUL, и завершающая пустая строка как двойной NUL. Передаются только пути, а не сами файлы
hdr["Структура DROPFILES (pFiles = смещение начала списка / fWide = 1)"] --> p1["C:\\data\\a.txt + NUL"]
p1 --> p2["C:\\data\\b.txt + NUL"]
p2 --> tail["Завершающая пустая строка (двойной NUL)"]
hdr -.-> note["Передаются только пути, а не сами файлы"]
3.3. Зарегистрированные форматы — RegisterClipboardFormat и «HTML Format»
Данные, которые стандартные форматы не выражают, можно разделять как зарегистрированный формат под выбранным именем. Передайте имя в RegisterClipboardFormat и получите идентификатор формата. Регистрация того же имени из другого приложения даёт тот же идентификатор, поэтому согласование имени между приложениями и есть точка соприкосновения.1
Когда передаёте структурированные данные внутри своего набора приложений, используйте имя, которое не столкнётся, например KomuraSoft.Report.RowData.
HTML Format — это «UTF-8 с заголовком»
Представительный зарегистрированный формат — «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 — на начало и конец фрагмента, который выделил пользователь. Единица — байты, не символы.
При генерации собирайте в таком порядке.
- Зарезервируйте поля смещений фиксированной ширины (например, 10 цифр).
- Соберите тело HTML и закодируйте его как UTF-8.
- Измерьте байтовые позиции после кодирования и запишите их обратно в заголовок.
В UTF-8 с японскими символами число знаков и число байтов не совпадают. Ошибитесь — и при вставке в другое приложение обрежется начало или конец.4
flowchart TB
accTitle: Как заголовок HTML Format связан со смещениями
accDescr: В заголовке StartHTML и EndHTML указывают на весь HTML, а StartFragment и EndFragment — на фрагмент, который выделил пользователь, все как байтовые позиции от начала данных. Поскольку число знаков и число байтов в UTF-8 расходятся, заполняйте их байтовыми позициями, измеренными после кодирования
header["Заголовок (Version / StartHTML / EndHTML / StartFragment / EndFragment)"] --> html["Весь HTML (от StartHTML до EndHTML)"]
html --> frag["Выделенный фрагмент (от StartFragment до EndFragment)"]
header -.-> byte["Каждое смещение = байтовая позиция от начала данных (измерена после кодирования UTF-8 и записана обратно)"]
Кроме них, для табличных данных часто используют CSV (DataFormats.CommaSeparatedValue в .NET). Для совместимости с Excel одновременное предложение HTML Format (с оформлением), CSV (только значения) и CF_UNICODETEXT (с табуляцией) работает при любой цели вставки.
4. Практика стороны вставки — приоритет форматов и проверка
4.1. Ищите от богатых форматов вниз
Сторона вставки ищет форматы, которые умеет обработать, начиная с того, в котором больше информации. Можно опираться на порядок, в котором сторона копирования клала форматы, от самого выразительного вниз, либо задать свой порядок приоритета.6
| API Win32 | Как выбирает |
|---|---|
EnumClipboardFormats |
Перечисляет в порядке, в котором клала сторона копирования; берите первый формат, который узнаёте |
GetPriorityClipboardFormat |
Выбирает доступный формат из списка приоритетов, который передаёт сторона вставки |
В .NET ветвление выглядит так.
// Вставка таблицы: искать от богатого к простому
var data = Clipboard.GetDataObject();
if (data is null) return;
// Объявление формата не гарантирует, что полезная нагрузка — строка. Входите в эту
// ветку только когда тип тоже сходится; иначе падайте к следующему кандидату
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 разваливается». Структура таблицы никогда не доходит до приложения, которое читает только простой текст. Насколько далеко вниз по списку форматов вы принимаете — проектное решение стороны вставки.
flowchart TB
accTitle: Ветвление вставки, которое ищет от богатых форматов вниз
accDescr: Если есть HTML Format и полезная нагрузка — строка, импортировать как таблицу; иначе CSV; если и его нет — текст с табуляцией, падая от самого выразительного формата вниз. Если кандидатов нет, отказать
startsel["Начать вставку"] --> h{"Есть HTML Format и полезная нагрузка — строка?"}
h -->|"Да"| useh["Проверить заголовок и импортировать как таблицу"]
h -->|"Нет"| c{"Есть CSV?"}
c -->|"Да"| usec["Импортировать как CSV"]
c -->|"Нет"| t{"Есть UnicodeText?"}
t -->|"Да"| uset["Импортировать как текст с табуляцией"]
t -->|"Нет"| giveup["Отказать"]
4.2. Данные при вставке — это внешний ввод
Это легко упустить, но содержимое буфера обмена — данные внешнего происхождения, и вы не знаете, какое приложение их положило. Сама Microsoft в документации OLE-буфера предупреждает: «данным буфера обмена нельзя доверять; разбирайте их осторожно, прежде чем использовать в приложении».5
Одного присутствия формата недостаточно, чтобы решить, что данные можно импортировать. Проверяйте и тип фактической полезной нагрузки, и следующие пункты.
| Что проверять | На что смотреть |
|---|---|
| Заголовок HTML Format | Не указывают ли смещения за пределы диапазона. Часть приложений выдаёт сломанные заголовки |
| Значения вроде чисел, дат и кодов | Проходят ли ту же проверку, что и ввод на экране |
| Размер данных | Не превышает ли предел приёма, как у изображений в сотни мегабайт или текста в миллионы строк |
Учтите: материализация начинается до того, как можно проверить размер
Для защиты от огромных данных различайте, нагрузку какого этапа вы можете предотвратить. GetData в .NET тоже запускает отложенный рендеринг при вызове, а для текста материализует до управляемой строки. Проверка размера только после получения не предотвращает нагрузку этой материализации.
Если проверить HGLOBAL, который возвращает Win32 GetClipboardData, через GlobalSize, можно защититься от проталкивания огромных данных дальше в преобразование в управляемую строку и разбор. Однако для форматов с отложенным рендерингом сам GetClipboardData запускает рендеринг. Вы не можете помешать источнику копирования материализовать сами данные.
Чтобы UI не застывал, вынесите получение с UI-потока и отказывайтесь от данных сверх предела. Даже тогда Clipboard в .NET требует STA. Используйте выделенный поток, выставленный в STA, а не пул потоков (MTA) Task.Run (раздел 5.1).
Идея «значение, которое пришло извне, проверяют до использования, каким бы ни был путь» — та же, что в статье «Никогда не используйте декодированное значение QR-кода как есть». Предположение, что вставка безопасна, потому что это действие пользователя, как раз и приводит к сбою.
flowchart LR
accTitle: Проверка данных при вставке до использования
accDescr: Данные, взятые из буфера обмена, проходят проверки присутствия формата, типа полезной нагрузки, предела размера и содержимого в этом порядке; если любая проверка не проходит, отказать или перейти к следующему формату-кандидату
present["Проверить, что формат есть (GetDataPresent)"] --> type["Проверить тип полезной нагрузки (is string / string[])"]
type --> size["Проверить предел размера"]
size --> content["Проверить содержимое (заголовок, пути, значения)"]
content --> ok["Импортировать"]
type -.->|"Неверный тип"| rej["Отказать / следующий формат-кандидат"]
size -.->|"Слишком большой"| rej
content -.->|"Недопустимо"| rej
5. Практика стороны копирования — одновременное предложение нескольких форматов и отложенный рендеринг
5.1. Кладём несколько форматов сразу
Практика стороны копирования — зеркало 4.1: предлагайте богатый формат и простой одновременно. С DataObject WinForms/WPF это несколько строк.14
// WinForms (System.Windows.Forms). В WPF та же форма с System.Windows DataObject/Clipboard
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText); // строка HTML Format с заголовком
data.SetData(DataFormats.CommaSeparatedValue, csv); // CSV
data.SetData(DataFormats.UnicodeText, plainText); // простой текст
Clipboard.SetDataObject(data, copy: true); // copy:true = сохранить после выхода приложения
Здесь отдельно смотрим требование к потоку и срок жизни после выхода.
Требование к потоку: класс Clipboard в .NET можно использовать только из потока STA.14 UI-поток WinForms/WPF — STA из-за [STAThread], поэтому обычно это не проблема, но из фонового потока, который не STA, пользоваться нельзя. Фон разобран в «Основах COM STA/MTA».
Срок жизни после выхода: copy: true значит «сохранить после выхода приложения». Вместе со следующим отложенным рендерингом становится ясно, зачем нужна эта спецификация.
5.2. Отложенный рендеринг — почему «закрыл — и вставить уже нельзя»
Генерировать каждый из многих форматов при каждом копировании значит делать работу даже для форматов, которые никто не использует. Механизм, который этого избегает, — отложенный рендеринг.6
В момент копирования передайте NULL как дескриптор данных в SetClipboardData, регистрируя не сами данные, а обещание «построить, когда запросят». Когда этот формат запросят, источнику копирования приходит WM_RENDERFORMAT, и только тогда данные генерируются.
При выходе превратите «обещание» в данные
Если источник копирования выходит, оставив форматы неотрисованными, сторона вставки больше не может получить эти данные. Это и есть проблема «закрыл источник — и вставить уже нельзя».
Перед выходом источник копирования отвечает за ответ на WM_RENDERALLFORMATS и материализацию каждого неотрисованного формата. Форматы, которые не материализовали, теряются, когда источник копирования выходит.6
flowchart TB
accTitle: Поток отложенного рендеринга и «закрыл — и вставить уже нельзя»
accDescr: Источник копирования регистрирует только обещание с дескриптором NULL и материализует данные через WM_RENDERFORMAT, когда приходит запрос. При выходе он отвечает за материализацию каждого формата через WM_RENDERALLFORMATS; пренебрегите — и формат теряется
promise["Источник копирования: зарегистрировать только обещание через SetClipboardData(format, NULL)"] --> req["Сторона вставки запрашивает этот формат"]
req --> render["WM_RENDERFORMAT → сгенерировать данные на месте"]
promise --> quit["Источник копирования собирается выйти"]
quit -->|"Материализовать через WM_RENDERALLFORMATS"| ok["Вставка после выхода всё ещё работает"]
quit -->|"Пренебречь материализацией"| lost["Этот формат теряется (закрыл — и вставить уже нельзя)"]
На OLE-буфере вы кладёте IDataObject через OleSetClipboard. В этот момент буфер держит указатель на объект данных.
Вызов OleFlushClipboard при выходе материализует данные в буфере обмена, поэтому вставка после выхода всё ещё работает.7 Clipboard.SetDataObject(data, copy: true) в .NET задаёт именно это поведение «сохранить после выхода».
Когда в Excel копируете большой диапазон и затем выходите, запрос, хотите ли вы оставить большой объём информации в буфере, — это подтверждение, выполнять ли эту материализацию (flush). И в своём приложении проектируйте отложенный рендеринг и фиксацию при выходе комплектом.
Откладывание не гарантирует, что UI останется отзывчивым
Отложенный рендеринг — оптимизация производительности, но генерация запрошенных данных идёт синхронно внутри обработки сообщений. Цена в том, что UI застывает, если генерация занимает много времени.6
6. Практика мониторинга буфера обмена — слушатель, повтор и исключение из журнала
6.1. Используйте AddClipboardFormatListener
Требования вроде «обнаружить значение сканера штрихкодов или копию из основной бизнес-системы и импортировать автоматически» требуют мониторинга изменений буфера. Исторически есть три метода, но сегодня правильный ответ один.8
| Метод | Оценка |
|---|---|
| Читать периодически по таймеру (опрос) | Лишняя работа и можно пропустить изменения. Не использовать |
| SetClipboardViewer (цепочка просмотрщиков) | Сбой в одном приложении цепочки ломает её целиком. Оставлена только ради обратной совместимости |
| AddClipboardFormatListener | Рекомендуется. WM_CLIPBOARDUPDATE доставляется зарегистрированному окну |
flowchart LR
accTitle: Поток мониторинга буфера обмена
accDescr: Зарегистрироваться через AddClipboardFormatListener при создании дескриптора, и WM_CLIPBOARDUPDATE приходит, какое бы приложение ни копировало. Читать с повторами и симметрично снять регистрацию через RemoveClipboardFormatListener при уничтожении дескриптора
created["OnHandleCreated: AddClipboardFormatListener"] --> wait["Ждать"]
anyapp["Какое-то приложение копирует"] --> notify["Приходит WM_CLIPBOARDUPDATE"]
wait --> notify
notify --> readtry["Читать с повторами (раздел 6.2)"]
readtry --> wait
destroyed["OnHandleDestroyed: RemoveClipboardFormatListener"] -.->|"Снять регистрацию симметрично"| created
// Минимальная реализация WinForms
public partial class MainForm : Form
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
const int WM_CLIPBOARDUPDATE = 0x031D;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
AddClipboardFormatListener(Handle);
}
protected override void OnHandleDestroyed(EventArgs e)
{
// Снять регистрацию симметрично, в шаг с уничтожением и пересозданием дескриптора
RemoveClipboardFormatListener(Handle);
base.OnHandleDestroyed(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CLIPBOARDUPDATE)
{
// Здесь читать Clipboard.GetDataObject() и импортировать, если формат нужный
}
base.WndProc(ref m);
}
}
6.2. Повторяйте, когда открыть не удаётся
Буфер обмена может открыть только одно окно одновременно. Пока его держит другой процесс, OpenClipboard завершается ошибкой.6
Сразу после WM_CLIPBOARDUPDATE источник копирования или другое приложение мониторинга тоже может ещё с ним работать. Считайте временный сбой чтения нормальным событием и добавьте логику, которая ждёт несколько десятков миллисекунд и повторяет несколько раз.
В .NET важно: перегрузки, в которых можно задать число повторов и интервал, есть только на стороне записи, SetDataObject. На стороне чтения, GetDataObject и подобных, их нет.
| Сторона чтения | Обработка конкуренции |
|---|---|
| WinForms | Ловить ExternalException, ждать и повторять |
| WPF | Ловить COMException, ждать и повторять |
Ожидание и повтор для чтения реализуют на стороне приложения.
flowchart LR
accTitle: Поток повтора чтения буфера обмена
accDescr: Буфер обмена может открыть только одно окно одновременно, поэтому чтение сразу после уведомления об изменении может завершиться ошибкой из-за конкуренции с другим процессом. При исключении подождать несколько десятков миллисекунд и повторить; когда лимит исчерпан, отказаться в этот раз и подхватить на следующем обновлении
upd["WM_CLIPBOARDUPDATE"] --> tryread["Попытаться прочитать"]
tryread -->|"Успех"| useok["Импортировать (проверка главы 4)"]
tryread -->|"Сбой (окно занято другим)"| waitretry["Подождать несколько десятков миллисекунд"]
waitretry -->|"Повторить до нескольких раз"| tryread
waitretry -->|"Лимит исчерпан"| giveup2["Отказаться в этот раз (подхватить на следующем обновлении)"]
6.3. Не пускать в журнал и синхронизацию — учёт для функций копирования, которые несут секреты
В Windows есть журнал буфера обмена (Win+V) и синхронизация между устройствами (облачный буфер), и данные, которые кладёт приложение, по умолчанию попадают в оба. Приложение, чья функция копирования несёт секреты вроде паролей или номеров счетов, также кладёт зарегистрированный формат, который исключает данные из журнала и синхронизации.1
| Зарегистрированный формат | Что подавляет |
|---|---|
ExcludeClipboardContentFromMonitorProcessing |
Исключает всё скопированное содержимое и из журнала, и из синхронизации между устройствами |
CanIncludeInClipboardHistory (DWORD 0) |
Только журнал |
CanUploadToCloudClipboard (DWORD 0) |
Только синхронизация между устройствами |
Менеджеры паролей не пускают скопированные пароли в Win+V именно через этот механизм. Достаточно передать имя в RegisterClipboardFormat, получить идентификатор формата и положить его рядом с обычными данными, поэтому это стоит реализовать и в бизнес-приложениях, которые работают с секретами.
7. Буфер обмена с точки зрения ИТ — управление журналом, облачной синхронизацией и RDP
Управлять скопированным содержимым на стороне приложения и решать, что разрешает организация в целом, — разные вопросы. Администратор смотрит на три вещи: журнал, облачная синхронизация и перенаправление RDP.
Журнал буфера обмена накапливает недавно скопированное содержимое. Облачный буфер синхронизирует его между устройствами, в которые вошли с одной учётной записью Microsoft или Microsoft Entra.10
Удобно, но ведёт к остатку и утечке: персональные данные, скопированные из основной системы, остаются в журнале, или содержимое, скопированное на рабочем ПК, синхронизируется на личный.
7.1. Управление журналом и синхронизацией между устройствами
Две политики организационного контроля такие.
| Чем управляют | GPO (Конфигурация компьютера > Административные шаблоны > Система > Политики ОС) | Policy CSP (Intune) | По умолчанию |
|---|---|---|---|
| Журнал буфера обмена | Allow Clipboard History | Experience/AllowClipboardHistory | Разрешено |
| Синхронизация между устройствами | Allow Clipboard synchronization across devices | Privacy/AllowCrossDeviceClipboard | Разрешено |
Обе доступны начиная с Windows 10 версии 1809; при отключении соответствующие пункты в приложении «Параметры» становятся серыми, и политика вступает в силу сразу.910
7.2. Управление передачей между сеансами RDP
Перенаправление буфера обмена RDP (Remote Desktop) связывает локальный ПК и удалённый сеанс. Поскольку копирование и вставка по умолчанию работают, это также может стать путём выноса секретов с сервера.
Параметр, который блокирует оба направления, — политика «Do not allow Clipboard redirection» (значение реестра fDisableClip).11
В недавних выпусках Windows Server и Windows 11 появились и политики более тонкого контроля, например ограничить только направление сервер→клиент текстом. Запрещать полностью или поэтапно ограничивать направление и формат — баланс между работой и безопасностью.
flowchart LR
accTitle: Пути, по которым расходится содержимое буфера обмена, и точки управления
accDescr: Скопированное содержимое по умолчанию попадает в журнал и облачную синхронизацию, а через RDP уходит в другой сеанс через перенаправление. Каждым можно управлять политикой, а сторона приложения может исключить содержимое из журнала и синхронизации форматами исключения
cb["Буфер обмена"] --> hist["Журнал (Win+V)"]
cb --> cloud["Облачная синхронизация → другое устройство"]
cb --> rdp["Перенаправление RDP → другой сеанс"]
hist -.-> p1["Управление: AllowClipboardHistory"]
cloud -.-> p2["Управление: AllowCrossDeviceClipboard"]
rdp -.-> p3["Управление: fDisableClip"]
cb -.-> p4["Сторона приложения: исключить через ExcludeClipboardContentFromMonitorProcessing и подобные (раздел 6.3)"]
8. Перетаскивание — это COM: IDataObject + IDropSource + IDropTarget
8.1. Те же данные, что у буфера обмена, другой способ доставки
OLE-перетаскивание строится на трёх сторонах.15
| Роль | Кто реализует | Работа |
|---|---|---|
| IDataObject | Источник перетаскивания | Переносимые данные. Тот же многоформатный объект данных, что и у буфера обмена |
| IDropSource | Источник перетаскивания | Решение, продолжается перетаскивание или отменяется, и обратная связь курсора |
| IDropTarget | Приёмник сброса | Объявление приёма и получение данных в DragEnter/DragOver/DragLeave/Drop |
Операция идёт так.
- Источник перетаскивания вызывает
DoDragDrop, начиная цикл перетаскивания. - Когда мышь входит в окно приёмника, уведомляется
IDropTarget. - Приёмник объявляет, принимает ли, и при сбросе забирает нужный формат из
IDataObject.
Официальная документация также объясняет, что D&D даёт ту же функциональность, что копирование и вставка через буфер, и что приложению, которое уже реализует копирование и вставку, нужна лишь небольшая добавка.15 Иначе говоря, многоформатный DataObject, подготовленный в главах 2–5, — это и данные, которые несёт D&D.
flowchart LR
accTitle: Поток OLE-перетаскивания
accDescr: Источник перетаскивания вызывает DoDragDrop с IDataObject в качестве полезной нагрузки, и начинается цикл перетаскивания; IDropTarget приёмника объявляет приём в DragEnter и DragOver, а в Drop выбирает и забирает формат из IDataObject
src["Источник перетаскивания: IDataObject + IDropSource"] -->|"DoDragDrop"| loop["Цикл перетаскивания"]
loop -->|"Мышь входит в окно"| enter["IDropTarget.DragEnter/DragOver (каждый раз объявлять Effect)"]
enter -->|"Кнопка отпущена"| drop["IDropTarget.Drop"]
drop --> data["Выбрать и забрать формат из IDataObject"]
8.2. OleInitialize (STA) обязателен
Окно приёмника регистрируют через RegisterDragDrop. Как предпосылки проверьте две вещи: инициализацию OLE и обработку сообщений.
Для инициализации используйте OleInitialize. Если вместо этого взять CoInitialize / CoInitializeEx, RegisterDragDrop завершится с E_OUTOFMEMORY. OleInitialize инициализирует COM как STA.12
Регистрирующий поток должен крутить насос сообщений. OLE D&D — функция, укоренённая в окнах и обработке сообщений; пренебрегите — и другие приложения зависают во время перетаскивания.12 Фон — та же модель потоков, что в «Основах COM STA/MTA».
В WinForms/WPF принимайте через события
В WinForms/WPF платформа берёт на себя инициализацию OLE и реализации интерфейсов. Разработчик пишет объявление приёма и фактический импорт в событиях.
// WinForms: принимать сброшенные файлы
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
// Также проверить, что источник разрешает Copy (некоторые источники разрешают только Move/Link)
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
&& (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
? DragDropEffects.Copy // Принять: получить как копию
: DragDropEffects.None; // Не принимать
};
listView1.DragDrop += (s, e) =>
{
// Данные перетаскивания тоже внешний ввод. Даже если объявлен FileDrop, полезная нагрузка
// может быть null или другого типа, и само получение может завершиться ошибкой
object data;
try { data = e.Data.GetData(DataFormats.FileDrop); }
catch (COMException) { return; }
if (data is not string[] paths) return;
foreach (var path in paths)
{
// Проверить путь до импорта (раздел 9.3)
}
};
В WPF картина та же. Принимайте через AllowDrop="True" на элементе и события DragOver / Drop, а массив путей забирайте через e.Data.GetData(DataFormats.FileDrop).
Объявлять приём (Effect) каждый раз в DragEnter / DragOver — соглашение IDropTarget. Опустите — и курсор останется на «нельзя». Проверяйте не только присутствие формата, но и, как в примере, разрешает ли источник перетаскивания Copy.
9. Ловушки D&D — повышение, Move и проверка путей
9.1. На приложение, повышенное до администратора, сбросить нельзя
Сбросить файл из Проводника на приложение, запущенное через «Запуск от имени администратора», и ничего не происходит — это не ошибка реализации, а проект ОС. UIPI (User Interface Privilege Isolation) по умолчанию блокирует сообщения от процесса с более низким уровнем целостности к окну с более высоким, поэтому уведомления о сбросе от Проводника с обычными правами (средний уровень целостности) до повышенного приложения не доходят.13
flowchart LR
accTitle: Как UIPI блокирует сброс на повышенное приложение
accDescr: UIPI по умолчанию блокирует уведомление о сбросе от Проводника со средним уровнем целостности к повышенному приложению с высоким уровнем, поэтому оно не приходит. Держите UI на обычных правах и изолируйте привилегированную работу — и сброс дойдёт
explorer["Проводник (средний уровень целостности)"] -->|"Уведомление о сбросе"| uipi{"UIPI"}
uipi -->|"Заблокировано (по умолчанию)"| elevated["Повышенное приложение (высокий уровень): нет отклика"]
uipi -->|"Проходит"| normal["UI на обычных правах: сброс доходит"]
normal -.->|"Поручить только привилегированную работу"| broker["Отдельный процесс, изолирующий работу, которой нужно повышение"]
Есть и обход, который по отдельности разрешает WM_DROPFILES и подобные сообщения через ChangeWindowMessageFilterEx.13 Однако это мера для старого уведомления о сбросе через WM_DROPFILES, и она не решает OLE D&D целиком.
Фундаментальная рекомендация — не держать приложение постоянно повышенным. Если вынести в отдельный процесс только работу, которой нужно повышение, сам UI может остаться на обычных правах и принимать D&D. Проект изоляции подробно разобран в «Правах администратора и процессах-посредниках в приложениях Windows».
9.2. Что значит DragDropEffects — Move это договор, что «исходник исчезает»
Copy / Move / Link в DragDropEffects — не украшение курсора, а договор между источником перетаскивания и приёмником сброса.
Источник объявляет набор эффектов, которые разрешает, в DoDragDrop, а приёмник выбирает фактический эффект. По соглашению когда согласован Move, источник удаляет данные (файл).
Если принимающая сторона возвращает Move не думая, получается «сбросил — и исходный файл пропал». Для импорта в бизнес-приложениях безопасный по умолчанию вариант — принимающая сторона явно указывает Copy.
flowchart LR
accTitle: Договор DragDropEffects — при Move исходник исчезает
accDescr: Источник перетаскивания объявляет набор разрешённых эффектов в DoDragDrop, а приёмник сброса выбирает фактический эффект. Поскольку соглашение в том, что источник удаляет файл, когда согласован Move, принимающая сторона, которая импортирует, должна явно указать Copy
srcdecl["Источник: объявить разрешённые эффекты (Copy | Move | Link)"] --> tgtsel["Приёмник: выбрать фактический эффект"]
tgtsel -->|"Copy"| copyok["Исходный файл остаётся (безопасная сторона для импорта)"]
tgtsel -->|"Move"| moveact["Источник удаляет файл — откуда берётся «исходник пропал»"]
9.3. Проверка сброшенных путей
В CF_HDROP/FileDrop передаются только пути (раздел 3.2). Перед импортом прогоните их через ту же проверку, что и внешний ввод, как при вставке.
- Файл или папка: Как спецификацию решите, что происходит, когда сбрасывают целую папку (импортировать рекурсивно или отказать).
- Заполнители OneDrive: Если путь существует, но тело файла не локально — файл по запросу, — в момент открытия начинается загрузка, и она срывается без сети. Поведение и меры — в OneDrive «Файлы по запросу» и бизнес-приложения.
- Длинные и особые пути: Пути длиннее MAX_PATH, сетевые (UNC) и пути на сменных носителях принимайте только после того, как убедитесь, что нижестоящая обработка с ними справляется.
- Число и суммарный размер: Чтобы сброс тысяч файлов не застыл UI, делайте импорт асинхронным и добавляйте предел и показ прогресса.
10. Итог
- Буфер обмена — механизм, который кладёт одно и то же содержимое сразу в нескольких форматах в одной области, общей внутри одного рабочего стола (станции окон). Поскольку цель вставки выбирает формат, одна и та же копия даёт разный результат.
- Текст — CF_UNICODETEXT, файлы — CF_HDROP, форматированный текст — зарегистрированный формат HTML Format (заголовок байтовых смещений + UTF-8).
- Сторона вставки ищет форматы от богатых к простым и проверяет содержимое как внешний ввод. Сторона копирования предлагает несколько форматов сразу и, если использует отложенный рендеринг, реализует и фиксацию при выходе (WM_RENDERALLFORMATS / OleFlushClipboard).
- Мониторинг — AddClipboardFormatListener + WM_CLIPBOARDUPDATE. К конкуренции OpenClipboard готовьтесь повторами, а секреты исключайте из журнала и синхронизации через ExcludeClipboardContentFromMonitorProcessing и подобные.
- Сотрудники ИТ могут управлять журналом буфера обмена, облачной синхронизацией и перенаправлением RDP через GPO/Intune. Все по умолчанию разрешены, поэтому в средах с секретами решайте сознательно.
- D&D — это COM: IDropSource/IDropTarget передают тот же IDataObject, что и буфер обмена. RegisterDragDrop требует OleInitialize (STA).
- Сброс на повышенное приложение блокирует UIPI. Move в DragDropEffects — договор, что «исходник исчезает», а сброшенные пути проверяют до импорта.
Для пользователей копирование с вставкой и D&D — функции такие же незаметные, как воздух. Именно поэтому опыт так страдает, когда случается «не вставляется», «разваливается» или «пропало» — и наоборот, приложение, которое предлагает несколько форматов и тщательно обрабатывает сброс, уже этим делает повседневную работу глаже. Надеюсь, это послужит входом, когда вы взвешиваете приоритет доработок.
Похожие статьи
- Что такое COM / ActiveX / OCX — различия и связь
- STA и MTA в COM: модель потоков и как не получить зависание
- Интеграция с оболочкой Windows — контекстное меню, сопоставление файлов и изменения в Windows 11
- Почему после работы с Excel из C# остаётся EXCEL.EXE — освобождение COM-ссылок и решение о замене
- UX Windows-приложений: приоритеты по среде использования
- OneDrive «Файлы по запросу» и бизнес-приложения — какие предпосылки ломают заполнители и что с этим делать
Смежные области консультирования
KomuraSoft LLC занимается проектированием и реализацией поддержки копирования с вставкой и перетаскивания в бизнес-приложениях (предложение нескольких форматов, интеграция с Excel, импорт сброшенных файлов), расследованием причин дефектов вроде «при вставке разваливается» или «копия пропадает», автоматизацией ввода через мониторинг буфера обмена и реализацией защиты журнала и синхронизации для конфиденциальной информации. Случаи, которые затрагивают нижние слои COM и OLE, приветствуются даже если начинаются с разбора симптома.
- Разработка Windows-приложений
- Разработка COM-компонентов
- Техническая консультация и ревью проекта
- Связаться с нами
Справочные ссылки
-
Microsoft Learn, Clipboard Formats. О том, что окно может положить одну и ту же информацию в нескольких форматах буфера обмена; о зарегистрированных форматах через RegisterClipboardFormat (регистрация того же имени возвращает то же значение, поэтому приложения могут им делиться); о синтезируемых форматах; и об исключении содержимого из журнала буфера обмена и облачной синхронизации через ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory и CanUploadToCloudClipboard. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Standard Clipboard Formats. Об определениях стандартных форматов вроде CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB и CF_LOCALE и о том, что система неявно преобразует между CF_TEXT и CF_UNICODETEXT, используя кодовую страницу, связанную с CF_LOCALE. ↩ ↩2 ↩3
-
Microsoft Learn, Shell Clipboard Formats. О том, что CF_HDROP состоит из структуры DROPFILES плюс массив строк полных путей с двойным завершающим NUL; о получении отдельных путей через DragQueryFile; и о том, что форматы оболочки CFSTR_ требуют регистрации через RegisterClipboardFormat. ↩ ↩2
-
Microsoft Learn, HTML Clipboard Format. О том, что зарегистрированное имя — «HTML Format»; о структуре заголовка со смещениями (в байтах) вроде Version, StartHTML, EndHTML, StartFragment и EndFragment; о том, что кодировка всегда UTF-8; и о соглашении комментариев StartFragment/EndFragment. ↩ ↩2 ↩3
-
Microsoft Learn, OleGetClipboard function (ole2.h). О том, как получить IDataObject из буфера обмена, и о предупреждении, что данным буфера обмена нельзя доверять и их следует осторожно разбирать, прежде чем приложение их использует. ↩ ↩2
-
Microsoft Learn, Clipboard Operations. О том, что буфер обмена может открыть только одно окно одновременно; о размещении форматов от самого выразительного вниз в момент копирования; о выборе формата при вставке через EnumClipboardFormats / GetPriorityClipboardFormat; об отложенном рендеринге через передачу NULL в SetClipboardData и обязанностях WM_RENDERFORMAT / WM_RENDERALLFORMATS; и о компромиссах отложенного рендеринга. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, OleFlushClipboard function (ole2.h). О том, что после OleSetClipboard буфер держит только указатель на объект данных; о том, что OleFlushClipboard материализует данные в буфере, так что вставка остаётся возможной после выхода приложения; и об опустошении буфера через OleSetClipboard(NULL), когда при выходе данные сохранять не нужно. ↩ ↩2
-
Microsoft Learn, Using the clipboard. О сравнении трёх способов мониторинга буфера обмена (окна просмотрщиков, порядковые номера и слушатели форматов); о том, что новым программам следует использовать слушателя через AddClipboardFormatListener; о том, что цепочка просмотрщиков уязвима к сбоям в поддержании цепочки; и о том, что порядковые номера не стоит опрашивать. ↩ ↩2
-
Microsoft Learn, Policy CSP - Experience. О разрешении или блокировке журнала буфера обмена политикой Experience/AllowClipboardHistory; о доступности начиная с Windows 10 версии 1809; о том, что по умолчанию разрешено; и о соответствии GPO в «Система > Политики ОС» с немедленным вступлением изменений в силу. ↩ ↩2
-
Microsoft Learn, Policy CSP - Privacy. О разрешении или блокировке синхронизации буфера между устройствами политикой Privacy/AllowCrossDeviceClipboard; о том, что синхронизация идёт между устройствами, в которые вошли с одной учётной записью Microsoft или Microsoft Entra; и о том, что по умолчанию разрешено. ↩ ↩2 ↩3
-
Microsoft Learn, Policy CSP - ADMX_TerminalServer. О том, что TS_CLIENT_CLIPBOARD («Do not allow Clipboard redirection», значение реестра fDisableClip) может запретить совместное использование буфера обмена между локальным и удалённым в сеансе Remote Desktop, и о том, что перенаправление по умолчанию разрешено. ↩ ↩2
-
Microsoft Learn, RegisterDragDrop function (ole2.h). О том, как зарегистрировать окно приёмника и его IDropTarget; о том, что вызов всегда завершается с E_OUTOFMEMORY, когда COM инициализировали через CoInitialize/CoInitializeEx, поэтому нужен OleInitialize; и о зависании приложения-источника, когда вызывающий поток не крутит насос сообщений. ↩ ↩2 ↩3
-
Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). О том, что UIPI — механизм безопасности, который по умолчанию блокирует приём сообщений от отправителя с более низким уровнем целостности, и о разрешении конкретных сообщений (MSGFLT_ALLOW) фильтром сообщений на окно. ↩ ↩2 ↩3
-
Microsoft Learn, How to add data to the Clipboard (Windows Forms). О размещении данных в нескольких форматах сразу через DataObject и Clipboard.SetDataObject; о добавлении данных в нескольких форматах, чтобы другие приложения могли их узнать; и о том, что класс Clipboard можно использовать только из потока STA, поэтому нужен [STAThread]. ↩ ↩2
-
Microsoft Learn, Drag and Drop (COM). О том, что OLE-перетаскивание строится на трёх сторонах IDropSource (источник), IDropTarget (приёмник) и DoDragDrop (цикл, который предоставляет OLE); о предоставлении той же функциональности, что копирование и вставка через буфер, так что приложению, которое уже реализует копирование и вставку, нужна лишь небольшая добавка; и о видах обратной связи. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Что продумать перед заказом разработки Windows-приложения
Перед заказом разработки Windows-приложения разберём, что стоит прояснить: доработка существующего ПО, интеграция с оборудованием, COM/Ac...
«Не отвечает»: как Windows определяет зависание и как проектировать так, чтобы оно не возникало
Windows считает окно неотвечающим, если 5 секунд не извлекает сообщения, и подменяет его фантомным окном. Разбираем критерий, типичные пр...
Тёмный режим и темы контрастности в приложениях Windows ── тёмная панель заголовка DWM, следование системной теме в WinForms/WPF, отрисовка при высокой контрастности
Как заставить приложения WinForms и WPF следовать тёмному режиму и темам контрастности Windows 11. Тёмная панель заголовка DWM, SetColorM...
Окончание драйверов принтера Windows ── как готовить печать форм и этикеток в бизнес-приложениях
Microsoft поэтапно прекращает сопровождение драйверов принтера v3/v4; с июля 2026 IPP class driver предпочтут. Что исчезает в Windows pro...
Что такое OLE-объект ── внедрение, связывание и ловушки деловых документов
Таблица Excel внутри Word — это OLE-объект. Разбираем разницу внедрения и связывания, составной файл и структурированное хранилище, In-Pl...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Миграция ActiveX
Решения о сохранении, обёртке или замене компонентов COM / ActiveX / OCX.
Поток UI и таймеры
Поток UI WPF / WinForms, асинхронные операции, Dispatcher и проектирование таймеров.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Использование и перенос существующих активов
Помогаем использовать и переносить активы COM / ActiveX / OCX и зависимости 32/64 бит.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Почему таблица, скопированная из Excel, теряет оформление при вставке в наше приложение?
- В буфере обмена лежит не «один объект данных». Одно и то же содержимое размещается сразу в нескольких форматах (собственный формат приложения, HTML Format, CSV, текст Unicode и другие), а приложение, куда вставляют, выбирает формат, который понимает, и забирает его. Если оформление разваливается, типичная причина в том, что сторона вставки читает только простой текст (CF_UNICODETEXT). Чтобы принять ещё и структуру таблицы, реализуйте вставку так, чтобы она предпочитала HTML Format или CSV. Наоборот, если хотите, чтобы другие приложения корректно вставляли то, что скопировали у вас, в момент копирования отдавайте и богатый формат, и простой одновременно.
- Почему после закрытия приложения, из которого копировали, вставить уже нельзя?
- Потому что источник копирования использует отложенный рендеринг (delayed rendering). Приложения с большими данными в момент копирования не кладут сами данные в буфер обмена, а регистрируют только обещание «построить, когда запросят». Если источник затем завершается, не зафиксировав данные в ответ на WM_RENDERALLFORMATS при выходе, все ещё не отрисованные форматы теряются. Приложение, которое кладёт в буфер объект OLE (IDataObject), может сохранить возможность вставки после выхода: при завершении вызовите OleFlushClipboard, чтобы записать данные в буфер обмена.
- Как отслеживать изменения буфера обмена из своего приложения?
- Сейчас рекомендуемый способ — зарегистрировать своё окно как слушателя через AddClipboardFormatListener и обрабатывать сообщение WM_CLIPBOARDUPDATE, которое приходит при каждом изменении содержимого. Опрос содержимого по таймеру даёт лишнюю работу и может пропускать обновления. Старая цепочка просмотрщиков на SetClipboardViewer оставлена только ради обратной совместимости: сбой в одном приложении цепочки ломает её целиком. Учтите также, что OpenClipboard при чтении может завершиться ошибкой, если буфер держит другой процесс, поэтому для устойчивого чтения реализуйте повтор с короткой паузой.
- Можно ли не оставлять пароли и другие секреты в журнале буфера обмена (Win+V)?
- Есть два рычага: в приложении и в политике. На стороне приложения при копировании положите рядом зарегистрированный формат ExcludeClipboardContentFromMonitorProcessing — тогда это содержимое не попадёт ни в журнал, ни в синхронизацию между устройствами. По отдельности можно управлять через CanIncludeInClipboardHistory (только журнал) и CanUploadToCloudClipboard (только синхронизация). Именно так поступают менеджеры паролей. Если нужно выключить функции для всей организации, сами журнал и облачную синхронизацию отключают политиками AllowClipboardHistory и AllowCrossDeviceClipboard через групповую политику или Intune (Policy CSP).
- Почему нельзя перетащить файл на приложение, запущенное от имени администратора?
- Потому что механизм безопасности UIPI (User Interface Privilege Isolation) блокирует доставку сообщений от процесса с более низким уровнем целостности к окну с более высоким. Проводник работает с обычными правами (средний уровень целостности), поэтому уведомления о перетаскивании до окна повышенного приложения не доходят. Обход через ChangeWindowMessageFilterEx, который по отдельности разрешает сообщения вроде WM_DROPFILES, известен, но касается только старого уведомления о сбросе. Правильный путь — не держать приложение постоянно с повышенными правами и вынести в отдельный процесс только ту работу, которой повышение действительно нужно.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.