На главную

Библиотека Интернет Индустрии I2R.ru

Rambler's Top100

Малобюджетные сайты...

Продвижение веб-сайта...

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

Забобрить эту страницу! Забобрить! Блог Библиотека Сайтостроительства на toodoo
  Поиск:   
Рассылки для занятых...»
I2R » И2Р Программы » Программирование » Общее в программировании

UDT (user defined types) & Automation. Часть 1

Введение

UDT являются полезным средством в тех случаях, когда необходимо сгруппировать части информации в единое целое. В C++ для UDT используется ключевое слово struct, в Visual Basic - Type, а, скажем, в Pascal - record. Такое группирование может быть полезным в целом ряде практических задач. Каждый из современных языков умеет обращаться со структурами весьма удобным и надёжным образом, а понимании концепции структуры нет ничего сложного. Однако не всё так просто обстоит в случаях, когда структурой необходимо воспользоваться, не зная заранее ничего о самой структуре. Описанию решения этой проблемы и посвящена эта статья.

Задача

Большинство публикуемых мной статей возникло <по мотивам> решённых мной актуальных задач современного программирования. Возможно, это несколько нескромно - заявлять об этом, но я стараюсь решать возникающие проблемы максимально прогрессивными средствами (из-за чего бывает, встречается непонимание некоторыми из заказчиков того факта, что старое - оно и есть старое: совсем недавно от меня потребовали использовать в качестве СУБД Sybase SQL Anywhere 5.0, в то время, как я предлагал MS SQL Server 2000) хотя бы из-за того, что не хочется тратить время на устаревшие технологии. Впрочем, каждому - своё.

Итак, задача заключается в следующем: требуется создать компонент, который смог бы правильно организовать доступ к элементам структуры, вид которой станет известен только во время исполнения. Потребуется разобрать вид этой структуры и далее считать данные из неё и поместить в disconnected ADO-recordset. Другим отягчающим условием задачи является то, что язык, на котором будет реализована эта структура, также неизвестен и единственное разумное предположение, которое можно сделать на момент написания компонента - это то, что клиент может быть (но не обязан) написан на Visual Basic а механизм передачи структур должен быть Automation-совместимым.

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

Анализ

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

2
Рисунок 1. Вероятные сценарии использования разрабатываемого компонента.

Как известно, в COM присутствует стандартный механизм описания типов. Он так и называется: библиотека типов (имеет, как правило, расширение .tlb). Имеется богатый выбор интерфейсов, позволяющих выполнять различные запросы относительно типов, хранящихся в библиотеке, словом, как получить описание, понятно. Не менее понятно, как это описание создать: в случае использования VC++ для написания <создателя> структуры (см. рисунок), достаточно приблизительно следующего .idl-файла, описывающего эту структуру:

import "oaidl.idl";
import "ocidl.idl";

#pragma pack(4)

[
 uuid(1C0AEC27-ED8F-4C01-93CC-35E13B92C705),
 version(1.0),
 helpstring("structures 1.0 Type Library")
]
library STRUCTURESLib
{
 importlib("stdole32.tlb");
 importlib("stdole2.tlb");

 typedef 
 [
 uuid(052A1B68-7229-4196-8612-441AEF1683F0)
 ]
 struct{
 long FirstField;
 DATE SecondField;
 BSTR ThirdField;
 short FourthField;
 BYTE FifthField;
 float SixthField;
 double SeventhField;
 } StructTest1;
};

Таблица 1. idl-файл, содержащий необходимую структуру (StructTest1)

Обратите внимание на uuid, идентифицирующий структуру: не всякий midl-компилятор сможет правильно обработать этот атрибут. В частности, тот midl, который идёт в стандартной поставке Visual Studio с этой задачей не справляется, поэтому возьмите последнюю версию midl из Platform SDK.

Итак, к этому моменту известно, как идентифицировать структуру, храняющуюся в tlb: c помощью uuid, имеющего в данном случае <номер> . Разберёмся теперь, как использовать этот идентификатор:

Начиная с NT 4 SP4 (приблизительно декабрь 1998 года), DCOM расширен возможностью маршаллинга определённых пользователем типов (UDT) и, как следствие, расширением COM API (для устаревших систем Windows 95 потребуется DCOM 1.2). Среди новых возможностей появилась и такая, как создание SAFEARRAY по описателю структуры при помощи интерфейса IRecordInfo, а в VARTYPE был включён элемент VT_RECORD, являющийся индикатором (какого-то) пользовательского типа. Таким образом, DCOM предоставляет все средства для того, чтобы дать возможность серверу добавить описание структуры, а клиенту - это описание правильно понять.

Checkpoint

Для определения COM UDT в <до-.NET> - эпоху необходимо:

  1. Определить структуру в .idl, добавив атрибут uuid (поля структуры должны быть Automation-compatible для того, чтобы можно было воспользоваться созданным UDT из произвольного клиента)
  2. Скомпилировать .idl, получив .tlb
  3. Зарегистрировать .tlb (regtlib :)

Решение

Задача заключается не просто в том, чтобы получить данные из какой-то определённой структуры, а в том, чтобы получить эти данные из структуры с заранее неизвестными полями, поэтому кодируя компонент, которые будет работать с этой структурой, необходимо принять максимальные меры предосторожности.

Для использования UDT в клиенте, написанном на VC++, я воспользовался директивой #import, как наиболее простым и функциональным средством, позволяющим в одну строку сделать всё, что надо:

#import "structures.tlb" inject_statement("#pragma pack(4)")

Обратите внимание на атрибут inject_statement.Без него импортированный заголовок будет начинаться так:

#pragma once
#pragma pack(push, 8)
#include <comdef.h>
namespace STRUCTURESLib {
// User-injected statements
// Forward references and typedefs
// :


что недопустимо по соображениям совместимости с VB 6, который, как известно, выравнивает данные по границе двойного слова. Использование атрибута inject_statement с параметром ("#pragma pack(4)") приведёт к формированию следующего, уже совместимого с VB, описания структуры

#pragma once
#pragma pack(push, 8)
#include <comdef.h>
namespace STRUCTURESLib {
// User-injected statements
#pragma pack(4)
// :

Теперь, используя интерфейсы ITypeLib и IRecordInfo, можно организовать обработку данных. Ключевую роль в схеме работы моего компонента, работающего со структурами, неизвестными на момент компиляции, играет хорошо известный тип SAFEARRAY. Расширенный возможностью работы с UDT, программный интерфейс SAFEARRAY позволяет делать с пользовательскими структурами всё, что угодно. Следующие функции играют ключевую роль в работе с UDT:

Название функции
Краткое описание
SafeArrayCreateVectorEx
Создать вектор пользовательских записей (структур), используя в качестве описания такой записи интерфейс IRecordInfo
SafeArrayGetRecordInfo
Получить описание записи (структуры), хранящейся в SAFEARRAY

В процессе написания также будут использованы функции обычные функции доступа SafeArrayAccess/Unaccess-Data и SafeArrayDestroy.

Главное причиной, которая побудила меня использовать именно SAFEARRAY, является его замечательная способность возвращать хранимые данные в <сыром виде>. Это значит, что ничто не помешает поместить в SAFEARRAY, скажем, long, а затем обратиться к этому <лонгу>, как к четырём последовательным байтам. Идея далеко не нова, но будет в дальнейшем очень плодотворно использована. Поясню её следующим рисунком:

1
Рисунок 2. Поскольку это легко с SAFEARRAY - передать один тип, а затребовать другой, то ничто не помешает обратиться ко второму байту второго двойного слова в том случае, если в SAFEARRAY были записаны эти двойные слова.

Что применимо к словам, то применимо и к структурам: помещаем в SAFEARRAY запись, а извлекаем последовательность байт. Дальше в ход идёт обычная арифметика указателей, с которой не возникнет никаких проблем. Итак, идея, полагаю, понятна. Рассмотрим практическое её воплощение на следующей подзадаче:

  1. Получить описание структуры.
  2. Получить от клиента SAFEARRAY, уже заполненный этими структурами.
  3. Разобрать структуру и скопировать её в ADO.Recordset для дальнейшего использования.

Получение описания

Следующий фрагмент кода получает описание структуры, помещённой (кем-то) в tlb:

LPTYPEINFO pTypeInfo = 0;
LPTYPELIB pTypeLib = 0;
IRecordInfo * pRecInfo = 0;

struct __declspec(uuid("052A1B68-7229-4196-8612-441AEF1683F0")) fake_str {};
struct __declspec(uuid("1C0AEC27-ED8F-4C01-93CC-35E13B92C705")) fake_lib {};

HRESULT hr = LoadRegTypeLib(__uuidof(fake_lib), 1, 0, 0, &pTypeLib);
hr = pTypeLib->GetTypeInfoOfGuid(__uuidof(fake_struct), &pTypeInfo);
hr = GetRecordInfoFromTypeInfo(pTypeInfo, &pRecInfo);

pTypeInfo->Release();
pTypeLib->Release();

Здесь для простоты изложение принимается, что UUID библиотеки равен , а структуры - . К моменту написания этой статьи мне был неизвестен более простой способ кодирования UUID, чем через <подставные структуры>. Впрочем, здесь нет ничего экстраординарного, этот способ документирован и повсеместно применяется.

Создание вектора

Целью вышеприведённого кода было получить интерфейс IRecordInfo, <хранящий> описание структуры с тем, чтобы было можно начать работу с SAFEARRAY. Используя функцию SafeArrayCreateVectorEx теперь можно разместить вектор импортированных из библиотеки записей. Это делает нижеприведённый код.

SAFEARRAY * sa = SafeArrayCreateVectorEx(VT_RECORD, 0, 10, (PVOID)pRecInfo);

Этим вызовом создаётся вектор на 10 элементов-записей, описание которых <хранится> в pRecInfo - указателе на IRecordInfo. Поясню, почему я в который уже раз заключаю слово <хранится> в кавычки. Я всего лишь желаю сохранить изложение как можно более точным, и поскольку инкапсулирование в COM более, чем строгое, то говорить о хранении не приходится. Вот и всё.

Работа с вектором

Разместив вектор записей вызовом SafeArrayCreateVectorEx, можно теперь обращаться к его элементам в обычной манере:

STRUCTURESLib::StructTest1 * t = 0;

SafeArrayAccessData(sa, (PVOID*)&t);

for (long a = 0; a < 10; a++)
{
 t[a].FirstField = a + 1;
 t[a].SecondField = (DATE)a + 1;
 t[a].ThirdField = SysAllocString(L"Это просто такая строка");
 t[a].FourthField = a + 1;
 t[a].FifthField = a + 1;
 t[a].SixthField = a * 2.0 + 1;
 t[a].SeventhField = a * 2.0 + 1;
}

SafeArrayUnaccessData(sa);

Со структурами, описанными в библиотеке типов, можно было работать с давних пор и наверняка кто-то из читателей уже хочет спросить - так что же здесь нового? Ведь до настоящего момента переданный последним аргументом pRecInfo так нигде и не использовался, да и зачем он, если и так известны поля структуры? А что, если предположить, что структура неизвестна? Как вы поступить в этом случае? Вот здесь на помощь и приходит тот самый pRecInfo - передав его в качестве аргумента в создаваемый вектор однажды в одном компоненте, это описание может быть получено где угодно, даже в том компоненте, который ничего не знает ни о библиотеке, из которой берётся структура, ни даже о том языке, на котором эта структура была описана.

Как именно должен выглядеть компонент, перерабатывающий такие <анонимные> структуры, будет показано в следующей статье.

Юрий Кулешов
Софтерра

Другие разделы
C, C++
Java
PHP
VBasic, VBS
Delphi и Pascal
Новое в разделе
Базы данных
Общие вопросы
Теория программирования и алгоритмы
JavaScript и DHTML
Perl
Python
Active Server Pages
Программирование под Windows
I2R-Журналы
I2R Business
I2R Web Creation
I2R Computer
рассылки библиотеки +
И2Р Программы
Всё о Windows
Программирование
Софт
Мир Linux
Галерея Попова
Каталог I2R
Партнеры
Amicus Studio
NunDesign
Горящие путевки, идеи путешествийMegaTIS.Ru

2000-2008 г.   
Все авторские права соблюдены.
Rambler's Top100