Как работает буфер обмена в Windows

https://blogs.msdn.microsoft.com/ntdebugging/2012/03/16/how-the-clipboard-works-part-1/
  • Перевод
Недавно у меня появилась возможность выполнить отладку буфера обмена в Windows, и я решил, что хорошо бы поделиться информацией, которую удалось узнать. Буфер обмена — это тот компонент Windows, который многие из нас используют десятки (сотни?) раз в день, особо не размышляя об этом. Прежде чем взяться за это дело, я даже никогда не задумывался, как всё устроено. Как выяснилось, там столько интересного, что вы даже не можете себе представить. Сначала опишу, как приложения хранят разные типы данных в буфере обмена и как извлекают их оттуда, а затем — как приложения могут «прицепиться» к буферу и отслеживать изменения в нём. В обоих случае вы увидите отладочные записи, которые показывают, как получить доступ к данным из отладчика.

Начнём с обсуждения форматов буфера обмена. Такие форматы используются для описания, какие типы данные можно поместить в буфер. Там есть некоторое количество предопределённых стандартных форматов, которые может использовать приложение, вроде битового массива, текста ANSI, текста в Юникоде и TIFF. Windows также позволяет приложению установить собственный формат. Например, текстовый процессор может зарегистрировать формат, включающий в себя текст, форматирование и картинки. Конечно, это ведёт к определённой проблеме: что произойдёт, если вы скопируете данные из текстового редактора и вставите их в «Блокнот», который не понимает всего этого форматирования и не отображает картинки?

Выход — разрешить одновременное хранение данных в буфере обмена в нескольких форматах. Когда я раньше думал о буфере обмена, то представлял, что там хранится единственный объект («мой текст» или «моя картинка»), но на самом деле мои данные хранятся в буфере в разных формах. Программа, которая берёт информацию из буфера, получает её в том формате, который она может использовать.

Как же данные появляются в буфере обмена? Очень просто, приложение сначала объявляет о праве собственности на буфер обмена через функцию OpenClipboard. После этого программа может очистить буфер обмена командой EmptyClipboard и, наконец, поместить туда свои данные командой SetClipboardData. SetClipboardData принимает два параметра. Первый — это идентификатор одного из форматов буфера обмена, которые мы упоминали выше. Второй — дескриптор сегмента в памяти, который содержит данные в этом формате. Приложение может неоднократно вызывать команду SetClipboardData для каждого из форматов, какие она хочет поместить в буфер, от лучшего к худшему (поскольку то приложение, куда будут вставляться данные, выберет первый подходящий формат из списка). Чтобы облегчить жизнь разработчику, Windows автоматически обеспечивает конвертацию некоторых типов форматов для буфера обмена. По окончании процесса программа вызывает CloseClipboard.

Когда пользователь нажимает кнопку «Вставить», целевое приложение вызывает OpenClipboard и одну из следующих функций для определени я доступных форматов данных: IsClipboardFormatAvailable, GetPriorityClipboardFormat или EnumClipboardFormats. Если оно находит подходящий формат, то тогда вызывает GetClipboardData с идентификатором нужного формата в качестве параметра, чтобы получить данные. В конце приложение использует команду CloseClipboard для закрытия буфера.

Теперь взглянем, как с помощью отладчика определить, какие данные записаны в буфер обмена. (Заметьте, что все мои записи сделаны в системе Win7/2008 R2 — так что на других версиях ОС они могут выглядеть несколько иначе). Поскольку буфер является частью Win32k.sys, вам понадобится отладчик ядра. Я люблю использовать в качестве контрольной точки win32k!InternalSetClipboardData+0xe4. В таком смещении хорошо то, что оно находится за регистром RDI, заполненным данными из SetClipboardData в структуре, известной как tagCLIP.

kd> u win32k!InternalSetClipboardData+0xe4-c L5
win32k!InternalSetClipboardData+0xd8:
fffff960`0011e278 894360          mov     dword ptr [rbx+60h],eax
fffff960`0011e27b 8937            mov     dword ptr [rdi],esi
fffff960`0011e27d 4c896708        mov     qword ptr [rdi+8],r12
fffff960`0011e281 896f10          mov     dword ptr [rdi+10h],ebp
fffff960`0011e284 ff15667e1900    call    qword ptr[win32k!_imp_PsGetCurrentProcessWin32Process (fffff960`002b60f0)]

kd> dt win32k!tagCLIP
   +0x000 fmt              : Uint4B
   +0x008 hData            : Ptr64 Void
   +0x010fGlobalHandle     : Int4B

Вот как выглядит вызов к SetClipboardData от «Блокнота»:

kd> k
Child-SP          RetAddr           Call Site
fffff880`0513a940 fffff960`0011e14f win32k!InternalSetClipboardData+0xe4
fffff880`0513ab90 fffff960`000e9312 win32k!SetClipboardData+0x57
fffff880`0513abd0 fffff800`01482ed3 win32k!NtUserSetClipboardData+0x9e
fffff880`0513ac20 00000000`7792e30ant!KiSystemServiceCopyEnd+0x13
00000000`001dfad8 00000000`7792e494 USER32!ZwUserSetClipboardData+0xa
00000000`001dfae0 000007fe`fc5b892b USER32!SetClipboardData+0xdf
00000000`001dfb20 000007fe`fc5ba625 COMCTL32!Edit_Copy+0xdf
00000000`001dfb60 00000000`77929bd1 COMCTL32!Edit_WndProc+0xec9
00000000`001dfc00 00000000`779298da USER32!UserCallWinProcCheckWow+0x1ad
00000000`001dfcc0 00000000`ff5110bc USER32!DispatchMessageWorker+0x3b5
00000000`001dfd40 00000000`ff51133c notepad!WinMain+0x16f
00000000`001dfdc0 00000000`77a2652d notepad!DisplayNonGenuineDlgWorker+0x2da
00000000`001dfe80 00000000`77b5c521 kernel32!BaseThreadInitThunk+0xd
00000000`001dfeb0 00000000`00000000ntdll!RtlUserThreadStart+0x1d

Итак, теперь мы можем просмотреть содержимое RDI как tagCLIP и увидеть, что записано в буфер:

kd> dt win32k!tagCLIP @rdi
   +0x000 fmt              : 0xd
   +0x008 hData            : 0x00000000`00270235 Void
   +0x010fGlobalHandle     : 0n1

Fmt — это формат для буфера обмена. 0Xd — это число 13, что соответствует тексту в формате Юникода. Однако мы не можем просто запустить du по значению hData, потому что это дескриптор, а не прямой указатель на данные. Так что нужно поискать его в глобальной структуре win32k — gSharedInfo:

kd> ?win32k!gSharedInfo
Evaluate expression: -7284261440224 = fffff960`002f3520
kd> dt win32k!tagSHAREDINFO fffff960`002f3520
   +0x000 psi              : 0xfffff900`c0980a70 tagSERVERINFO
   +0x008 aheList          : 0xfffff900`c0800000 _HANDLEENTRY
   +0x010 HeEntrySize      : 0x18
   +0x018 pDispInfo        : 0xfffff900`c0981e50 tagDISPLAYINFO
   +0x020ulSharedDelta     : 0
   +0x028 awmControl       : [31] _WNDMSG
   +0x218DefWindowMsgs     : _WNDMSG
   +0x228DefWindowSpecMsgs : _WNDMSG

aheList в gSharedInfo содержит массив с дескрипторами, и последние два байта hData, умноженные на размер записи дескриптора, показывают адрес записи нашего дескриптора:

kd> ?0x00000000`00270235 & FFFF
Evaluate expression: 565 = 00000000`00000235
kd> ??sizeof(win32k!_HANDLEENTRY)
unsigned int64 0x18
kd> ? 0xfffff900`c0800000 + (0x235*0x18)
Evaluate expression: -7693351766792 = fffff900`c08034f8
kd> dt win32k!_HANDLEENTRY fffff900`c08034f8
   +0x000 phead            : 0xfffff900`c0de0fb0 _HEAD
   +0x008 pOwner           : (null)
   +0x010 bType            : 0x6 ''
   +0x011 bFlags           : 0 ''
   +0x012 wUniq            : 0x27

Если посмотреть phead со смещением 14, то мы получим наши данные (это смещение может отличаться на разных платформах):

kd> du fffff900`c0de0fb0 + 0x14
fffff900`c0de0fc4 "Hi NTDebugging readers!"

Представим другой сценарий. Я скопировал какой-то текст из Wordpad, и команда SetClipboardData отработала определённое количество раз, чтобы разместить данные в разных форматах. Запись в формате Юникода выглядит так::

Breakpoint 0 hit
win32k!InternalSetClipboardData+0xe4:
fffff960`0011e284 ff15667e1900   call    qword ptr[win32k!_imp_PsGetCurrentProcessWin32Process (fffff960`002b60f0)]
kd> dt win32k!tagCLIP @rdi
   +0x000 fmt              : 0xd
   +0x008 hData            : (null)
   +0x010fGlobalHandle    : 0n0

hData равен нулю! Почему так? Оказывается, буфер обмена позволяет приложению передавать нуль в качестве параметра SetClipboardData для определённого формата. Это означает, что приложение способно предоставить данные в данном формате, но сделает это позже, в случае необходимости. Если я захочу вставить текст в «Блокнот», для чего в буфере должен быть текст в Юникоде, Windows отправит сообщение WM_RENDERFORMAT в окно WordPad, и тогда WordPad предоставит данные в новом формате. Конечно, если приложение закрывается до того, как предоставило данные во всех форматах, Windows понадобятся все форматы. В этом случае Windows отправит сообщение WM_RENDERALLFORMATS, чтобы другие приложения могли использовать данные из буфера обмена после закрытия материнского приложения.

Теперь посмотрим, как приложение может отслеживать буфер обмена на предмет изменений. Это важно знать, потому что в этом месте Windows позволяет сторонним приложениям подключаться к системе. Если у вас наблюдаются непонятные глюки с копированием и вставкой, причиной может быть некорректное поведение какой-то из таких программ. Начнём с того, что рассмотрим механизмы подключения к буферу обмена. Затем рассмотрим, можно ли с помощью отладчика идентифицировать приложения, которые используют такие хуки.

Есть три способа отслеживать буфер обмена на предмет изменений: просмотр буфера, прослушивание форматов буфера и запрос порядкового номера буфера. Мы сосредоточимся на первых двух способах, потому что они предусматривают получение уведомлений, когда содержимое буфера обновилось. В третьем методе приложение должно само проверять каждый раз, изменился ли буфер, и этот метод нельзя использовать в цикле опросов.

Функциональность Clipboard Viewer появилась ещё в версии Windows 2000, если не раньше. Принцип работы довольно простой: приложение, которое заинтересовано в получении уведомлений об изменении в буфере, вызывает SetClipboardViewer и передаёт дескриптор своего окна. Windows хранит этот дескриптор в структуре win32k, и каждый раз при изменении буфера обмена Windows отправляет сообщение WM_DRAWCLIPBOARD в зарегистрированное окно.

Конечно, зарегистрироваться для просмотра буфера могут несколько окон — как Windows справится с этим? Ну, если приложение вызывает SetClipboardViewer, а другое окно раньше уже зарегистрировалось для просмотра буфера обмена, то Windows возвращает новому окну значение дескриптора предыдущего окна. И теперь новое окно, следящее за буфером, обязано вызвать SendMessage каждый раз, когда получает WM_DRAWCLIPBOARD, и уведомить об изменении буфера следующее окно в цепочке тех, кто следит за буфером. Каждое из окон, следящих за буфером, также должно обрабатывать сообщения WM_CHANGECBCHAIN. Такие сообщения уведомляют все остальные окна об удалении одного звена в цепочке и сообщают, какое звено становится следующим в очереди. Это позволяет сохранить цепочку.

Очевидная проблема подобной архитектуры состоит в следующем: она рассчитывает, что каждое приложение, следящее за буфером, будет вести себя корректно, не завершать неожиданно работу и в целом будет хорошим гражданином в системе. Если какое-то из приложений начнёт вести себя недружественно, то оно не отправит уведомление об изменении буфера обмена следующему приложению в цепочке, в результате чего вся цепочка останется без уведомлений.

Чтобы справиться с такими проблемами, в Windows Vista добавили механизм прослушивания формата буфера обмена — Clipboard Format Listener. Он работает во многом так же, как просмотр буфера обмена, за исключением того, что Windows сама ведёт список приложений, которые прослушивают буфер, а не полагается на добропорядочность приложений, которые должны сохранять цепочку.

Если приложение хочет прослушивать буфер, оно вызывает функцию AddClipboardFormatListener и передаёт дескриптор своего окна. После этого обработчик сообщений окна будет получать сообщения WM_CLIPBOARDUPDATE. Когда приложение собирается завершить работу или больше не хочет получать уведомления, оно вызывает RemoveClipboardFormatListener.

Мы рассмотрели, как зарегистрировать просмотр/прослушивание буфера обмена. Теперь посмотрим, как с помощью отладчика определить, какие программы участвуют в этих процессах. Сначала нужно идентифицировать процесс в сессии, где мы хотим проверить мониторинг буфера обмена. Это может быть любой процесс win32 в этой сессии — он нужен нам просто для того, чтобы найти указатель на Window Station. В этом случае я бы использовал окно «Блокнота», как и раньше:

kd> !process 0 0 notepad.exe
PROCESS fffff980366ecb30
    SessionId: 1  Cid: 0374   Peb: 7fffffd8000  ParentCid: 0814
    DirBase: 1867e000  ObjectTable: fffff9803d28ef90  HandleCount: 52.
    Image: notepad.exe

Если вы делаете это в процессе отладки ядра, то понадобится интерактивно сменить контекст (используя .process /I<address>, затем нажать g и подождать, пока отладчик прорвётся назад). Теперь запускаем DT на адрес процесса как _EPROCESS, и смотрим на поле Win32Process:

kd> dt _EPROCESS fffff980366ecb30 Win32Process
nt!_EPROCESS
   +0x258 Win32Process : 0xfffff900`c18c0ce0 Void

Далее посмотрим адрес Win32Process как win32k!tagPROCESSINFO и узнаем значение rpwinsta:

kd> dt win32k!tagPROCESSINFO 0xfffff900`c18c0ce0 rpwinsta
   +0x258 rpwinsta : 0xfffff980`0be2af60 tagWINDOWSTATION

Это наша Window Station. Сливаем содержимое через dt:

kd> dt 0xfffff980`0be2af60 tagWINDOWSTATION
win32k!tagWINDOWSTATION
   +0x000 dwSessionId      : 1
   +0x008 rpwinstaNext     : (null)
   +0x010 rpdeskList       : 0xfffff980`0c5e2f20 tagDESKTOP
   +0x018 pTerm            : 0xfffff960`002f5560 tagTERMINAL
   +0x020 dwWSF_Flags      : 0
   +0x028 spklList         : 0xfffff900`c192cf80 tagKL
   +0x030 ptiClipLock      : (null)
   +0x038 ptiDrawingClipboard: (null)
   +0x040 spwndClipOpen    : (null)
   +0x048 spwndClipViewer  : 0xfffff900`c1a4ca70 tagWND
   +0x050 spwndClipOwner   : 0xfffff900`c1a3ef70 tagWND
   +0x058 pClipBase        : 0xfffff900`c5512fa0 tagCLIP
   +0x060 cNumClipFormats  : 4
   +0x064 iClipSerialNumber : 0x16
   +0x068 iClipSequenceNumber : 0xc1
   +0x070 spwndClipboardListener : 0xfffff900`c1a53440 tagWND
   +0x078 pGlobalAtomTable: 0xfffff980`0bd56c70 Void
   +0x080 luidEndSession   : _LUID
   +0x088 luidUser         : _LUID
   +0x090 psidUser         : 0xfffff900`c402afe0 Void

Обратите внимание на поля spwndClipViewer, spwndClipboardListener и spwndClipOwnerfields. Здесь spwndClipViewer — это последнее зарегистрированное окно в цепочке тех, кто просматривает буфер обмена. Также spwndClipboardListener — последнее зарегистрированное окно прослушивания буфера в списке Clipboard Format Listener. Окно spwndClipOwner — это то окно, которое разместило данные в буфере обмена.

Ели мы знаем окно, то осталось несколько шагов, чтобы узнать, к какому процессу оно относится. Нас интересуют forspwndClipViewer, spwndClipboardListener и spwndClipOwner. Сначала запускаем dt, чтобы узнать значение tagWND. Для этой демонстрации мы используем spwndClipViewer:

kd> dt 0xfffff900`c1a4ca70 tagWND
win32k!tagWND
   +0x000 head             : _THRDESKHEAD
   +0x028 state            : 0x40020008
   +0x028 bHasMeun         : 0y0
   +0x028 bHasVerticalScrollbar : 0y0
…

Нас интересует только значение head — так что если смещение 0, делаем dt для того же адреса на _THRDESKHEAD:

kd> dt 0xfffff900`c1a4ca70 _THRDESKHEAD
win32k!_THRDESKHEAD
   +0x000 h                : 0x00000000`000102ae Void
   +0x008 cLockObj         : 6
   +0x010 pti              : 0xfffff900`c4f26c20tagTHREADINFO
   +0x018 rpdesk           : 0xfffff980`0c5e2f20 tagDESKTOP
   +0x020 pSelf            : 0xfffff900`c1a4ca70  "???"

Теперь запускаем dt для адреса, указанного в поле pti как tagTHREADINFO:

kd> dt 0xfffff900`c4f26c20 tagTHREADINFO
win32k!tagTHREADINFO
   +0x000 pEThread         : 0xfffff980`0ef6cb10 _ETHREAD
   +0x008 RefCount         : 1
   +0x010 ptlW32           : (null)
   +0x018 pgdiDcattr       : 0x00000000`000f0d00 Void

Теперь нам интересно только значение поля pEThread, которое мы можем передать в !thread:

kd> !thread 0xfffff980`0ef6cb10 e
THREAD fffff9800ef6cb10 Cid 087c.07ec  Teb: 000007fffffde000 Win32Thread: fffff900c4f26c20 WAIT: (WrUserRequest) UserModeNon-Alertable
    fffff9801c01efe0  SynchronizationEvent
Not impersonating
DeviceMap                 fffff980278a0fc0
Owning Process            fffff98032e18b30       Image:         viewer02.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount     5435847        Ticks: 33 (0:00:00:00.515)
Context Switch Count     809            IdealProcessor: 0                 LargeStack
UserTime                  00:00:00.000
KernelTime                00:00:00.062
Win32 Start Address 0x000000013f203044
Stack Init fffff880050acdb0 Current fffff880050ac6f0
Base fffff880050ad000 Limit fffff880050a3000 Call 0
Priority 11 BasePriority 8 UnusualBoost 0 ForegroundBoost 2IoPriority 2 PagePriority 5
Child-SP          RetAddr           Call Site
fffff880`050ac730 fffff800`01488f32 nt!KiSwapContext+0x7a
fffff880`050ac870 fffff800`0148b74f nt!KiCommitThreadWait+0x1d2
fffff880`050ac900 fffff960`000dc8e7 nt!KeWaitForSingleObject+0x19f
fffff880`050ac9a0 fffff960`000dc989 win32k!xxxRealSleepThread+0x257
fffff880`050aca40 fffff960`000dafc0 win32k!xxxSleepThread+0x59
fffff880`050aca70 fffff960`000db0c5 win32k!xxxRealInternalGetMessage+0x7dc
fffff880`050acb50 fffff960`000dcab5 win32k!xxxInternalGetMessage+0x35
fffff880`050acb90 fffff800`01482ed3 win32k!NtUserGetMessage+0x75
fffff880`050acc20 00000000`77929e6a nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`050acc20)
00000000`002ffb18 00000000`00000000 0x77929e6a

Как видим, просмотр буфера обмена зарегистрировн от имени процесса viewer02.exe. Поскольку просмотр идёт по цепочке, определить следующий процесс в цепочке будет непросто. Но мы можем сделать это для тех, кто прослушивает буфер. Снова взглянем на нашу Window Station:

kd> dt 0xfffff980`0be2af60 tagWINDOWSTATION
win32k!tagWINDOWSTATION
   +0x000 dwSessionId      : 1
   +0x008 rpwinstaNext     : (null)
   +0x010 rpdeskList       : 0xfffff980`0c5e2f20 tagDESKTOP
   +0x018 pTerm            : 0xfffff960`002f5560 tagTERMINAL
   +0x020 dwWSF_Flags      : 0
   +0x028 spklList         : 0xfffff900`c192cf80 tagKL
   +0x030 ptiClipLock      : (null)
   +0x038 ptiDrawingClipboard : (null)
   +0x040 spwndClipOpen    : (null)
   +0x048 spwndClipViewer  : 0xfffff900`c1a4ca70tagWND
   +0x050 spwndClipOwner   : 0xfffff900`c1a3ef70tagWND
   +0x058 pClipBase        : 0xfffff900`c5512fa0 tagCLIP
   +0x060 cNumClipFormats  : 4
   +0x064 iClipSerialNumber : 0x16
   +0x068 iClipSequenceNumber : 0xc1
   +0x070 spwndClipboardListener: 0xfffff900`c1a53440 tagWND
   +0x078 pGlobalAtomTable: 0xfffff980`0bd56c70 Void
   +0x080 luidEndSession   : _LUID
   +0x088 luidUser         : _LUID
   +0x090 psidUser         : 0xfffff900`c402afe0 Void

Если запустить dt на spwndClipboardListener, то увидим поле spwndClipboardListenerNext с указанием следующего прослушивающего процесса:

kd> dt 0xfffff900`c1a53440 tagWND spwndClipboardListenerNext
win32k!tagWND
   +0x118 spwndClipboardListenerNext : 0xfffff900`c1a50080 tagWND

При достижении последнего процесса в списке прослушивающих буфер tagWND, значение его поля spwndClipboardListenerNext будет нулевым:

kd> dt 0xfffff900`c1a50080 tagWND spwndClipboardListenerNext
win32k!tagWND
   +0x118 spwndClipboardListenerNext : (null)

Используя адрес окна, мы можем тем же методом добраться до названия процесса. Как упоминалось ранее, поскольку tagWND — это структура ядра, ОС сама хранит указатели spwndClipboardListener/spwndClipboardListenerNext, так что они не могут привести к таким проблемам с отслеживанием буфера, как цепочки просмотра.

На этом заканчивается наш обзор буфера обмена Windows. Надеюсь, для вас он стал информативным. Хотите узнать больше о мониторинге буфера обмена? Вот хорошая статья MSDN об этом.
Поделиться публикацией

Похожие публикации

Комментарии 40

    +3
    Еще бы понять почему в Windows ломается кодировка у русского текста если при копировании этого текста в буфер обмена не включена русская раскладка клавиатуры. Интересна причина этого бага.
      +1
      Это где так? Из того, что я встречал — некоторые программы что-то делают с локалями и будучи запущенными ломают кодировку при попадании текста в буфер (даже если сама программа в это время не активна). Явно помню, что клиент UltraVNC этим грешил.
        +3
        Обычно это происходит в программах, использующих ANSI-интерфейсы в юникодной среде. Когда какой-нибудь GetWindowTextA превращается прослойкой совместимости в GetWindowTextW, а строковой ANSI-параметр конвертируется в юникодный.
          0
          Это где так?
          очень часто сталкиваюсь в qip клиенте. И да, icq еще пользуются :)
            –1
            До сих пор?

            Подходил к концу 2017-й год. Unicode существует уже больше 26 лет. Илон Маск скоро улетит на Марс. Русские программисты никак не научатся использовать Unicode.

            Я ещё лет 10 назад отказался от всех кривых русских приложений именно из-за проблем с кодировками.
          +4
          Потому что для перекодировки юникодного текста из буфера в ANSI и обратно Windows выбирает язык по текущей раскладке, а не по системной локали (использующейся для всех остальных конвертаций). К сожалению, не знаю, на какую планету аутсорсили эту часть и какими частями тела там принято разрабатывать системы…
            +3
            Из-за этого в некоторых прогах применяют кунфу по переключению раскладки из программы перед копированием текста в буфер обмена. Странно что этот старинный баг в Microsoft не считают нужным исправлять.
              +2
              Одно время был популярен хак с подстановкой кодовой страницы 1251 на место 1252-й в реестре (а особо рьяные и не задумывающиеся делали то же самое для целой пачки страниц 1250–1258). Рабочий способ, но, увы, не без недостатков.

              Что касается исправления, в голове у меня крутятся какие-то остатки когда-то прочитанного, что это трактуется как фича — типа на каком языке печатаешь, на том и будет вставлен текст. Так что у меня стойкое подозрение, что исправлено это не будет никогда, несмотря на очевидный идиотизм.
                +2
                Он перестал быть популярен с уменьшением доли программ, работающих с однобайтными кодировками. Раньше по работе настолько часто сталкивался, что приходилось править. Сейчас большинство работает в юникод и проблема всплывает слишком редко, чтобы заморачиваться.
                типа на каком языке печатаешь, на том и будет вставлен текст.
                В смысле предполагалось, что туда переводчик впихнут? Потому, что в остальных случаях явный бред. Кстати а в буфере обмена для однобайтных кодировок вообще сохраняется инфа о кодовой странице?
                  0
                  В смысле предполагалось, что туда переводчик впихнут? Потому, что в остальных случаях явный бред.
                  Вот уж не знаю, что там предполагалось, я тоже не смог придумать внятного сценария, когда это было бы полезно.
            0
            Могу рассказать, как я решил для себя эту проблему. Я установил английскую и русскую раскладку в русском языке, а английскую раскладку в английском языке удалил. Получается, что какой бы набор букв на клавиатуре не был бы выбран, то язык все равно русский. Правда, не уверен, что такой хак сработает в Windows 10.
              0
              А если в ворде на таком «английском» печатать, какой язык присваивается тексту? Одна из проблем подмены кодовой страницы была как раз в неправильном языке (из-за чего документ отображался криво там, где хака не было). Интересно, подвержен ли этот способ такой проблеме?
                0
                Будут латинские буквы на русском языке. Но это тоже лечится. Нажимаешь Ctrl-H, задаешь Найти: ([a-z]); Заменить: Формат\язык английский; ставишь флажок Подстановочные знаки, нажимаешь Заменить все.

                Потом никто не мешает временно вернуть английскую раскладку с английским языком. Я так иногда делал, когда надо было на английском документы писать.
                  0
                  Спасибо за инфу!
                  Жаль, я надеялся, что найдено полноценное решение проблемы… :-(
                    +1
                    Полноценное решение — не пользоваться программами с однобайтовыми полями ввода.
              +5
              Когда приложение копирует в буфер plain text (CF_TEXT), то хорошей манерой будет также положить в буфер код локали (CF_LOCALE) для этого текста. Если кода локали в буфер не положили, то Windows присвоит код локали по текущей раскладке. Пруф

              Ну, и все англоязычные приложения на установку CF_LOCALE дружно забивают. Зачем? Они же знают, что все работают в своей раскладке.
              0
              Как в 2003-м сервере в терминальной сессии глючил буфер обмена при частом использовании, так и сейчас в 7-ке тоже самое.
              Блин, базовый компонент сделать просто работающим не могли…
                +1

                как я от этого страдал… когда буфер просто переставал работать. А способ лечения знал только перезагрузкой. А удаленный сервер перезагружать было нельзя. Качал файлы через выкладывание в IIS, даже простые тексты выкладывал на вебсервере, чтобы тупо скопировать текст (( И только когда проблема ушла с новой виндой, я случайно узнал, что это можно было решить перезапуском rdpclip.exe

                  0
                  А еще и отваливающаяся полностью переключение раскладок клавиатуры. Иногда перестающее открываться меню по кнопке Пуск. А еще может отваливаться открытие любых параметров системы с сообщением об ошибке «Rundll32.exe точка входа не найдена» очень лаконичным и написанным для пришельцев.
                  И это в Windows 10 в 2017 году…
                    +2
                    По большому счёту, «отвалить» в системе можно что угодно, благодаря стороннему софту от индусов. А воспроизводятся ли эти баги на голой винде без драйверов и прочего ПО, созданного не майкрософтом?
                      0
                      А зачем такая ОС, которую может сломать любая индусская программа? Многозадачные ОС как раз и созданы для изоляции процессов друг от друга.
                      Конечно, есть драйвера, которые для скорости работают в режиме ядра с полным доступом, но как оно может пересекаться с калькулятором, который у меня однажды на десятке отвалился до следующего крупного обновления, я не представляю.
                        0
                        А зачем такая ОС, которую может сломать любая индусская программа?
                        Ну дык эта. Чтоб було.

                        Многозадачные ОС как раз и созданы для изоляции процессов друг от друга.
                        К UI это не относится. Linux (вернее X Windows System), MacOS и Windows написаны так, что UI там работает нормально только если «кривых» программ в системе нет.

                        Вот iOS/Android (ну и почивший в бозе Windows Phone) — там да, есть защита. Но это уже новое поколение.

                        Конечно, есть драйвера, которые для скорости работают в режиме ядра с полным доступом, но как оно может пересекаться с калькулятором, который у меня однажды на десятке отвалился до следующего крупного обновления, я не представляю.
                        Очень просто: очень много вещей на десктопе шарится между программами. GDI-обьекты в Windows, разные X Windows обьекты в Linux и их аналоги в MacOS — тоже. Ну просто никто об этом не думал — считалось что защищать процессы, принадлежащие одному пользователю друг от друга — нет смысла.

                        Сейчас потихоньку этим начинают занимать, но сколько лет пройдёт пока до ума доведут — одному богу ведомо.
                      0

                      Это на какой-то инсайд сборке? Лично ни разу не видел подобного. Вот проблемы с драйверами из центра обновлений частое явление. Правда с одной оговоркой — это касается ноутбуков с apu от амд.

                        0
                        Иногда наблюдается на последних стабильных билдах, как вообще без сторонних программ, так и со сторонними программами.
                    +1
                    Странноватая статья. Почти все описанное я узнал еще из хелпа к Borland Pascal for Windows, затем неоднократно освежал в памяти, читая различные буквари по windows API. Отладчик для буфера обмена? Нет, не требовался. Разве что нюансы про unicode прошли мимо моего внимания, ибо всё это было очень давно…
                    Неужели есть программисты, которые до сих пор не в курсе, как устроен буфер обмена?! Хотя бы в общих чертах, о чем как раз статья…
                      +7

                      Таки Вы не верите, что существуют программисты моложе Вас, которые не читали хелп к древним средам разработки, и у которых не доходили руки посмотреть, как работает то, с чем они, к примеру, не сталкивалсь как программисты? Или любой, кто моложе Вас или не знет, как работать с буфером обмена, для Вас по определению не программист?


                      Странноватый, но, к сожалению, типичный комментарий в стиле "зачем писал? Я это знаю, значит и другим неинтересно"

                        –2
                        Я, грешным делом, думал, что какие-то базовые понятия знают все. Ну как таблицу умножения. Если уж я, не программист, знаю, то ПРОГРАММИСТы-то уж точно должны… Ан нет.

                        Ну извините, ошибся.
                          +3
                          А ещё есть программисты, которые не знают этого просто потому, что БО им по работе нафиг не нужен. Однако чисто для информации вполне интересно.

                          Ну и я, например, использую программу перекодировки текста (snoop) которая с буфером что-то химичит и не всегда корректно. Вот теперь думаю, а можно ли было по другому сделать.
                            +2

                            Допустим, я работаю на MacOS, пишу приложения для Android, и я должен быть в курсе, как устроен буфер обмена на Windows? Хотя почитать было интересно.

                        +2

                        Спасибо переводчику за труд и Микрософту за интересную статью. Когда начинаешь по-настоящему знакомиться с устройством Windows, куда-то исчезает весь этот пафос типа "маздай" и "отстой";) Про всякое нутро очень много и интересно написано в книгах Windows Internals.
                        Архитектура с цепочкой вызовов, конечно, смелая. Видимо, ребята в тот момент не понимали, насколько много "индусского" кода, и что вообще многие кодят по туториалам, не вдаваясь в подробности (а потом ругают, что "тупая винда" не работает так, как они представляют, не понимая, что их представление ошибочно). Сам грешен.
                        Хорошо, что потом исправили. И, кстати, довольно изящно — хочешь, чтобы тебе никто не подпортил слежение за буффером, используй новый интерфейс. Мигрируют все добровольно и с песней;)

                          0
                          Архитектура с цепочкой вызовов, конечно, смелая.

                          В delphi все события так работают. Наверно, в то время так было принято.

                            +1
                            Когда всё это проектировалось ресурсов на вытесняющую многозадачность не было, все тогдашние «пользовательские» ОС использовали кооперативную или были вообще однозадачными.

                            На этом фоне какая-то там «цепочка следящих за буфером» — мелочи.
                          0

                          Интересная статья. Немного странно видеть её в 2017м, а не раньше, ведь технология очень старая.

                            +3
                            В статье помоему изучается достаточно старый API, основанный на оконных сообщениях (WM_*), хотя в Windows давно есть возможность в Clipboard запихнуть COM обьект (эту штуку я так понимаю они развивали для OLE/OLE2) и вообще не думать об WM_*. Этот COM обьект может поддерживать тучу интерфейсов, в том числе интерфейс конвертации в обычный текст, и старые приложения работающие через WM_* смогут его достать без специальных движений (адаптеры встроены в Windows).

                            Когда мы писали приложения под windows и надо было сделать copy/paste сложных обьектов, мы сделали интересную фишку — в Clipboard копировался наш COM обьект с проприетарным интерфейсом, когда его вставляли в наше же приложение — мы копировали обьект 1к1 (так как могли получить этот проприетарный интерфейс), а если например в Notepad то он конвертировался в XML «на лету». Удобно было смотреть в текстовых редакторах что наредактировали в GUI через обычный copy/paste. Насколько я знаю копирование из Excel в Word так и сделано, через COM обьекты, а не текст, а вся эта надстройка WM_* — сугубо для поддержки старых приложений.

                              0
                              Вот мне не хватает такой программы.

                              а) нажимаешь горячую клавишу и появляется окошко с выбором
                              б) можно выбрать различные настраиваемые действия
                              в) результат этих действий кладется в буфер и сразу же вызывается вставка из буфера в активное
                              г) если содержимое буфера соответствует определенным правилам, то вместо вставки вызывается внешняя программа, например, браузер.
                              г) и чтобы можно было посмотреть историю последних N объектов, лежавших в буфере.

                              Я бы например настроил в такой программе вставку текущей даты, нового GUID, форматирования XML, автоматический перевод текста на русский и еще кое-что по мелочи.

                              Может, подскажете, что почитать, чтобы написать такую утилиту самому?
                                +1
                                Если на WinAPI
                                а) RegisterHotKey
                                б) Создаете и показываете popup меню
                                в) Вставка в буфер описана в статье, а заставить текущее окно сделать вставку из буфера можете отправкой сообщения WM_PASTE активному элементу, который получается из GetGUIThreadInfo, поле hwndFocus. Сообщение стандартное, но не факт, что будет работать для всех окон и элементов.
                                г) CreateProcess
                                д) Записываете текст из буфера в текстовый же файл, а когда надо показать — открываете блокнот с этим файлом тем же CreateProcess
                                  0
                                  Спасибо. Будет время — попробую. Эти функции доступны в C# или нужно на C++ код написать?
                                    0
                                    По ссылкам примеры на C++
                                    Что есть или нет в C# мне неведомо. Полагаю, что аналоги или обертки должны быть.
                                      0
                                      C# умеет в винапи, PInvoke же
                                  0
                                  Так вы пишете про другую задачу. Для Copy/Paste оконные сообщения не нужны. Вопрос в том, как следить за изменением буфера без WM_*, не делая периодических попыток читать буфер.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое