ГЛАВА 8. ТЕХНОЛОГИИ ОБМЕНА ИНФОРМАЦИЕЙ В ОС WINDOWS 1 глава




 

§ 8.1.Обменинформациейпосредствомбуфераобмена Windows

 

8.1.1. Структура и основные форматы буфера обмена

 

Буфер обмена Windows состоит из двух разных частей: утилиты просмотра

Clipbrd.EXE и непосредственно буфера. Сам буфер реализован с помощью системного

модуля User и обеспечивает набор функций для временного хранения информации в та-

кой форме, которая позволяет осуществлять обмен данными между различными прило-

жениями. Конечно, приложение-источник тоже может прочитать собственную инфор-

мацию, записанную в буфер обмена, но дело в том, что эта информация является

глобальной и доступна для любых программ [6, 12].

Буфер обмена предоставляет набор возможностей для временного (не дискового)

хранения информации. Данные, записанные в буфер обмена, могут передаваться между

различными приложениями или возвращаться в исходное приложение, из которого они

попали в буфер.

Приложение-источник может копировать данные в буфер обмена в одном из пре-

допределенных или пользовательских форматов (будут рассмотрены ниже). Буфер само-

стоятельно управляет выделением памяти и размещением переданных ему данных.

После того как данные попадут в буфер обмена, любое приложение сможет полу-

чить к ним доступ, определить тип данных и, при желании, скопировать их.

Когда программа просмотра Clipbrd.EXE является активной, она регулярно запра-

шивает буфер обмена о том, содержатся ли в нем данные и каков их тип. Затем, если это

возможно, программа копирует данные из буфера и отображает их в своем окне.

Хотя буфер обмена выполняет свои "обязанности" очень хорошо, ему свойственны

и некоторые недостатки:

• имеется только один буфер;

• все данные, записанные в буфер, являются общедоступными;

• записанные в буфер данные могут быть разрушены.

Так как буфер обмена только один, все приложения должны использовать его со-

вместно. Но совместное использование неизбежно связано с возможностью конфликтов.

Предположим, что приложение А записало в буфер обмена растровое изображение, а

затем приложение Б записало в него блок текстовых данных. Поскольку приложение Б

вполне закономерно начинает с того, что очищает буфер обмена, картинка, записанная

приложением А, удаляется. Если приложение В, для которого была предназначена эта

картинка, уже успело ее прочитать, то все в порядке. Если же приложение В не скопиро-

вало изображение до того, как приложение Б записало в буфер обмена текст, картинка

пропадает.

Общедоступность буфера обмена также может стать причиной возможных ошибок.

Поскольку элемент данных, записанный в буфер, не может быть адресован какому-либо

конкретному приложению, доступ к этой информации может быть ошибочно получен

другим приложением, которое работает с данными того же типа [12].

Буфер обмена может содержать данные нескольких типов, записанных одним или

несколькими приложениями. В этом случае проблема заключается в том, как различить

эти блоки - например, несколько блоков текста, записанных из разных источников. По

этой причине приложение обычно очищает буфер обмена, перед тем как записывать в

него новый материал.

Эти факторы необходимо учитывать, но они не представляют собой серьезной

проблемы. Если перечисленные недостатки накладывают дополнительные ограничения

на работу программы, можно воспользоваться технологиями каналов (pipe), сокетов

 

 


 

(sockets) или динамического обмена данными (DDE), которые будут рассмотрены далее.

Эти технологии позволяют реализовать безопасный канал для обмена информацией.

Windows поддерживает 14 стандартных форматов данных буфера обмена, опреде-

ленных в файле WinUser.H. Это форматы:


 

CF_BITMAP

CF_DIB

CF_DIF

CF_ENHMETAFILE

CF_METAFILEPICT


 

CF_OEMTEXT

CF_PALETTE

CF_PENDATA

CF_RIFF

CF_SYLK


 

CF_TEXT

CF_TIFF

CF_UNICODETEXT

CF_WAVE


 

В файле WinUser.H определен также ряд специальных форматов, или флагов фор-

мата. Кроме того, каждое приложение может предложить собственный пользователь-

