Программисту-профессионалу
PC Magazine/RE logo
©СК Пресс 11/96
PC Magazine, December 19, 1995, p. 249

Структурированная память OLE: взгляд из Delphi

Джон Лем


Как программы обмениваются информацией и хранят объекты других программ.

По мере распространения Windows 95 и 32-разрядных приложений, OLE (Object Linking and Embedding - компоновка и встраивание объектов) становится основным приемом для разработки крупных приложений Windows. Фактически, если вы хотите, чтобы в вашей программе на экране появлялась заставка Windows 95, в ней должны быть реализованы функции OLE. Что значит "функции OLE"? У OLE два "лица" - одно видит пользователь, другое - разработчик.

С точки зрения пользователя, OLE представляет собой технологию, позволяющую разным программам совместно использовать одну и ту же информацию. Можно сформировать документы, собранные из кусков, созданных разными OLE-совместимыми программами, и беспрепятственно передавать информацию из одной OLE-совместимой программы в другую при помощи механизма "drag-and-drop". Наконец, возможность редактировани "по месту" позволяет изменять встроенные "чужие" части документа, не покидая текущей программы. Вконечном счете все это означает для пользователя переход от программно- ориентированного к документо- ориентированному стилю работы.

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

Другой нелегкой проблемой была организаци долговременного хранения данных. В прошлом от разработчиков требовалось лишь придумать для своей программы формат файла, в котором сохранялись бы данные. Теперь же, когда пользователям необходима возможность переноса данных, созданных одной программой, в любую другую, эта задача стала много сложнее. Преодоление трудностей, связанных с добавлением объектов в файл приложения, расширением уже имеющихся объектов и их уничтожением требует много времени и сил программистов. Команда разработчиков OLE из Microsoft предложила решение этих проблем, создав расширение операционной системы (названное структурированной памятью OLE (СП)), обеспечивающее стандарт для доступа к составным файлам через общий набор интерфейсных средств. Оно и будет в центре внимания данной статьи. После рассмотрения Модели элементарного объекта (МЭО) и того, как она связана со структурированной памятью, мы с помощью системы Delphi фирмы Borland создадим в качестве образца программу, просматривающую СП-файлы.

Структурированная память OLE

Что дает пользователю структурированная память OLE? Одним из наиболее популярных средств являетс диалоговое окно Summary Info. Можно работать с его данными в программе, создавшей документ, а также производить поиск документов из любой программы, предусматривающей чтение справочной информации. Эта особенность интенсивно используется через пункт меню Find File в программах, входящих в Microsoft Office.

А каковы перспективы для разработчиков программ? Возможно, вы уже догадались, что справочная информаци хранится в СП-файле. Чтобы лучше понять, как получить доступ к этим данным, сначала рассмотрим структуру СП-файла.

Попросту говоря, СП-файл - это "файловая система внутри файла". Имея опыт работы с DOS, большинство программистов ПК хорошо знают о том, что такое каталоги и файлы. В обозначениях OLE хранилища (storages) и потоки (streams) - это термины, используемые вместо каталогов и файлов. Том MS-DOS содержит корневой каталог. Аналогичной структурой СП-файла служит корневое хранилище. Мы работаем с СП-хранилищами и потоками, используя соответствующие интерфейсы и их функции. В табл. 1 перечислены СП интерфейсы и эквивалентные функции DOS. Но что такое интерфейс? Чтобы понять это, нам придется немного углубиться в OLE и кратко обсудить модель компонентного объекта.

Таб. 1. Сравнение функций структурированной памяти OLE и функций работы с файлами DOS.

Функция IStorage Эквивалентная функция прерывания 21h DOS Описание
CreateStream 5Bh - Создать новый файл Создать новый поток
OpenStream 3Dh - Открыть файл с помощью указателя Открыть существующий поток
CreateStorage 39h - Создать каталог Создать новое хранилище
OpenStorage 3Bh - Сменить текущий каталог Открыть существующее хранилище
EnumElements 4Eh - Найти первый файл Перенумеровать потоки в хранилище
4Fh - Найти следующий файл
RenameElement 56h - Переименовать файл Переименовать поток
DestroyElement 41h - Уничтожить файл Уничтожить поток

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

Модель компонентного объекта - это сердце OLE, она определяет стандартный способ обмена информацией между объектами. В рамках этой модели вводится понятие интерфейса, который представляет собой просто набор соответствующих функций, обслуживающих общую задачу. Любой объект, снабженный по крайней мере одним интерфейсом OLE - IUnknown - будем называть Объектом Windows (для краткости здесь и далее - просто объект). Один объект может обмениваться информацией с другим, только получив указатель на какой-либо интерфейс. Это выполняется целевым объектом.

Интерфейс представляет собой таблицу указателей на функции (рис. 1). Поэтому, используя смещени указателей в таблице интерфейса, объект-клиент может получить динамический адрес функций объекта-сервера. Однако манипуляции со смещениями указателей - это довольно громоздкий метод получения адресов принадлежащих функций вызываемого объекта. К счастью, структура интерфейса МЭО идентична структуре таблицы виртуальных функций языка Си++.

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

В большинстве книг по программированию OLE обычно используется язык Си/Си++. Однако Си++ - не единственный язык, подходящий для создани OLE-приложений. Любой язык, где предусмотрено понятие указателей, годится для подготовки OLE-программ (тем самым исключаются языки с псевдокодом, подобные языку Visual Basic, который можно использовать лишь дл управления другими OLE-приложениями). Удобно, например, что компилятор Object Pascal из пакета Delphi генерирует таблицы виртуальных функций, идентичные создаваемым компилятором Си++, поэтому для подготовки OLE-приложений можно применять Delphi. В нашей статье для обращений к OLE-интерфейсам будет использован язык Object Pascal, но все сделанное можно без особого труда реализовать и на Си++.

При использовании Delphi для создания OLE-программ возникает ряд трудностей. Давайте не забывать, что OLE - это просто двоичная спецификация, созданная фирмой Microsoft для описания поведения объектов Windows. Поскольку Си++ - самый популярный объектно-ориентированный язык, двоичная спецификаци OLE определяется в OLE 2.01 SDK в форме файлов-заголовков языка Си++. По этому поводу у мен две новости: хорошая и плохая. Хорошая заключается в том, что фирма Borland перенесла многие файлы заголовков OLE 2.01 SDK C++ и объединила их в двух файлах модулей языка Turbo Pascal: OLE2.PAS и DISPATCH.PAS. Плохая же - в том, что при переносе были допущены ошибки. Как сообщили в фирме Borland, часть ошибок устранена, но некоторые пока остались.

Рис. 1. Интерфейс IUnknown должен быть у каждого объекта OLE. Используя смещения в таблице указателей интерфейса, объект-клиент может получить адреса функций объекта-сервера во время выполнения программы.
Интерфейс IUnknown ------------------ __________________ IUnknown ----> | IUnknown | |------------------| | Addref ----> функция AddRef: HResult |------------------| | Release ----> функция AddRef: HResult | QueryInterface ----> функция QueryInterface (riid:REFID, |__________________| ppvObj: Pointer): HResult Объект Интерфейс Объект

Интерфейс IUnknown: ключ к OLE

Ранее я называл объектом Windows объект, включающий в себя по крайней мере интерфейс IUnknown. Основные принадлежащие функции интерфейсов OLE показаны на рис. 1.

У каждого объекта должен быть счетчик, фиксирующий число ссылок на него из других объектов для того, чтобы его можно было выгрузить из памяти, как только он станет ненужным. Для этого предназначены функции AddRef и Release.

Другая принадлежащая функция интерфейса IUnknown - это QueryInterface, с помощью которой можно запросить объект о том, есть ли у него какие-либо другие интерфейсы. Например, чтобы узнать, содержит ли он интерфейс IPersistStorage (позволяющий объекту-серверу хранить свои данные в файле объекта-клиента), можно использовать следующую функцию:

function GetIPersistStorage( pUnknown: IUnknown ): IPersistStorage; var hr: HResult; pPersistStorage: Pointer; begin hr := pUnknown.QueryInterface( IID_ IPersistStorage, pPersistStorage); if SUCCEEDEDHR( hr ) then Result := IPersistStorage( pPersistStorage ) else Result := nil; end;

Переменная hr имеет тип HResult, представляющий собой 32-разрядное целое, используемое всеми функциями OLE в качестве return-значения. Успешность выполнени операции можно проверить с помощью определенной в Delphi функции SUCCEEDEDHR(hr), принимающей значение TRUE, если не произошло ошибок. Успешное завершение QueryInterface в параметре pPersistStorage передает в вызывающую программу обобщенный указатель на интерфейс IPersistStorage, который должен быть преобразован в тип "указатель на IPersistStorage". Используя этот способ, можно получить указатель на любой интерфейс заданного объекта.

При вызове QueryInterface параметр IID_ IPersistStorage определяет интерфейс, на который ссылаются. Для того чтобы однозначно идентифицировать все объекты и интерфейсы системы пользователя, фирма Microsoft присвоила им уникальные 128-бит описатели, называемые Глобальными уникальными идентификаторами (Globally Unique Identifiers, или GUIDs). Все основные интерфейсы OLE, такие, как IUnknown и IPersistStorage, имеют заранее определенные значения типа GUID, называемые Идентификаторами интерфейса (Interface Identifiers6 или IIDs) и описанные в файлах OLE2.PAS или DISPATCH.PAS.

Таким образом, с помощью указателя на интерфейс IUnknown, используя функцию QueryInterface, можно получить доступ к любому другому интерфейсу. Но как получить начальный указатель на интерфейс IUnknown или, более того, доступ к объекту, который еще не создан? Вот теперь на сцену выходят две ключевые функции OLE API - CoCreateInstance и CLSIDFromProgID - а также системный файл REG.DAT.

Файл-реестр REG.DAT содержит всю информацию о присутствующих в вашем компьютере объектах. В частности, вместо использования зашифрованных 128-бит значений GUID можно воспользоваться более удобным представлением, известным как ProgID. Например, значение идентификатора ProgID для объекта Microsoft Word 6.0 Word Basic - это просто "Word.Basic". Следующий фрагмент программы демонстрирует, как получить указатель на интерфейс IUnknown для объекта Word Basic:

type aclsID: CLSID; pUnk: IUnknown; begin if SUCCEEDEDHR (CLSIDFromProgID('Word.Basic', aclsID)) then CoCreateInstance(aclsID, nil, CLXCTX_SERVER, IID_IUnknown, pUnk);

Функция CLSIDFromProgID ищет "Word.Basic" в файле REG.DAT и передает в вызывающую программу связанный с ним CLSID в параметре aclsID. Функция CoCreateInstance создает экземпляр объекта Word.Basic и запрашивает указатель на интерфейс IUnknown, который передается ей в параметре pUnk. Параметр pUnk функции CoCreateInstance описан в файле OLE2.PAS как ссылка на неопределенный тип. Таким образом, можно передавать любой тип переменной, но поскольку pUnk в действительности представляет собой 32-разрядный указатель (или в терминах Си++ - дальний указатель), а IUnknown тоже дальний указатель, то нет необходимости проводить преобразование типов в этой функции.

Предыдущий пример иллюстрирует, как узнать начальный указатель интерфейса на объект. В этой статье речь идет о структурированной памяти, а СП-файлы не объекты Windows; как же тогда получить указатель на интерфейс IStorage, обслуживающий корневое хранилище файла? IStorage является объектом Windows и находится в библиотеке STORAGE.DLL. Как можно догадаться, все, что от нас требуется, - это связать имя файла с объектом IStorage. Функция API, которая позволяет добитьс этого, называется StgOpenStorage.

Как только будет получен указатель на интерфейс IStorage, мы сможем управлять СП-файлом с помощью принадлежащих функций, удивительно похожих на подобные функции DOS (табл. 1). Внутри каждого IStorage-интерфейса можно узнать индивидуальные интерфейсы IStream, которые открывают доступ к аналогам файлов DOS - СП-потокам.

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

Программа просмотра структурированной памяти

Для того чтобы перейти от рассуждений о СП-файлах к практике, я продемонстрирую простую программу, позволяющую просматривать СП-файлы. Программа реализована полностью в среде Delphi, чтобы подчеркнуть возможность использования ее для разработки OLE-приложений наряду с Си++. Одно из основных преимуществ Delphi - изобилие разнообразных компонентов интерфейса пользователя, входящих в состав пакета. Наш интерфейс пользователя образован двумя из них - TOutline и TDrawGrid. TOutline выводит на экран иерархию каталогов, подобную дереву в окне программы Explorer и отображающую иерархию хранилищ СП-файла. Процедура TDrawGrid дает пользователю возможность просматривать содержимое любого потока в СП-файле без загрузки его в память.

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


Лист. 1. Стандартные вызов и выход из библиотек Delphi, необходимые для любой программы, где применяется OLE. Initialization Code procedure TMainForm.ForceCreate(Sender: TObject); var dwVer, hr: HResult; begin m_fOleInitialized := false; dwVer := CoBuildVersion; if rmm <> HIWORD(Longint(dwVer)) then ShowMessage('Invalid OLE library version'); else begin; if FAILEDHR(CoInitialize(nil)) then ShowMessage('Failed OLE library initialization'); else; m_fOleInitialized := true; end; end; Termination Code procedure TMainForm.FormDestroy(Sender: TObject); begin; if m_fOleInitialized then OleUninitialize; ... end;

Для библиотек OLE версии 2.01 значение rmm равно 23. Необходимо эту переменную описать в прикладной программе как константу, поскольку она не определена ни в модуле OLE2.PAS, ни в DISPATCH.PAS. В качестве индикатора, показывающего, были библиотеки OLE успешно инициализированы или нет, я использую переменную m_fOleInitialized. Для базового доступа к СП-файлам требуются лишь библиотеки COMPOBJ.DLL и STORAGE.DLL. После их инициализации операции, выполняемые программой просмотра, можно разбить на три категории:

Как только библиотеки инициализированы, появляетс возможность просмотра файловой системы с помощью стандартного диалога Delphi TOpenDialog. После того как файл выбран, необходимо вызвать процедуру StgIsStorage, чтобы убедиться, является ли файл, который мы пытаемс открыть, СП-файлом. Если это так, мы продолжаем выполнение программы, считывая структуру хранилищ СП-файла (лист. 2.), представляющую собой иерархию, подобную иерархии каталогов DOS. Для ее просмотра используется рекурсивная процедура, алгоритм которой приведен ниже:

PROCEDURE ViewStorage( storage ) WHILE есть еще элементы в текущем хранилище DO создать понятное пользователю представление текущего элемента добавить его в управление схемой IF текущий элемент - хранилище THEN CALL ViewStorage (это хранилище) END

Лист. 2. Команды считывания структуры хранилищ СП-файла. procedure TMainForm.OpenItemClick(Sender: TObject); var pszFilename, pszName, PChar; sc: SCODE; snbExclude: PStr; hr: HResult; dummyExclude: IStorage; pIStorage: ^IStorage; pMyStatStg: TMyStatStg; dummyULI: Comp; begin if OpenDialog.Execute then begin { Попытка открыть документ структурированной памяти. Анализ имени файла происходит в OpenDialog. } try pszFilename := StrAlloc(Length(OpenDialog.Filename) + 1); { +1 байт для символа NULL } StrPCopy(pszFilename, OpenDialog.Filename); sc := GetSCode(StgIsStorageFile(pszFilename)); if sc = STG_E_FILENOTFOUND then ShowMessage(OpenDialog.Filename + 'was not found.') else if sc = S_FALSE then ShowMessage(OpenDialog.Filename + ' is not a valid OLE2 Structured Storage file.') else if sc = S_OK then begin { Теперь действительно открываем файл и получаем указатель на IRootStorage } FreeIStorageList; snbExclude := nil; pIRootStorage := nil; if SUCCEEDEDHR (StgOpenStorage(pszFilename, nil, STGM_DIRECT or STGM_READWRITE or STGM_SHARE_EXCLUSIVE, snbExclude, Longint(nil); IStorage(pIRootStorage))) then begin { Создаем корневой элемент TOutline. У него *ВСЕГДА* ItemIndex = 0 } Outline1.AddChild(0, 'Root Storage: ' + StrPas(pszFilename)); { Команды создания списка открытых хранилищ, могу открыть их, когда впоследствии у меня будет необходимость просмотреть содержимое. } pszName := StrAlloc(100); StrCopy(pszName, 'Root Storage: '); StrCat(pszName, pszFilename); dummyULI := 0; pMyStatStg := TMyStatStg.Create(pszName, pIRootStorage, 0, dummyULI); m_lstIStorage.Add(pMyStatStg); ViewStorage(1, pIRootStorage); { вызов рекурсивной процедуры заполнения схемы } end; end; finally StrDispose(pszFilename); end; end;

Технология OLE позволяет программе перебирать элементы интерфейса, используя определенный в OLE интерфейс перечисления (enumerator). Он выполняет функции Next, Skip, Reset и Clone. Как только IStorage станет доступным, вызвав принадлежащую функцию EnumElements интерфейса IStorage, мы получаем указатель на интерфейс перечисления IEnumStatStg. Использу принадлежащую функцию Next для перебора элементов (они передаются в вызывающую программу как return-значения) можно ознакомиться с содержимым хранилища IStorage.

Функция Next интерфейса IEnumStatStg в ответ на вызов передает структуру TStatStg, в которой указано, является ли элемент хранилищем или потоком, а также информация об особенностях каждого типа хранилищ (таких, как размер потока, даты создания и последнего изменения). Используя эту информацию, мы преобразуем данные в понятное пользователю представление и помещаем их в запись (структуру) Outline1. Информацию, содержащуюся в структуре TStatStg, мы запоминаем также в записи типа TMyStatStg, которая содержит указатель на открытое в данный момент хранилище. Эту запись мы храним в структуре TList, реализованной в Delphi и представленной в нашей программе переменной m_lstIStorage (лист. 3).


Лист. 3. Команды обхода дерева хранилищ СП-файла procedure TMainForm.ViewStorage(inID: Integer; lpStorage: IStorage); var lpEnum: IEnumStatStg; ss: TStatStg; ulCount, ldwType: Longint; sc: SCODE; pSubStg: IStorage; subID: Integer; sType: String[12]; hr: HResult; snbExclude: PStr; pMyStatStg: TMyStatStg; begin snbExclude := nil; pSubStg := nil; if FAILEDHR(lpStorage.EnumElements(0, nil, 0, lpEnum)) then ShowMessage('Could not get a Storage Enumerator at level: ' + IntToStr(inID)) else begin sc := S_OK; while sc = S_OK do begin sc := GetSCode(lpEnum.Next(1, ss, ulCount)); if (sc <> S_OK) and (sc <> S_FALSE) then begin ShowMessage('IEnumStatStg returned an error! SCODE = ' + IntToStr(Longint(sc))); Exit; end; else ldwType := Longint(ss.dwType); case ldwType of STGTY_STREAM: sType := 'Stream'; STGTY_STORAGE: sType := 'Storage'; STGTY_LOCKBYTES: sType := 'LockBytes'; STGTY_PROPERTY: sType := 'Property'; else sType := '**Unknown**'; end; { Добавим эту запись в управление схемой. Заметьте, что переменная ss.cbSize (размер потока) представляет собой целое число длиной 64 бит. } if sc = S_OK then begin { Добавим запись TStatStg в управление схемой Outline. Нужно воспользоваться вспомогательной функцией GetLowPart для преобразования ss.cbSize - целого длиной 64 бит } subID := Outline1.AddChild(inID, StrPas(ss.pwcsName) + ', Type: ' + sType + ', Size: ' + IntToStr(GetLowPart(ss.cbSize))); if ldwType = STGTY_STORAGE then begin { Мы находимся в хранилище - рекурсивно перебираем хранилища, вызывая сами себя } hr := lpStorage.OpenStorage(ss.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, nil, Longint(nil), pSubStg); if SUCCEEDEDHR(hr) then begin pMyStatStg := TMyStatStg.Create(ss.pwcsName, pSubStg, ss.dwType, ss.cbSize); m_lstIStorage.Add(pMyStatStg); ViewStorage(subID, pSubStg); { вызываем сами себя } end else ShowMessage('Failed substorage open! hr = ' + IntToStr(Longint(hr))); end else begin { Это было не хранилище, так что записываем текущий lpStorage в таблицу } pMyStatStg := TMyStatStg.Create(ss.pwcsName, lpStorage, ss.dwType, ss.cbSize); m_lstIStorage.Add(pMyStatStg); end; end; end; lpEnum.Release; lpEnum := nil; end;

Для того чтобы просмотреть какой-то поток СП-файла, щелкните дважды на нужный строке окна Outline1. А дл того, чтобы увидеть соответствующую запись в m_lstIStorage, можно просто использовать смещения, передаваемые в элементе структуры Outline1-SelectedItem, так как структура m_lstIStorage - это зеркальное отражение структуры окна Outline1. Зная указатель на хранилище и имя потока, выбранного пользователем для просмотра, можно открыть этот поток, используя принадлежащую функцию OpenStream интерфейса IStorage (лист. 4).


Лист. 4. Команды, открывающие поток в хранилище. procedure TMainForm.Outline1DblClick(Sender: TObject); var pszName: pChar; pIStorage: IStorage; pIStream: IStream; pMyStatStg: TMyStatStg; aOffset, dummyLargeint: Comp; begin if Outline1.SelectedItem > 0 then begin pMyStatStg := TMyStatStg(m_lstIStorage. Items[Outline1.SelectedItem-1]); StatusBar.Caption := 'Selected Item: ' + StrPas(pMyStatStg.pszName); { Теперь попытаемся открыть поток для узла, но прежде закроем текущий поток } if m_pStream <> nil then m_pStream.Release; if pMyStatStg.dwType = Longint(STGTY_STREAM) then begin pIStorage := pMyStatStg.pIStorage; pszName := pMyStatStg.pszName; { Если поток уже был открыт, закроем его } if SUCCEEDEDHR (pIStorage.OpenStream(pszName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, pIStream)) then begin { Настроим таблицу DrawGrid для демонстрации потока на экране } m_dwStreamSize := GetLowPart(pMyStatStg.cbSize); { установим переменную размера потока } m_pStream := pIStream; { сохраним указатель на интерфейс } Drawgrid1.RowCount := m_dwStreamSize div 16; { установим размер таблицы DrawGrid } Drawgrid1.Visible := True; { Перейдем к началу файла } aOffset := 0; m_pStream.Seek(aOffset, Longint(STREAM_SEEK_SET), dummyLargeint); end; end; else DrawGrid1.Visible := False; { Спрячем Grid, если объект не является потоком } end; end;

DrawGrid1 - это экземпляр класса TDrawGrid, реализованный в Delphi и созданный для управлени таблицей, задаваемой пользователем. Она состоит из одной колонки и особенно полезна для нашей программы просмотра, так как TDrawGrid передает номер строки в виде 32-разрядного целого числа со знаком. Это означает, что потенциально она может содержать 2 млрд строк, т. е. можно просмотреть 4x16 = 64 млрд байт потоков. Так как TDrawGrid выводит по требованию строки на экран, а не хранит эти данные, то нет необходимости загружать поток в память. Вместо этого, когда происходит событие DrawCell, для формировани соответствующих строк DrawGrid1 используютс принадлежащие функции Seek и Read класса IStream. На лист. 5 показана реализация обработчика событий DrawCell, за одно обращение заполняющего строку длиной 16 байт таблицы DrawGrid1.


Лист. 5. Реализация таблицы, задаваемой пользователем. Строки DrawGridl содержат по 16 байт. procedure TMainForm.DrawGrid1DrawCell(Sender: TObject; Col, Row, Longint; Rect: Trect; State: TGridDrawState); var TheText: string[16]; i: Integer; byteArray: Array[0..15] of Byte; lBytesRead: Longint; aOffset, dummyLargeint: ULargeint; begin begin { Вычислить байтовое смещение в потоке и перейти к этой позиции } aOffset := Row shl 4; if SUCCEEDEDHR(m_pStream.Seek(aOffset, Longint(STREAM_SEEK_SET), dummyLargeint)) then begin if SUCCEDEDHR(m_pStream.Read(@byteArray, 16, lBytesRead)) then begin { xxxxxxxx: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx abcdefghijklmnopq } TheText := IntToHex(Row shl 4, 6) + ':' ; SetBkColor(DrawGrid1.Canvas.Handle, GetSysColor(COLOR_WINDOW)); DrawGrid1.Canvas.TextOut(Rect.Left + 2 * m_iCharWidth, Rect.Top, TheText); for i := 0 to lBytesRead - 1 do begin TheText := IntToHex(byteArray[i], 2); DrawGrid1.Canvas.TextOut(Rect.Left + 10 * m_iCharWidth + m_iCharWidth * i * 3, Rect.Top, TheText); end; { Поместить в выходную строку печатные символы и точки, если символы - непечатные } TheText := ''; for i := 0 to lBytesRead - 1 do if IsCharAlphaNumeric(Char(byteArray[i])) then TheText := TheText + Char(byteArray[i]) else TheText := TheText + '.'; DrawGrid1.Canvas.TextOut(Rect.Left + 60 * m_iCharWidth, Rect.Top, TheText); end; end; end; end;

Требуемое округление

Delphi содержит встроенный тип данных Comp, который имеет длину 64 двоичных разряда. Так как структурированная память использует для смещений потоков 64-разрядные целые, удобно применять тип Comp. Но имеющиеся в Delphi функции преобразования типов, такие, как IntToStr, "не понимают" типа данных Comp. Поэтому из данных типа Comp необходимо выделять младшую половину. Это делает следующая вспомогательная функция:

{ Функция выделяет младшие 32 разряда из переменной типа Comp } function TMainForm.GetLowPart(X: Comp): Longint; begin Move( X, Result, 4); end;

Заключение

Надеюсь, изложенный материал показал, что реализаци структурированной памяти OLE является мощным интерфейсом управления файлами, который можно использовать при разработке своих программ. Богатый набор функций для этого был продемонстрирован на примере объектов IStorage и IStream, содержащихся в библиотеке STORAGE.DLL. Эти функции позволяют разработчикам объединять файлы данных приложений в единый, что, в свою очередь, дает возможность пользователям этих приложений поддерживать данные в одном файле.

Вы можете получить тексты всех программ, содержащихся в этой статье, в службе PC Magazine Online.