    Использование OLE для работы с буфером обмена 

    В предыдущих главах рассматривались те аспекты MFC, которые являются 
общими для всех Windows-программ. На их основе формируются основные принципы 
программирования под Windows с использованием MFC. Но в библиотеке содержится 
также множество других функций и подсистем. Наверное, самой важной из 
подсистем, которую стоит изучить, является OLE. OLE играет все более 
существенную роль в Windows. К сожалению, эта подсистема очень велика и 
чрезвычайно сложна. Ее полное описание заняло бы целую книгу. Но есть одна 
небольшая часть OLE, которую очень просто использовать: это буфер обмена. 
Кроме того, организация работы с буфером обмена необходима всем программам, 
претендующим на то, чтобы считаться "OLE-совместимыми". Поэтому включить в 
программу базовую поддержку OLE достаточно просто. 
    В данном приложении рассматривается пример написания короткой 
OLE-программы, предназначенной для работы с буфером обмена. В этой программе 
используется лишь малая часть функций OLE и незначительная часть функций 
работы с буфером обмена, так как в ней демонстрируются только базовые принципы 
написания OLE-программ. 

    523 

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

    Что такое OLE? 

    OLE расшифровывается как Object Linking and Embedding (Связывание и 
встраивание объектов). В широком смысле, технология OLE является формой 
межзадачного взаимодействия. В частности, она позволяет одному приложению 
подключать или встраивать в себя информацию, созданную другим приложением. При 
этом создается так называемый составной документ, который еще называют OLE-документом. 
    Необходимо понимать, что с момента появления технология OLE прошла 
длительный путь развития. OLE версии 1.0 была представлена в 1991 году, но 
использовалась редко. С появлением OLE версии 2.0 технология совершила 
качественный скачок вперед по части возможностей и сферы применения. Поэтому 
ожидается, что число OLE-совместимых приложений будет продолжать стремительно расти. 
    Главное, что OLE 2 — это не просто новая версия OLE I. OLE 1 специально 
разрабатывалась для связывания и встраивания объектов, тогда как OLE 2 
предназначена для поддержки создания расширяемых приложений. И хотя процесс 
расширения приложения включает в себя связывание и встраивание объектов, OLE 2 
идет гораздо дальше этих двух операций. 
    Поскольку OLE 2 полностью вытеснила OLE 1, в оставшейся части приложения 
будет использоваться просто термин "OLE". 

    Примечание: В данном приложении содержится лишь базовое описание средств 
OLE-программирования. На полное изучение данной темы необходимо затратить 
много времени и усилий. Для этого вам понадобятся также справочные руководства 
по OLE. 

    Что такое связывание и встраивание объектов? 

    Поскольку ядром OLE являются операции связывания и встраивания, необходимо 
разобраться, что же означают сами эти термины. Составной документ может 
содержать два типа объектов: связанные и встроенные. Когда объект связан с 
документом, это означает, что в составном документе содержится ссылка на 
объект, но сам объект в действительности в нем не хранится. Когда объект 
встроен в документ, те он является частью документа. У каждого метода имеются 
свои преимущества и недостатки. 
    Основным преимуществом связывания объектов является то, что связанные с 
документом объекты могут быть изменены другим приложением, и это изменение 
автоматически отобразится в документе. Другими словами, связывание объекта 
гарантирует, что в документе всегда будет содержаться самая последняя версия 
объекта. Еще одним преимуществом является то, что размер составного документа 
будет меньше, чем если бы объект был встроен, поскольку в документе хранится 
только ссылка на объект, а не он сам. Принципиальным недостатком такого метода 

    524                                    MFC: основы программирования 

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

    Модель компонентного объекта 

    Технология OLE основана на модели компонентного объекта. Эта модель 
определяет способ, по которому OLE-совместимые приложения взаимодействуют друг 
с другом. В частности, на ее основе определяются OLE-интсрфейсы, а также 
механизмы управления памятью, межзадачного взаимодействия и динамической 
загрузки требуемого объекта. Но самое главное, что необходимо знать об OLE (и 
что является основным принципом разработки OLE-программ), это то, что все 
OLE-приложсния должны иметь стандартные интерфейсы, через которые приложения 
взаимодействуют друг с другом. 
    В модели компонентного объекта выделяется два типа приложений: контейнеры 