ский формат данных буфера обмена, которые будут рассмотрены далее. Но для боль-

шинства целей вполне достаточно стандартных форматов. Рассмотрим эти форматы

более подробно.

Текстовыеформаты. Простейший формат данных буфера обмена - это текстовый

формат CF_TEXT, который состоит из набора строк ANSI-символов, заканчивающегося

нулевым символом. Каждая строка завершается символами возврата каретки (0x0D) и

перевода строки (0x0A). Формат CF_OEMTEXT представляет собой набор OEM-

символов. Формат CF_UNICODETEXT использует 32-разрядные символы набора

Unicode.

Передав текст в буфер обмена, исходное приложение не имеет возможности об-

ратиться к этому тексту, не запросив доступ к буферу.

Форматрастровыхизображений. Формат CF_BITMAP служит для хранения

растровых изображений путем передачи буферу обмена дескриптора изображения. За-

писав изображение в буфер, исходное приложение не имеет возможности обратиться к

этому изображению, не запросив доступ к буферу обмена.

Форматыметафайлов. Формат CF_METAFILEPICT служит для обмена мета-

файлами, находящимися в памяти (а не на диске), между различными приложениями.

Этот формат использует структуру METAFILEPICT, которая определена в файле

WinGDI.H следующим образом:

 

typedef struct tagMETAFILEPICT

{

LONG mm;

LONG xExt;

LONG yExt;

HMETAFILE hMF;

} METAFILEPICT, FAR *LPMETAFILEPICT;

 

Первые три поля этой структуры отображают различия, наблюдаемые при переда-

че метафайлов посредством буфера обмена и через дисковые файлы. Первое поле, mm,

указывает предпочтительный режим отображения. Второе и третье поля, xExt и уExt,

задают ширину и высоту содержащегося в метафайле изображения. Поле hMF содержит

дескриптор метафайла.

Формат CF_ENHMETAFILE аналогичен формату CF_METAFILEPICT, но в отли-

чие от последнего он идентифицирует метафайл, в котором используются расширенные

команды форматирования.

DIB-формат. Формат CF_DIB применяется для передачи в буфер обмена DIB-

файлов (аппаратно-независимых растровых изображений). DIB-изображение записыва-

 



 

 

ется в виде глобального блока памяти, начинающегося с заголовка BITMAPINFO, после

которого следуют данные изображения.

Передав растровое изображение в буфер обмена, исходное приложение не имеет

возможности обратиться к соответствующему глобальному блоку памяти, не запросив

доступ к буферу обмена.

Форматыпалитрыипера. Форматы CF_PALETTE и CF_PENDATA служат для

передачи в буфер обмена дескриптора цветовой палитры и дескриптора пера соответст-

венно. Формат CF_PALETTE часто применяется в сочетании с форматом CF_DIB для

записи цветовой палитры, используемой растровым изображением.

Wave-формат. Формат CF_WAVE предназначен для передачи между различными

приложениями аудиоинформации.

Форматыспециальногоназначения. Три следующих формата изначально пред-

назначались для применения в специализированных приложениях.

• Формат CF_TIFF используется для передачи данных в формате TIFF (Tagged Image

File Format).

• Формат CF_DIF служит для передачи данных в формате DIF (Data Interchange For-

mat), который был предложен фирмой Software Arts и первоначально применялся в

редакторе электронных таблиц VisiCalc. Теперь лицензия на данный формат при-

надлежит фирме Lotus. DIF-формат основан на ASCII-строках, завершающихся па-

рой символов CR/LF (возврат каретки/перевод строки).

• Формат CF_SYLK предназначен для передачи данных в формате Microsoft Symbolic

Link, который первоначально применялся для обмена данных между Microsoft-

приложениями Multiplan (электронные таблицы), Chart и Excel. Этот формат осно-

ван на ASCII-строках, завершающихся парой символов CR/LF (возврат карет-

ки/перевод строки).

 

8.1.2. Операции с буфером обмена

 

Управление глобальными блоками памяти, которые содержат данные, помещенные

в буфер обмена, осуществляется с помощью флагов выделения памяти. При записи ин-

формации в буфер программа выделяет блок памяти с помощью функции GlobalAlloc()

и флага GHND (определенного как GMEM_MOVABLE | GMEM_ZEROINIT).

Обычно при закрытии исходного приложения выделенная ему глобальная область

памяти удаляется (освобождается) операционной системой. Но если приложение вызо-

вет функцию SetClipboardData() с указанием дескриптора глобального блока памяти,

Windows возьмет этот блок в свою собственность, а точнее, в собственность буфера об-

мена, изменив флаги выделения.

Принадлежность глобального блока памяти назначается функцией GlobalRealloc():

 

GlobalRealloc(hMem, NULL, GMEM_MODIFY | GMEM_DDESHARE);

 

После этого вызова выделенный блок памяти исходному приложению не принад-

лежит и доступен только через буфер обмена, с помощью функции GetClipboardData().

Эта функция предоставляет вызвавшему ее приложению временный доступ к данным,

записанным в буфере обмена, передавая программе дескриптор глобального блока памя-

ти. Однако принадлежность блока данных по-прежнему сохраняется за буфером обмена,

а не передается приложению.

Следовательно, данные, записанные в буфере обмена, могут быть удалены только

путем вызова функции EmptyClipboard.

 

 


 

 

Хотя многие ресурсы Windows предназначены для совместного использования не-

сколькими приложениями, доступ к буферу обмена в каждый момент возможен только

одной программой. Это позволяет предотвратить конфликты между приложениями.

Прежде чем приложение начнет читать, записывать или удалять содержимое буфе-

ра обмена, необходимо запросить доступ к нему с помощью функции OpenClipboard().

Эта функция возвращает значение TRUE, если буфер открыт и доступ к нему разрешен,

и значение FALSE, если доступ к буферу запрещен по той причине, что в данный мо-

мент право доступа принадлежит другому приложению.

Закончив работу с буфером обмена, приложение должно вызвать функцию CloseC-

lipboard, которая делает буфер доступным для других программ.

Следует отметить, что каждый вызов функции OpenClipboard() всегда должен со-

провождаться вызовом функции CloseClipboard(). Приложение не должно пытаться дли-

тельное время удерживать буфер обмена открытым, а должно стремиться отдать кон-

троль над ним как можно быстрее.

Для записиданных в Clipboard можно использовать универсальную функцию

TransferToClipboard(), копирующую блок памяти в буфер обмена [12].

 

BOOL TransferToClipboard(HWND hwnd, HANDLE hMemBlock, WORD FormatCB)

{

if(OpenClipboard (hwnd))

{

EmptyClipboard();

SetClipbpardData(FormatCB, hMemBlock);

CloseClipboard ();

return(TRUE);

}

return(FALSE);

}

 

Функция TransferToClipboard() начинается с запроса на право доступа к буферу

обмена. Затем в буфер копируется одиночный блок памяти. Наконец, функция закрывает

буфер обмена, освобождая его для доступа другим приложениям.

Функция TransferToClipboard() имеет довольно универсальную структуру и допус-

кает любой тип дескриптора hMemBlock. Однако для ее работы необходим параметр

FormatCB, указывающий тип данных, копируемых в буфер обмена.

Термин блокпамяти не подразумевает блок какого-то определенного размера.

Размер блока был определен ранее функцией GlobalAlloc. Каждый блок памяти может

содержать абзац текста, несколько записей или другие данные, но обязательно одного

типа [12].

А как же быть, если приложение должно передать в буфер обмена смешанные дан-

ные, скажем растровое изображение, метафайл, палитру и фрагмент текста? Решение

этой проблемы довольно простое. Сначала каждый фрагмент данных копируется от-

дельно в глобальную выделенную память с сохранением дескриптора блока памяти, на-

пример hBitmap, hMetafile, hPalette и hText. После этого открывается и освобождается

буфер обмена и в него передается каждый из дескрипторов:

 

if(OpenClipboard(hwnd))

{

EmptyClipboard();

SetClipboardData(CF_BITMAP, hBitmap);

SetClipboardData(CF_PALETTE, hPalette);

 

 


 

 

}


 

 

SetClipboardData(CF_METAFILEPICT, hMetaFile);

SetClipboardData(CF_TEXT, hText);

CloseClipboard();


 

После этого буфер обмена закрывается и освобождается для доступа к нему других

приложений.

Правда, реализовать описанный выше подход на практике достаточно сложно. Но

поскольку идентификаторы форматов данных имеют тип WORD, а все дескрипторы

также могут быть приведены к единому типу HANDLE, гораздо удобнее собрать иден-

тификаторы и дескрипторы в массивы, а функции TransferToClipboard() передать допол-

нительный параметр, указывающий количество записанных элементов данных.

 

if(OpenClipboard(hwnd))

{

EmptyClipboard();

for(i=0; i<nCount; i++)

SetClipboardData(cfType[i], hData[i]);

CloseClipboard();

}

 

Прежде чем начинать чтениеинформации из буфера обмена, необходимо опре-

делить, данные какого типа содержатся в нем [12]. А поскольку для различных типов

данных необходимо применять разные операции, приложение должно заранее знать, что

именно ему придется читать, и подготовиться к определенному виду обработки. В част-

ности, вы можете запросить данные определенного типа и посмотреть на результат. Од-

нако такой подход не слишком элегантен, да и эффективность его не очень высока.

Более эффективный способ анализа содержимого буфера обмена заключается в ис-

пользовании API-функции IsClipboardFormatAvailable() или EnumClipboardFormats().

Функция IsClipboardFormatAvailable() возвращает булево значение, которое сооб-

щает, содержит ли буфер обмена данные нужного формата. Синтаксис вызова функции

таков:

 

if(IsClipboardFormatAvailable(CF_xxxx))

 

Функция EnumClipboardFormats проверяет наличие данных всех возможных фор-

матов. При первом вызове функции с параметром NULL она возвращает информацию о

первом доступном формате. При каждом последующем вызове возвращаются сведения о

других форматах. Таким образом, для получения списка форматов можно воспользо-

ваться следующим алгоритмом:

 

wFormat = NULL;

OpenClipboard(hwnd);

while(wFormat = EnumClipboardFormats(wFormat))

{

... код обработки различных форматов...

}

CloseClipboard();

 

В этом списке используемые форматы будут перечислены в том порядке, в кото-

ром исходное приложение записывало в буфер обмена соответствующие данные, Это

 

 



 

 

позволит запрашивающему приложению найти первый подходящий для себя формат.

Исходное приложение может записывать данные в любом другом заданном порядке, на-

пример в порядке убывания степени надежности данных.

Если список форматов исчерпан, буфер обмена пуст или если он не был открыт,

результат выполнения функции EnumClipboardFormats() будет равен нулю. Параметру

wFormat можно присвоить некоторое значение, с тем чтобы повторно прочитать список,

начиная с используемого в данный момент формата. Кроме того, информацию о количе-

стве форматов данных, находящихся в буфере обмена, можно получить с помощью опе-

ратора:

 

nFormats = CountClipboardFormats();

 

Когда приложение установит, что буфер обмена содержит данные нужного типа,

процесс чтения этих данных будет состоять из двух этапов:

• получение дескриптора блока памяти, соответствующего данным, которые находят-

ся в буфере обмена;

• выполнение определенных операций с данными с использованием имеющегося де-

скриптора.

Первый этап выполнить очень просто с помощью функции RetrieveCB:

 

HANDLE RetrieveCB(HWND hwnd, WORD FormatCB)

{

HANDLE hCB;

 

if(! IsClipboardFormatAvailable(FormatCB))

return(NULL);

OpenClipboard(hwnd); hCB = GetClipboardData(FormatCB);

CloseClipboard();

return(hCB);

}

 

Содержащаяся в этом фрагменте кода обобщенная процедура возвращает нетипи-

зированный дескриптор блока памяти, который соответствует данным, находящимся в

буфере обмена. Если запрашиваемый формат данных отсутствует, функция возвращает

значение NULL.

Следует отметить, что на операции, которые могут выполняться с буфером обмена,

налагается несколько ограничений.

• Прежде чем копировать данные в буфер обмена, необходимо вызвать функцию

EmptyClipboard(), предназначенную для удаления текущего содержимого. Сам факт

доступа к буферу обмена еще не означает, что будет получен контроль над его со-

держимым. Функция EmptyClipboard() позволяет стать владельцем буфера и одно-

временно удалить его текущее содержимое.

• Любое приложение может получить доступ к содержимому буфера обмена, но лишь

владелец буфера, т.е. приложение, вызвавшее функцию EmptyClipboard(), имеет

право записывать в него данные. Поскольку владельцем буфера обмена может быть

только одно приложение, данные, записанные предыдущим владельцем, удаляются,

даже если таковым было это же самое приложение.

• Хотя в буфер обмена может быть записано несколько блоков данных, передавать их

нужно в течение одной операции. Буфер обмена нельзя открыть, записать в него

данные, закрыть, а затем снова открыть и добавить другие данные, не удалив пре-

дыдущий фрагмент.

 


 

 

• В буфере обмена одновременно может находиться только по одному элементу дан-

ных каждого типа. Объясняется это той простой причиной, что не существует спо-

соба разделения нескольких элементов данных одного и того же типа. Однако при

наличии данных нескольких типов приложение, получившее доступ к буферу обме-

на, может запросить только один элемент, несколько элементов или все элементы. В

этом случае данные каждого типа должны запрашиваться отдельно.

Буфер обмена может многократно открываться для запроса других элементов дан-

ных или повторного запроса того же элемента. Но в целом, запрашивая фрагмент дан-

ных из буфера обмена, лучше всего создать его локальную копию, а не повторять один и

тот же запрос снова и снова. Кроме того, не забывайте: нет никакой гарантии того, что

при следующем запросе тот же самый элемент данных останется неизменным, посколь-

ку буфер обмена является общедоступным.

 

8.1.3. Частные форматы буфера обмена

 

В Windows определено несколько "частных" форматов: CF_DSPTEXT,

CF_DSPBITМАР, CF_DSPMETAFILEPICT и CF_DSPENHMETAFILE. Они соответст-

вуют форматам CF_TEXT, CF_BITMAP, CF_METAFILEPICT и CF_ENHMETAFILE, но

имеют одно принципиальное ограничение: приложения, запрашивающие стандартные

форматы, не смогут получить доступ к специальным форматам буфера обмена. В этой

связи следует сделать несколько замечаний.

• Данные, находящиеся в одном из специальных форматов, предназначаются для об-

мена информацией между двумя экземплярами одного и того же приложения или

между двумя приложениями, специально разработанными для совместной работы.

• В процессе подобного обмена может передаваться служебная информация, напри-

мер о форматировании и/или шрифтах.

Термин частныйформат может ввести в заблуждение. Дело в том, что получить

доступ к таковому может любое приложение. Ведь разрабатывались подобные форматы

не в целях безопасности, а лишь для обеспечения закрытого обмена данными через бу-

фер обмена [12].

Хотя два экземпляра одного и того же приложения или два связанных приложения

должны понимать свои частные форматы, применение этих форматов вовсе не гаранти-

рует, что данные поступили от другого экземпляра того же приложения или родственно-

го приложения. Иными словами, ничто не мешает использованию одинаковых частных

форматов разными, совершенно не связанными друг с другом программами.

Однако на этот случай предусмотрены специальные меры. Вы можете узнать "ав-

тора" содержимого буфера обмена с помощью функции GetClipboardOwner():

 

hwndCBOwner = GetClipboardOwner();

 

Приложение, вызвавшее функцию EmptyClipboard() для подготовки к копирова-

нию данных, становится новым владельцем буфера обмена. Другие приложения, обра-

щающиеся к буферу для чтения информации, его владельцами не являются - они просто

получают доступ. Таким образом, за содержимое буфера обмена ответственность несет

только его владелец. Приложение не может записать данные в буфер обмена, не став

сначала его владельцем и не удалив его предыдущее содержимое. Поэтому любое при-

ложение, получившее доступ к информации в частном формате, может также опреде-

