ТЕХНОЛОГИИ КОМПАНИИ АРСЕНАЛЪ


Библиотека Lexicon Toolkit
и разработка текстовых редакторов

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

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

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


Что такое Lexicon Toolkit
Для быстрого создания произвольных текстовых редакторов московская компания Арсеналъ, разработчик программ серии Русский Офис, выпустила инструментальное средство Lexicon Toolkit, позволяющее разработчику без особых усилий подключать и использовать в собственном программном приложении всю функциональность текстового процессора Лексикон-97.

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

В сущности, Lexicon Toolkit позволяет создавать обычный элемент управления, то есть стандартное окно MS Windows, реализующее собственную функциональность. Если такое окно использовать в качестве дочернего окна, полностью покрывающего рабочую область несущего (родительского), то в этом случае наше приложение будет настоящим текстовым процессором.

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

Lexicon Toolkit поставляется в 16- и 32-разрядном исполнении и состоит, в основном, из двух главных динамических библиотек.

1. Lxview16/32.dll библиотека функций для создания программ просмотра документов, поддерживающая следующие форматы:

2. Lxedit16/32.dll библиотека функций для создания собственно редактора текстов в перечисленных выше форматах.

Кроме того, в состав Toolkit входят два заголовочных файла с объявлениями интерфейсов и модуля загрузки динамической библиотеки, служебные динамические библиотеки, а также Руководство программиста с подробным описанием доступных интерфейсов и их методов.


Идеология работы Lexicon Toolkit
Программный интерфейс библиотеки повторяет идеологию модели компонентных объектов (COM) основы, на которой построены многие технологии, в частности, технология OLE. Модель компонентных объектов это метод разработки программных компонентов в виде двоичных исполняемых файлов, которые предоставляют необходимые сервисы приложениям или другим компонентам. Разработка компонента подобна разработке динамического объектно-ориентированного API. Компоненты можно менять без перекомпиляции или перекомпоновки приложения.

Использование этой модели позволяет:

Кроме привычных глобальных функций, в библиотеках Lexicon Toolkit также используются интерфейсы - объекты, доступ к которым осуществляется только посредством вызова функций. Фактически, это наследует идеологию C++, хотя интерфейс может вызываться и из простого C.

Как известно, в иерархии интерфейсов есть корень - интерфейс IUnknown, описание которого можно найти в описании COM. Все остальные интерфейсы наследуют его или другие интерфейсы. На самом деле интерфейс - это таблица виртуальных функций, через вызов которых и идет вся работа. В Lexicon Toolkit для порождения интерфейса используются функции типа Create других интерфейсов, для удаления после использования - функция Release данного интерфейса. В полном соответствии с классическим принципом COM получить указатель на какой-либо интерфейс можно не только с помощью глобальных функций библиотеки типа Create, но и запросив этот интерфейс через привычную функцию QueryInterface другого интерфейса.

В Lexicon Toolkit в полной мере использованы преимущества наследования интерфейсов. Так исходными является набор интерфейсов, реализующих функции просмотра документов, а редактор наследует интерфейс просмотра, добавляя в него ряд методов, необходимых для редактирования. Это позволяет программисту, использующему Toolkit в собственном приложении для просмотра документов, переделать его в редактор в минимальные сроки и без особых усилий.

Однако, хотя Lexicon Toolkit и повторяет идеологию COM, строго говоря, он не является компонентом в классическом его понимании. Компонент Лексикон-97 никак не регистрируется в системном реестре. Поэтому динамическая библиотека, в которой реализован компонент Лексикон-97, должна быть явно загружена с диска самим приложением, ее использующим (классические компоненты загружаются автоматически через регистрацию).

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

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


Как это работает:
программа просмотра файлов
Рассмотрим использование компонента из Lexicon Toolkit на примере простого приложения, выполняющего просмотр файлов, а затем модифицируем его до настоящего текстового процессора, позволяющего создавать и редактировать документы. В примере используем заготовку проекта, созданную средствами Microsoft Visual C++ v. 5.0 для приложения, реализующего для простоты однодокументный интерфейс (SDI) с архитектурой Doc/View.

Из всех классов в проекте нам потребуется модифицировать лишь один, а именно: собственный класс, производный от CView (в примере - CLexpadView). Прежде всего, в заголовочный файл класса необходимо добавить директивы:

включающие два файла с объявлениями интерфейсов компонента и модуля динамической библиотеки соответственно.

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

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


КОМПЬЮТЕР-ИНФОРМ