Продолжается подписка на наши издания! Вы не забыли подписаться?

ASC - компоненты

В.Чистяков
Данная статья довольно сильно устарела, и имеет только историческое занчение. О современном состоянии проекта ASC можно узнать на нашей странице Software.

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

Поначалу разработки велись на 4-GL (Gupta SQLWindows) и MS SQL Server, но такой подход оказался ущербным из-за:

  1. Низкой расширяемости системы (а значит и отчуждаемости).
  2. Плохой масштабируемость.
  3. Невозможности создания распределенных систем.

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

В качестве компонентной модели мы выбрали COM/DCOM/ActiveX. Выбор на эту технологию пал потому что:

  1. Ком охватывает все виды деятельности, от компонентов бизнеслогики располагающихся распределено по сети до компонентов графического интерфейса.
  2. Поддержка COM/DCOM/ActiveX интегрирована с самыми популярными операционными системами (Windows 95/98/NT).
  3. Почти все популярные средства разработки поддерживают COM/DCOM/ActiveX.
  4. Ну и последнее эту технологию продвигает самый мощный (на рынке софта для PC) гигант – Microsoft (к тому же COM/DCOM/ActiveX становятся открытыми стандартами).

Мы разбили компоненты на группы в зависимости от их уровня абстракции и принадлежности к решаемой проблеме.

Так как до этого мы привыкли использовать курсоры для доступа к базам данных, то и для общения между уровнями хотелось использовать подобную идеологию. Протестированные нами средства (ADO, RDO, Delphi Midas:DCOMxxx и некоторые другие) показали свою непригодность, одни по причине низкой производительности другие оказались просто неприменимы а ADO и RDO еще к тому же оказались недостаточно гибки. Передача данных через SAFEARRAY или через массивы структур (с использованием size_is(…)) тоже применимы далеко не во всех случаях. Работать с БД с использованием массивов совершенно неудобно, не говоря уже о том, что организовать запись (обновление) rowset-ов, да еще и нестандартным образом, становится довольно сложной задачей, и к тому же приводит к появлению большого количества ошибок. Передавать массивы между уровнями не так сложно, как визуализировать эти массивы в виде редактируемых списков с "умным" возвратом результатов и синхронизацией с находящимися на сервере и в памяти ресурсами (таблицы БД, списки и т.д.). При больших размерах массивов автоматически обеспечивать блочную передачу массива по запросу пользователя становится просто невозможно - для этого необходимо создавать специальные библиотеки. Сами компоненты ADO в принципе не предназначены для передачи курсоров по сети, т.к. такая передача делается средствами СОМ (маршаллингом) и на стороне клиента появляется не сам объект, а только его объектная ссылка. Любое обращение к методу объекта через такую ссылку приводит к запросу по сети. Поэтому даже установив ADO-компоненту размер кэша, равный количеству записей в resultset, при вызове любого метода (например, MoveNext) осуществляется вызов по сети компонента, расположенного на сервере. Для решения этой проблемы Микрософт создал компонент RDS и идеологию disconnected rowset-а, но RDS рассчитан принципиально на работу с HTML и требует передачи данных клиенту в потоковом режиме, хотя и с возможностью фоновой закачки данных, а disconnected rowset хорош дотех пор пока с ним не начинается настоящая работа.

Примерно год тому назад, вдоволь намучавшись с вышеперечисленными средствами, мы принял решение изобрести свой “велосипед”. Средством разработки был выбран MS Visual C++. На сегодняшний день мы создали набор компонентов для работы с курсорами в который входят:

ascLocalDB.DLL

Компонент

Краткое описание

Визуальный

ascLoader во время разработки позволяет визуально задать DCOM-объект который будет загружаться при установке свойства ascActive в True. DCOM-объект создается на удаленном сервере.

ascVResultSet во время разработки позволяет визуально задать метод который будет вызываться из объекта заранее выбранного в аscLoader.

ascCResultSet Главный не визуальный компонент на стороне клиента (Cashed Result Set). Осуществляет кэширование данных на стороне клиента (обеспечивая при необходимости сохранение данных на диске), обеспечивает навигационный стиль работы, поддерживает редактирование. ascCResultSet создается ascVResultSet-ом или подключается к нему для обеспечения интерактивной визуализации данных. ascCResultSet совместим с Automation.

  

ascRemoteDB

Компонент

Краткое описание

Визуальный

ascServer

Глобальный компонент, обеспечивающий основной сервис со стороны сервера. С помощью его методов создаются серверные курсоры (ascResultSet).

 

ascResultSet

Главный компонент на стороне сервера Серверный курсор. Его можно создавать, пользуясь методами объекта ascServer. (такими как ExecuteWithResults). ascResultSet реализует интерфейс IascResultSet. Этот интерфейс и передается по сети, но обычно не отдельно, а в составе ASC_RESULT_SET_INFO.

 
LP_ASC_RESULT_
SET_INFO

Это главная хитрость, позволяющая передавать курсор и информацию о нем за один вызов по сети. LP_ASC_RESULT_
SET_INFO это указатель на некую структуру в которую помещается первый буфер с данными, информация о курсоре, информация о колонках и указатель на интерфейс IascResultSet (который используется, если все записи не уложились в первый буфер). Для этой структуры написан wire маршалинг, что позволяет сократить объем передаваемых по сети данных. Получить эту структуру можно, если воспользоваться методами ascServer (такими как ExecuteWithResultsInfo).

 

ascDBControls

Компонент

Краткое описание

Визуальный

ascVDbEdit

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

*

ascVDbGrid

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

*

Как построена работа с курсорами?

Наша библиотека компонентов (для работы с курсорами) изначально была рассчитана на работу в сетях с низкой пропускной способностью. Главным замедляющим фактором в таких условиях является передача данных по сети. Причем в виду латентности сети выгодно как можно сильнее сократить количество сетевых вызовов. Этого можно добиться, например, запаковав данные в массив. Но в реальных условиях иногда, например при визуализации, выгодно не передавать все имеющиеся данные по сети, а осуществить блочную передачу данных частями оптимальных размеров. Можно разбить передаваемые данные на несколько небольших массивов и получать их отдельными вызовами указывая в дополнительном параметре метода, номер такого массива… В общем можно придумать много хитростей с целью оптимизации передачи данных. Чем больше таких хитростей приходит в голову, тем больше начинаешь задумываться над тем, что без облечения их в законченную абстрактную форму не обойтись. Главная хитрость наших компонентов заключается в разделении на серверную и клиентскую часть (слова “серверную” и “клиентскую” используются только как термины описывающие принимающую и отправляющую стороны, а не как приговор двухуровневой технологии клиент-сервер). Первый компонент серверной части ascServer является сервисным и обеспечивает функциональность по подключению к базе данных, исполнению (через OleDB) SQL запросов и возврату курсоров. ascServer создается один раз для процесса. Он не поддерживает маршалинг, а значит к его методам нельзя обратиться напрямую с других машин. В место этого вы должны создать свой DCOM объект методы которого будут вызывать методы ascServer и через [out] параметры возвращать интерфейс курсора (ascResultSet) или спец. структуру о которой пойдет речь дальше. Нами написан “wire_marshal”-маршалинг полей этой загадочной структуры и для типов данных входящих в описание методов ascResultSet и некоторых других. У интерфейса ascResultSet есть много методов с которыми вы познакомитесь позже но почти всеми ими (кроме одного - FetchBuffer) можно не пользоваться если воспользоваться спец. структурой “ASC_RESULT_SET_INFO”. Она состоит из структуры содержащей мета информацию о курсоре (ASC_RESULT_SET_SHORT_INFO), указателя на первый буфер данных (LP_DATA_BUFFER) и указатели на интерфейсы: IascResultSet (сам курсор) и IascResultSetChangeBase (поддержка редактирования).

Буфер данных представляет собой часть строк rowset-а (количество строк в буфере задается при создании курсора) считанных из базы данных плюс некоторая дополнительная информация необходимая для обработки этих данных.

Информация о курсоре содержит:

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

Хотя, на клиентской стороне можно пользоваться методами серверного объекта ascResultSet, все же лучше воспользоваться кэширующим курсором.

Кэширующим курсор (ascCResultSet)

В задачи ascCResultSet входит:

Взаимодействие между кэширующим и серверным компонентами показано на Рис. 1.


Рис. 1

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

Листинг 1
// Описание вашего метода, возвращающего курсор.
// Этот метод реализуется вашим компонентом, который может быть 
// как внутри процессным (типа MTS-компонента), так и внешнепроцессным.
GetCustomers([out] LP_ASC_RESULT_SET_INFO * ppCustCursorInfo, [in] long ID_Department)
// На стороне сервера ...
HRESULT GetCustomers([out] LP_ASC_RESULT_SET_INFO * ppCustCursorInfo, 
   [in] long ID_Department)
{
   TCHAR szSQL[256];
   wsfrintf(szSQL, "SELECT * FROM customer WHERE ID_Department = %d", ID_Department);
   pascServer = ::GetServer();
   pascServer->ExecuteWithResultsInfoDefault (szSQL, // SQL
        // Тип курсора может быть: ascCUR_INSENSITIVE, ascCUR_KEYSET,
        // ascCUR_DYNAMIC или ascCUR_FORWARD, причем даже на курсоре типа
        // FORWARD в последствии можно будет создать
        // прокручиваемый кэширующий (клиентский) курсор с возможностью произвольного
        // доступа к считанным записям, а INSENSITIVE и KEYSET позволяют
        // осуществлять произвольный доступ к любой (даже не прочитанной с сервера)
        // записи (правда SQL Server заметно тормозит при создании такого курсора
        // на resultset-ах с большим количеством записей (> 5000)).
        ascCUR_FORWARD, // 
        300 // FetchBufferSize
        ppCustCursorInfo // для этого параметра написан wire маршалинг
   );
   return S_OK;
}
// На стороне клиента ...
   // NULL обязательно для правильной работы маршалинга
   LP_ASC_RESULT_SET_INFO pCustCursorInfo = NULL;
   // Получаем ResultSet с сервера
   pSomeObj->GetCustomers(&pCustCursorInfo, 12 /* 12-й департамент */);
   // Создаем кэширующий курсор
   CComPtr<IascCResultSet> spIascCResultSet;
   spIascCResultSet.CoCreateInstance(CLSID_ascCResultSet);
   // Подключаем pCustCursorInfo к кэширующему курсору
   spIascCResultSet. rsAttachInfo (pCustCursorInfo, 
   // освобождать память, занятую под pCustCursorInfo, после закрытия resultset
      TRUE 
   );
   long ID;
   CComBSTR sbsCustName;
   // Пользуемся курсором
   while(spIascCResultSet.FetchNext())
   { // получаем данные по позиции колонки
      spIascCResultSet.ColByIndexLong(1, &ID);
      spIascCResultSet.ColByIndexString(2, &sbsCustName);
      // ...
   }
   // переходим к 25-й записи
   HRESULT hr = spIascCResultSet.FetchRandom(25);
   If(SUCCEEDED(hr))
   { // получаем данные по имени колонки
      spIascCResultSet.ColByNameString(L"FirstName", &sbsCustName);
      // ...
   }

Клиентский код можно написать и на VB. Но для этого необходимо написать заглушку на C++, возвращающую указатель на IascCResultSet.

На VB можно вообще не писать кода, а воспользоваться визуальными компонентами ascLoader и ascVResultSet. ascLoader загрузит серверный объект, ascVResultSet произведет вызов необходимого метода и отобразит данные на экране через ascVDbEdit и ascVDbGrid. Компонентов для отображения информации пока два, но есть открытый API и возможность создавать дополнительные редакторы для ячеек ascVDbGrid. Мы сознательно отвергли Microsoft-овский DataBinding, так как он требует дополнительной поддержки от контейнера, а такую поддержку оказывают не все (например, ее нет в Delphi). Наш DataBinding не зависит от контейнера. Мы планируем создание OleDB provider-а для ascResultSet, что позволит на стороне клиента использовать ADO-совместимые компоненты. Но это в далеких планах, а в ближайшее время мы планируем начать тестирование с MTS.

Запланировано также создание компонента ascMemoryResultSet, главным отличием которого от SAFEARRAY будет возможность автоматического обновления его серверного представления, а также визуального отображения и редактирования, не требующего программирования на клиентской стороне. Надеемся также что в нашей реализации потребление памяти будет значительно меньше - благодаря бинарному представлению данных (в отличие от SAFEARRAY, где используется тип данных Variant).

Мы бы, были бы рады потеснее интегрировать серверную часть наших курсоров с VB, но, к сожалению, он напрочь отказывается понимать методы c wire_marshal параметрами. Так что если кто может помочь идеей, то милости просим.


Copyright © 1994-2016 ООО "К-Пресс"