и серверы. В простейшем случае контейнер является приложением, которому 
требуются данные, а сервер — это приложение, которое такие данные 
предоставляет. Контейнер, как правило, представляет собой составной документ. 
Приложения-контейнеры также называют клиентами. Контейнеры и серверы 
взаимодействуют друг с другом через интерфейсы, определенные в OLE. 

    OLE-интерфейсы 

    Все OLE-совместимые приложения поддерживают интерфейс, через который 
осуществляется взаимодействие между ними. И хотя в OLE имеется несколько 
интерфейсов, общим для всех приложений является один: lUnknown. С помощью 
функций данного интерфейса приложение может узнать, какие еще интерфейсы 
доступны в другом приложении. 
    Поскольку рассмотрение технических подробностей выходит за рамки данной 
главы, необходимо хотя бы в общих чертах разобраться, что же такое 
OLE-интерфейс и как программы взаимодействуют друг с другом через него. В 
каждом интерфейсе имеется таблица указателей на содержащиеся в нем функции. 
Когда контейнер устанавливает связь с сервером, он получает указатель на 
интерфейсную таблицу сервера. Таким образом, через указатели этой таблицы 
контейнер может получать доступ к функциям сервера. 
    Поскольку в OLE определено несколько типов интерфейсов, возникает вопрос: 
каким образом приложение-клиент узнает о том, какие интерфейсы доступны на 

    В. Использование OLE для работы с буфером обмена                   525 

    сервере? Для этого используется функция Query Interface (), содержащаяся в 
интерфейсе lUnknown. С ее помощью одно приложение может узнать, какие 
интерфейсы доступны в другом приложении, и получить указатели на 
соответствующие интерфейсные таблицы. 
    Использование MFC для написания OLE-совместимых приложений удобно тем, что 
большинство частных деталей, связанных с OLE, скрываются и обрабатываются 
автоматически. Однако интерфейс lUnknown и общая процедура его использования 
по-прежнему должны в явном виде присутствовать в программе. 

    OLE-автоматизсщия 

    Другой важной особенностью OLE является автоматизация. С помощью механизма 
автоматизации одно приложение может получать доступ к объектам другого 
приложения, управлять и манипулировать ими. Благодаря OLE-автоматизации 
можно'создавать сложные программы системного уровня, имеющие доступ к функциям 
различных приложений, работающих в системе. OLE-автоматизация — это передовая 
область программирования в Windows. 

    Является ли OLE будущим Windows? 

    Хотя технология OLE первоначально была разработана только для поддержки 
связывания и встраивания объектов, ее роль с тех пор коренным образом 
изменилась. Области применения OLE значительно выходят за пределы этих двух 
операций. Кроме того, OLE-автоматизация открывает пути для написания целого 
класса совершенно новых прикладных программ. Важнее всего то, что OLE 
предлагает альтернативный, объектно-ориентированный способ представления всей 
среды Windows. Результаты применения технологии OLE полностью проявят себя 
лишь через несколько лет, однако уже сейчас можно смело утверждать, что в 
будущем OLE станет важнейшей областью программирования в Windows. 

    Буфер обмена 

    Во многих Windows-приложениях не требуется связывание или встраивание 
посторонних объектов. Кроме того, многие приложения не создают данные, которые 
могут быть связаны или встроены в другие приложения. Таким образом, в 
небольших программах использовать большую часть функций OLE не имеет смысла. 
Но есть одна подсистема OLE, работа с которой ведется во многих приложениях, — 
это буфер обмена. И хотя с ним можно работать без использования OLE, 
реализовать это средствами OLE настолько легко, что в большинстве случаев 
именно так и поступают. Тем более, что организация работы с буфером обмена 
через OLE является первым шагом на пути превращения программы в 
OLE-совместимое приложение. 
    Как известно, буфер обмена в Windows выполняет две функции. Во-первых, он 
служит временным хранилищем для записанных в него данных. Например, чтобы 
переместить блок текста в текстовом редакторе, необходимо временно скопировать 

    526                                    MFC: основы программирования 

    этот блок в буфер обмена, а затем скопировать его из буфера в новую 
позицию документа. Во-вторых, буфер обмена является простым способом 
межзадачного взаимодействия. Это одно из наиболее популярных средств обмена 
данными между различными программами. Работа с буфером обмена ведется 
одинаково как в случае кратковременного хранения данных, так и при обмене ими. 
    Буфер обмена — это область глобальной памяти, которая доступна для всех 
процессов, выполняющихся в системе. Программа может как читать данные из 
буфера, так и записывать их в буфер. Более того, одна программа может 
записывать данные в буфер, а другая программа — тут же читать их. 
    Хотя буфер обмена можно использовать и без OLE, работа с ним через OLE 
осуществляется более легко и гибко. В данном параграфе описывается пример 
создания программы, которая демонстрирует использование OLE-объектов для 
чтения текстовых данных из буфера обмена. 

    Подсистема работы с буфером обмена в MFC 

    В MFC работа с буфером обмена ведется через специальную подсистему, в 
которой определен ряд OLE-классов и функций-членов. Эта подсистема также 
обрабатывает множество деталей, связанных с OLE. Организовать работу с буфером 
обмена очень легко, поскольку она включает только две операции: занесение 
данных в буфер и извлечение их из него. В MFC оба действия определены 
достаточно четко. 
    Не следует забывать, что в буфер обмена могут быть занесены данные 
различных форматов. Однако в рассматриваемом примере используются только 
текстовые данные. Поэтому здесь представлены лишь самые необходимые сведения. 

    Два OLE-класса 

    Когда работа с буфером обмена ведется через подсистему OLE, используются 
два класса: COIeDataSource и COleDataObject. Первый класс определяет объект, 
являющийся источником данных. Другими словами, в объект класса COIeDataSource 
приложение заносит данные, которые оно хочет поместить в буфер обмена. Второй 
класс определяет объект, принимающий данные. Таким образом, объект типа 
COIeDataSource используется для передачи данных объекту типа COleDataObject. В 
следующем примере осуществляется лишь чтение данных из буфера. Это означает, 
что используется только класс COleDataObject. 
    При использовании OLE-классов в программу должен быть включен файл AFXOLE.H. 

    Функция AfxOleInitO 

    Перед обращением к подсистеме OLE ее необходимо инициализировать. Для 
этого в начале программы должна вызываться функция AfxOleInitO. Лучше всего 
разместить этот вызов внутри функции CWinApp::InitInstance(). Прототип функции 
AfxOleInitO таков: 

    BOOL AFXAPI AfxOleInitO; 

    Эта функция возвращает ненулевое значение при успешном завершении и ноль — 
в противном случае. 

    В. Использование OLE для работы с буфером обмена                   527 


    Чтение данных из буфера обмена 

    Для чтения данных из буфера обмена необходимо выполнить следующие действия: 
    1. Подключить буфер обмена. 
    2. Определить, присутствуют ли в нем данные требуемого формата. 
    3. Получить указатель на данные, хранящиеся в буфере. 
    4. Скопировать данные из буфера. 
    Ниже приведен текст функции, в которой реализуются вес перечисленные действия: 
    
    // Чтение данных из буфера обмена 
    afx msg void CMainWin::OnClipRead() 
    ( 
    COleDataObject OleOb; 
    char str[MAXSIZE] ; 
    char *p; 
    HGLOBAL hGin; 
    int i; 
    OleOb.AttachClipboard () ; 
    if(OleOb.IsDataAvailable(CF_TEXT)) 
    hGin = OleOb.GetGlobalData(CF_TEXT); 
    else return; 
    if(ihGin) ( 
    MessageBox("Cannot obtain handle to data.", "Error"); 
    return; 
    } 
    p = (char *) GlobalLock(hGin); 
    for (i=0; KMAXSIZE && *p; i++) str[i] = *p++; 
    str[i] = '\0' ; 
    GlobalUnlock(hGin) ; 
    MessageBox(str, "Read from Clipboard"); 
    GlobalFree (hGin) ; 
    ) 

    Прежде чем данные будут прочитаны из буфера, он должен быть подключен к 
объекту типа COleDataObject с помощью функции AttachClipboard(), прототип 
которой следующий: 

    BOOL COleDataObject::AttachClipboard() ; 

    Функция возвращает ненулевое значение при успешном завершении и ноль — в 
противном случае. Эта функция предоставляет вызвавшему ее процессу право 
использовать буфер обмена и предотвращает использование буфера другими 
программами. Права на буфер обмена автоматически аннулируются, когда объект 
типа COleDataObject, к которому был подключен буфер, уничтожается. 

    528                                    MFC: основы программирования 

    Следующим действием функция OnClipReadQ определяет, находятся ли в буфере 
обмена данные требуемого формата. Для этого используется функция 
IsDataAvailable(), которая имеет такой прототип: 

    BOOL COleDataObject::IsDataAvailable(CLIPFORMAT DataFormat, 
    LPFORMATETC -IpFormafc=NULL) ; 

    Здесь параметр DataFannat определяет требуемый формат данных. Параметр 
IpFomat может использоваться для указания дополнительной информации о формате 
данных. Если он равен NULL, то формат определяется только параметром 
DataFormat. Функция возвращает ненулевое значение, если данные указанного 
формата есть в буфере, и ноль — в противном случае. 
    В параметре DataFormat можно, помимо всего прочего, указывать один из 
стандартных форматов, а также специальные OLE-форматы. Последние используются 
при связывании и встраивании объектов. Ниже приведены некоторые из наиболее 
часто используемых стандартных форматов (табл. B.I): 

    Таблица В. 1. Стандартные форматы дачных 

    Формат              Тип данных 

    CF_BITMAP            Растровое изображение 
    CF_DIB                Растровое изображение плюс дополнительная информация 
типа BITMAPINFO 
    CF_OEMTEXT         Текст в формате OEM 
    CF_PALETTE          Цветовая палитра 
    CF_TEXT             Текст 
    CF_WAVE             Звуковой WAVE-файл 
    CF_UNICODETEXT    Текст в формате UNICODE_________________ 

    В форматах CF_TEXT, CF_OEMTEXT и CFJJNICODETEXT текст заканчивается 
символом '\0', а каждая строка — последовательностью "возврат каретки/перевод 
строки". В последующем примере используется формат CF_TEXT. 
    Если данные требуемого формата присутствуют в буфере, то с помощью функции 
GetGlobalData() запрашивается дескриптор области глобальной памяти, связанной 
с буфером обмена. Эта функция имеет следующий прототип: 

    HGLOBAL COleDataObject::GetGlobalData(CLIPFORMAT DataFormat, 
    LPFORMATETC IpFormat^NULL) ; 

    Здесь параметр DataFonnat определяет требуемый формат данных (как было 
описано выше). Параметр IpFormat может использоваться для указания 
дополнительной информации о формате данных. Если он равен NULL, то формат 
определяется только параметром DataFormat. Информация о формате важна, 
поскольку данные в буфере обмена могут храниться одновременно в нескольких форматах. 
    Функция GetGlobalDataQ возвращает дескриптор объекта, содержащего данные 
указанного формата. Если такие данные не обнаружены, возвращается NULL. Для 
преобразования дескриптора, возвращаемого функцией GetGlobalData(), в 
указатель используется функция GlobalLockQ. Ее прототип таков: 

    LPVOID *GlobalLock(HGLOBAL hObj) ; 


    В. Использование OLE для работы с буфером обмена                   529 

    Эта функция блокирует объект, который определяется дескриптором, 
содержащимся в параметре hObj. Блокированный объект не может быть перемешен 
или обнулен. При успешном завершении функция GetGlobalDataQ возвращает адрес 
начала объекта, в противном случае — NULL. Объекты, хранящие данные из 
глобальной памяти, должны блокироваться перед использованием в программе. Это 
также предотвращает одновременный доступ к ним со стороны других процессов. 
    Следующим действием программа должна скопировать данные из области памяти, 
связанной с буфером обмена. Это необходимо выполнить в течение одной операции, 
до того как управление будет передано Windows. Причина очевидна: когда 
управление передано Windows, в буфер обмена может быть записана другая информация. 
    Когда данные скопированы, область памяти должна быть разблокирована с 
помощью API-функции GlobalUnlockO, которая имеет следующий прототип: 

    BOOL GlobalUnlock(HGLOBAL hObj); 

    Эта функция возвращает ненулевое значение при успешном завершении и ноль — 
в противном случае. Дескриптор области памяти передается в параметре hObj. 
    Когда работа с областью глобальной памяти, предоставленной функцией 
GetGlobalData(), завершена, эта область должна быть освобождена с помощью 
API-функции GlobalFreeQ. Прототип этой функции таков: 

    HGLOBAL GlobalFree(HGLOBAL hGmem); 

    В случае успешного завершения функция возвращает ноль. Необходимо 
понимать, что в описанной только что процедуре представлены лишь самые 
необходимые действия, используемые при чтении данных из буфера обмена. В 
реальных приложениях применяется более сложный механизм работы с буфером. 
Например, для освобождения памяти, используемой при обмене данными через 
буфер, вызывается API-функция ReleaseStgMedium(). Кроме того, обычно 
необходимо обрабатывать различные типы данных, а не только текст. 

    Демонстрационная программа работы с буфером обмена 

    В следующей программе демонстрируется использование описанных выше функций 
работы с буфером обмена. Программа позволяет читать текстовую информацию из 
буфера. (Попробуйте, для интереса, расширить программу таким образом, чтобы 
она позволяла читать и другие типы данных.) Пример работы программы показан на 
рис. B.I. Программа читает данные из буфера, когда пользователь выбирает 
команду Read из меню Clipboard. Если в буфере есть текстовая информация 
(например, в него был скопирован текст из текстового редактора), то она будет 
прочитана и отображена в окне сообщения. В качестве эксперимента попробуйте 
дополнить программу таким образом, чтобы она могла записывать информацию в буфер. 
    Вот текст файла ресурсов программы: 

    ; CLIP.RC 
    ^include <afxres.h> 
    ^include "ids.h" 
    ClipboardMenu MENU 


    530                                    MFC: основы программирования 


    ; 
    POPUP "SDemo" 
    { 
    
    MENUITEM "SRead From Clipboard\tF2", IDM_READ 
    MENUITEM "E&xit\tF3", IDM^EXIT 
    ) 
    MENUITEM "SHelp", IDM_HELP 
    t 
    
    ClipboardMenu ACCELERATORS 
    ( 
    VK_F1, IDM_HELP, VIRTKEY 
    VK_F2, IDM_READ, VIRTKEY 
    VK_F3, IDM_EXIT, VIRTKEY 
    ) 
    Это текст библиотечного файла IDS.H: 
    // IDS.H 
    #define IDM_READ    100 
    #define IDM_EXIT    101 
    #define IDM_HELP    102 
    ^define MAXSIZE     255 
    Это текст файла CLIP.H, содержащего объявления порождаемых классов: 
    // CLIP.H 
    
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    f 
    public: 
    CMainWin() ; 
    afx msg void OnClipRead () ; 
    afx msg void OnExitQ; 
    afx msg void OnHelpO; 
    DECLARE_MESSAGE__MAP() 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance () ; 
    }; 

    Ниже приведен текст самой программы: 

    // Чтение данных из буфера обмена через механизм OLE. 
    #include <afxwin.h> 


    в. Использование OLE для работ с буфером обмена                   531 


    ^include <afxole.h> 
    ^include "clip.h" 
    ^include "ids.h" 
    // Инициализация главного окна 
    CMainWin::CMainWin () 
    ( 
    Create(NULL, "OLE-Based Clipboard Demo", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "ClipboardMenu"); 
    // Загрузка таблицы быстрого доступа 
    if('LoadAccelTable("ClipboardMenu")) 
    MessageBox ("Cannot Load Accelerators", "Error"); 
    } 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    { 
    m pMainWnd = new CMainWin; 
    m pMainWnd->ShowWindow(m_nCmdShow) ; 
    m pMainWnd->UpdateWindow(); 
    if(!Afx01eInit ()) 
    AfxMessageBox("OLE Initialization Error"); 
    return TRUE; 
    } 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_COMMAND(IDM_READ, OnClipRead) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    
    // Чтение данных из буфера обмена 
    afx msg void CMainWin::OnClipRead() 
    < 
    COleDataObject OleOb; 
    char str[MAXSIZE] ; 
    char *p; 
    HGLOBAL hGin; 
    int i; 
    OleOb.AttachClipboard() ; 
    
    if(01eOb.IsDataAvailable(CF_TEXT) ) 
    hGin = OleOb.GetGlobalData(CF_TEXT) ; 
    else return; 
    if('hGin) { 
    MessageBox("Cannot obtain handle to data.", "Error"); 
    return; 
    532                                    MFC: основы программирования 

    ) 
    р = (char *) GlobalLock(hGin); 
    for (i=0; i<MAXSIZE && *p; i++) str[i] = *p++; 
    str[i] =  '\0'; 
    GlobalUnlock(hGin) ; 
    MessaqeBox (str, "Read from Clipboard"); 
    GlobalFree(hGin) ; 
    ) 
    
    // Обработка сообщения IDM_HELP 
    afx msg void CMainWin::OnHelp () 
    { 
    MessaqeBox("Clipboard Demo", "Placeholder Help"); 
    ) 
    
    // Обработка сообщения IDM EXIT 
    afx msg void CMainWin::OnExit() 
    { 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if(response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 
    ) 
    CApp Арр; // создание экземпляра приложения 
    

    Рис. В. 1. Пример работы с буфером обмена 


    В. Использование OLE для работы с буфером обмена                   533