лить принадлежность буфера обмена.

Хотя функция GetClipboardOwner() возвращает дескриптор, указывающий на вла-

дельца буфера обмена, сам по себе этот дескриптор ни о чем не говорит. Но имея деск-

риптор, имя класса приложения можно запросить с помощью другой функции:

 

 


 

GetClassName(hwndCBOwner, SszClassName, 16);

 

Теперь значение переменной szClassName можно сравнить с именем класса теку-

щего приложения или списком имен классов связанных приложений и таким образом

выяснить источник информации, записанной в буфере обмена.

Передача копии исходной информации в буфер обмена подразумевает дополни-

тельный расход памяти из-за необходимости дублировать блоки данных. При некоторых

обстоятельствах этот фактор может сыграть весьма существенную роль, особенно если

копируемые блоки достаточно велики. Одно из возможных решений, заключается в том,

чтобы передавать данные в буфер обмена, не сохраняя их копию, что полностью снима-

ет проблему.

Но есть и другое решение, которое применяется, в частности, при передаче боль-

ших объемов информации: отложенное копирование. В этом случае в буфер обмена не-

посредственно записывается только спецификация формата, а вместо дескриптора гло-

бального блока памяти соответствующему параметру присваивается значение NULL:

 

SetClipboardData(wFormat, NULL);

 

Когда приложение запрашивает данные, которые идентифицируются значением

NULL, а не дескриптором блока, Windows расценивает это как запрос отложенного ко-

пирования и вызывает владельца буфера обмена (приложение, поместившее в него дан-

ные) с помощью сообщения WM_RENDERFORMAT, в котором запрашиваемый формат

задан посредством параметра wParam.

В ответ на сообщение WM_RENDERFORMAT приложение вызывает функцию

SetClipboardData(), которой передается дескриптор глобального блока памяти и иденти-

фикатор формата (вместо вызова функций OpenClipboard() и EmptyClipboard()). Таким

образом, реальные данные передаются только тогда, когда получатель готов принять их.

В буфер обмена можно поместить несколько элементов в виде комбинации обыч-

ных и отложенных данных.

Когда приложение перестает быть владельцем буфера обмена, Windows посылает

ему сообщение WM_DESTROYCLIPBOARD, констатирующее этот факт. В ответ на это

сообщение приложение может вернуть себе право на владение буфером обмена и снова

передать в него те же самые данные, однако такой способ рекомендуется применять

лишь в исключительных ситуациях.

Кроме того, если приложение готово завершить свою работу, но является владель-

цем буфера обмена, который содержит дескрипторы данных со значением NULL, Win-

dows посылает сообщение WM_RENDERALLFORMATS без указания каких-либо спе-

цификаций формата. В ответ на это сообщение приложение-владелец должно либо

полностью очистить буфер обмена, либо завершить отложенные вызовы.

Но в этом случае, в отличие от реакции на сообщение WM_RENDERFORMAT,

приложение, прекращающее свою работу, не будет вызывать функцию

SetClipboardData(), а просто очистит буфер обмена и целиком запишет в него новые дан-

ные, как если бы отложенные вызовы вообще не существовали.

Еще один частный формат данных буфера обмена объявляется следующим обра-

зом:

 

SetClipboardData(CF_OWNERDISPLAY, NULL);

 

Данные типа CF_OWNERDISPLAY всегда передаются с глобальным дескриптором

памяти, имеющим значение NULL (по аналогии с форматом отложенного копирования).

 

 


 

 

Но поскольку в этом случае владелец буфера обмена несет непосредственную ответст-

венность за отображение данных, при их запросе Windows не посылает сообщение

WM_RENDERFORMAT. Просто сообщение из программы просмотра должно быть пе-

редано непосредственно программе-владельцу буфера обмена.

Как было сказано ранее, идентифицировать владельца буфера можно с помощью

функции GetClipboardOwner(). И наоборот, программа-владелец может определить про-

грамму просмотра с помощью функции GetClipboardViewer().

При использовании данного формата программа просмотра посылает приложению-

владельцу запрос на формирование реального изображения и предоставляет ему доступ



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-04-02 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: