
ТЕХНОЛОГИИ КОМПАНИИ АРСЕНАЛЪ
Известно, что создать редактор текста, особенно отвечающий современным требованиям, чрезвычайно трудно. При внешней простоте задачи она отличается крайне большим количеством параметров, с которыми необходимо работать, и неформализованностью входных данных, т.е. собственно текстов, которые обрабатываются редактором.
Вместе с тем, сейчас практически каждый программный продукт нуждается в собственном редакторе. Действительно, трудно вообразить себе область деятельности, в которой не нужно было бы работать с документами. Эти документы могут отличаться некоторой спецификой, например, могут быть насыщены большим количеством таблиц и расчетов, как в бухгалтерии, но при этом они остаются обычными документами, в которых нужно набирать и редактировать текст, форматировать его, наконец, нужно печатать документы.
На сегодняшний день каждая фирма-разработчик программного обеспечения решает этот вопрос по-своему. Думаю, что многие пытались написать нужный редактор самостоятельно, однако, с возрастанием требований к нему, убеждались, насколько это непростая задача. В определенный момент становятся бесполезными и некоторые инструментальные средства, которые, казалось бы, предназначены именно для этой цели. В частности, известный RichEdit Control из библиотеки MFC практически невозможно использовать, если разработчику нужен хоть сколько-нибудь серьезный модуль редактирования текстов.
При этом разработчик имеет возможность применять Toolkit двояко раздельно подключать к собственному приложению как функции только просмотра, так и все функции редактирования документов в наиболее распространенных в нашей стране форматах.
В сущности, Lexicon Toolkit позволяет создавать обычный элемент управления, то есть стандартное окно MS Windows, реализующее собственную функциональность. Если такое окно использовать в качестве дочернего окна, полностью покрывающего рабочую область несущего (родительского), то в этом случае наше приложение будет настоящим текстовым процессором.
Кроме того, его можно применять и в качестве элемента управления, например, в диалоге, для просмотра и редактирования различной информации, вместо обычно используемых в таких случаях классов CEdit и CRichEdit. По сравнению с этими классами Lexicon Toolkit предоставляет пользователю гораздо больше возможностей.
Lexicon Toolkit поставляется в 16- и 32-разрядном исполнении и состоит, в основном, из двух главных динамических библиотек.
1. Lxview16/32.dll библиотека функций для создания программ просмотра документов, поддерживающая следующие форматы:
2. Lxedit16/32.dll библиотека функций для создания собственно редактора текстов в перечисленных выше форматах.
Кроме того, в состав Toolkit входят два заголовочных файла с объявлениями интерфейсов и модуля загрузки динамической библиотеки, служебные динамические библиотеки, а также Руководство программиста с подробным описанием доступных интерфейсов и их методов.
Использование этой модели позволяет:
Кроме привычных глобальных функций, в библиотеках Lexicon Toolkit также используются интерфейсы - объекты, доступ к которым осуществляется только посредством вызова функций. Фактически, это наследует идеологию C++, хотя интерфейс может вызываться и из простого C.
Как известно, в иерархии интерфейсов есть корень - интерфейс IUnknown, описание которого можно найти в описании COM. Все остальные интерфейсы наследуют его или другие интерфейсы. На самом деле интерфейс - это таблица виртуальных функций, через вызов которых и идет вся работа. В Lexicon Toolkit для порождения интерфейса используются функции типа Create других интерфейсов, для удаления после использования - функция Release данного интерфейса. В полном соответствии с классическим принципом COM получить указатель на какой-либо интерфейс можно не только с помощью глобальных функций библиотеки типа Create, но и запросив этот интерфейс через привычную функцию QueryInterface другого интерфейса.
В Lexicon Toolkit в полной мере использованы преимущества наследования интерфейсов. Так исходными является набор интерфейсов, реализующих функции просмотра документов, а редактор наследует интерфейс просмотра, добавляя в него ряд методов, необходимых для редактирования. Это позволяет программисту, использующему Toolkit в собственном приложении для просмотра документов, переделать его в редактор в минимальные сроки и без особых усилий.
Однако, хотя Lexicon Toolkit и повторяет идеологию COM, строго говоря, он не является компонентом в классическом его понимании. Компонент Лексикон-97 никак не регистрируется в системном реестре. Поэтому динамическая библиотека, в которой реализован компонент Лексикон-97, должна быть явно загружена с диска самим приложением, ее использующим (классические компоненты загружаются автоматически через регистрацию).
После явной загрузки динамической библиотеки приложение получает ссылку на главный интерфейс компонента, хранит ее в глобальной переменной и отпускает главный интерфейс (а вместе с ним и компонент) лишь по окончании работы приложения. Все остальные интерфейсы приложение получает через глобальный указатель на главный интерфейс по мере необходимости и после использования сразу их освобождает.
Таким образом, разработчик отвечает только за реализацию интерфейса программы с пользователем и, по большому счету, вся его роль сводится к трансляции действий пользователя компоненту. Приложение получает от компонента параметры (например, абзаца), открывает диалог, инициализирует его параметрами из компонента и по окончании диалога передает модифицированные пользователем параметры обратно в компонент. При этом приложение ничего не знает о том, какой абзац является текущим или относятся ли данные параметры к одному или группе абзацев, попавших в выделение.
Из всех классов в проекте нам потребуется модифицировать лишь один, а именно: собственный класс, производный от CView (в примере - CLexpadView). Прежде всего, в заголовочный файл класса необходимо добавить директивы:
#include lexapi.h
#include lxmodule.h,
включающие два файла с объявлениями интерфейсов компонента и модуля динамической библиотеки соответственно.
В объявление самого класса необходимо включить несколько новых переменных - членов класса.
class CLexpadView: public CView
{
LexDll m_dllLex;
LPLEXICON m_pLexicon;
LPLEXDOCVIEWER m_pLexDoc;
LPLEXVIEW m_pLexView;
HWND m_hwndLex;
<прочие переменные и методы класса >
};
Строго говоря, из добавленных переменных обязательными являются только две первые, а именно: m_dllLex (объект, явно загружающий динамическую библиотеку с диска в пространство процесса) и m_pLexicon (та самая глобальная ссылка на компонент Lexicon, которую необходимо держать все время работы приложения и отпускать лишь по выходе из приложения). Пока эта ссылка не будет отпущена, компонент Lexicon удерживается в памяти даже при отсутствии ссылок на любые другие интерфейсы компонента. Остальные переменные могут получаться динамически, но поскольку они часто используются в различных операциях, имеет смысл внести их в объявление класса и получить один раз.
В конструкторе объекта m_dllLex инициализируется именем модуля динамической библиотеки, которую мы собираемся использовать (для создания программы просмотра файлов - Lxview16/32.dll). Следует еще раз заметить, что поскольку компонент Lexicon не прописывает себя в системном реестре, то вместо привычного вызова функции API CoCreateInstance для создания экземпляра класса по его уникальному идентификатору, необходимо явно загружать динамическую библиотеку, в которой реализован компонент.
Если библиотека загрузилась успешно, то необходимо воспользоваться ее функцией LexInitialize(LPLEXICON*), которая собственно и создает экземпляр класса и возвращает указатель на его главный интерфейс ILexicon. Интерфейс ILexicon наследует интерфейс IUnknown.
CLexpadView::CLexpadView():
m_dllLex(Lxview32.dll),
m_pLexicon(NULL),
m_pLexDoc(NULL),
m_pLexView(NULL),
m_hwndLex(NULL)
{
// TODO: add construction code here
if (&m_dllLex.LexInitialize)
m_dllLex.LexInitialize(&m_pLexicon);
}
В деструкторе необходимо сообщить компоненту, что он больше не нужен, отпустив указатель на его главный интерфейс ILexicon.
CLexpadView::~CLexpadView()
{
if (m_pLexDoc)
m_pLexDoc->Release();
if (m_pLexView)
m_pLexView->Release();
if (m_pLexicon)
m_pLexicon->Release();
}
Таким образом, для запроса указателей на главный (ILexicon) и другие интерфейсы объекта мы используем некие глобальные функции из динамической библиотеки компонента вместо привычных CoCreateInstance и QueryInterface. В этом заключен положительный момент, т.е. программисту не надо оперировать уникальными идентификаторами интерфейсов, которые совершенно не важны для программиста и необходимы лишь для их запроса через другие интерфейсы. Отрицательная же сторона такого подхода заключается в том, что слово Interface никак не фигурирует в сигнатуре таких глобальных функций, и поэтому неискушенному программисту (для которых, кстати, и предназначены подобные Tooolkit) легко забыть, что возвращаемое значение - указатель действительно на интерфейс, и поэтому после использования его необходимо отпустить, вызвав функцию Release().
Для того, чтобы открыть файл для просмотра, необходимо получить указатель на интерфейс ILexDocViewer. Этот интерфейс имеет методы, позволяющие работать с документом только на уровне данных и не имеющие никакого отношения к отображению документов. Таким образом, реализуется классический принцип разделения собственно данных и способов их отображения. Поэтому данный интерфейс аналогичен классу Cdocument из библиотеки MFC. Указатель на него запрашивается с помощью методов интерфейса ILexicon:
ILexicon::CreateViewer(LPLEXDOCVIEWER FAR * lplpLexDocViewer)
Если указатель m_pLexDoc на интерфейс ILexDocViewer был уже проинициализирован (в приложение уже загружен другой документ), то по этому указателю необходимо вызвать функцию Release().
if (m_pLexDoc)
{
m_pLexDoc->Release();
m_pLexDoc = NULL;
}
m_pLexicon-
>CreateViewer(&m_pLexDoc);
(Продолжение следует)
Александр Алексеев, ведущий программист компании Арсеналъ-СПб,
тел.(812) 112-9066