Monday, June 7, 2010

Странные ошибки и ExternalException при работе с буфером обмена

В комментарии к предыдущей записи Омен подбросил задачку:
… предлагаю для решения простую задачку: посмотреть, как работает пример из этого поста при запущенной вот этой маленькой программке. Выяснить, что и почему происходит, и как с этим бороться.
Чур без рефлектора:)
Ну что же, давайте разбираться. Грузим архив, открываем приложение. Видим обычную форму без каких-либо компонентов. Что же с ней не так? В этот момент я отвлекся на сообщение в скайпе, а когда попытался скопировать оттуда ссылку, вот что получилось:
Получается, наш TestClipboard.exe каким-то образом запорол доступ к буферу обмена (причём, не только для скайпа — чтобы сделать скриншот выше, тест-приложение пришлось закрыть). Как он это сделал? Можно, конечно, посмотреть рефлектором, но нас же просили этого не делать — поэтому пойдем слегка обходным путём:
  1. У меня уже была открыта Visual Studio с каким-то проектом, и я банально нажал F11, чтобы перейти в режим отладки. Открытый проект совершенно не важен, потому как нам будет нужен Immediate Window. Давайте попробуем из него получить текст из буфера обмена:
    Clipboard.GetText()
  2. Студия, как и скайп раньше, не может получить доступ к буферу обмена, только в этот раз у нас куда больше информации:
  3. Параметр ErrorCode — как раз то, что нам нужно. Поскольку под классом Clipboard находятся нативные функции Windows, мы будем смотреть, что не так у них. Конвертируем -2147221040 в hex, получаем 0x800401d0. Поискав число в MSDN или заглянув в winerror.h, узнаём, что числу соответствует константа CLIPBRD_E_CANT_OPEN.
  4. Давайте посмотрим теперь, какая функция WinAPI вернула нам такой результат. В описании константы в MSDN это указано:
    0x800401D0 CLIPBRD_E_CANT_OPEN
    OpenClipboard failed.
    Итак, «виновница» торжества — функция WinAPI OpenClipboard.
В процессе расследования мы спустились с вершин дотнета в старое доброе WinAPI. И вот как в нём устроена работа с буфером обмена:
  1. Приложение вызывает функцию OpenClipboard для получения доступа к буферу обмена. С этого момента доступ к буферу закрыт для всех остальных приложений.
  2. Приложение вызывает либо функцию GetClipboardData для получения данных, либо EmptyClipboard для его очистки и SetClipboardData для записи данных в буфер обмена.
  3. Приложение закрывает буфер обмена при помощи функции CloseClipboard.
Итак, после первого пункта больше никто, кроме приложения, не может получить доступ к буферу. Если вызвать OpenClipboard и не закрывать буфер (что и делает наше тестовое приложение), то можно отобрать доступ к буферу у всех остальных (не делайте так, пожалуйста). Также работа с буфером может вызвать исключение, если в момент вызова какое-то приложение находится на пункте 2 работы. Это довольно редкая ситуация, но её стоит учитывать — поэтому работа с буфером всегда должна обрабатывать ExternalException. Мы учтём это в следующей версии нашего монитора буфера обмена.

OpenClipboard Function (Windows)
Clipboard (Windows)

    No comments:

    Post a Comment