Статьи Королевства Дельфи

         

Получение параметров отчета


Теперь о том как получить параметры отчета с помощью которых производится управление.

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

Функция PEGetNParameterFields имеет только один параметр - дескриптор задачи, в результате возвращается количество параметров. В примере показано как работать с параметрами:

var ParameterInfo: PEParameterFieldInfo; ValueInfo: PEValueInfo; . . . // Получить количество параметров. CountParams:= PEGetNParameterFields(FHandleJob); if CountParams <> -1 then begin for i:= 0 to CountParams - 1 do begin // Запросить информацию о параметре i. PEGetNthParameterField(FHandleJob, i, ParameterInfo); ValueInfo.ValueType := ParameterInfo.valueType; // Получить значение параметра. PEConvertPFInfoToVInfo(@ParameterInfo.DefaultValue, ValueInfo.ValueType, ValueInfo); ... end; end;
Описания структур довольно большие, поэтому я опишу только те поля которые используются в примере. ParameterInfo.Name - имя параметра. ParameterInfo.ValueType - тип данных параметра. ParameterInfo.DefaultValue - значение по умолчанию. Структура ValueInfo содержит в одном из своих полей значение параметра. Вы можете посмотреть в примере функцию FormatCrystalValue, чтобы разобраться с полями структуры.



Получение списка COM applications


Во всех приведенных ниже примерах мы будем создавать компонент для управления MTS динамически. Тем читателям, которые будут использовать Delphi 6, делать это необязательно. Можно просто воспользоваться компонентом COMAdminCatalog, который находится на странице COM+. Кстати, в этом случае вы можете воспользоваться справочной системой по методам и свойствам всех рассматриваемых ниже интерфейсов.

В следующем примере мы создадим каталог и получим доступ к списку всех COM+ пакетов, находящихся в папке Applications (Рисунок 1).

Сначала необходимо создать сам COMAdminCatalog.

uses COMAdmin_TLB; .... var MainCatalog : ICOMAdminCatalog; Apps : ICatalogCollection; ....... MainCatalog := CoCOMAdminCatalog.Create; //Create Main catalog // Getting Application folder Apps := MainCatalog.GetCollection('Applications') as ICatalogCollection;

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

После того, как вы получили интерфейс коллекции, необходимо обязательно вызвать метод Populate этого интерфейса. Дело в том, что метод GetCollection не читает список элементов коллекции и если вы попытаетесь обратиться к методам интерфейса без вызова этого метода, то увидите, что список пуст.

Найти нужный элемент в коллекции также можно только путем полного перебора всех его элементов, поскольку метод PopulateByQuery в интерфейсе (по крайней мере, на момент написания статьи) не реализован. В данном фрагменте в список помещаются имена всех компонентов, которые входят в коллекцию. Заметим, что поскольку свойство Item возвращает IDispatch, то используется приведение типов (неявный вызов функции QueryInterface).

Apps.Populate; //Refresch Application folder contents Appcount := Apps.Count; //Applications count for i := 0 to AppCount -1 do begin //Put all application's names into the list App := ICatalogObject(Apps.Item[i]); List.Add(App.Name); end;

Ниже приводится функция, полностью реализующая эту возможность.

function GetApplicationsList(List: TStrings): boolean; var MainCatalog : ICOMAdminCatalog; Apps : ICatalogCollection; App : ICatalogObject; AppCount : integer; i : integer; begin try List.Text := ''; //Empty List; MainCatalog := CoCOMAdminCatalog.Create; //Create Main catalog // Getting Application folder Apps := MainCatalog.GetCollection('Applications') as ICatalogCollection; Apps.Populate; //Refresch Application folder contents Appcount := Apps.Count; //Applications count for i := 0 to AppCount -1 do begin //Put all application's names into the list App := ICatalogObject(Apps.Item[i]); List.Add(App.Name); end; result := true; except result := false; end end;



Получение списка компонентов выбранного пакета


Как уже упоминалось ранее, интерфейсы, предоставляемые ComAdmin.dll позволяют добираться к компонентам только путем последовательного просмотра всего дерева. Таким образом, для того, чтобы получить имена всех компонентов, которые содержатся в данном пакете, сначала нужно пробежать по списку всех пакетов. При этом поиск можно проводить как по имени (свойство Name), так и по свойству Key (application GUID).

После того, как пакет найден, можно просмотреть COM компоненты, которые в нем находятся. Они так же объединены в коллекцию. Для получения коллекции мы вызываем метод GetCollection и указываем, что нам нужен элемент Component, который принадлежит пакету с нужным нам GUID. То есть, если вы заранее знаете application GUID, то производить поиск по имени необязательно.

comps := ICatalogCollection(Apps.GetCollection('Components', app.Key)); comps.Populate; //Refresh list of components compsCount := comps.Count; //Components count for j := 0 to compsCount - 1 do begin //Put all componetes into list comp := ICatalogObject(Comps.Item[j]); List.Add(comp.Name); end

Обратите внимание на то, что и здесь перед просмотром списка используется метод Populate. Ниже приводится полный текст функции.

function GetComponentsList(ApplicationName : string; List: TStrings): boolean; var MainCatalog : ICOMAdminCatalog; Apps : ICatalogCollection; App : ICatalogObject; Comps : ICatalogCollection; comp : ICatalogObject; AppsCount, CompsCount : integer; i, j : integer; begin try List.Text := ''; //Empty List; MainCatalog := CoCOMAdminCatalog.Create; //Create Main catalog // Getting Application folder Apps := MainCatalog.GetCollection('Applications') as ICatalogCollection; Apps.Populate; //Refresch Application folder contents Appscount := Apps.Count; //Applications count for i := 0 to AppsCount -1 do begin //Search Application by name App := ICatalogObject(Apps.Item[i]); if App.Name = ApplicationName then begin // Application found //Getting aplication's components by application key (GUID) comps := ICatalogCollection(Apps.GetCollection('Components', app.Key)); comps.Populate; //Refresh list of components compsCount := comps.Count; //Components count for j := 0 to compsCount - 1 do begin //Put all componetes into list comp := ICatalogObject(Comps.Item[j]); List.Add(comp.Name); end end end; result := true; except result := false; end end;



Порядок действий.


Первым делом нам необходимо подсоединиться к интересующему нас пространству имён. Для данной цели используется интерфейс IWbemLocator и его единственный метод ConnectServer, который позволит нашей программе (в данном случае наша программа является WMI клиентом) получить указатель на интерфейс IWbemServices, который связан с выбранным нами пространством имён на выбранном нами компьютере (не стоит забывать, что технология WMI основана на COM/DCOM, что позволяет использовать её удалённо). Методы объектов типа SWbemServices предназначены для произведения операций в выбранном пространстве имён.
Да, надеюсь, вы знаете, где можно посмотреть имя интересующего вас пространства имён и WMI класса, если нет, то перечитываем предложенные к прочтению статьи или открываем WMI SDK и просматриваем раздел Win32 Classes. После того, как мы подсоединились к выбранному нами пространству имён и получили указатель на объект, связанный с данным пространством имён, нам необходимо получить описание интересующего нас класса в выбранном нами пространстве имён (возможно, звучит мудрёно, но на практике всё гораздо проще, в чём вы скоро сами убедитесь). Для этого воспользуемся методом Get объекта SWbemServices, который вернёт описание (definition) указанного нами класса (на этом этапе вы уже можете считать имена свойств класса и названия поддерживаемых им методов, естественно, все свойства будут содержать null; так же вы можете создать свой класс на базе полученного описания и дополнить его своими свойствами и методами). Таким образом, мы получим указатель на объект типа SWbemObject. Затем нам необходимо получить непосредственно экземпляр (instance) класса, описание которого мы получили. Для этого воспользуемся методом Instances_ объекта SWbemObject, который создаст объект, осуществляющий нумерацию всех экземпляров (instances) данного класса типа SWbemObjectSet. Говоря по-русски, будет создана коллекция всех объектов данного класса. Полученный объект SWbemObjectSet будет содержать объекты SWbemObject. Вот собственно и всё. Получив из объекта SWbemObjectSet экземпляр класса, нам остаётся считать его свойства и воспользоваться его методами. Теперь перейдём к рассмотрению самих примеров. Вашему вниманию будут предложены четыре примера, полные исходные тексты и рабочие exe-файлы которых вы найдёте в прилагаемом к статье архиве.



Послесловие.




На этом я закончу рассказ о применении технологии WMI. Надеюсь, что теперь вы прониклись мыслью о том, что WMI крайне удобная и относительно несложная в реализации технология, которая поможет решить массу ваших проблем :) Microsoft продолжает активно развивать WMI, и в скором времени мы получим мощнейший инструмент, области применения которого очень и очень обширны. Нам, людям занимающимся программированием, как никому другому приходится год от года, месяц от месяца пополнять свой "боевой" арсенал всё новыми и новыми знаниями, приёмами, инструментами и методологиями. На мой взгляд, технология WMI может занять достойное место в вашем личном арсенале знаний.

P.S.
Если вам что-то не понятно в примерах или самой теории, то прежде чем отягощать кого-либо своими расспросами, откройте Platform SDK Documentation, затратьте немного своего драгоценного времени и поищите самостоятельно ответы на свои вопросы. Как показывает практика многих поколений - решение, найденное самостоятельно, стоит нескольких, полученных от кого-то. Но если всё же у вас ничего не выйдет - пишите мне, будем разбираться вместе :)



Построение байтового дерева для сверхбыстрого поиска.


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

Задача формулировалась так: Имеется список звонков или один звонок.
Звонок содержит в себе тариф и номер, но заранее не известна длина тарифа. Например 845678606708 : 8-это международный код 45678 - тариф 606708-телефон. В базе тарифов могут присутствовать тарифы 45, 456, 45678 и нужно выбрать самый длинный (максимальный тариф). Соответственно поиск по индексу не подходит, необходим перебор вариантов, то есть для каждого звонка необходимо перебрать все тарифы. У меня было 500 тарифов и 100000 звонков. В результате получается 500*100,000=50,000,000 операций. Можно это сделать с оптимизацией, но все равно необходим перебор вариантов. У меня операция пересчета занимала 1 час. Поэтому я решил прикинуть тарифы в память, а поиск выполнять во время считывания звонка и записывать ID тарифа. Этим я убивал сразу 2 зайцев. Скорость поиска тарифа сокращалась почти до нуля, и упрощался алгоритм пересчета. Весь пересчет сводился к заурядному SQL-запросу плюс функция расчета цены. Но поскольку к скорости сохранения данных предъявляются весьма жесткие требования, алгоритм должен быть весьма быстрым! Я посмотрел различные источники, подумал о бинарных деревьях и решил, что мне это не подходит. Немного подумав, создал свое дерево, в нем каждый элемент дерева представлен одним байтом 0 до 9; разумеется, мне нужно всего 4 бита, но для операций сравнения удобней использовать весь байт. Вот пример узла дерева
sNode = packed record Item: Byte; ID: Word; Point: Array of Word; end;

Item - это элемент дерева от 0 до 9 ID - Идентификатор тарифа Point - динамический массив состоящий из указателей на дочерние узлы Причем указатели представлены в виде индексов главного массива. Главный массив описан как List: Array of sNode;

Я выбрал динамический массив для хранения дерева как наиболее удобный и экономичный способ хранения. При использовани масива мы икономим 2 байта на указателе, это накладывает ограничение на количество элементов масива 65535. Если необходимо больше, то можно заменить индекс адресом. Но при этом обций размер масива увеличится в 3-4 раза.

При таком посторении дерева экономится память. То есть, для нахождения тарифа 82345 надо сделать 4 шага от корня и при каждом шаге проверять содержание дочерних элементов для нахождения нужной ветки. Если при построении дерева встречаются два похожих тарифа типа 82346, то добавляется только узел 6. Этим экономится много памяти. Все остальное вместе с примерами можно найти в архиве. Там релизован класс TsmallTree который имеет необходимые методы

procedure AddArray(Var Tarif: Array of ShortString); строит дерево из массива procedure AddElement(Key: ShortString; ID: Cardinal); function Find(Key: ShortString): Word; procedure RecursiveBeat(AProc: TBuildNode); создает указатель на процедуру. procedure ShowTree; - вызывает эту процедуру для каждого узла(я использовал при отладке) В секцию public вынесены две переменные Step: Word; - а это как следует из названия шаг увеличения памяти. List: Array of sNode; - это само дерево

Поскольку при построении дерева количество элементов обычно больше 100, то невыгодно прибавлять по 1 элементу (очень медленно). Поэтому я сделал, как обычно в этих случаях, свой менеджер, который при превышении границы массива прибавляет N элементов. Я советую величину шага ставить на порядок меньше элементов масива. То есть Array:1000 Step:100, Array:10000 Step:1000 , тогда нет торможения программы.

У меня при 10000 элементах все работало нормально, но при 12000 начинались какие-то проблемы, возможно, это связано с теоретическим пределом в 65535 узлов дерева.

В описании упоминаются два массива - один массив строк (исходный материал для построения), другой масив - это само дерево, не перепутайте!


сентябрь 2001г.
Специально для

Скачать файлы проекта : Исходные коды (6K) Исполняемый файл (190K)

<
table WIDTH="100%" BGCOLOR="#FAEBD7" CELLPADDING="3" CELLSPACING="0" >

Преамбула:


Однажды мне захотелось перемещать панели разделенные TSplitter с клавиатуры. Не удобно, при большом вводе пользоваться еще и мышью. Первая мысль, что пришла в голову, это изменить размеры у панели при нажатии определенных клавиш. PanelLeft.Width:=PanelLeft.Width-20; Сделал быстро и ... В общем получилась гадость. Все прыгало, мигало, в общем одна жуть.
Ладно, решил пойти другой дорогой, т.е. другим методом. Решил сделать панель в виде полоски и ее перемещать. Форма изменялась как ей было и положено, но сама панель противно кривлялась. В общем тоже мало эстетичное зрелище.

Решил написать наследника от TSplitter, но уже с возможностью эмуляции клавиатуры. Поковырялся, поковырялся и решил, что за день я не управлюсь, тем более что возня с первыми заняла пол дня.
И вот тогда решил сэмулировать движения мышью. Решил что дело выеденного яйца не стоит и начал искать готовый пример и инете. И к своему великому удивлению - не нашел :(

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



я хочу поведать вам об


Приветствую всех любителей Delphi! В этой статье я хочу поведать вам об одной из замечательных, с моей точки зрения, технологии, разработанной Microsoft для облегчения нашей жизни. Теперь любой программист, используя любой современный язык программирования (не исключая и скрип языков!) может с лёгкостью узнать о своём компьютере практически всё. Теперь программисты всех "вероисповеданий" могут определить, какое оборудование установлено на их компьютере, узнать информацию о материнской плате и процессоре, параметры БИОСа, какие процессы запущены в данный момент, какова температура процессора и скорость вращения кулера, какие настройки графической системы, какие.… Одним словом, все о чём вы так долго мечтали, стало доступно благодаря WMI. Звучит заманчиво, не так ли? ;)
Естественно, что WMI - это не только набор параметров. А что это такое - читайте ниже.

Представление списков


Строка списка без пробелов представляет собой набор групп чисел, разделенных запятой ",". Группа может состоять из одного числа или диапазона. Последний задается двумя числами, разделенными дефисом "-" (начальное и конечное значение). Опционально после диапазона может стоять значение шага, отделенное знаком плюс "+". По-умолчанию шаг равен 1. Если шаг указан, то конечное значение можно опустить - тогда оно по-умолчанию будет равно максимальному значению в контексте назначения данного списка. Начало диапазона по-умолчанию равно 0.

Символ звездочки "*" вместо группы означает весь диапазон возможных значений в данном контексте. Порядок следования групп в строке списка роли не играет.

Пример. Cписок вида: 0-5,8,12,20-30+2
интерпретируется как последовательность: 0,1,2,3,4,5,8,12,20,22,24,26,28,30



Преимущества технологии


Разнообразие импортируемых функций не ограничено ничем Не изменяются коды библиотеки (компоненты) Не происходит разбухания объектного кода, т.к. не используются шаблоны - все функции конкретно и явно описаны в заголовочных файлах Полностью автоматизированный процесс генерации кода, включая опреде-ление идентификаторов функций (параметров для GetProcAddress) Мнемоника кода не ухудшается: имена функций остаются неизменными Минимальный объём ручного кодирования - всего 2 строки: Включение заголовочного файла Вызов метода LoadDll Технология применима не только для BCB, но и для, например, Visual C++, а также - с небольшой адаптацией - для любого языка/среды разработки; Например, в Visual C++: сгенерированный код можно использовать без изменений (только за-комментировав включение vcl.h) вместо компоненты TAskDll следует создать класс. Многие разработчики делают компоненты-обёртки для функций DLL - их применение намного удобнее. Для этих целей как нельзя лучше подходит данная технология: Создаётся компонента, производная от TAskDll Сгенерированный модуль (Example_Load.cpp) включается в проект пакета В конструкторе компоненты свойству DllName присваивается имя DLL В методе Loaded компоненты вызывается метод LoadDll. Всё!



Причины перехода от BDE к ADO


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

Итак, для хранения атрибутивной информации был выбран формат MS Access, который имеет то обстоятельство, что все таблицы хранятся в одном файле (в отличие от Paradox и Dbase) и не требует при этом запущенного сервера, как, к примеру, Interbase. Необходима также связь с файлами форматов dbf и db для загрузки/выгрузки данных в/из БД. Для написания программы мы используем Delphi 4, а для подключения к файлам БД использовалась BDE. И все это было отлично, но вот появились два важных обстоятельства: Вышел MS Access 2000. BDE отказывается работать с этим форматом. Как мне удалось найти ответ после долгих поисков на сайте Inprise - Inprise не знает как производить коннект к этому формату. Цитата: 'Для доступа к данным MS Access мы используем DAO 2.5, который не может работать с форматом MS Access 2000. Если Вам необходим доступ к БД формата MS Access 2000, используйте, пожалуйста, компоненты ADO Delphi 5. По нашей (возможно неверной) информации причина здесь в отсутствии официальной документации от Microsoft. 2. Была найдена интересная особенность поведения BLOB потоков под управлением Windows NT 4. Допустим, нам необходим доступ к BLOB полям таблиц в БД формата MS Access 97. Как произвести подключение через BDE к MS Access 97 я говорить не буду, т.к. многие знают, а кто не знает, тот легко найдет эту информацию. Итак, подключение произведено. Отлично. Вот фрагмент программы: Var AStream: TBLOBStream; Data: Integer; Begin // Открываем таблицу (обычный TTable) ATable.Open; // Создаем поток. AStream := TBLOBStream(ATable.CreateBLOBStream(ATable.FieldByName('Поле'))); // Что-либо читаем из него. AStream.Read(Data, SizeOf(Data)); // Освобождаем поток и закрываем таблицу. AStream.Free; ATable.Close; End; Казалось бы - абсолютно тривиальный код. НО! Строка, где производится чтение из потока, вызывает исключительную ситуацию - 'External error - EEFFACE'. И в исходных текстах VCL от Delphi 5 мы находим потрясающее объяснение - это, оказывается, 'C++ Exception'. Интересно, а при чем тут C++? Единственный ответ, какой я знаю, - Delphi написана на C++.
Плюс ко всему, если вы запускаете эту программу из-под Delphi - ошибка не возникает, а если запускаете ее прямо в Windows - ошибка будет непременно. Я дошел в своих поисках до вызовов прямых вызовов BDE API - вот там-то ошибка и возникает, так что я думаю тут очередная ошибка BDE, хотя я использовал на тот момент самую последнюю версию с сайта Inprise - BDE 5.11.
Так что, господа, если Вы используете нечто подобное в своих программах, то знайте, что под Windows NT 4.0/Windows 2000 Ваши программы работать не будут. Самое интересное, что компоненты из библиотеки VCL, которые используют подобный метод для получения данных (к примеру, TDBRichEdit) тоже не работают! Итак, этих двух причин оказалось достаточно для нашей фирмы, чтобы начать переход от BDE к ADO.



Приложение со свойствами платформы. Простая платформа.




Если читатель желает начать знакомство с самого начала, нужно найти исчезнувший журнал Программист, в котором были опубликованы статьи по данной платформе:

Приложение со свойствами платформы, №7, 2002г., Приложение со свойствами платформы. Типы полей, №2, 2003г.

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

Главная идея

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

Такие требования характерны, например, для лечебного учреждения, в котором пациент приходит для записи на прием в регистратуру, а потом проходит осмотр и/или лечение у разных врачей: терапевта, окулиста, хирурга и т.д. Все врачи работают с одной и той же информационной единицей, т.е. с амбулаторной картой больного, или с медицинской картой, если это стационар. Но каждый из них имеет свою специфику. Если не принять специальных мер при создании программного обеспечения системы автоматизации лечебного процесса, то придется для каждого врача-специалиста создавать отдельное приложение, причем львиная доля программного кода будет одна и та же во всех этих приложениях.

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

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

Потребовался универсальный механизм вывода отображаемых данных на принтер или в Excel, что является своего рода отчетом. Дальнейшие накрутки пошли при работе с запросами. Составление запроса, как известно, целая наука. Очень утомительно писать запросы повторно, т.е. их нужно где-нибудь хранить. Решено было записывать запросы в ту же самую системную базу данных. Но тогда нельзя гарантировать, что записанный в системную базу запрос останется актуальным при очередной загрузке приложения. Любая таблица за время между запусками могла быть реконструирована так, что запрос, работавший с ним, уже был бы ошибочным. Отсюда появилась задача формирования текста запроса после загрузки информации о пользовательской базе данных. Точнее говоря, текст SQL-запроса формируется в момент использования запроса, автоматически контролируя при этом соответствие структуре базы данных.

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

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

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



Приведённый код компоненты достаточно прозрачно


Приведённый код компоненты достаточно прозрачно иллюстрирует всё вышесказанное. К сожалению, у меня не хватает времени сделать его самодостаточным для беспроблемной компиляции: в коде встречаются объекты классов (IniFile, ToFile, TrFile, TwFile) и компоненты (TAskDisk), которые являются неотъемлемой составной частью моего инструментального пакета ASK Tools, который будет описан в ближайшее время. Механизм перехвата ИС великолепно работает во всех случаях, когда они генерируются приложением. К сожалению, код, унаследованный из языка «С», не генерирует ИС: когда-то вся обработка ошибочных ситуаций ложилась на плечи прикладного программиста. Таким является весь код из стандартных «С» библиотек, в частности, работа с файлами через дескрипторы и указатели (FILE *). Таким образом, делаем вывод, что для вящей надёжности следует весь унаследованный код, который Вы используете, заключить в оболочку классов, перехватывающих коды возврата всех функций и преобразующих их в ИС. Я не люблю работать с файловыми потоками С++: мне крайне неудобно пользоваться манипуляторами для форматирования выводимых строк. То ли дело форматная строка printf ! Сторонников потоков прошу не спускать на меня всех собак: о вкусах не спорят, к тому же привычка – вторая натура. Поэтому пришлось реализовать иерархию классов TxFile. Основным их достоинством является то, что любую файловую ошибку (в том числе и ошибки чтения/записи) они преобразуют в ИС, которая обрабатывается стандартным способом, в чём можно убедиться, глядя на Рисунок 1. Ну и, кроме того, я кое-чего добавил для более удобного пользования. Хочется отметить, что когда я в родной конторе собрал на сервере локальной сети сообщения с компьютеров всех пользователей и посмотрел статистику, это реально помогло мне найти и обезвредить некоторые глюки. При желании в компоненте можно реализовать опциональную отправку сообщений о сбоях разработчику, что так полюбила теперь Microsoft.

Скачать исходные коды (пример на С++): (4K)
Кочетов Андрей
май 2003г.
Специально для
Библиография:
“C++ Builder 5. Developer’s Guide”
J.Hollingworth,D.Butterfield,B.Swart,J.Allsop

Применимость


1. Метод подходит для создания собственных типов XML документов. Когда есть необходимость оперативно разработать свой формат для обмена данными, то проблем возникать не должно. В такой ситуации это может быть оптимальным подходом. Поскольку формат разрабатывается заново, то мы можем учесть все ограничения предложенной реализации и не использовать атрибуты. При этом достаточно спроектировать необходимые нам классы, а вся остальная работа будет проделана автоматически.
2. Метод подходит для обработки XML документов, в которых не используются атрибуты. Подобных типов документов не много, но если они подходят, то такой путь их обработки достаточно удобен. Так элемента, а подобное ограничение можно обойти предварительной обработкой загружаемого документа.
Применимость может стать почти универсальной, если доработать код для обработки атрибутов элементов.



В качестве примера использования класса


В качестве примера использования класса TDataEditor я написал компонент (TGraph), который строит графики функций. А также я сделал ActiveX объект, который демонстрирует возможности компонента TGraph. ActiveX Вы можете увидеть чуть ниже, но сначала несколько слов о методах и свойствах TGraph: function XCoord(X: Double): Double; возвращает координату оси X в масштабе графика. Параметр функции X указывается в масштабе компонента function YCoord(Y: Double): Double; возвращает координату оси Y в масштабе графика. Параметр функции Y указывается в масштабе компонента function Coordinates(X, Y: Double): TCoord;
TCoord = record X, Y: Double; end; возвращает координаты в масштабе графика. Параметры функции X и Y указывается в масштабе компонента property Picture: TBitmap; содержит изображение графика property Detailization: Integer; уровень детализации графика. Это свойство регулирует количество точек, которые будут рассчитаны начиная с минимального до максимального значений оси X. При повышении детализации, повышается количество расчетных точек на оси X, соответственно повышается количество соответствующих им точек Y. Поэтому эффект от повышения детализации заметнее на графиках типа Y = TAN X, где больше вертикальных линий (практически нет смысла детализировать функции типа Y = X или Y = SIN X). После расчета графика создается массив точек, который затем подвергается фильтрации (чтобы избежать двух точек с одинаковыми координатами - побочный эффект чрезмерной детализации). property FramePen: TPen; отвечает за прорисовку линии рамки property GraphPen: TPen; отвечает за прорисовку линии графика property GridPen: TPen; отвечает за прорисовку линии координатной сетки property HorzSpacing: Double; шаг координатной сетки по горизонтали property ShowAxis: Boolean; определяет видимость координатных осей property ShowFrame: Boolean; определяет видимость рамки property ShowGrid: Boolean; определяет видимость координатной сетки property ShowText: Boolean; определяет видимость формулы property Text: string; содержит формулу property TracePen: TPen; отвечает за прорисовку линий трассировки property Tracing: Boolean; определяет трассировку графика property VertSpacing: Double; шаг координатной сетки по вертикали property XMaxValue: Integer; максимальное значение оси X property YMaxValue: Integer; максимальное значение оси Y property OnTrace: TTraceEvent; TTraceEvent = procedure(Sender: TObject; X, Y: Double; var Continue: Boolean) of object; событие, возникает при трассировки графика. Параметры X, Y возвращают координаты текущей точки, параметр Continue позволяет прекратить трассировку На рисунке приведен скриншот ActiveX-компонента, который Вы можете установить у себя, скачав предлагаемый проект, или проведя on-line тестирование на страницах Королевства.
Прежде чем переходить по ссылке, обратите внимание, придется скачать ActiveX размером 304К.

Пример просмотра отчета


Ниже приведен код процедуры для просмотра отчета из примера

procedure TFrmMain.btnReportPreviewClick(Sender: TObject); var // Дескриптор окна в котором производится просмотр отчета FWindow: THandle; // Информация об источнике данных. // См. раздел "Получение параметров и свойств источника" lt: PELogOnInfo; begin // В зависимости от флага устанавливаем дескриптор окна. // При нуле, отчет будет показан в независимом внешнем окне. if chkWindow.Checked then FWindow:= 0 else FWindow:= pnlPreview.Handle; // Открываем отчет и получаем дескриптор задачи. FHandleJob:= PEOpenPrintJob(PChar(edtPathReport.Text)); // Получение параметров источника данных отчета. FillChar(lt, SizeOf(PELogOnInfo), 0); lt.StructSize := SizeOf(PELogOnInfo); PEGetNthTableLogOnInfo(FHandleJob, 0, lt); // Устанавливаем новые параметры источника данных отчета. StrPCopy(@lt.ServerName, ExtractFilePath(edtPathReport.Text) + 'source_db.mdb'); PESetNthTableLogOnInfo(FHandleJob, 0, lt, False); // Настраиваем окно вывода. PEOutputToWindow(FHandleJob, PChar(TForm(Self).Caption), 0, 0, 0, 0, 0, FWindow); // Выводим отчет. PEStartPrintJob(FHandleJob, True); // Закрываем дескриптор задания. PEClosePrintJob(FHandleJob); end;



Пример реализации


Эффект использования описанной технологии повышается при увеличении сложности программы, для простых программ она вряд ли целесообразна. В качестве примера я расскажу об одной из своих разработок: Visual2k - Интегрированная среда для программирования микроконтроллерных кукол-роботов. Подробнее о ней и о других программах, использующих "многозвенное программирование" можно узнать на моем web-сайте.

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

Visual2k содержит подсистемы, которые позволяют всем технологам работать над проектом в одной и той же интегрированной среде. Задача первого технолога (электронщика) - не только сконструировать аппаратуру, но и начать создание базы проекта. Вот так выглядит подсистема, в которой он работает:

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


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

Здесь стоит отметить, что Visual2k немного напоминает Delphi, с той разницей, что здесь все представлено визуальными компонентами, даже переменные и операторы. Режиссер выстраивает сценарий, выкладывая на рабочую область компоненты-операторы, и назначает их свойства с помощью инспектора объектов. Программа сценария, полученная таким образом, может выполняться либо на персональном компьютере (для сложных говорящих кукол или спектаклей), либо на одном микроконтроллере или сети микроконтроллеров. Исходный вид сценария один и тот же, но генерируется либо текст на языке Object Pascal, который компилируется dcc32, либо текст на языке Cи, объединяемый с частью, заданной электронщиком и компилируемый Cи-компилятором. Поскольку в операторах присваивания и в условиях могут быть выражения, Visual2k включает в себя синтаксический анализатор выражений, обнаруживающий все синтаксические ошибки и заменяющий русские имена переменных и функций на имена, допустимые для языка Pascal. Если кукольный проект будет выполняться под управлением персонального компьютера, то сценарий компилируется в DLL, которая вызывается исполняющим ядром. Такая структура позволяет передавать заказчику только исполняющее ядро и DLL, не раскрывая фирменных секретов спектакля. Параллельные процессы, необходимые для адекватного описания сценария реализуются на базе библиотеки параллельного программирования Gala. Если же сценарий выполняется в микроконтроллере, то используется специально разработанная многозадачная среда с кооперативной мультизадачностью и сценарий зашивается прямо в ПЗУ.


Когда мы получили первый заказ на кукольный спектакль, программы Visual2k еще не было, и я писал сценарий самостоятельно (на Delphi) - режиссер сидел рядом ничего не понимая в том, что я делаю, и только давал советы. Я плохо понимал, чего хочет режиссер, а режиссер вообще не понимал, что я делаю. После создания Visual2k, я занимался только развитием интегрированной среды, добавлял поддержку новых типов микроконтроллеров и новых типов приводов, совершенно не вникая в то, какие делались спектакли. Режиссер очень быстро освоил простой язык описания сценариев и получил полную свободу в реализации своих режиссерских замыслов. Так мне удалось расправиться с целым стадом зайцев - существенно облегчить себе задачу сопровождения программы, освободиться от написания конкретных сценариев, освободить электронщика от написания программ для микроконтроллера и высвободить себе время для других разработок.
The end.
Сергей Гурин

Специально для
Cкачать файлы проекта (80K)

Пример реализации компонента EventClass


Допустим, у нас существует задача на базе существующей системы, функционирующей в среде COM+, реализовать систему ведения собственно журнала событий в текстовом файле. Для начала нам нужно реализовать компонент EventClass, о котором речь шла выше. Он будет представлять собой пустую заглушку для подписчика. Именно через него будут запускаться наши подписчики в каталоге COM+.

Когда компонент-издатель будет создавать событие, компонент EventClass передаст все входящие параметры события и активизирует всех подписчиков.

Важно!
Метод события может содержать только входные параметры [in]. Выходным может быть только результирующий тип HRESULT, принятый в COM для определения статуса завершения S_OK, в результате удачи или E_FAIL, в результате неудачи выполнения метода.

Запустим среду Delphi, создадим простую ActiveX библиотеку и поместим в неё объект автоматизации (Automation Object). Определим нумератор типов ошибок и создадим интерфейс ISysLogEvent с методом ReportLog. type LogMessageTypes = TOleEnum; const lmtInformation = $00000000; lmtWarning = $00000001; lmtError = $00000002; lmtFatal = $00000003; lmtDebug = $00000004; lmtUnknown = $00000005; type TSysLogEvent = class(TAutoObject, ISysLogEvent) protected procedure ReportLog(enMsgType: LogMessageTypes; const strUserName, strModuleName, strMsgText: WideString); safecall; { Protected declarations } end; Библиотека типов изображена на Рисунок 1.

В разделе Implementation создадим заглушку метода для EventClass: implementation uses ComServ; procedure TSysLogEvent.ReportLog(enMsgType: LogMessageTypes; const strUserName, strModuleName, strMsgText: WideString); begin // Event class methods are not implemented. end; initialization TAutoObjectFactory.Create(ComServer, TSysLogEvent, Class_SysLogEvent, ciMultiInstance, tmApartment); end.

На этом закончим. Остается зарегистрировать заглушку в нашем приложении COM+. Если приложение не создано, создайте его через средства ComponentServices.

Окно регистрация компонента EventClass изображено на Рисунок 2.



Пример реализации методов издателя


Создадим простенькое приложение и проверим существующею связку. Создайте бизнес-объект COM+ инициирующий в любом своем методе метод-событие ReportLog.
Пример реализации объекта приведен ниже: unit BsObjectUnit; interface uses ComObj, ActiveX, BsObject_TLB, StdVcl, LogEvent_TLB; type TBusinessObject = class(TAutoObject, IBusinessObject) protected function NewObject(param1: Integer): HResult; safecall; { Protected declarations } end; implementation uses ComServ; function TBusinessObject.NewObject(param1: Integer): HResult; var LogEvent : ISysLogEvent; begin LogEvent := CoSysLogEvent.Create; try LogEvent.ReportLog(lmtInformation, 'Nonamed', 'BsObjectUnit','TBusinessObject.NewObject executed!') except LogEvent.ReportLog(lmtInformation, 'Nonamed', 'BsObjectUnit','TBusinessObject.NewObject failed!') end; end; initialization TAutoObjectFactory.Create(ComServer, TBusinessObject, Class_BusinessObject, ciMultiInstance, tmApartment); end. После вызова метода NewObject у объекта BusinessObject будет создано событие, которое создаст объект SysLog и запишет и отобразит информацию в диалоговом окне. Подписчиков у созданного объекта EventClass может быть неограниченное количество с самыми разнообразными функциями, от отображения диалогового окна до записи данных в отдельную БД.



Пример реализации Объекта-подписчика


После регистрации компонента EventClass создадим компонент-подписчик.
Точно так же, как при создании компонента EventClass создадим библиотеку и объект автоматизации. Немного будет отличаться наполнение реализации методов и метод регистрации.
Создадим интерфейс с методом, аналогичным методу интерфейса ILogEvent – ISysLog (Рисунок 3):

Важно!
Не забудьте подключить в вашу библиотеку типов зарегистрированную в ComponentServices библиотеку с заглушкой EventClass и укажите интерфейс ISysLogEvent в разделе Implements как показано на Рисунок 3а.

Ниже приведен код компоненты подписчика, который будет получать события от издателя. Из реализации бизнес-логики видно, что при возникновении метода-события ReportLog, компонент-подписчик будет выдавать диалоговое окно с информацией для записи в журнал. Если вы замените реализацию этого метода программным кодом записи в файл, вы получите готовый компонент для регистрации ваших событий в бизнес объекте (вывода информации, сообщений об ошибках, отладке и т.д.). unit SysLogUnit; interface uses ComObj, ActiveX, SystemLogger_TLB, StdVcl, LogEvent_TLB, Dialogs; type TSysLog = class(TAutoObject, ISysLog, ISysLogEvent) protected procedure ReportLog(enMsgType: LogMessageTypes; const strUserName, strModuleName, strMsgText: WideString); safecall; { Protected declarations } end; implementation uses ComServ, SysUtils; procedure TSysLog.ReportLog(enMsgType: LogMessageTypes; const strUserName, strModuleName, strMsgText: WideString); begin ShowMessage('MessageType : '+IntToStr(enMsgtype)+#10#13+ 'ModuleName : '+strModuleName+#10#13+ 'UserName : '+strUserName+#10#13+ 'TextMessage : '+strMsgText); end; initialization TAutoObjectFactory.Create(ComServer, TSysLog, Class_SysLog, ciMultiInstance, tmApartment); end. Зарегистрируйте компонент в каталоге COM+ и подпишите его к компоненту EventClass. Как это сделать? Смотрите Рисунок 4.


Рисунок 4

Далее следуйте инструкциям визарда и сделайте выбор как показано на Рисунок 5


Рисунок 5

Итак, у вас на компьютере установлены объекты EventClass и подписчик.



Принцип работы Инспектора


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

Описание Инспектора (показаны только основные поля, свойства и методы):

TInspector = class private FForm: TInspForm; FullParticuls: TParticulList; CurrParticuls: TParticulList; ActiveIndex: Integer; ActiveEditor: TParticulEditor; procedure ChangeIndex(NewIndex: Integer); procedure Paint; procedure ToolButtonClick(Sender: TObject); public property Visible: Boolean read GetVisible write SetVisible; constructor Create; destructor Destroy; override; procedure Change; procedure Make; end; где FForm - форма Инспектора; FullParticuls - все особенности редактируемого элемента управления (или группы элементов); CurrParticuls - текущий список особенностей (для реализации отдельных страниц для свойств, методов или событий), отображается в настоящий момент именно он; ActiveIndex - индекс редактируемой в настоящий момент особенности в списке CurrParticuls; ActiveEditor - редактор текущей особенности (отображается только он один); ChangeIndex - смена текущего индекса (при щелчке мышью на поле Инспектора); Paint - отрисовка Инспектора; ToolButtonClick - общая процедура для обработки клика по кнопкам "Свойство", "Метод", "Событие"; Visible - показывает/скрывает форму Инспектора; Change - заново перерисовывает Инспектор при изменении списка активных элементов управления Actives; Make - обновление Инспектора при изменении особенности редактируемого объекта (или группы объектов). Инспектор создаётся в одном экземпляре (и это естественно!) автоматически при включении в проект файла Insp.pas. Его экземпляр - переменная Inspector типа TInspector. В рабочей области Инспектора названия запрещённых особенностей отображаются светло-серым цветом, особенности только для чтения выделяются курсивом. Имя редактируемой в настоящее время особенности выделяется полужирным шрифтом.
Алгоритм работы Инспектора при щелчке мышью на элементе управления следующий. При щелчке мышью на элементе управления этот элемент добавляется в список Actives (с очисткой его или без в зависимости от состояния клавиши Shift), затем метод MouseDown элемента вызывает метод Change Инспектора. Метод Change производит очистку Инспектора и его полей, устанавливает имена и типы редактируемых элементов в список (верхний в форме Инспектора), формирует полный список особенностей всех элементов из списка Actives (так же как в Delphi, если у разных элементов есть одинаковые особенности и их значения равны, то они отображаются, иначе - нет). Полный список образуется специальным слиянием списков особенностей всех редактируемых элементов (метод класса TParticulList.Comparing). Далее метод Change вызывает метод ToolButtonClick Инспектора, который формирует текущий список CurrParticuls в зависимости от нажатой кнопки "Свойства", "Методы" или "События".
Алгоритм работы Инспектора при щелчке мышью в его рабочем поле (поле отображения особенностей) таков. Метод PaintBoxMouseDown определяет, на какой особенности был сделан щелчок и вызывает метод ChangeIndex с индексом этой особенности. Метод ChangeIndex удаляет старый редактор (оставшийся от прошлого щелчка на рабочей области), в списке CurrParticuls получает особенность, для которой должен быть сформирован новый редактор. Затем с помощью процедур Reference и Executor отыскивает соответственно редактор и обработчик для типа данных TParticul.Data, зарегистрированного ранее, создаёт по этим данным новый редактор и вставляет его в рабочую область Инспектора. В свойство Particul редактора устанавливается найденная особенность.
Алгоритм работы Инспектора при изменении особенности следующий. Редактор, изменивший особенность, записывает его в своё свойство Particul и вызывает метод Инспектора Make. Метод Make вызывает метод SetParticul для редактируемого элемента (если их несколько, то для каждого), в качестве параметра передаёт изменённую особенность из свойства редактора Particul. После этого метод Make заново считывает все особенности редактируемого элемента (или группы) и отрисовывает их в рабочей области Инспектора.



Принудительное скрытие/показ особенностей


Поле TParticul.Visible, как уже упоминалось, отвечает за "видимость" особенности в рабочей области Инспектора. В основном оно используется "внутри" Инспектора и массива особенностей TParticulList; например, при выделении нескольких объектов редактирования, оно скрывает те общие их особенности, поля Code которых не равны (таким же образом поступает Delphi при выделении нескольких компонентов на форме в DesignTime).

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

Приведу пример из своего опыта, опять же из области САПР. Допустим, есть схема ТСхема, обладающая свойством База: ТБаза, которое принимает значения бзПневматическая, бзКинематическая, бзЭлектронная. Как известно из автоматики, при замене в схеме, скажем, всех электронных элементов и связей на пневматические, логика работы схемы не меняется. Пусть у нас схема имеет свойство База = бзПневматическая. Тогда в ней важны такие параметры, как давление, поток через проводники, ёмкости резервуаров и другие гидравлические параметры. Мы меняем в Инспекторе свойство База на бзЭлектронная. Тогда станут важны другие параметры, такие как напряжение, сила тока, ёмкости конденсаторов и пр. Как в этом случае быть с давлением и потоком? Можно, конечно, их запретить (TParticul.Enabled := False), однако, при этом загромождается рабочая область Инспектора "лишними" в этом случае особенностями, мешающими нормальной работе пользователя. Поэтому, в этом случае лучше их скрыть вообще: function ТСхема.GetParticuls: TParticulList; var P: TParticul; begin ... P := DoProperty('Давление', dtReal, True, База = бзПневматическая, ДавлениеToStr(FДавление), '', False); Add(P); ... end; Приведу другой пример. Пусть у нас редактируется элемент управления, который при редактировании может менять свой класс, например TComboBox, TColorBox и TShellComboBox (у кого нет последних двух компонентов, поясню: TColorBox - комбобокс выбора цвета, TShellComboBox - выбор папки (как в окнах папок Windows 98 сверху)). Все эти компоненты "комбобоксобразные", у них есть общие свойства, но есть и индивидуальные, например общие: Width, Left, DropDownCount и пр., индивидуальные: TComboBox - Items, CharCase; TColorBox - DefaultColor, Selected; TShellComboBox - Root, UseShellImages. type TMultiControl = class(TExternalControl) private FComboBox: TComboBox; FColorBox: TColorBox; FShellComboBox: TShellComboBox; ... end;


Смена элемента управления будет осуществлена так:

... function TMultiControl.GetParticuls: TParticulList; var P: TParticul; L: TStringList; begin ... {в начале запишем в L три строки - названия классов каждого элемента управления (например, FComboBox.ClassName)} P := DoProperty('Класс', dtEnum, True, True, ExternalObject.ClassName, L.CommaText, False); ... end;

Естественно, до этого в конструкторе свойству ExternalObject должно быть присвоено какое-либо из полей (FComboBox, например) и все поля должны быть проинициализированы.

... procedure TMultiControl.SetParticul(Value: TParticul); var SParent: TWinControl; begin ... if Value.Name = 'Класс' then begin SParent := (ExternalObject as TControl).Parent; (ExternalObject as TControl).Parent := nil; if Value.Code = FComboBox.ClassName then ExternalObject := FComboBox else if Value.Code = FColorBox.ClassName then ExternalObject := FColorBox else ExternalObject := FShellComboBox; (ExternalObject as TControl).Parent := SParent; end; ... end; ...

В таком случае обработка частных особенностей будет происходить таким образом:

... function TMultiControl.GetParticuls: TParticulList; var P: TParticul; begin ... P := DoProperty('Цвет по умолчанию', dtColor, True, ExternalObject is TColorBox, ColorToString(FColorBox.Color), '', False); ... end; Обработка в SetParticul происходит обычным способом, так как "защита от несанкционированных действий" уже предусмотрена в GetParticuls.

В примере Example4 продемонстрирована разработка и использование "мультиэлемента".


Про "софт".


Чтобы не было путаницы в головах, договоримся, что далее под микшером мы будем понимать его "программную ипостась".

Для простоты понимания (и дальнейшей работы) представим микшер в виде небольшой иерархической модели.

уровень 1: микшер. Имеется в виду собственно микшер-устройство (device). Функции микшера позволяют определить число микшеров-устройств, представленных в системе и их возможности. уровень 2: аудиолиния. Это основной элемент архитектуры микшера. Аудиолиния состоит из одного или более каналов данных, исходящих из одиночного источника. Например, стерео аудиолиния имеет 2 канала данных, но считается за одну аудиолинию, так как исходит из одного источника. Различаются аудиолинии-источники и аудиолинии-приемники. У микшера может быть несколько аудиолиний-источников сигнала, но аудиолиния-приемник сигнала одна -- на то ведь микшер так и называется :) На картинке аудиолинии-источники показаны линиями со стрелочками к кругу, а аудиолиния-приемник -- это линия, уходящая от круга. уровень 3: элемент управления (audio-line control, mixer control) аудиолинии. Каждая аудиолиния имеет ассоциированные с ней элементы управления (контролы). Контрол аудиолинии может выполнять любые функции в зависимости от характеристик ассоциированной аудиолинии. Набор доступных контролов зависит от используемого "железа". уровень 4: свойства элементов управления (control details). Каждый контрол имеет свой набор свойств, которые можно менять. Изменение свойств влечет изменение звука.

Как видим, уровни 1 и 2 соответсвуют "железной" модели и имеют прямые аналоги в виде устройств и соединений. Элементы уровня 1 и 2 в современном "железе" размещаются в чипе кодека. Уровни 3 и 4 напрямую не соотносятся с моделью "железа". Они отвечают за характеристики сигнала, который мы слушаем.

Надеюсь, к этому моменту у нас в головах есть сформированная модель, план. Остались технические вопросы multimedia API в части управления микшером.

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

Уровень 1. Микшер. Структуры и функции, предназначенные для работы с микшером в целом.



Про то, как вычисляется размер зашифрованного текста.


Не всегда можно предположить какой размер будет выходного шифрованного текста, а функции проводящие преобразование требуют что бы память под него была уже выделена (разработчики PGPsdk почему-то это не предусмотрели), и если памяти не хватает - возникает исключение о нехватки памяти. Мною опытным путем была установлена формула для вычисления размера блока: outBufLen := inBufLen*5; if(outBufLen<10000) then outBufLen:=10000; outBufRef := StrAlloc(outBufLen);

Временные ключики

В процессе работы программы появляются резервные файлы ключей, имеющие следующий вид - (pub|sec)ring-bak-##.pgp – предусмотрен откат от изменений. В принципе, если Вы правильно используете контекст и правильно его закрываете, этот файл корректно удаляется при освобождение контекста. Но на всякий случай можно его удалять следующим образом (повесить можно на закрытие формы или вызывать принудительно):

Procedure DeleteBakPGPFiles; Var P : TPreferenceRec; FileSearch : String; SearchRec : TSearchRec; Begin spgpGetPreferences(@P, PGPPrefsFlag_PublicKeyring or PGPPrefsFlag_PrivateKeyring); FileSearch:=P.PublicKeyring; Insert('-bak-*',FileSearch,Pos('.',FileSearch)); FindFirst(FileSearch, faAnyFile, SearchRec); if(SearchRec.Name<>'')then if not(DeleteFile(ExtractFilePath(FileSearch)+SearchRec.Name))then ShowEvent('Not delete file::'+ ExtractFilePath(FileSearch)+SearchRec.Name,0); while(FindNext(SearchRec)=0)do if not(DeleteFile(ExtractFilePath(FileSearch)+SearchRec.Name)) then ShowEvent('Not delete file::'+ ExtractFilePath(FileSearch)+SearchRec.Name,0); FindClose(SearchRec); End;

Интерфейс пользователя

PGP_sdkUI.dll – это библиотека пользовательских интерфейсов, фирменные штучки от Network Associates, использовав их у Вас будут диалоги такие же как у фирменного пакета PGP. Вам уже не нужно будет строить диалоги самому: Для Генерации ключей; При выборе получателей сообщений; При запросе пароля и т.п.

Вывод:

Если Вы читаете эту статью - то Вы наверное уже знаете где в своих приложениях можно применить криптование, PGP это позволит сделать быстро, надежно, открыто и самое главное – переносимо. Но я могу посоветовать еще одно применение - это защита Ваших программ от несанкционированного копирования. Зашить открытый ключ в exe-файл, и рассылать секретный, нужным людям. Вот тут то и появляется поле для простора.


Перечень функций SPGP

{ spgpDecrypt - decryption & signature verification functions } function spgpdecode(BufferIn, BufferOut: PChar; BufferOutLen: LongInt; Pass, SigProps: PChar): LongInt; function spgpdecodefile(FileIn, FileOut, Pass, SigProps: PChar): LongInt; function spgpdetachedsigverify(SigFile, SignedFile, SigProps: PChar):LongInt; { spgpEncrypt - encryption & signing functions } function spgpencode(BufferIn, BufferOut: PChar; BufferOutLen: LongInt; Encrypt, Sign, SignAlg, ConventionalEncrypt, ConventionalAlg, Armor, TextMode, Clear: LongInt; CryptKeyID, SignKeyID, SignKeyPass, ConventionalPass, Comment: PChar): LongInt; function spgpencodefile(FileIn, FileOut: PChar; Encrypt, Sign, SignAlg, ConventionalEncrypt, ConventionalAlg, Armor, TextMode, Clear: LongInt; CryptKeyID, SignKeyID, SignKeyPass, ConventionalPass, Comment: PChar): LongInt; { spgpFeatures - functions to determine PGPsdk version and availability } { of PGPsdk features } function spgpsdkapiversion: Longint; function spgppgpinfo(Info: pPGPInfoRec): LongInt; function countkeyalgs: LongInt; function countcipheralgs: LongInt; { spgpKeyGen - key-generation functions } function spgpkeygenerate(UserID, PassPhrase, NewKeyHexID: PChar; KeyAlg, CipherAlg, Size, ExpiresIn, FastGeneration, FailWithoutEntropy, WinHandle: Longint): LongInt; function spgpsubkeygenerate(MasterKeyHexID, MasterKeyPass, NewSubKeyHexID: PChar; KeyAlg, Size: Longint; ExpiresIn, FastGeneration, FailWithoutEntropy, WinHandle: Longint): LongInt; { spgpKeyIO - Key import/export functions } function spgpkeyexport(pKeyID,BufferOut: PChar;BufferOutLen,ExportPrivate,ExportCompatible: LongInt):LongInt; function spgpkeyexportfile(pKeyID,FileOut: PChar; ExportPrivate,ExportCompatible: LongInt):LongInt; function spgpkeyimport(BufferIn,KeyProps: PChar; KeyPropsLen: LongInt):LongInt; function spgpkeyimportfile(FileIn,KeyProps: PChar; KeyPropsLen: LongInt):LongInt;
Евгений Дадыков
апрель 2002г.

Список используемой литературы и интернет ресурсы

Владимир Жельников "Криптография от папируса до компьютера" М:ABF, 1996 Tatu Ylonen "Introduction to Cryptography" Брюс Шнайер "Прикладная криптография" PGP Software Developer's Kit "PGPsdk, Reference Guide Version 1.7" PGP Software Developer's Kit "PGPsdk, Users Guide Version 1.7"

Про "железо".


Я начну издалека, с давних 90-х годов. В те времена звуковые карты (недорогие пищалки) делали на шину ISA по принципу "все-в-одном". В одном чипе были собраны контроллер шины, аудиоконтроллер, ЦАП и АЦП, усилители, синтезатор, интерфейсы джойстика и MIDI и т.д. Получалось интересно: с одной стороны к чипу шли дороги от шины компьютера, а с другой -- к динамикам. Вот такая мельница.

Нынче все по-другому. С появлением спецификации AC`97 звуковые платы стали, как минимум, двухкомпонентными: аудиоакселератор (аудиоконтроллер) и кодек. Аудиоакселератор отвечает за взаимодействие с PCI-шиной компьютера и, как правило, имеет блок 3D-эффектов, преобразует данные с помощью встроенного DSP и т.п., то есть реализует аппаратную поддержку всех наворотов ( DirectX, EAX и пр.). Аудиоконтроллер выполняет задачи поскромнее, фактически, только обеспечивает работу с шиной. На выходе аудиоакселератора(аудиоконтроллера) мы имеем ACLink и гоним цифровой звук в формате AC`97. Примеры можно найти на ESS, Cirrus Logic, Creative.

На другом конце ACLink у нас находится кодек, который преобразует цифровой звук в аналоговый и обратно, микширует, фильтрует, усиливает и делает другие вещи. От него фактически зависит качество звука. За примерами можно сходить на ESS, Cirrus Logic или SigmaTel. Кстати, кодеки SigmaTel -- лучшие для своей ценовой категории ( это не реклама :). Для простоты в качестве модели мы возьмем чип CS4235 производства Cirrus Logic. Звуковые платы с ним назывались Crystal и были под шину ISA.

Нас особенно интересует правая часть картинки. Два круга вверху и внизу и есть микшеры, причем верхний микшер работает на вход (I-микшер), а нижний -- на выход (O-микшер).

Полюбовавшись на картину, отметим три вещи:
Первое. Канал от I-микшера к АЦП ( от ЦАП к O-микшеру) только один. С учетом стерео, конечно.
Второе. Мы можем передать данные с I-микшера к O-микшеру напрямую, не оцифровывая звук.
Третье. На картинке с правой стороны есть ряд блоков GAIN. Блоки GAIN -- это аналоговые усилители с цифровым управлением. Они нам интересны тем, что реализуют функции Volume и Mute, то есть регулируют или отключают звук.

И один важный вывод: регулировать громкость можно двумя способами. Первый способ -- "аналоговый", когда мы изменяем усиление через Volume на блоке GAIN. Второй способ -- "цифровой", когда мы изменяем значение каждой выборки уже оцифрованного звука. Если вам важно быстро менять громкость, не влезая в сам сигнал, и вам не требуется большой точности -- первый способ для вас.

Однако, есть некоторые сложности, зависящие от кодека:

реальный уровень сигнала немного уплывает при каждом включении компьютера характеристики усилителя GAIN "в некоторой степени" нелинейны. при использовании Mute возможен "дребезг", очень короткая высокочастотная помеха, слышится как щелчок. На этом "железную" часть заканчиваем.



Проблемы взаимодействия клиента и сервера


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

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

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

объекты ядра ОС для синхронизации потоков (события,семафоры,мьютексы) сообщение потоку сообщение окну вызов процедуры клиента



Process: Explorer.exe. Modules Information.




Usage Image Addr Base Addr Size Module 1 00B10000 10000000 32768 F:\PROGRA~1\Adobe\ACROBA~1\Reader\ActiveX\AcroIEHelper.ocx 1 00F00000 10000000 24576 F:\PROGRA~1\Logitech\MOUSEW~1\SYSTEM\LGMOUSHK.dll Fixed 01000000 1011712 F:\WINDOWS\Explorer.EXE 2 01750000 10000000 552960 F:\WINDOWS\System32\NOVNPNT.DLL 2 017E0000 10000000 221184 F:\WINDOWS\System32\MAPBASE.dll 1 018E0000 6A400000 65536 F:\WINDOWS\System32\NLS\ENGLISH\NWSHLXNR.DLL 2 01A20000 10000000 184320 F:\WINDOWS\System32\NWSHLXNT.dll 1 01D10000 6A400000 225280 F:\WINDOWS\System32\NLS\ENGLISH\NOVNPNTR.DLL 1 01ED0000 10000000 114688 F:\PROGRA~1\Common Files\Adobe\Shell\PSICON.DLL 1 02210000 10000000 53248 F:\PROGRA~1\KASPER~1\ANTIVI~1\avpshlex.dll 1 02230000 01500000 147456 F:\Program Files\WinRAR\rarext.dll 1 02A70000 10000000 217088 F:\Program Files\7-ZIP\7-zipn.dll 1 0FFD0000 139264 F:\WINDOWS\System32\rsaenh.dll 1 10000000 16384 F:\Program Files\Punto Switcher\correct.dll 4 1F7B0000 200704 F:\WINDOWS\System32\ODBC32.dll 1 1F850000 90112 F:\WINDOWS\System32\odbcint.dll 1 32270000 28672 F:\Program Files\Miranda\Plugins\BOSSKEY.DLL 1 32520000 73728 F:\Program Files\Microsoft Office\Office10\msohev.dll 8 50D00000 86016 F:\WINDOWS\System32\CLNWIN32.DLL 6 50D20000 163840 F:\WINDOWS\System32\CALWIN32.DLL 6 50D50000 282624 F:\WINDOWS\System32\NETWIN32.DLL 6 50DA0000 45056 F:\WINDOWS\System32\CLXWIN32.DLL 6 50DB0000 167936 F:\WINDOWS\System32\NCPWIN32.dll 16 50DF0000 131072 F:\WINDOWS\System32\LOCWIN32.DLL Fixed 5AD70000 212992 F:\WINDOWS\System32\UxTheme.dll 1 5B630000 458752 F:\WINDOWS\System32\themeui.dll 1 68880000 258048 F:\WINDOWS\System32\hnetcfg.dll 1 6A400000 110592 F:\WINDOWS\System32\NLS\ENGLISH\MAPBASER.DLL 2 6C1B0000 274432 F:\WINDOWS\System32\DUSER.dll 16 71AA0000 32768 F:\WINDOWS\system32\WS2HELP.dll 26 71AB0000 86016 F:\WINDOWS\system32\WS2_32.dll 2 71AD0000 32768 F:\WINDOWS\System32\WSOCK32.dll 12 71B20000 69632 F:\WINDOWS\system32\MPR.dll 4 71BF0000 69632 F:\WINDOWS\System32\SAMLIB.dll 1 71C10000 53248 F:\WINDOWS\System32\ntlanman.dll 31 71C20000 323584 F:\WINDOWS\System32\NETAPI32.dll 2 71C80000 24576 F:\WINDOWS\System32\NETRAP.dll 1 71C90000 245760 F:\WINDOWS\System32\NETUI1.dll 2 71CD0000 90112 F:\WINDOWS\System32\NETUI0.dll 1 71D40000 110592 F:\WINDOWS\System32\ACTXPRXY.DLL 2 72410000 102400 F:\WINDOWS\System32\mydocs.dll 1 72430000 73728 F:\WINDOWS\System32\browselc.dll 2 72D10000 32768 F:\WINDOWS\System32\msacm32.drv 4 72D20000 36864 F:\WINDOWS\System32\wdmaud.drv 2 73000000 143360 F:\WINDOWS\System32\WINSPOOL.DRV 1 73380000 331776 F:\WINDOWS\System32\zipfldr.dll 1 74770000 585728 F:\WINDOWS\System32\MLANG.dll 2 74AD0000 28672 F:\WINDOWS\System32\POWRPROF.dll 1 74AE0000 28672 F:\WINDOWS\System32\CFGMGR32.dll 1 74AF0000 36864 F:\WINDOWS\System32\BatMeter.dll 1 74B00000 131072 F:\WINDOWS\System32\stobject.dll 1 74B30000 266240 F:\WINDOWS\System32\webcheck.dll 1 74B80000 532480 F:\WINDOWS\System32\printui.dll 1 74ED0000 61440 F:\WINDOWS\System32\wbem\wbemsvc.dll 1 74EF0000 40960 F:\WINDOWS\System32\wbem\wbemprox.dll 1 74FC0000 65536 F:\WINDOWS\System32\CLUSAPI.dll 2 75290000 229376 F:\WINDOWS\System32\wbem\wbemcomn.dll 1 755F0000 593920 F:\WINDOWS\System32\netcfgx.dll 1 75690000 598016 F:\WINDOWS\System32\wbem\fastprox.dll 4 75970000 987136 F:\WINDOWS\System32\MSGINA.dll 10 75A70000 667648 F:\WINDOWS\system32\USERENV.dll 2 75CF0000 1638400 F:\WINDOWS\system32\NETSHELL.dll 1 75E90000 659456 F:\WINDOWS\System32\SXS.DLL 2 75F40000 118784 F:\WINDOWS\system32\appHelp.dll 1 75F60000 24576 F:\WINDOWS\System32\drprov.dll 1 75F70000 36864 F:\WINDOWS\System32\davclnt.dll Fixed 75F80000 1032192 F:\WINDOWS\System32\BROWSEUI.dll 1 760F0000 491520 F:\WINDOWS\System32\urlmon.dll 1 76170000 557056 F:\WINDOWS\System32\shdoclc.dll 2 76200000 618496 F:\WINDOWS\system32\WININET.dll 5 762A0000 61440 F:\WINDOWS\system32\MSASN1.dll 5 762C0000 565248 F:\WINDOWS\system32\CRYPT32.dll 11 76360000 61440 F:\WINDOWS\System32\WINSTA.dll 2 76380000 20480 F:\WINDOWS\System32\MSIMG32.dll 5 763B0000 282624 F:\WINDOWS\system32\comdlg32.dll 2 76400000 2076672 F:\WINDOWS\System32\msi.dll 5 76600000 110592 F:\WINDOWS\System32\CSCDLL.dll 3 76620000 319488 F:\WINDOWS\System32\cscui.dll 7 76670000 933888 F:\WINDOWS\System32\SETUPAPI.dll 1 76980000 28672 F:\WINDOWS\System32\LINKINFO.dll 5 76990000 147456 F:\WINDOWS\System32\ntshrui.dll Fixed 769C0000 1347584 F:\WINDOWS\System32\SHDOCVW.dll 8 76B20000 86016 F:\WINDOWS\System32\ATL.DLL 17 76B40000 180224 F:\WINDOWS\System32\WINMM.dll 2 76C00000 184320 F:\WINDOWS\system32\credui.dll 1 76C30000 176128 F:\WINDOWS\System32\WINTRUST.dll 1 76C90000 139264 F:\WINDOWS\system32\IMAGEHLP.dll 2 76D30000 16384 F:\WINDOWS\system32\WMI.dll 2 76D40000 90112 F:\WINDOWS\system32\MPRAPI.dll 7 76D60000 86016 F:\WINDOWS\system32\iphlpapi.dll 3 76D80000 106496 F:\WINDOWS\system32\DHCPCSVC.DLL 2 76DA0000 196608 F:\WINDOWS\system32\WZCSvc.DLL 2 76DE0000 155648 F:\WINDOWS\system32\netman.dll 4 76E10000 147456 F:\WINDOWS\system32\adsldpc.dll 3 76E40000 192512 F:\WINDOWS\system32\ACTIVEDS.dll 9 76E80000 53248 F:\WINDOWS\system32\rtutils.dll 4 76E90000 69632 F:\WINDOWS\system32\rasman.dll 4 76EB0000 172032 F:\WINDOWS\system32\TAPI32.dll 5 76EE0000 225280 F:\WINDOWS\system32\RASAPI32.dll 3 76F20000 151552 F:\WINDOWS\system32\DNSAPI.dll 3 76F50000 32768 F:\WINDOWS\System32\WTSAPI32.dll 3 76F60000 180224 F:\WINDOWS\system32\WLDAP32.dll 9 76F90000 65536 F:\WINDOWS\System32\Secur32.dll 2 76FD0000 491520 F:\WINDOWS\System32\CLBCATQ.DLL 2 77050000 806912 F:\WINDOWS\System32\COMRes.dll Fixed 77120000 569344 F:\WINDOWS\system32\OLEAUT32.dll Fixed 771B0000 1155072 F:\WINDOWS\system32\ole32.dll Fixed 772D0000 405504 F:\WINDOWS\system32\SHLWAPI.dll 14 77340000 569344 F:\WINDOWS\system32\comctl32.dll Fixed 773D0000 8339456 F:\WINDOWS\system32\SHELL32.dll 1 77BD0000 28672 F:\WINDOWS\System32\midimap.dll 2 77BE0000 81920 F:\WINDOWS\System32\MSACM32.dll 10 77C00000 28672 F:\WINDOWS\system32\VERSION.dll Fixed 77C10000 339968 F:\WINDOWS\system32\msvcrt.dll Fixed 77C70000 262144 F:\WINDOWS\system32\GDI32.dll Fixed 77CC0000 479232 F:\WINDOWS\system32\RPCRT4.dll Fixed 77D40000 577536 F:\WINDOWS\system32\USER32.dll Fixed 77DD0000 569344 F:\WINDOWS\system32\ADVAPI32.dll Fixed 77E60000 937984 F:\WINDOWS\system32\kernel32.dll Fixed 77F50000 692224 F:\WINDOWS\System32\ntdll.dll

Примечание: 1-я колонка: Fixed-Нет таблицы перемещения, 1,2,3..-сколько раз модуль был загружен 2-я колонка: Адрес по которому загружен модуль 3-я колонка: Базовый адрес модуля(если пуст, то модуль загружен по базовому адресу) 4-я колонка: Размер модуля в байтах 5-я колонка: Полный путь к модулю



Продолжение


Раздел Поземелье Магов
Оглавление. Организация данных в виде связанных указателями структур.

§3 Организация данных в виде связанных указателями структур.

Актуальность задачи распределения и перераспределения памяти сохранилась, когда появились 32-разрядные ОС и СРП, позволяющие адресоваться из одного массива данных к оперативной памяти размером до 4 Гбайт, так как редко встречаются алгоритмы, обходящиеся одним массивом, как правило, их бывает несколько и в случае, если в алгоритме используется память полиномиального размера [2], перед разработчиком встает вопрос о способах организации данных. Прежде чем подавать на вход алгоритма исходные данные, надо договориться о том как они представляются в "понятном для компьютера виде". До появления в СРП Delphi 4, вышедшем в 1998 г., новой структуры данных, под названием динамический массив (ДМ), который позволяет работать с массивами данных, резервируя место в памяти по мере необходимости, при программировании на Pascal обходились линейными списками [1]. При программировании на Delphi для организации списков можно воспользоваться классом (объектом) TList из модуля classes, но требуется дополнительное программирование объектов наследников от TList. Возможны две нотации: 1) либо наследовать от TList

TDataListI = class (TList) // (1*) protected procedure Put(Index: Integer; Item: TData); function Get (Index: Integer): TData; public procedure Add (Obj: TData); property Items[Index: Integer]: TData read Get write Put; default; end; 2) либо вставлять класс TList в класс контейнер (оболочку)

TDataListW = class(TObject) // (2*) private FList: TList; function Get(Index: Integer): TData; procedure Put(Index: Integer; Item: TData); function GetCount: Integer; public constructor Create; destructor Destroy; override; function Add(Item: TData): Integer; function Equals(List: TDataListW): Boolean; property Count: Integer read GetCount; property Items[Index: Integer]: TData read Get write Put; default; end; Тип TData, как правило, является классом, но может быть любым типом. Если тип элемента не класс сложнее освободить память, т.к. операции освобождения ложатся не на функцию Destroy, принадлежащую классу, а на дополнительные модули или операторы в Ваших модулях. Как видно из описания классов унификация внутри модуля относительно типа элементов (записи, класса и т. п.), из которых состоят списки, существует только для классов, Т.к. только классы "знают" как себя освобождать. Для реализации этой идеи нужно переписать класс TDataListW, например, следующим образом:

TDataListС = class // (3*) private LType: TClass; FList: TList; function Get (Index: Integer): TObject; procedure Put (Index: Integer; Item: TObject); function GetCount: Integer; public constructor Create (CType: TClass); destructor Destroy; override; function Add (Item: TObject): Integer; function Equals(List: TDataListС): Boolean; property Count: Integer read GetCount; property Items [Index: Integer]: TObject read Get write Put; default; end; Идентификаторы методов в переводе отражают их предназначение. Тексты не приводятся в силу их тривиальности. В итоге, применяя методы Create и Add, можно создавать списки и добавлять в них новые элементы. Обращаться к элементам списков можно при помощи идентификатора Items или как к обычному элементу массива, т. к. свойство Items определено как default. Кроме того, в Delphi определены классы: TClassList и TObjectList (из модуля contnrs) наследуемые от Tlist и похожие на класс TDataListС; TStack, TObjectStack, TQueue, TОbjectQueue наследуемые от TOrderedList, реализующие различные виды линейных списков [1]; TCollection (из модуля classes) наследуемые от TPersistent, в котором реализована возможность синхронизации доступа к элементам благодаря методам BeginUpdate, EndUpdate; TStrings (из модуля classes) наследуемые от TPersistent, абстрактный базовый класс для манипуляции со строками; TStringList (из модуля classes) наследуемые от TStrings, управляющий списками строк и присоединённых к ним объектов с замещенными абстрактными методами из TStrings. Как видите, многообразие довольно широкое. Могут быть и проблемы. У всего 16 байт, но при интенсивной работе со списками это приводило к нехватке оперативной памяти. Поэтому, по-видимому, имеет право на существование подход, когда разработчик не использует чужих классов, а всё пишет сам. Это не сложно. Сначала создается запись, которая будет являться элементом списка.

PItem_Tree = ^TItem_Tree; TItem_Tree = record { Рабочая часть записи } ... {-------------------------------------------------} { Часть записи для организации списка } Next: PItem_Tree; end; Затем пишется класс, реализующий список.

TRecList = class // (4*) private Head, Last: PItem_Tree; BeforeSet: PItem_Tree; IndexBeforeSet: integer; BeforeGet: PItem_Tree; IndexBeforeGet: integer; FCount: integer; RetCode: byte; function GetItem(Index: integer): TItem_Tree; procedure SetItem(Index: integer; Rec: TItem_Tree); function Empty: boolean; function GetNodeSet(i:integer): PItem_Tree; function GetNodeGet(i:integer): PItem_Tree; protected function GetCount: integer; public constructor Create; destructor Destroy; override; procedure Clear; function Add(Rec: TItem_Tree): integer; virtual; function AddBegin(Rec: TItem_Tree): integer; virtual; procedure Assign(var Output: TRecList); procedure Insert(Index: integer; const Rec: TItem_Tree); virtual; procedure DeleteFromListCheckedFlagItog; procedure CopyRec(const Input: TItem_Tree; var Output: TItem_Tree); property Items[i: integer]:TItem_Tree read GetItem write SetItem;default; property Count: integer read GetCount; end; function TRecList.Empty:boolean; begin if Head <> nil then begin RetCode:=Succes; if Head^.Next=nil then Empty:=TRUE else Empty:=FALSE end else begin RetCode:=NotFill; Empty:=TRUE; end; end; procedure TRecList.CopyRec(const Input:TItem_Tree; var Output:TItem_Tree); begin with OUTPUT do begin { Присвоение рабочей части записи } ... { Часть записи для организации списка } Next:=nil; end; end; constructor TRecList.Create; begin inherited Create; Head:=nil; Last:=Head; Before:=Head; IndexBefore:=0; BeforeSet:=Head; IndexBeforeSet:=0; BeforeGet:=Head; IndexBeforeGet:=0; FCount:=0; end; destructor TRecList.Destroy; begin Clear; inherited Destroy; end; procedure TRecList.Clear; var P,P1:PItem_Tree; begin if Head<>nil then begin if Empty and (RetCode=Succes) then begin Dispose(Head); Head:=nil; RetCode:=Succes; Exit; end; P:=Head; while P<>nil do begin P1:=P^.Next; Dispose(P); P:=P1; end; RetCode:=Succes; Head :=nil; Last :=nil; Before :=nil; IndexBefore:=0; BeforeSet :=nil; IndexBeforeSet:=0; BeforeGet :=nil; IndexBeforeGet:=0; FCount:=0; end else RetCode:=NotFill; end; function TRecList.GetNodeSet(i:integer): PItem_Tree; var j: integer; P: PItem_Tree; begin RetCode:=Succes; if (i-1=IndexBeforeSet) and (BeforeSet <> nil) then begin P:=BeforeSet^.Next; BeforeSet:=P; IndexBeforeSet:=i; GetNodeSet:=P; end else begin P:=Head; j:=0; while P<>nil do begin if i=j then break; P:=P^.Next; Inc(j); end; BeforeSet:=P; IndexBeforeSet:=i; GetNodeSet:=P; end; end; function TRecList.GetNodeGet(i: integer): PItem_Tree; var j: integer; P: PItem_Tree; begin RetCode:=Succes; if (i-1=IndexBeforeGet) and (BeforeGet <> nil) then begin P:=BeforeGet^.Next; BeforeGet:=P; IndexBeforeGet:=i; GetNodeGet:=P; end else begin P:=Head; j:=0; while P<>nil do begin if i=j then break; P:=P^.Next; Inc(j); end; BeforeGet:=P; IndexBeforeGet:=i; GetNodeGet:=P; end; end; procedure TRecList.SetItem(Index: integer; Rec: TItem_Tree); var P, P1: PItem_Tree; begin if Index>FCount then begin RetCode:=ErrIndex; Exit; end; P:=GetNodeSet(Index); if RetCode=Succes then begin P1:=P^.Next; CopyRec(Rec, P^); P^.Next:=P1; end; end; function TRecList.GetItem(Index: integer): TItem_Tree; var P:PItem_Tree; begin if Index>FCount then begin RetCode:=ErrIndex; Exit; end; P:=GetNodeGet(Index); if RetCode=Succes then if P<>nil then CopyRec(P^, Result); end; function TRecList.Add(Rec: TItem_Tree): integer; begin if Head=nil then begin New(Head); if Head<>nil then begin CopyRec(Rec, Head^); Last:=Head; FCount:=1; Result:=1; end else Result:=-1; end else begin New(Last^.Next); Last:=Last^.Next; CopyRec(Rec, Last^); Inc(FCount); Result:=FCount; end; end; function TRecList.Addbegin(Rec: TItem_Tree): integer; var P: PItem_Tree; begin if Head=nil then begin New(Head); if Head<>nil then begin CopyRec(Rec, Head^); Last:=Head; FCount:=1; Result:=1; end else Result:=-1; end else begin New(P); P^.Next:=Head; Head:=P; P:=P^.Next; BeforeSet:=Head; IndexBeforeSet:=0; BeforeGet:=Head; IndexBeforeGet:=0; CopyRec(Rec, Head^); Head^.Next:=P; Inc(FCount); Result:=FCount; end; end; procedure TRecList.Assign(var Output: TRecList); begin output.Clear; output.Head:=Head; output.Last:=Last; output.BeforeSet:=BeforeSet; output.IndexBeforeSet:=IndexBeforeSet; output.BeforeGet:=BeforeGet; output.IndexBeforeGet:=IndexBeforeGet; output.FCount:=FCount; inherited Destroy; end; procedure TRecList.Insert(Index: integer; const Rec: TItem_Tree); var P,P1,P2:PItem_Tree; i: integer; begin New(P); Inc(FCount); CopyRec(Rec, P^); if Head=nil then Head:=P else { Если список не пуст } begin P1:=Head; P2:=Head; i:=0; while (P2<>nil) and (ido begin P1:=P2; P2:=P2^.Next; Inc(i) end; { Пройден весь список-элемент в конец } if P2=nil then P1^.Next:=P else begin P^.Next:=P2; { В начало списка } if P2=Head then Head:=P else { Внутрь списка } P1^.Next:=P end; end; end; function TRecList.GetCount: integer; begin GetCount:=FCount; end; procedure TRecList.DeleteFromListCheckedFlagItog; var P, P1: PItem_Tree; begin P:=Head; while P<>nil do if not P^.FlagItog then begin { Удаление из начала списка } if P=Head then begin Head:=Head^.Next; Dispose(P); Dec(FCount); P:=Head; end else begin { Удаление из середины списка } P1^.Next:=P^.Next; Dispose(P); Dec(FCount); P:=P1^.Next; end; end else begin { Переход на следующий элемент списка} P1:=P; P:=P^.Next; end; end; Метод Empty предназначен для проверки списка на наличие элементов, используется в других методах класса. CopyRec используется для заполнения элементов списка. Create и Destroy для создания и уничтожения списка соответственно. Clear - удаляет все элементы из списка. Методы GetNodeSet, GetNodeGet совместно с полями BeforeSet, IndexBeforeSet, BeforeGet, IndexBeforeGet используются как внутренние и обеспечивают простенькую оптимизацию без дополнительных связей, основанную на том, что при чтении и записи элементов подряд достаточно хранить индекс предыдущего элемента для проверки и ссылку на предыдущий элемент для выполнения действий. Этот способ оптимизации для однонаправленного списка сказывается, естественно, только для больших списков (сотни тысяч элементов). Методы SetItem и GetItem обслуживают доступ к элементам через свойство Items. Добавление элементов в конец, начало и указанное место списка обслуживается методами Add, AddBegin, Insert. Assign при помощи полей Head (указатель на первый элемент) и Last (указатель на последний элемент) поддерживает копирование из списка в список. DeleteFromListCheckedFlagItog тоже метод с "хитринкой". Если Вы, работая с большим списком, попытаетесь поэлементно удалять из него, это займет много времени. Однако, можно просто пометить какое-то поле в элементе вместо удаления, а затем, просматривая список один раз, удалить все помеченные элементы. Попутно отметим, что в объектах аналогичных выше приведенным возможно выполнять сжатие данных (хранить в элементах списка типы данных меньшего размера, чем данные, с которыми производятся какие-то действия). При программировании на Delphi для сжатия узла списка можно воспользоваться классом (объектом) TBits из модуля classes. В случае, если поля узла списка имеют тип массива байт, слов и т. п., можно при сохранении структуры в узле списка производить операции сжатия данных, например, как в функции приведённой ниже.

function ByteTo2Bit(B: array of Byte; var Bit: TBits): boolean; var i, j: integer; begin ByteTo2Bit:=True; Bit:=TBits.Create; Bit.Size:=Length(B)*2; for i:=Low(B) to High(B) do begin j:=(i-Low(B))*2; if B[i]=2 then Bit.Bits[j]:=True else if B[i]=1 then Bit.Bits[j+1]:=True; end; end; А при извлечении данных из узла списка для работы, использовать функцию Bit2ToByte.

function Bit2ToByte(Bit: TBits; var B: array of Byte): Boolean; var i, j: integer; begin Bit2ToByte:=True; if Length(B)*2 < Bit.Size then begin Bit2ToByte:=False; Exit; end; i:=0; while i<Bit.Size do begin j:=(i div 2)+Low(B); if Bit.Bits[i] then B[j]:=2 else if Bit.Bits[i+1] then B[j]:=1 else B[j]:=0; inc(i,2); end; end; В приведенной ниже таблицы представлены результаты тестирования классов типа TDataListW, поддерживающих динамические безразмерные списки без и со сжатием. Размер записи до сжатия составлял 3688 байт, после сжатия 68 байт. Тесты показывают, что при "навешивании" операций сжатия на класс динамических списков, память экономится в разы, а время обработки растёт на порядки. Из чего следует, что надо сжимать данные в списках, если другого выхода по алгоритму нет. В поддиректории "Списки" можно найти разложенные по поддиректориям исходные тексты модулей с классами, поддерживающими вышеописанный механизм хранения данных.

Тип объекта Размер списка Время счёта в [мин:]сек Затраченная ОП в Кбайт
Без сжатия 50000
100000
150000
200000
2
4
12
25
171000
343000
470000
472884
Со сжатием 10000
50000
100000
150000
500000
21
1:48
3:37
5:47
18:41
13560
62404
123472
184528
481372



Продолжение следует…



10 мая 2001г.
Специально для

Титов Олег

, часть I
, часть II
, часть III
, часть IV



Если вы честно прочитали всё,


Если вы честно прочитали всё, что написано выше, и не поленились посмотреть в MSDN'е описание упомянутых в тексте функций, вы уже можете самостоятельно написать программу, подобную Canvas2. Правда, новичков нередко ставит в тупик задача создания "резиновой" линии, особенно кривой, которую потом можно изменять. Однако эта задача не требует ни каких-либо специальных знаний, ни особого напряжения интеллекта. Достаточно просто набраться терпения, просчитать все возможные состояния процесса и описать реакцию на все эти состояния в программе.

Начиная с этого момента я предполагаю, что вы уже успели попробовать Canvas2 и поэтому хорошо представляете процесс рисования кривых с её помощью.



Итак, у нас есть два основных состояния: когда рисование кривой ещё не начато, и нажатие левой кнопки мыши приведёт к появлению "резиновой" прямой, которая затем станет основой для кривой, и когда кривая уже нарисована, но ещё не "впечатана" в рисунок, т.е. её крайние и промежуточные точки можно передвигать. Переменная TCurveForm.NewLine типа Boolean указывает, в каком из двух состояний находится программа: в первом (True) или во втором (False).

Когда пользователь схватил точку и тащит её, возможно шесть вариантов: схвачена одна из четырёх контрольных точек редактируемой кривой, рисуется "резиновая" прямая или при редактировании кривой пользователь промахнулся и не схватил ни одной из контрольных точек. Для описания того, какая точка сейчас перемещается пользователем, объявлена переменная TCurveForm.DragPoint специально созданного типа TDragPoint. Эта переменная может иметь следующие значения:

ptNone - пользователь пытается тянуть несуществующую точку ptFirst - пользователь перемещает вторую точку "резиновой" прямой ptBegin - пользователь перемещает начало кривой ptInter1, ptInter2 - пользователь перемещает промежуточные точки ptEnd - пользователь перемещает конец кривой

Для хранения координат кривой используется массив TCurveForm.Curve. Его нулевой элемент хранит начало прямой, третий - её конец, а первый и второй - промежуточные точки. В режиме "резиновой" прямой первый и второй элементы не используются, поэтому они могут иметь произвольные значения.

Процедура OnPaint должна учитывать состояние программы: до редактирования кривой ещё не дошло (NewLine=True), нужно проверить, не перемещает ли пользователь концевую точку прямой, и если да, нарисовать эту прямую. Если редоктирование кривой уже начато, надо отобразить кривую и дополнительные элементы для редактирования (касательные и маркеры контрольных точек).

Когда пользователь нажимает кнопку мыши, программа должна проверить, в каком состоянии находится программа. Если редактирование кривой ещё не начато, это нажатие означает начало рисования "резиновой" прямой. Для перехода в этот режим значение DragPoint устанавливается в dpFirst. Если редактирование кривой уже начато, необходимо проверить, попадает ли позиция курсора мыши в окрестность какой-либо контрольной точки, и на основании результатов проверки присвоить соответствующее значение переменной DragPoint. Для проверки определена функция PtNearPt. Строго говоря, необходимо также запоминать, насколько отстоят координаты курсора мыши от координат контрольной точки, чтобы при первом перемещении не было скачка. Но так как окрестность точки очень мала, этот прыжок практически незаметен, и в данном случае этим можно пренебречь, чтобы не усложнять программу.

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

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

Нажатие кнопки "Завершить" осуществляет выход из режима редактирования кривой. Кривая переносится на растр TCurveForm.Back, а значение NewLine снова устанавливается в True.

Функция рисования кривой достаточно проста. Сначала рассчитываются координаты опорных точек. Если включен режим рисования по опорным точкам, координаты этих точек хранятся в первом и втором элементах массива Curve. Если включен режим рисования по промежуточным точкам, координаты опорных точек вычисляются по формулам (2). Затем на основе кривой создаётся траектория, затем она преобразуется в ломаную, и координаты её узлов записываются в массив PtBuf. В массив TpBuf записываются типы точек, но в данном случае они нам неинтересны: траектория содержит только один контур, состоящий только из отрезков прямых. Далее последовательно вызывается функция LineDDA для каждого из отрезков. При этом вычисляется длина отрезка и смещения координат DX и DY. Это нужно для построения поперечных линий. Как показала практика, начало и конец отрезка иногда совпадают, и его длина равна нулю, поэтому нужна дополнительная проверка, позволяющая избежать деления на ноль.

Функция LineDDA передаёт в вызываемую ею LineDrawFunc один дополнительный параметр, который использован для передачи объекта холста (Canvas), на котором следует рисовать отрезок. Этот параметр в соответствии с описанием функции является целым числом, но контроль типов здесь отсутствует, поэтому можно использовать любую 32-разрадную величину. Так как все переменные объектов в Delphi являются 32-разрядными указателями, объект TCanvas может быть передан в качестве этого параметра. Функция LineDDA не считает точки, поэтому это приходится делать самостоятельно с помощью переменной TCurveForm.Counter. Так как значение этой переменной между рисованием отдельных ломаных не меняется, кривая имеет целостный вид.

Функция LineDrawFunc достаточно проста для понимания. В некотором комментарии нуждается только выбор толщины пера при рисовании стилем "плакатное перо". Предположим, некоторая точка прямой имеет координаты (X,Y), а соседняя с ней - координаты (X+1,Y-1). Тогда при проведении через эти точки наклонной линии одинарной ширины между ними останутся незаполненные точки, как на шахматной доске. Поэтому потребовалось увеличить толщину пера.

Надеюсь, изложенные здесь материал оказался вам полезным
С пожеланиями творческих успехов

Специально для

Скачать (170K)

Для данного материала нет комментариев.



Программируем


Первым надо "запустить машину" CR, посредством вызова функции PEOpenEngine для инициализации механизма отчетов. Надо заметить, что вызов данной функции справедлив только для одного потока.

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

Синтаксис функции PEOpenPrintJob(PathToReport: PChar): SmallInt; где, PathToReport - путь к файлу отчета. Результат функции - дескриптор полученной задачи. Пример: FHandleJob:= PEOpenPrintJob(PChar(edtPathReport.Text));

Получив дескриптор, мы можем, манипулировать отчетом как нам будет угодно. Получать информацию о параметрах, об источнике данных, управлять разделами отчета и формулами.
Далее необходимо сказать системе, куда выводить отчет: в окно предварительного просмотра (…ToWindow) или на принтер (…ToPrinter).

Синтаксис функций: PEOutputToWindow(printJob : Smallint; title: PChar; left: Integer; top: Integer; width: Integer; height: Integer; style: DWord; parentWindow : HWnd): Bool; PEOutputToPrinter(printJob: Word; nCopies: Integer)): Bool; где, printJob - дескриптор задачи title - заголовок окна left, top, width, height - координаты окна style - стиль окна (типа WS_VSCROLL, WS_VISIBLE и т.д.) parentWindow - дескриптор окна в котором будет окно отчета. nCopies - количество копий. Пример: Result:= PEOutputToWindow(FHandleJob, PChar(TForm(Self).Caption), 0, 0, 0, 0, 0, FWindow);

Подготовив механизм вывода отправляем отчет для вывода функцией PEStartPrintJob.

Синтаксис функции: function PEStartPrintJob(printJob: Word; waitUntilDone: Bool): Bool; где, printJob - дескриптор задачи. WaitUntilDone - зарезервирован. Всегда должен быть True. Пример: PEStartPrintJob(FHandleJob, True);

После отправки отчета, если не надо производить с ним операций, закрываем задание функцией PEClosePrintJob.

Синтаксис функции: function PEClosePrintJob (printJob: Word): Bool; где, printJob - дескриптор задачи. Пример: PEClosePrintJob(FHandleJob);


Между вызовами функций PEOpenPrintJob и PEClosePrintJob может стоять сколько угодно вызовов функций PEOutputTo…, PEStartPrintJob.

В итоге получается схема вызовов:
PEOpenEngine
|
PEOpenPrintJob
|
PEOutputToWindow
|
PEStartPrintJob
|
PEClosePrintJob
|
PECloseEngine

Производительность


Код загрузки XML документа в объект дает вполне приемлемую производительность. Тестирование дало следующие результаты.

Документ: формат ONIX XML размером 10 мб.

Системная конфигурация: Celeron 450 / 256 / Windows 2000prof

парсер приблизительное время загрузки
MS XML Parser 2.6 синхронная загрузка без проверки состоятельности ~6 сек
MS XML Parser 2.6 синхронная загрузка с проверкой состоятельности ~11 сек
Компонент TglXMLSerializer 8,5 сек
Компонент TglXMLSerializer загружает данные в синхронном режиме. Он не использует DTD или схемы. При загрузке проводится проверка правильности (well-formed) и частично - состоятельности (valid). При нарушении правильности документа парсер выдаст соответствующее исключение и прекратит загрузку.

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



Просмотрщик событий из журнала событий Windows.


Раздел Подземелье Магов рь,
дата публикации 14 февраля 2002г.

Примечание:
Данный материал не является исчерпывающей статьей по заявленной теме.
товил пояснительный текст к своему проекту.
Основное смотрите в исходных кодах.

В одном из моих проектов мне понабилось просматривать сообщения из журнала событий Windows от одного из источников. Почитав справочник MSDN, я решил, что необходимо сначала все-таки написать выбор всех сообщений из одного из журналов событий, а уж затем отфильтровать нужные мне. Нигде по конференциям и сайтам посвященным Delphi я не нашел ответа на мои вопросы как же там все устроено и решил разобраться сам. Что из этого получилось предоставляю на Ваш строгий суд. Обо всех ошибках и недочетах про сообщать мне по электронной почте .

Что же из себя представляет журнал событий и как с ним работать. Каждый из журналов хранится в системной директории. %SystemRoot%\system32\config\*.evt

Как известно их всего три: Application log - APPEVENT.EVT Security log - SECEVENT.EVT System log - SYSEVENT.EVT Для чтения записей из журнала используеться функция ReadEventLog предварительно открыв журнал функцией OpenEventLog вот их описание: The OpenEventLog function opens a handle to an event log. HANDLE OpenEventLog( LPCTSTR lpUNCServerName , // server name LPCTSTR lpSourceName // file name ); Параметры: lpUNCServerName [in] Pointer to a null-terminated string that specifies the Universal Naming Convention (UNC) name of the server on which the event log is to be opened. lpSourceName [in] Pointer to a null-terminated string that specifies the name of the logfile that the returned handle will reference. This can be the Application, Security, or System logfile, or a custom registered logfile. If a custom registered logfile name cannot be found, the event logging service opens the Application logfile, however, there will be no associated message or category string file. Return Values:
В случае удачи, функция возвращает handle журнала сообщений. В противном случае будет возвращено Null. Для более подробной информации смотрите GetLastError.
Примечание
Для того, чтобы закрыть журнал событий, используйте функцию CloseEventLog.


The ReadEventLog function reads a whole number of entries from the specified event log. The function can be used to read log entries in chronological or reverse chronological order. BOOL ReadEventLog ( HANDLE hEventLog , // handle to event log DWORD dwReadFlags,, // how to read log DWORD dwRecordOffset, // offset of first record LPVOID lpBuffer, // buffer for read data DWORD nNumberOfBytesToRead,// bytes to read DWORD * pnBytesRead, // number of bytes read DWORD * pnMinNumberOfBytesNeeded // bytes required );

Параметры: hEventLog[in] Handle to the event log to read. This handle is returned by the OpenEventLog function. dwReadFlags [in] Specifies how the read operation is to proceed. This parameter must include one of the following values.
ValueMeaning
EVENTLOG_SEEK_READThe read operation proceeds from the record specified by the dwRecordOffset parameter. This flag cannot be used with EVENTLOG_SEQUENTIAL_READ.
EVENTLOG_SEQUENTIAL_READ The read operation proceeds sequentially from the last call to the ReadEventLog function using this handle. This flag cannot be used with EVENTLOG_SEEK_READ.

If the buffer is large enough, more than one record can be read at the specified seek position; you must specify one of the following flags to indicate the direction for successive read operations.
ValueMeaning
EVENTLOG_FORWARDS_READThe log is read in chronological order. This flag cannot be used with EVENTLOG_BACKWARDS_READ.
EVENTLOG_BACKWARDS_READThe log is read in reverse chronological order. This flag cannot be used with EVENTLOG_FORWARDS_READ.
dwRecordOffset[in] Specifies the log-entry record number at which the read operation should start. This parameter is ignored unless dwReadFlags includes the EVENTLOG_SEEK_READ flag. lpBuffer [out] Pointer to a buffer for the data read from the event log. This parameter cannot be NULL, even if the nNumberOfBytesToRead parameter is zero. The buffer will be filled with an EVENTLOGRECORD structure. nNumberOfBytesToRead[in] Specifies the size, in bytes, of the buffer. This function will read as many whole log entries as will fit in the buffer; the function will not return partial entries, even if there is room in the buffer. pnBytesRead[out] Pointer to a variable that receives the number of bytes read by the function. pnMinNumberOfBytesNeeded[out] Pointer to a variable that receives the number of bytes required for the next log entry. This count is valid only if ReadEventLog returns zero and GetLastError returns ERROR_INSUFFICIENT_BUFFER. Return Values:
В случае удачи, функция возвращает ненулевое значение. В противном случае будет возвращен 0. Для более подробной информации смотрите GetLastError.
Примечание:
When this function returns successfully, the read position in the error log is adjusted by the number of records read. Only a whole number of event log records will be returned.
Note
The configured filename for this source may also be the configured filename for other sources (several sources can exist as subkeys under a single logfile). Therefore, this function may return events that were logged by more than one source.



В принципе уже видно, что для открытия и работы с журналом нам необходимо получить на него указатель передав в функцию в качестве параметра наименование одного из трех журналов. А затем считывать записи в буфер и форматировать их. Как видно буфер который заполняет функция имеет структуру EVENTLOGRECORD.

typedef struct _EVENTLOGRECORD { DWORD Length; DWORD Reserved; DWORD RecordNumber; DWORD TimeGenerated; DWORD TimeWritten; DWORD EventID; WORD EventType; WORD NumStrings; WORD EventCategory; WORD ReservedFlags; DWORD ClosingRecordNumber; DWORD StringOffset; DWORD UserSidLength; DWORD UserSidOffset; DWORD DataLength; DWORD DataOffset; // // Then follow: // // TCHAR SourceName[] // TCHAR Computername[] // SID UserSid // TCHAR Strings[] // BYTE Data[] // CHAR Pad[] // DWORD Length; // } EVENTLOGRECORD, *PEVENTLOGRECORD;

Members
LengthSpecifies the length, in bytes, of this event record. Note that this value is stored at both ends of the entry to ease moving forward or backward through the log. The length includes any pad bytes inserted at the end of the record for DWORD alignment. ReservedReserved RecordNumberContains a record number that can be used with the EVENTLOG_SEEK_READ flag passed in a call to the ReadEventLog function to begin reading at a specified record. TimeGeneratedThe time at which this entry was submitted. This time is measured in the number of seconds elapsed since 00:00:00 January 1, 1970, Universal Coordinated Time. TimeWrittenSpecifies the time at which this entry was received by the service to be written to the logfile. This time is measured in the number of seconds elapsed since 00:00:00 January 1, 1970, Universal Coordinated Time. EventID Specifies the event. This is specific to the source that generated the event log entry, and is used, together with SourceName, to identify a message in a message file that is presented to the user while viewing the log. EventTypeSpecifies the type of event. This member can be one of the following values. For more information, see Event Types. NumStringsSpecifies the number of strings present in the log (at the position indicated by StringOffset). These strings are merged into the message before it is displayed to the user. EventCategorySpecifies a subcategory for this event. This subcategory is source specific. ReservedFlagsReserved ClosingRecordNumberReserved StringOffsetSpecifies the offset of the strings within this event log entry. UserSidLengthSpecifies the length, in bytes, of the UserSid member. This value can be zero if no security identifier was provided. UserSidOffsetSpecifies the offset of the security identifier (SID) within this event record. To obtain the user name for this SID, use the LookAccountSid function. DataLength Specifies the length, in bytes, of the event-specific data (at the position indicated by DataOffset). DataOffsetSpecifies the offset of the event-specific information within this event record. This information could be something specific (a disk driver might log the number of retries, for example), followed by binary information specific to the event being logged and to the source that generated the entry. SourceName Contains the variable-length null-terminated string specifying the name of the source (application, service, driver, subsystem) that generated the entry. This is the name used to retrieve from the registry the name of the file containing the message strings for this source. It is used, together with the event identifier, to get the message string that describes this event. ComputernameContains the variable-length null-terminated string specifying the name of the computer that generated this event. There may also be some pad bytes after this field to ensure that the UserSid is aligned on a DWORD boundary. UserSidSpecifies the security identifier of the active user at the time this event was logged. This member may be empty if the UserSidLength member is zero. Remarks The defined members are followed by the replacement strings for the message identified by the event identifier, the binary information, some pad bytes to make sure the full entry is on a DWORD boundary, and finally the length of the log entry again. Because the strings and the binary information can be of any length, no structure members are defined to reference them. The event identifier together with SourceName and a language identifier identify a message string that describes the event in more detail. The strings are used as replacement strings and are merged into the message string to make a complete message. The message strings are contained in a message file specified in the source entry in the registry. To obtain the appropriate message string from the message file, load the message file with the LoadLibraryEx function and use the FormatMessage function. The binary information is information that is specific to the event. It could be the contents of the processor registers when a device driver got an error, a dump of an invalid packet that was received from the network, a dump of all the structures in a program (when the data area was detected to be corrupt), and so on. This information should be useful to the writer of the device driver or the application in tracking down bugs or unauthorized breaks into the application. Как видно структура содержит несколько полей, некоторые из которых необходимо тоже форматировать. Для идентификации пользователя используеться функция LookAccountSid. А описание свойства (event) содержит лишь параметры для детального описания сообщения, которое форматируется с помощью функции FormatMessage. Схему форматирования иллюстрирует следующая диаграмма:





Также необходимо учитывать что EventID должны быть получены при наложении маски $0000FFFF (EventID And $0000FFFF). А время форматировать из Unix формата со смещением от нулевого меридиана.

Здесь находиться исходный текст свободно расспространяемой программы: (14K)
Ниже перечислены все функция для работы с журналом событий BackupEventLog ClearEventLog CloseEventLog DeregisterEventSource GetEventLogInformation GetNumberOfEventLogRecords GetOldestEventLogRecord NotifyChangeEventLog OpenBackupEventLog OpenEventLog ReadEventLog RegisterEventSource ReportEvent В статье использованы материалы из MSDN Library - January 2000, Copyright (c) 1995-2000 Microsoft Corp. All rights reseved.


Cпециально для

ValueMeaning
EVENTLOG_ERROR_TYPEError event
EVENTLOG_WARNING_TYPEWarning event
EVENTLOG_INFORMATION_TYPEInformation event
EVENTLOG_AUDIT_SUCCESSSuccess Audit event
EVENTLOG_AUDIT_FAILUREFailure Audit event

Просто и ясно о PageMaker и Delphi


Дорогие коллеги, данной статьей я хочу показать вам основные принципы работы с Adobe Pagemaker из Delphi.

Итак небольшой экскурс -

Adobe Pagemaker - довольно распространенный в нашей стране пакет издательской верстки, в основном он применяется в газетах и журналах для подготовки материала к отпечатке в типографию. Данный пакет имеет в свое роде некоторые преимущества перед например Microsoft Word - ом при подготовке журнальных полос или верстке книг. На примере компонента TKDPageMaker я хочу показать вам возможность управления данной программой из Delphi.

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

Итак начнем.

В документации поставляемой фирмой Adobe к данному пакету написано что PageMaker поддерживает динамический обмен данными (DDE) с любыми приложениями.

В данном компоненте реализована 3 основных метода , это - Property PathPageMaker : String Read FPathPageMaker Write SetPathPageMaker; метод служащий для указания пути к исполняемому файлу Pagemaker, используется для того что-бы TDDEClientConv запустил данное приложение в виде DDE сервера

Property Enable : Boolean Read FEnable Write SetEnable; метод используется для запуска Adobe PageMaker , т.е фактически для установки DDE соединения

Property UnitMeasurement : TUnitMeasurement Read FunitMeasurement Write SetUnitMeasurement; метод определяет единицы измерения которыми будет оперировать PageMaker при передаче команд. Например команда PageMaker Script Language PageSize позволяет установить размер страницы как в миллиметрах так и в дюймах - что бы не заботится об установки единиц измерения в командах и создан этот метод позволяющий гибко реализовать задание размеров в той форме в какой установил программист.



Рабочий пример


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

Для удобства использования серверами, находящимися в DLL, и экспортирующими только функции (не классы), необходимые клиенту определения вынесены в отдельный unit:

(********************************************************************* * Notifier object definitions * *********************************************************************) unit NotifyDef; interface uses Windows; const tnEvent = 0; // Use kernel object Event (SetEvent) tnThreadMsg = 1; // Use message to thread ID (PostThreadMessage) tnWinMsg = 2; // Use message to window HWND (PostMessage) tnCallBack = 3; // Asynchronous call user function TNotifierProc tnCallEvent = 4; // Asynchronous call object event handler TNotifierEvent type TNotifierProc = procedure(Owner: THandle; Msg,UserParam : dword); TNotifierEvent = procedure(Sender : TObject; Msg,UserParam : dword) of object; implementation end.

Для создания объекта нотификатора заданного типа сервер может использовать функцию (см. модуль ):

function MakeNotifier(hEventObj,hSender : THandle; EventType : byte; MsgID,UserParam : dword) : TNotify;

Описание параметров ПараметрНазначение Значение, использование в способах нотификации Событие Сообщение потоку Сообщение окну Процедура клиента
hEventObj Хэндл низкоуровнего объекта, используемого для нотификации хэндл события - результат вызова CreateEvent ID потока (можно узнать GetCurrentThreadID) хэндл окна адрес процедуры
hSender Условный хэндл объекта сервера - то, что сервер хочет сообщить о себе не используется TMessage.LParam TMessage.LParam Owner в TNotifierProc
EventType Тип объекта нотификатора - см. константы в модуле tnEvent tnThreadMsg tnWinMsg tnCallBack
MsgID Идентификатор сообщения не используется TMessage.Msg TMessage.Msg Msg в TNotifierProc
UserParam Пользовательский параметр не используется TMessage.WParam TMessage.WParam UserParam в TNotifierProc
Собственно, параметры hSender,MsgID,UserParam могут быть заряжены произвольными данными на усмотрение программиста, нотификаторы не используют их для своих нужд.


Нотификатор типа "асинхронный вызов обработчика события" (tnCallEvent) нельзя создать через функцию MakeNotifier - требуется явный вызов конструктора. Это связано с тем, что адрес TNotifierEvent нельзя привести к четырехбайтному типу THandle. Параметры конструктора аналогичны параметрам MakeNotifier при EventType=tnCallback.

Далее приводится собственно текст юнита реализации библиотеки нотификаторов.

(****************************************************************************** * The Collection of Notifier objects. * ******************************************************************************) unit Notify; interface uses Windows,NotifyDef; type TNotify = class protected hNotify : THandle; hOwner : THandle; Message, UParam : dword; public constructor Create(hEventObj : THandle); procedure Execute; virtual; property Owner : THandle read hOwner write hOwner; property Param : dword read UParam write UParam; end; TThreadNotify = class(TNotify) public constructor Create(hEventObj,hSender : THandle; MsgID,UserParam : dword); procedure Execute; override; end; TWinNotify = class(TThreadNotify) public procedure Execute; override; end; TCallBackNotify = class(TThreadNotify) public procedure Execute; override; end; TCallEventNotify = class(TThreadNotify) private fOnNotify : TNotifierEvent; public constructor Create(hEventObj : TNotifierEvent; hSender : THandle; MsgID,UserParam : dword); property OnNotify : TNotifierEvent read fOnNotify write fOnNotify; procedure Execute; override; end; function MakeNotifier(hEventObj,hSender : THandle; EventType : byte; MsgID,UserParam : dword) : TNotify; implementation function MakeNotifier(hEventObj,hSender : THandle; EventType : byte; MsgID,UserParam : dword) : TNotify; begin case EventType of tnEvent : result := TNotify.Create(hEventObj); tnThreadMsg : result := TThreadNotify.Create(hEventObj, hSender, MsgID, UserParam); tnWinMsg : result := TWinNotify.Create(hEventObj, hSender, MsgID, UserParam); tnCallBack : result := TCallBackNotify.Create(hEventObj, hSender, MsgID, UserParam); else result := nil; end; end; (*** TNotify ***) constructor TNotify.Create(hEventObj : THandle); begin hNotify := hEventObj; end; procedure TNotify.Execute; begin SetEvent(hNotify); end; (*** TThreadNotify ***) constructor TThreadNotify.Create(hEventObj,hSender : THandle; MsgID,UserParam : dword); begin inherited Create(hEventObj); Owner := hSender; Message := MsgID; UParam := UserParam; end; procedure TThreadNotify.Execute; begin PostThreadMessage(hNotify, Message, UParam, hOwner); end; (*** TWinNotify ***) procedure TWinNotify.Execute; begin PostMessage(hNotify, Message, UParam, hOwner); end; (*** TCallbackNotify ***) procedure TCallbackNotify.Execute; begin TNotifierProc(hNotify)(hOwner, Message, UParam); end; (*** TCalleventNotify ***) constructor TCalleventNotify.Create(hEventObj : TNotifierEvent; hSender : THandle; MsgID,UserParam : dword); begin OnNotify := hEventObj; Owner := hSender; Message := MsgID; UParam := UserParam; end; procedure TCalleventNotify.Execute; begin if assigned(OnNotify) then OnNotify(TObject(Owner), Message, UParam); end; end.
Порядок работы очень прост. Сервер инициализирует экземпляр нужного типа, воспользовавшись функцией MakeNotifier или прямым вызовом конструктора. Виртуальный метод TNotify.Execute реализует заданный способ нотификации, именно его сервер вызывает, когда нужно выполнить извещение клиента.

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

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


Специально для


Расположение эксперта внутри DLL библиотеки


Если вы хотите расположить вашего эксперта не в пакете, а в DLL библиотеке, библиотека должна экспортировать функцию INITWIZARD0001 следующего формата:

type TWizardRegisterProc = function(const Wizard: IOTAWizard): Boolean; type TWizardTerminateProc = procedure; function INITWIZARD0001(const BorlandIDEServices: IBorlandIDEServices; RegisterProc: TWizardRegisterProc; var Terminate: TWizardTerminateProc): Boolean stdcall;

Для регистрации вашего эксперта вызовите внутри этой функции RegisterProc и передайте ей экземпляр заранее созданного класса вашего эксперта. BorlandIDEServices - указатель на основной интерфейс для работы со всей IDE. Отдельные части его мы рассмотрим далее. По окончании работы IDE или при принудительной выгрузке вашего эксперта будет вызвана функция Terminate, которую вы должны передать среде. Поместите полный путь к DLL в ключ реестра
HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Experts
или
HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Delphi\7.0\Experts
Именем ключа может быть произвольная строка.

Эксперт будет запущен только при перезапуске среды, если она выполнялась. Вуаля!



Распределенные системы на основе COM+


Для создания распределенных систем, т.е. систем, элементы которых работают на нескольких компьютерах, в Delphi используется технология Midas. Данная технология имеет как достоинства, так и недостатки, связанные с тем, что для ее функционирования необходимо устанавливать дополнительные компоненты, осуществляющие связь между компьютерами и проводить их настройку. Оказывается, что использование COM+ дает возможность проектировать такие системы средствами, встроенными в Windows. Единственным условием, которое обязательно должно быть выполнено - в сети должен обязательно присутствовать контроллер домена, осуществляющий идентификацию пользователей. Причем не обязательно под Windows 2000. Вы можете использовать сеть с контроллером домена под Windows NT 4.0 с установленными апдейтами.

Внимание!
Распределенные системы в одноранговой сети (WORKGROUP) на основе COM+ работать не будут.

В этом разделе мы рассмотрим пример создания простого приложения и организацию его работы под управлением COM+.

Для создания компонента воспользуемся Мастером New Transaction Object в Delphi 6, Рисунок 10 (заметим, что в Delphi 5 вам придется сначала создать библиотеку ActiveX и лишь затем вызвать Мастер MTS Object).

Затем с помощью Редактора Библиотеки Типов создадим в его интерфейсе метод SayHello (Рисунок 11) и напишем его реализацию.

unit uHello; {$WARN SYMBOL_PLATFORM OFF} interface uses ActiveX, Mtsobj, Mtx, ComObj, HelloMTS_TLB, StdVcl; type THelloTest = class(TMtsAutoObject, IHelloTest) protected procedure SayHello(const Mess: WideString); safecall; { Protected declarations } end; implementation uses ComServ, Dialogs; procedure THelloTest.SayHello(const Mess: WideString); begin ShowMessage(Mess); end; initialization TAutoObjectFactory.Create(ComServer, THelloTest, Class_HelloTest, ciMultiInstance, tmBoth); end.

Думаю, что приведенный код в комментариях не нуждается.
Далее с помощью утилиты Component Services сначала создадим пустой пакет (Рисунок 12), а затем установим в него наш компонент (Рисунок 13).




Теперь напишем простое Windows-приложение, которое будет вызывать единственный метод данного компонента. Код, который для этого используется, ничем не отличается от обычного вызова COM компонента.

unit uTest; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TfrmMaim = class(TForm) btCallHello: TButton; procedure btCallHelloClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var frmMaim: TfrmMaim; implementation {$R *.dfm} uses HelloMTS_TLB; procedure TfrmMaim.btCallHelloClick(Sender: TObject); var HelloTest : IHelloTest; begin HelloTest := CoHelloTest.CreateRemote('AlexAD'); HelloTest.SayHello('Hell Word!'); end; end.
В данном примере указано имя компьютера и нажать кнопку, то через некоторое время на экране появится сообщение (Рисунок 15).



В том, что компонент запущен под управлением MTS, легко убедиться с помощью той же утилиты Component Services - дело в том, что активный компонент начинает вращаться (Рисунок 15).

Если вы теперь возьмете и запустите созданное тестовое приложение на другом компьютере, и попытаетесь выполнить ту же операцию, то система выдаст сообщение о том, что компонент не зарегистрирован.

Теперь организуем связь между двумя компьютерами через COM+. Делается это очень просто. Сначала выберем в утилите Component Services мышкой папку, где установлен наш компонент и вызовем Application Export Wizard. Далее укажем, что мы хотим экспортировать не сам компонент, а только Application proxy (Рисунок 16).



После этого Мастер создаст нам MSI файл, где будет находиться вся информация для организации связи между компьютерами. Теперь останется только скопировать этот файл на нужный компьютер и щелкнуть по нему мышкой. При этом автоматически запустится Мастер, который проведет proxy в MTS. В этом легко убедиться с помощью утилиты Component Services.

Теперь, если вы запустите тестовое приложение, то на компьютере, где установлен MTS компонент, появится сообщение (Рисунок 15).


В прошлом к.т.н.,
доц. Рыбинской Государственной Авиационной Технологической Академии
и нач. отделения программирования НПО КРИСТА,
по программированию.
В настоящее время - Senior Software Engeener, RegScan Inc., PA, USA.


Расширенная обработка исключительных ситуаций




Описанный ниже механизм иллюстрирует один из методов регистрации программных ошибок времени выполнения, что может быть полезно при тестировании приложения. Идея заключается в комбинированном использовании компоненты, которая регистрирует все возникающие во время выполнения исключительные ситуации (Exceptions, в дальнейшем – ИС) в файле журнала и формы, которая визуализирует полученный LOG-файл. Предлагаемый способ обработки ИС обладает следующими преимуществами по сравнению со стандартным: Вся информация записывается в файл для последующего просмотра сотрудниками, выполняющими поддержку кода Создаётся снимок системы в целом: информация об ИС, объекте, который её породил, приложении и ОС Имеется возможность регистрировать сообщения, которые не являются результатом ИС



Расширяемость


Приведенная реализация имеет ряд ограничений. Первое и основное - это отказ от использования элементов в атрибутах XML документов. Это ограничение может быть снято переработкой кода парсера и процедур сохранения XML. Для отличия элементов от атрибутов в интерфейсе объектов можно придти к следующему соглашению: Все классовые типы являются элементами Все простые типы являются атрибутами соответствующих объектов Пример. TPerson = class; TMyXMLMessage = class(TPersistent) published property LanguageOfText: WideString; property ToPerson: TPerson; end; TPerson = class(TPersistent) published property FirstName: WideString; property LastName: WideString; end; Таким образом, в первом случае объект приведенного выше класса TMyXMLMessage при сериализации даст следующий XML код: <TMyXMLMessage> <LanguageOfText>english</LanguageOfText> <ToPerson> <FirstName>Osama</FirstName> <LastName>Unknoun</LastName> </ToPerson> </TMyXMLMessage> При обработке простых типов как атрибутов получим следующий более компактный код: <TMyXMLMessage LanguageOfText="english"> <ToPerson FirstName="Osama" LastName="Unknoun"/> </TMyXMLMessage> Второй вариант позволяет работать с любыми документами, однако надо решить, каким образом описывать данные #CDDATA. Возможно, для этого придется зарезервировать какой-либо тип.

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

Приведенная реализация будет постоянно обновляться, в том числе и на основании Ваших, уважаемый читатель, предложений. Последняя версия компонента с исходными текстами входит в библиотеку Globus VCL Extention Library.

Чудин Андрей, октябрь 2001г.
Специально для




Разбиение объектного пространства сцены путём построения octree-дерева




Have you ever stood and stared at it, Morpheus?
Marveled at its beauty. Its genius. Billions of people just living out their lives... oblivious.

Did you know that the first Matrix was designed to be a perfect human world?
Where none suffered, where everyone would be happy. It was a disaster. No one would accept the program.
Entire crops were lost.

Some believed we lacked the programming language to describe your perfect world.
But I believe that, as a species, human beings define their reality through suffering and misery.

Agent Smith. The Matrix.

Хотя в Королевстве статьи такого типа публикуются нечасто, я всё же попросил разрешения у Королевы сделать это, так как на тематику статей сайта никакие ограничения не накладываются, а сама программа написана на Object Pascal в среде Delphi. Для тех, кто читал мои ранние статьи по DirectX, хочу предупредить, что свои исследования в области прикладного API вроде DirectX и OpenGL я прекратил, и теперь больше занимаюсь алгоритмами, необходимыми для реализации трёхмерной графики. Ну да это так, к слову.

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

Обычно тем, кто только начинает пробовать свои силы в 3D-пространстве, трудно понять, для чего же нужно разбиение пространства. Действительно, если опыты не выходят за рамки "солнышка и земли вокруг него", то заниматься этим нет смысла. Но допустим, мы взялись за рендеринг сложной сцены, вроде уровня Quake 3. Количество полигонов в сцене может достигать нескольких десятков тысяч (из года в год это значение неудержимо растёт), и если этот массив данных целиком отправлять на графический конвейер в каждом кадре, ни о какой интерактивности в игре и речи быть не может. Графический процессор будет тратить время на просчёт каждого полигона у каждого объекта, даже если в результате он жестоко не попадёт на экран.

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

Здесь я собираюсь рассмотреть метод разделения объектного пространства, который называется octree (по-моему, от латинского octa - восемь, и английского tree - дерево). Восьмеричное дерево. Вообще подобные алгоритмы были разработаны ещё в 70-х годах, например, для точного описания ландшафта, но позже нашли своё применение в компьютерной графике.

Данный алгоритм производит разделение объектного пространства на восемь подпространств. Общую схему работы можно представить следующими шагами:

Помещаем всю сцену в выровненный по осям куб. Этот куб описывает все элементы сцены и является корневым (root) узлом дерева. Проверяем количество примитивов в узле, и если полученное значение меньше определённого порогового, то производим связывание (ассоциацию) данных примитивов с узлом. Узел, с которым ассоциированы примитивы, является листом (leaf). Если же количество примитивов, попадающих в узел, больше порогового значения, производим разбиение данного узла на восемь подузлов (подпространств) путём деления куба двумя плоскостями. Мы распределяем примитивы, входящие в родительский узел, по дочерним узлам. Далее процесс идёт рекурсивно, т. е. для всех дочерних узлов, содержащих примитивы, выполняем пункт 2. Данный процесс построения дерева может содержать несколько условий прекращения рекурсии: Если количество примитивов в узле меньше или равно пороговому значению. Если рекурсия (количество делений) достигла какой-то определённой глубины вложенности. Если количество созданных узлов достигло порогового значения.


Самый простое и наглядное условие - это проверка на количество попадающих в узел геометрических примитивов (в нашем случае таким примитивом является треугольник). Это значение можно свободно варьировать, однако если вы хотите, чтобы скорость врендеринга была действительно хорошей, необходимо учитывать особенности современных видеокарт. Т. е. для отдельного вызова на графический конвейер необходимо подавать 200-300+ вершин, поэтому количество треугольников в узле должно быть достаточно большим - 100 и более. С другой стороны, бессмысленно делать это значение слишком большим - вне зависимости от того, видны ли все примитивы листа или нет, на графический конвейер они будут отправлены все, а это приведёт в большинстве случаев к бессмысленной обработке зачастую невидимой геометрии.

А теперь о том, как же именно применение данного алгоритма может помочь быстро откинуть части невидимой геометрии. Те элементы, что отображаются при рендеринге на экране, попадают в так называемый viewing frustum - пространство видимости. Графически оно выглядит вот так:


Рисунок 1. Viewing frustum

Теперь, в процессе рендеринга мы рекурсивно выполняем следующую процедуру: начиная с базового (root) куба, мы проверяем, попадает ли данный куб в поле зрения (viewing frustum). Если НЕТ - на этом всё и заканчивается, если же ДА - перемещаемся вглубь рекурсии на один шаг, т. е. поочерёдно проверяем видимость каждого из восьми подузлов корневого узла и т. д. Преимущество заключается в том, что если определено, что данный узел не виден, то можно смело не выводить и всю геометрию этого узла - она тоже будет не видна. Таким образом, ценой всего лишь нескольких проверок, мы отбросим значительную часть сцены. А в случае, если не виден корневой узел, на экран не будет выведено ничего. Сплошная экономия!

Представленная программа как раз и демонстрирует работу данного алгоритма. Из двоичного файла загружается сцена (я взял готовую модель для 3D Studio MAX), представляющая собой интерьер простой кухни. Для этой сцены строится octree-дерево, в качестве порогового количества примитивов в узле установлено значение 300. В сцене я специально разместил два высокополигональных объекта, чтобы можно было "прочувствовать" преимущество алгоритма. Это бутылки из-под кетчупа на столе. Как только они попадают в область видимости, fps резко падает, но при выходе их за пределы видимости fps возрастает. Если же направить камеру в пустоту, fps возрастает до максимально возможного, так как в этом случае рендеринг сцены не производится. Если бы мы не использовали алгоритм разбиения пространства, fps был бы неизменно низким, словно бутылки с кетчупом видны постоянно.

К сожалению, от достоинств алгоритма а нужно перейти к его недостаткам, коих немало. Первый из них - это возможное деления примитива ребрами кубов дерева, например, вот так:


Рисунок 2. Проблемный случай



Обычно примитив относят к тому узлу, в пределах которого лежат вершины, образующие примитив. К какому из узлов отнести треугольник в данном случае? Казалось бы, взять да и отнести его к узлу I. Но так делать нельзя, и вот почему. Предположим, что мы связали треугольник с узлом I - тогда, если в процессе рендеринга мы определили, что узел I не виден, то и треугольник выведен не будет. Однако при этом возможна ситуация, когда узлы II и III будут в пространстве вида - полигон "выпадет" из сцены. Для избежания этого примитив относят к узлу, если хотя бы одна вершина примитива находится в пределах узла. В данном случае при таком подходе один и тот же треугольник будет ассоциирован с узлами I, II и III. В принципе, это не так уж страшно, так как данные можно индексировать, и этим избежать значительных расходов памяти. Однако в процессе рендеринга, если видны все три узла, полигон придётся вывести три раза. А это уже неприятно. Тем более, что такой полигон обычно не один, их множество.

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

Всё бы хорошо, да с делением примитива есть ещё одна проблема. Допустим, узлы I, II и III не видны. Однако узел IV может всё же попадать в поле зрения камеры, и видно, что частичка полигона всё же может выпасть, вот так:


Рисунок 3. Полигон выпал

Что делать? Очевидно, что нельзя относить примитив к узлу, ориентируясь только на факт принадлежности вершины примитива к этому узлу. Наверное, все же вместо этого надо проверять, проходит ли РЕБРО примитива через пространство узла. Это может потребовать гораздо больше времени при построении дерева, но зато мы избежим выпадения полигонов. Кстати, структуру один раз построенного дерева можно записать в файл, и уже больше не строить его каждый раз при старте программы, а загружать из файла. В приведённом случае полигон придётся отнести ко всем четырём узлам.

Второй недостаток octree-дерева - это вывод всех объектов, находящихся в поле viewing frustum, но на самом деле в конечном счёте невидимых. Например, стена кухни может закрывать бутылки с кетчупом, но они всё равно будут отсылаться на конвейер (так как находятся в области viewing frustum), и fps будет низким. По-видимому, чистым octree-based алгоритмом здесь не обойтись, необходимо дополнительно реализовать так называемый Z-buffer, например, на тайловой основе. Коротко его работу можно описать так:

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



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

Представленная программа написана в IDE Delphi 5 без использования VCL, поэтому проблем с компиляцией в средах различных версий выше 3 я не предвижу. Используется API OpenGL (стандартный модудь Delphi opengl.pas), и дополнительный небольшой модуль myglext.pas, где я описал некоторые необходимые для работы программы расширения OpenGL (всё-таки удивительно, насколько стандартный модуль неполон в этом вопросе - ведь некоторые недокументированные в нём API-функции уже давно входят в ядро OpenGL и являются фундаментальными для 3D-графики). Для более эффективного рендеринга используется функция ядра glDrawElements() и не используются специфические расширения конкретных видеокарт, поэтому программа по идее должна работать на любом компьютере. Управление: мышью можно вращать камеру, а левой и правыми кнопками мыши двигать камеру вперёд-назад. Клавишей D включается/отключается отрисовка рёбер узлов дерева - можно как бы увидеть его структуру. Класиша W задаёт wireframe-режим.

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

Если вам есть что сказать — пишите

Скачать проект



Реализация языка шаблонов для Object Pascal на Perl


Раздел Подземелье Магов сев А.В. ,
дата публикации 24 января 2002г.



Реализация передач команд серверу


Для этого в компоненте в разделе реализована функция Procedure ExecuteMacroPM(Str:String); // Выполнить макрос Которая в свою очередь вызывает метод TDDEClientConv.

К сожалению разработчики немного запутали передачу логических данных в процедуры и функции Script Language и в разные функциях булевые значения могут передаваться в виде '0' -логического "НЕТ" , так и в виде строкового ".F." , "OFF" или "False"

В общем для обработки преобразования реализовано несколько функций

// вернуть строковый On Off Function ReturnOnOffStr(Value: Boolean) : String; // вернуть строковый .T..F. Function ReturnTrueFalseStr(Value: Boolean) : String; // вернуть строковый 0 , 1 Function Return0_1Str(Value: Boolean) : String; // более подробно смотрите в компоненте…



Реализация приема данных из DDE сервера


Запрос данных с сервера в компоненте к сожалению не реализован функцией т.к. компонент в свое время был написан за два часа в очень скоростном режиме, если вы обратите внимание на некоторые функции то запрос данных с DDE сервера в них реализован примерно так var PcharReply : Array[0..1023] of char; begin PcharReply := DDE.RequestData('GetPMState'); В основе вызывается стандартная функция RequestData TDDEClientConv.Для более подробной информацие обратитесь в справку по TDDEClientConv.



Обработка событий является одним из


Раздел Подземелье Магов
Введение

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

GUI пользователя должен уметь обрабатывать различное количество событий, например, таких как: нажатие на кнопку мыши, перемещение мыши по экрану и т.д. Приблизительно так же может возникнуть потребность обрабатывать события внутри объектов COM. В данной статье мы рассмотрим принцип работы свободно связанных событий и создадим наглядное приложение для демонстрации использования такого типа событий в COM+. (Для более детальной информации о события в COM+ смотрите статью А.Новика «Система поддержки событий COM+» на сайте журнала «Клиент-Сервер»).


Редактирование реквизитов поля


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



Редактирование реквизитов таблицы


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



Как прекрасна была бы жизнь,


Как прекрасна была бы жизнь, если б можно было все насущные нужды разработчика удовлетворить только средствами языка разработки! Увы, нет в мире совершенства, как говорил Лис, и поэтому фирмы-разработчики средств разработки генерируют всё новые, всё более мощные среды разработки (IDE), а также развивают сами языки программирования - взять те же Delphi, BCB, C# : сравните языковые средства с Pascal и C++. Вспомните также, сколько дополнительных (встроенных в IDE и отдельных) инструментальных средств входит в поставку BCB и Delphi.
Borland Software Corporation, отдав дань уважения OWL, задвинула её подальше и стала развивать Delphi и BCB на платформе VCL. Совершенно не вижу, почему бы благородным донам не поступить так же J.
Суть моего решения состоит в интеграции средства языка - компоненты - и дополнительного инструментального средства собственной разработки - DllWizard. Интерфейс-оболочку к DLL обеспечивает компонента TAskDll (исходный код - в архиве AskDll.zip). В её методах инкапсулированы: динамическая загрузка DLL (функция LoadLibrary) в методе LoadDll обработка исключительных ситуаций: при возникновении любых проблем с за-грузкой DLL формируется сообщение на языке локализации Windows и генерируется ис-ключение (Exception) для обработки в вызывающем приложении выгрузка DLL и освобождение памяти (функция FreeLibrary), выполняемые авто-матически при прекращении существования компоненты (например, при закрытии формы, на которой расположена компонента) Загрузка DLL и инициализация импортируемых функций осуществляется вызовом одного лишь метода компоненты - LoadDll. Параметр метода - указатель на функцию: bool (*FuncGetProc)(HMODULE PtrDll) Это должна быть функция вида: bool Example_Load(HMODULE PtrDll) { if((Func1=(DLL_Func1)GetProcAddress(PtrDll, "@Func1$qiii")) == NULL) return false; if((Func2=(DLL_Func2)GetProcAddress(PtrDll, "@Func2$qpct1")) == NULL) return false; return true; }
Всё, что нам нужно - это написать подобный код. Именно эта часть работы наиболее тру-доёмка, и когда мы сможем выполнить её легко, быстро и безошибочно, это и будет красивым венцом нашей технологии.
Задача решается с помощью DllWizard в 3 прохода: Автоматическое формирование описаний функций с их параметрами и возвращае-мыми значениями. Автоматическое формирование идентификаторов функций (строковых параметров для функции GetProcAddress) Генерация исходных текстов Рассмотрим пример работы с Example.DLL, экспортирующей 2 функции: int __cdecl Func1(int i1, int i2, int i3); char* __cdecl Func2(char *s1, char *s2


Итак, начинаем:
Запускаем DllWizard и создаём список всех функций, которые мы хотим импорти-ровать из DLL. Если DLL собственной разработки, достаточно просто указать путь к её исходнику и нажать кнопку "Найти": список сформируется автоматически (см.Рисунок 1) Указываем путь к DLL и нажимаем кнопку "Найти" (см.Рисунок 2) Нажимаем кнопку "Сгенерировать" - в каталогах, указанных на закладке "На-стройки" будут сформированы файлы

Рисунок 1

Рисунок 2
Имя DLL-ки является префиксом у всех сгенерированных файлов и у функции в модуле CPP. Исходный текст DLL и тестового приложения находится в архиве DllTest.zip
Подкаталог DLL содержит исходный текст библиотеки: UnitExample.cpp Подкаталог EXE содержит исходный текст тестового приложения: UnitDllTest.cpp и в подкаталоге DllWizard - сгенерированные файлы: Заголовочные файлы: Example_Descript.h является служебным и содержит описание функций Example_Declare.h является служебным и содержит объявления указателей на функции Example_Extern.h следует включить в тот исходный модуль проекта прило-жения, из которого вызываются функции, импортируемые из DLL. Example_Load.cpp содержит функцию загрузки Example_Load

Подводим итоги. Ниже описан порядок


Подводим итоги. Ниже описан порядок разработки пользовательского приложения, им-портирующего функции из динамически подключаемой библиотеки функций: С помощью DllWizard генерируем включаемые модули В проект включаем сгенерированный модуль Example_Load.cpp "Бросаем" на главную форму компоненту TAskDll и в её свойстве DllName указы-ваем имя DLL-ки (см.Рисунок 3)

Рисунок 3 В модуль главной формы и во все модули, в которых предполагается использовать импортируемые из DLL функции, включаем заголовочный файл Example_Extern.h Пишем пользовательский код и компилируем проект. Всё! Скриншот работы тес-тового приложения приведён на Рисунок 4

Рисунок 4



9 октября 2001 г
Специально для
Скачать архив: (92 K)
Примечания

(1) — кстати, компиляция приложения и dll с пакетом vcl50 полностью снимает проблему дочерних окон и использование менеджера памяти BORLNDMM.DLL (подключение ShareMem) становится не только не нужным, но и опасным .
(2) — как ни странно, но и от Microsoft бывает что-нибудь хорошее…
(3) — когда одно приложение управляет другим или позволяет себе "уведомлять"другое приложение о своих событиях
(4) — когда приложения выполняются на разных компьютерах в локальной сети (или internet) и взаимодействуют друг с другом.
(5) — подробнее о VMT будет рассказано далее.
(6) — если произойдет "сдвиг" VMT, последствия могут быть непредсказуемыми -но фактический вызов будет неверным.
(7) — когда один пакет ссылается на другой, а тот, в свою очередь, на третий, а тот …
(8) — только выполняют при загрузке дополнительную работу
(9) — кстати, использование TActionList довольно хорошая практика
(10) — New/Package
(11) — объектно-ориентированное программирование
(12) — так называемый GUID - глобальный уникальный идентификатор
(13) — я всегда испытываю некоторый скепсис, когда слышу про то, что чего-то "нам надолго хватит". В памяти еще остались ощущения от программирования под 16 разрядные среды (DOS и Windows) с 64k барьером и 640k общей памяти (которой, по тогдашнему мнению Microsoft, должно было хватить надолго). Но, как мне кажется, возможностей GUID, по крайней мере, на наш век хватит. Тем более в пределах одного приложения.
(14) — функция QueryInterface более подробно обсуждается далее
(15) — кстати, возникновение такой ситуации свидетельствует о плохом проектировании программы и следует пересмотреть всю идеологию системы в целом.
(16) — в виду отсутствия в Delphi механизма множественного наследования, агрегация (то есть включение одного объекта в другой с транспортацией его свойств и методов в объект-контейнер) довольно часто применяется в Delphi.
(17) — в частности, при переводе компонента в элемент ActiveX
(18) — вместе с _AddRef и _Release она реализует интерфейс IUnknown, являющийся базовым интерфейсом для всех остальных интерфейсов (как TObject является базовым классом для всех классов Delphi). Для любителей порассуждать о том, какой язык лучше вот информация к размышлению: для реализации механизма интерфейсов (да и, пожалуй, полностью всего COM) в C++ (причем в классической реализации по Страуструпу) не нужно ничего, кроме быстрых и умелых рук. Тогда как в Delphi Object Pascal потребовалось для его поддержки пришлось вносить изменения в сам язык.
(19) — допустим, IUnknown
(20) — включая получение интерфейса IUnknown.
(21) — но следует более тщательней подходить к реализации пакета базовых классов. То есть нужно постараться предусмотреть все, что только можно, ибо после запуска проекта в самостоятельное плавание малейшее изменение в этом пакете может потребовать перекомпиляцию, причем как основного приложения, так и всех без исключения plugin's
(22) — правда, особого смысла я в этом не вижу
(23) — а вот это может оказаться полезным (одна интеграция с MS Office'ом стоит многого)
(24) — Библиотека поддержки COM в Delphi это использует на полную катушку
(25) — пока не будет проинициализировано внутренне поле FVCLComObject TComponent. А это случится только при создании на основе TComponent ActiveX объекта.
(26) — если вы не хотите все испортить :)

С учетом критики и дополнений


Раздел Подземелье Магов Статья обновлена
Я не профи в Win API, просто у меня возникла именно такая проблема. Я нашел решение устраивающее меня. И к тому же решил, поделился с вами.
Если кому-то требуется что-то другое - дерзайте, я с удовольствием прочту на "Королевстве" что и как у вас получилось.
Handle = Хэндл = Рукоятка :)

Хочу предложить 2 способа: 1) Простой, с использованием command.com /c имя_консольной_проги > имя_файла_куда_переназначить_StdOut 2) С использованием Win API (2 штуки) Вы уж сами выберите, что вам подходит больше. Я использую способ № 2.2.

Рассмотрим их более подробно на примерах.



Сервер удаленного доступа. Часть I


Раздел Подземелье Магов Автор Александр Галилов
дата публикации 05 ноября 1999г.

Введение В этой статье рассматривается проектирование сервера удаленного доступа под Windows 95/98, позволяющего осуществлять подключение клиентов к командному интерпретатору COMMAND.COM. Прошу читателей отнестись с пониманием к возможным ошибкам и неточностям, т.к. я сравнительно недавно занялся данной темой.
Приведенный в статье пример реализован на C++ Builder 1 и Delphi 3. Обратите внимание на то, что автор НЕ ТЕСТИРОВАЛ примеры Win NT. Имеются все основания предполагать некорректность их работы в этой операционной системе. Если хотите - проверьте.

Под WindowsNT прилагаемый проект не работает, проверено. Что, впрочем, автор и не обещает.
Лена Филиппова

Часть 1 Первая часть статьи посвящена вопросу построения внутрисистемного интерфейса сервера удаленного доступа. Здесь под термином "внутрисистемный интерфейс" подразумевается способ взаимодействия нитей (threads) сервера непосредственно с программой, производящей выполнение пользовательских запросов. В данном случае пользовательские запросы поступают во всем известный командный интерпретатор COMMAND.COM (кстати, в Windows95/98 этот файл имеет формат EXE). Для организации взаимодействия с командным интерпретатором я использовал механизм неименованных трубок (anonymous pipes). Данный механизм был выбран по причине отсутствия в Win95 таких средств, как именованные трубки (Named Pipes) в Win NT. Именованные трубки позволяют реализовать рассмотренный здесь пример со значительно меньшими усилиями. Практически отличия Win NT и Win95 таковы, что простой, в принципе, механизм приходится реализовывать весьма нетривиальным способом.
Трубка - это по сути канал передачи данных. Трубка имеет два файловых идентификатора - один для записи данных, другой - для чтения имеющейся в трубке информации. Порядок продвижения байтов в трубке - FIFO (первый поступивший байт первым оказывается на выходе). С помощью API функции CreateProcess мы запускаем командный процессор, но при этом стандартные ввод и вывод перенаправляем на наши трубки. После проделывания всех этих операций мы получаем пару файловых идентификаторов, при помощи которых можем общаться с "Сеансом MS-DOS", однако, имейте ввиду, что этот механизм НЕ ПОЗВОЛЯЕТ получать/принимать данные с не стандартного ввода (STDIN) и вывода (STDOUT), т.е. Вы не сможете работать через трубки с Norton Commander или Far manager, хотя без проблем можете их запустить из командного интерпретатора COMMAND. А вот использовать все команды DOS (даже format d:) - это запросто :). Ничего не мешает работать и с другими программами, имеющими стандартный ввод-вывод, например Турбо ассемблер (TASM).
Теперь немного уточню насчет использования трубок. Конечно, pipes - это не изобретение Микрософт. Когда-то их описание я обнаружил в руководстве системного программирования под Unix, но подозреваю, что и там они появились не впервые. Вот что написано про трубки в Win32 Developer's References:


A pipe is a communication conduit with two ends; a process with a handle to one end can communicate with a process having a handle to the other end.

Рассмотрим более подробно создание трубки. Функция CreatePipe создает трубку, предоставляемую программисту в виде "двух концов" - идентификаторов:



BOOL CreatePipe( PHANDLE hReadPipe, // address of variable for read handle
PHANDLE hWritePipe, // address of variable for write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes
DWORD nSize // number of bytes reserved for pipe );

hReadPipe и hWritePipe - указатели на идентификаторы. Идентификаторы получают значение при выполнении этой функции.
lpPipeAttributes - если Вы в Win95/98 можете это опустить и указать просто NULL, если вы в Win NT - см. Win32 Developer's References.
nSize- предположительный размер буфера. Система опирается на это значение для вычисления реального размера буфера. Этот параметр может быть равным нулю. В этом случае система выберет размер буфера "по умолчанию", но какой именно - я не знаю.
Если трубка создана, функция возвратит ненулевое значение, в случае ошибки - вернет нуль.

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



BOOL CreateProcess( LPCTSTR lpApplicationName,

// pointer to name of executable module
LPTSTR lpCommandLine, // pointer to command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // pointer to process security attributes
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes
BOOL bInheritHandles, // handle inheritance flag
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // pointer to new environment block
LPCTSTR lpCurrentDirectory, // pointer to current directory name
LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION );

lpApplicationName и lpCommandLine - указатели на "нуль-терминированные" (PChar) строки с именем запускаемого модуля (напр. "c:\command.com") и с командной строкой, которая будет передана запущенной программе в качестве аргумента. Если lpApplicationName равен нулю, то имя запускаемой программы должно быть первым в строке lpCommandLine и отделено пробелами от аргументов. Для Win NT - см. подробности в Win32 Developer's References.
lpProcessAttributes - в Win95/98 игнорируется, установите его в нуль. Для Win NT - см. подробности в Win32 Developer's References.
lpThreadAttributes - тоже, что и для lpProcessAttributes.
bInheritHandles - показывает как наследуются идентификаторы из вызывающего процесса. Если равно TRUE, то все идентификаторы, которые могут наследоваться, наследуются новым процессом. Унаследованные идентификаторы имеют то же самое значение и привелегии, что и оригинальные.
dwCreationFlags - определяет дополнительные флаги. Могут иметь следующие значения (список не полный, только для нашей задачи):
CREATE_DEFAULT_ERROR_MODE - новый процесс не наследует режим ошибок (error mode) от вызывающего процесса, вместо этого CreateProcess назначает новому процессу режи по умолчанию.
CREATE_NEW_CONSOLE - новый процесс создает новую консоль вместо родительской консоли. Этот флаг нельзя использовать вместе с флагом DETACHED_PROCESS.
DETACHED_PROCESS - для консольных процессов - новый процесс не имеет доступа к консоли родительского процесса.
HIGH_PRIORITY_CLASS - высокий приоритет. Используется в критичных ко времени выполнения процессах.
IDLE_PRIORITY_CLASS - нити процесса выполняются только при простое системы (idle).
NORMAL_PRIORITY_CLASS - приоритет для обыкновенных процессов без специальных задач.
REALTIME_PRIORITY_CLASS - наивысший приоритет. Системные сообщения могут теряться при выполнении потока с этим приоритетом.
lpEnvironment - указатель на среду окружения. Указывает на блок нуль-терминированных строк вида ИМЯ=ЗНАЧЕНИЕ. Сам блок завершается двумя нулевыми байтами для блока строк в формате ANSI и четырьмя нулевыми байтами для блока строк в формате UNICODE (см. подробности в Win32 Developer's References).
lpCurrentDirectory - указатель на нуль-терминированную строку, содержащую текущий каталог. Если указатель равен NULL, то текущий каталог тот же, что и у родительского процесса.
lpStartupInfo - указатель на структуру STARTUPINFO , которая определяет как должно появляться оконо для нового процесса.
lpProcessInformation - указатель на структуру PROCESS_INFORMATION , заполняемую функцией CreateProcess. Эта структура содержит информацию о запущенном процессе.

Теперь более подробно рассмотрим структуры STARTUPINFO и PROCESS_INFORMATION .
STARTUPINFO содержит следующие поля:
DWORD cb - размер структуры в байтах
LPTSTR lpReserved - не используется
LPTSTR lpDesktop - только в Win NT, подробности см. Win32 Developer's References
LPTSTR lpTitle - указатель на нуль-терминированную строку-заголовок консоли для консольных приложений
DWORD dwX - игнорируется, если не установлен флаг STARTF_USEPOSITION в dwFlags. Определяет координаты левого верхнего угла создаваемого окна в пикселях по горизонтали. Подробности см. Win32 Developer's References
DWORD dwY - игнорируется, если не установлен флаг STARTF_USEPOSITION в dwFlags. Определяет координаты левого верхнего угла создаваемого окна в пикселях по вертикали. Подробности см. Win32 Developer's References
DWORD dwXSize- игнорируется, если не установлен флаг STARTF_USESIZE в dwFlags. Определяет размер создаваемого окна в пикселях по горизонтали. Подробности см. Win32 Developer's References
DWORD dwYSize- игнорируется, если не установлен флаг STARTF_USESIZE в dwFlags. Определяет размер создаваемого окна в пикселях по вертикали. Подробности см. Win32 Developer's References
DWORD dwXCountChars- Игнорируется, если не установлен флаг STARTF_USECOUNTCHARS. Для консольных приложений, создавших новую консоль, определяет размер экранного буфера по горизонтали. Для GUI приложений всегда игнорируется
DWORD dwYCountChars- Игнорируется, если не установлен флаг STARTF_USECOUNTCHARS. Для консольных приложений, создавших новую консоль, определяет размер экранного буфера по вертикали. Для GUI приложений всегда игнорируется
DWORD dwFillAttribute- Игнорируется, если не установлен флаг STARTF_USEFILLATTRIBUTE. Определяет начальные атрибуты (цвет текста и фона) для консольных приложений. Игнорируется для GUI-приложений. Может принимать значения: FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_RED, FOREGROUND_INTENSITY, BACKGROUND_BLUE, BACKGROUND_GREEN, BACKGROUND_RED и BACKGROUND_INTENSITY. Например FOREGROUND_RED | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE даст красный текст на белом фоне
DWORD dwFlags- Битовое поле, показывающее какие поля структуры STARTUPINFO следует учитывать при создании окна. Могут использоваться любые комбинации значений (список не полный, подробности см. Win32 Developer's References): STARTF_USESHOWWINDOW- Если этот флаг не установлен, то wShowWindow игнорируется
STARTF_USEPOSITION- Если этот флаг не установлен, то dwX и dwY игнорируются
STARTF_USESIZE- Если этот флаг не установлен, то dwXSize и dwYSize игнорируются
STARTF_USECOUNTCHARS- Если этот флаг не установлен, то dwXCountChars и dwYCountChars игнорируются
STARTF_USEFILLATTRIBUTE- Если этот флаг не установлен, то dwFillAttribute игнорируется
STARTF_USESTDHANDLES- Если установлен этот флаг, присвойте идентификаторы стандартного ввода, стандартного вывода и стандартной ошибки полям hStdInput, hStdOutput, и hStdError соответственно. Чтобы это работало, параметр fInheritHandles при вызове CreateProcess должен быть равен TRUE.
WORD wShowWindow- Игнорируется, если не установлен флаг STARTF_USESHOWWINDOW. Если флаг STARTF_USESHOWWINDOW установлен, присвойте этому полю константу, определяющую способ отображения главного окна, например SW_MINIMIZE
WORD cbReserved2- Зарезервировано, должно равняться нулю
LPBYTE lpReserved2- Зарезервировано, должно равняться нулю
HANDLE hStdInput- Игнорируется, если не установлен STARTF_USESTDHANDLES. Если флаг STARTF_USESTDHANDLES установлен, см. пункт про STARTF_USESTDHANDLES
HANDLE hStdOutput- -""-
HANDLE hStdError- -""-
Структура PROCESS_INFORMATION содержит следующие поля: HANDLE hProcess- Дескриптор созданного процесса
HANDLE hThread- Дескриптор первичной нити процесса (primary thread)
DWORD dwProcessId- Идентификатор процесса
DWORD dwThreadId- Идентификатор первичной нити процесса
Заполнять поля структуры PROCESS_INFORMATION не нужно, они заполняются при вызове функции CreateProcess.

Рассмотрим пример использования трубок и функции CreateProcess для обмена данными с COMMAND.COM



var stinfo: TStartupInfo; prinfo: TProcessInformation; ReadPipe,WriteToCommand,ReadFromCommand,WritePipe: integer; // обнуляем поля структур для CreateProcess FillChar(stinfo,sizeof(TStartupInfo),0); FillChar(prinfo,sizeof(TProcessInformation),0); // пытаемся выполнить CreatePipe для первой и второй трубки if (not CreatePipe(ReadPipe,WriteToCommand,nil,PipeSize)) or (not CreatePipe(ReadFromCommand,WritePipe,nil,PipeSize)) then ErrorCode:=1 else begin stinfo.cb:= sizeof(stinfo); stinfo.lpReserved:= nil; stinfo.lpDesktop:= nil; stinfo.lpTitle:= nil; stinfo.dwFlags:= STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; stinfo.cbReserved2:= 0; stinfo.lpReserved2:= nil; stinfo.hStdInput:= ReadPipe; stinfo.hStdOutput:= WritePipe; stinfo.hStdError:= WritePipe; stinfo.wShowWindow:= SW_HIDE; // запускаем COMMAND.COM CreateProcess(CommandPath,nil,nil,nil,true, CREATE_DEFAULT_ERROR_MODE or NORMAL_PRIORITY_CLASS, nil,CurrentDirectory, stinfo,prinfo) После выполнения этого фрагмента мы имеем в итоге запущенный командный интерпретатор и пару файловых идентификаторов для приема/передачи символьных сообщений между COMMAND.COM и нашей программой. Сейчас самое время решить, каким образом реализовать обмен данными с трубками.

Казалось бы, что трубки вполне заменяют очереди, но это не совсем так. Обратившись к трубке для чтения символа, мы попадем в очень неприятное положение в случае, если трубка окажется пустой. Функция чтения не завершиться, пока не прочитает указанное количество символов. То же самое относится и к попытке записи символа в заполненную трубку. Выход заключается в создании двух нитей, одна из которых занимается считыванием символов и их постановкой в очередь на обработку, а другая - передачей символов из очереди в трубку.

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



Приведенная в примере программа позволяет очень легко реализовать любой из этих способов. Ядро системы представлено в виде DLL, поэтому Вы можете использовать его не только в программах на Delphi.

Это дает нам возможность из основной нити программы обращаться к нашим очередям в совершенно произвольные моменты времени. Однако, следует учесть некоторые детали, касающиеся разделения нитями ресурсов. Необходимо гарантировать, что при обращении к очереди из основной нити программы мы не прервем операции с тои же очередью, производимые нитями, работающими с трубками. То же самое касается и Callback-процедур. Для избежания этого конфликта используется механизм критических секций. Критическая секция - это объект, поддерживаемый системой и разделяемый между двумя или более нитями. Суть работы критической секции состоит в том, что перед выполнением некоторого участка кода, которое нельзя прерывать, нить вызывает метод Enter, а перед завершением выполнения критического участка - метод Leave. Если во время выполнения критического участка другая нить вызовет метод Enter той же самой критической секции, то выполнение этой нити будет остановлено внутри метода Enter до тех пор, пока "предыдущая" нить, захватившая критический участок не вызовет метод Leave Этот механизм предотвращает доступ нитей к критическому участку, если он уже выполняется. Все нити для конкретного критического участка должны использовать один и тот же разделяемый объект критической секции.

Далее в примере используются функции API ReadFile и WriteFile. Сначала я опишу их, опираясь на Win32 Developer's References.

BOOL ReadFile( HANDLE hFile,// handle of file to read LPVOID lpBuffer,// address of buffer that receives data DWORD nNumberOfBytesToRead,// number of bytes to read LPDWORD lpNumberOfBytesRead,// address of number of bytes read LPOVERLAPPED lpOverlapped// address of structure for data );
hFile- Идентификатор файла для чтения. Должен быть создан с режимом доступа к файлу GENERIC_READ
lpBuffer- Указатель на буфер, в который будут помещены загруженные прочитанные данные
nNumberOfBytesToRead- Задает число байт, которые нужно прочитать
lpNumberOfBytesRead- Указатель на переменную, которая получит значение количества прочитанных байт
lpOverlapped- Указатель на структуру OVERLAPPED. В примере не используется. Для более подробной информации см. Win32 Developer's References

BOOL WriteFile( HANDLE hFile,// handle to file to write to LPVOID lpBuffer,// pointer to data to write to file DWORD nNumberOfBytesToRead,// number of bytes to write LPDWORD lpNumberOfBytesRead,// pointer to number of bytes written LPOVERLAPPED lpOverlapped// pointer to structure needed for overlapped I/O );
hFile- Идентификатор файла для записи. Должен быть создан с режимом доступа к файлу GENERIC_WRITE
lpBuffer- Указатель на буфер, из которого будет записываться информация
nNumberOfBytesToRead- Задает число байт, которые нужно записать
lpNumberOfBytesRead- Указатель на переменную, которая получит значение количества записанных байт
lpOverlapped- Указатель на структуру OVERLAPPED. В примере не используется. Для более подробной информации см. Win32 Developer's References

Обе описанные функции возвращают ненулевое значение в случае успешного завершения



//============================================================================= // Получение символа из очереди. Если символа в очереди нет - возвращает -1, // если символ есть - возвращает код символа (не char, а int !!!) function TFlowFromCommand.Get: integer; begin // входим в критическую секцию cs.Enter; Result:=GetQueue(end_chain,start_chain,chain_data,CHAIN_SIZE_FROM_COMMAND); // покидаем критическую секцию cs.Leave; end; //============================================================================= // устанавливает символ в очередь. Если символ в очередь установлен, // функция возвращает 1, если в очереди нет места - возвращает 0 function TFlowFromCommand.Put(c: char):integer; begin // входим в критическую секцию cs.Enter; Result:=PutQueue(c,end_chain,start_chain,chain_data,CHAIN_SIZE_FROM_COMMAND); // покидаем критическую секцию cs.Leave; end; //============================================================================= // вызов Callback-процедуры для передачи символа в основную нить procedure TFlowFromCommand.VCLExec; begin CallBackReceive(c,self); end; //============================================================================= procedure TFlowFromCommand.Execute; var read: integer; begin // входим в цикл repeat // если попытка чтения символа из трубки вызвала ошибку c:=0; if (not ReadFile(Pipe,c,1,read,nil)) then begin // отдаем оставшуюся часть кванта времени системе Sleep(0); continue; end // иначе делаем попытки поставить символ в очередь пока это наконец не // удасться успешно выполнить или вызываем обработчик, если он установлен else if @CallBackReceive=nil then while(Put(chr(c))=0) do Sleep(0) else begin gcs2.Enter; Synchronize(VCLExec); gcs2.Leave; end; until (Terminated or Suspended); end; //=============================================================================

Теперь у нас есть две очереди и методы Get и Put для доступа к ним. Еще мы имеем возможность "подцепить" Callback-процедуры и работать без использования очередей. Мы можем в любой момент воспользоваться этими методами для осуществления обмена информацией между запущенным командным интерпретатором и основной нитью нашей программы. Также мы можем использовать методы доступа к очередям совершенно произвольно в других нитях процесса.

Пример реализации описанного в статье механизма (C++Builder 1, Delphi 3) Вы можете скачать (16 K)

Александр Галилов


Сервер удаленного доступа. Часть II


Раздел Подземелье Магов Автор Александр Галилов
дата публикации 11 ноября 1999г.

Предисловие ко второй части

В первой части статьи был рассмотрен пример построения внутрисистемного интерфейса сервера удаленного доступа. К сожалению, неожиданно стали очевидными некоторые моменты, делающие использование DLL нежелательным. Во-первых, метод Synchronize далеко не всегда нормально работает в нитях (threads), созданных внутри DLL. Т.е. при вызове Synchronize(Something) метод Something не запускается. Этот эффект наблюдается только при использовании DLL и очевиднее всего именно в Win NT, что и послужило основной причиной неработоспособности примера в этой ОС. Во-вторых в Win 95/98 также есть ряд условий, отличных от таковых в Win NT, при которых Synchronize внутри DLL работает неправильно. В силу сложившихся обстоятельств в новом примере предоставляю исходник DLL, не использующей вышеупомянутый метод. Однако, при ее использовании остается проблема синхронизации с "main VCL thread". Мною были также исправлены "глюки" с обработкой очередей в предыдущей версии DLL и дополнен заголовочный файл. В-третьих, учитывая дополнительную сложность использования функций DLL для доступа к ее нитям, я решил подготовить новый пример полнофункционального (с "натягом", конечно) сервера без использования каких бы то ни было DLL. Все принципы, описанные в первой части, в примере реализованы полностью. В ходе этой работы замечены некоторые особенности работы компонента TServerSocket, которые Вы заметите, если начнете с ним активно "общаться". Но это уже так, к слову.

Введение
Обеспечение обслуживания клиентов - неотъемлемая функция любого сервера удаленного доступа. Рассмотренный здесь пример не является исключением. Обслуживание клиента состоит из четырех основных стадий: Аутентификация при подключении клиента; Подготовка рабочей среды, т.е. выделение ресурсов на обслуживание; Собственно процесс обслуживания, состоящий в выполнении клиентских запросов и отсылке сообщений о результатах; Отключение клиента и освобождение выделенных ресурсов; В качестве главного "интернетовского компонента" используется TServerSocket, позволяющий осуществлять многоканальное обслуживание.


Аутентификация
В данном случае аутентификация заключается в запросе пароля у пользователя и проверке правильности введенного слова. Процесс аутентификации реализован в виде отдельной нити, внутри которой происходит циклический опрос сокета пользователя на предмет наличия введенных символов. Время опроса и длина вводимой цепочки символов ограничена соответственно 30 секундами и 32 символами. Символы с кодом менее 32 (пробел) считаются признаком конца строки. После того, как пароль будет введен, выполняется проверка введенного слова на наличие в списке допустимых паролей. Если список не содержит такого слова, то пользователю отправляется сообщение о неудачной попытке и связь разрывается, после чего завершается и сама нить. В случае правильного ввода пароля происходит инициализация другой нити, производящей запуск ДОСовской сессии или подключение к уже запущенной сессии.

Выделение ресурсов
Для выделения ресурсов используется нить внутри которой происходит запуск командного интерпретатора. Сразу же после запуска инициализируются нити ввода-вывода данных, а управляющая нить переходит в режим ожидания завершения работы командного интерпретатора. Каждая сессия имеет специальную структуру-описатель, в которой храниться состояние соединения, ссылка на сокет клиента и ссылка на нить, запустившую командный интерпретатор. Указатель на эту структуру храниться в списке пользователей и паролей который, в свою очередь, заполняется информацией при инициализации программы-сервера на основании файла PASSWORDS.LST (см. пример). Сама структура формируется непосредственно перед активизацией сессии.

Обслуживание
Обслуживание клиентов происходит следующим образом: обработчик события, возникающего при поступлении информации от клиента, направляет поступающие данные (символы, строки) на входную очередь нити которая переносит данные через трубку (pipe) в командный интерпретатор или в запущенную из него программу на стандартное устройство ввода (STDIN). Для того, чтобы поступающие данные попадали в требуемый сеанс MS-DOS, сокет-источник данных сравнивается с сокетом в структуре-описателе сесии (см. предыдущий параграф). Когда командный интерпретатор или запущенная из него программа пытается осуществить вывод символов на стандартное устройство вывода (STDOUT) происходит передача символов через трубку в нить, контролирующую выходящий из сеанса MS-DOS поток. Эта нить вызывает Call-back функцию которая на основании данных из структуры-описателя осуществляет передачу информации через требуемый сокет.

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

Пример (Delphi 3) Вы можете скачать (19 K)

Александр Галилов


Собственно сам PGPsdk


28 октября 1997 г. PGP, Inc. объявила о поставке PGPsdk сторонним производителям программного обеспечения. PGPsdk - это средство разработки для программистов на С, позволяющее разработчикам программного обеспечения встраивать в него стойкие криптографические функции. Можно сказать что в PGPsdk реализованы все функции пакета PGP, мало того - версия PGP начиная с 5.0 хранит криптографические функции в динамических библиотеках – dll (о том насколько это не безопасно – вопрос к Крису Касперски, я лишь скажу что насколько я силен в математике).

PGPsdk - это динамическая библиотека, состоящая из трех файлов [табл. 1], поддерживающая базовые алгоритмы криптования (перечислены выше), гибкое управление ключами, сетевой интерфейс и др. (можно использовать одну библиотеку - PGP_sdk.dll, если Вы не будите использовать фирменный интерфейс пользователя от NAI и сетевую библиотеку).

Установка

Скачайте архив с PGPsdk [9], на момент написания статьи доступна версия 1.7.2 (должен заметить что архив занимает 3 с лишним мегабайт), необходимо его разархивировать и из каталога \Libraries\DLL\Release взять следующие файлы - табл. 1

Табл.1
PGP_SDK.dllдля криптования, управление ключами и т.д.
PGPsdkUI.dll (UI= user interface) интерфейсные штучки, если Вам нужно будет только шифровать/расшифровывать, то этот файл необязателен. Но очень полезен для ввода пароля, выбора получателей сообщений, генерации ключей и другое.
PGPsdkNL.dll (NL= network library) сетевая библиотека для работы с сервером ключей или для transport layer security. Ее мы рассматривать не будем, но в ближайшем будущем я попытаюсь ее описать.

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

Переходим к делу.

Для работы система предоставляет ряд низкоуровневых PGP API (Application Programmig Interface) функций. Заголовки (хеадеры, описания) этих функций поставляются вместе с пакетом на Ц и лежат в каталоге Headers. Если Вы как и я пишите на Delphi, можете сами сконвертировать их, а можете взять готовые тут [10]. Это проект по переводу Ц-ных хеадеров на любимый мною язык программирования. Занимается всем этим делом Стивен Хейлер (Steven R. Heller ).


Описатели переведены на Delphi по принципу как это сделано для Ц - разбросаны на кучи модулей (листинг 1). Все названия модулей аналогичны Ц-ным заголовкам, за исключением pgpEncode - переименовано в pgpEncodePas, из-за особенностей объявления в Delphi (нельзя чтоб имя процедуры совпадало с названием модуля).

Листинг 1. Объявление используемых библиотек. uses // PGPsdk pgpEncodePas, pgpOptionList, pgpBase, pgpPubTypes, pgpUtilities, pgpKeys, pgpErrors, // always last pgpSdk;
Единственная трудность, которая возникает на пути включения криптования в Ваше приложение - это использование слишком уж низкоуровневых PGP API функций. Для того что бы сделать какую-нибудь операцию - будь то подсчет публичных ключей в связке или просто зашифровать файл - необходимо создавать контекст, указать где находятся ключи, создать фильтр ключей, подготовить файловые дескрипторы, если с памятью - выделить ее (в случае шифрования-/-расшифрования), затем все это в обратном порядке освободить (если контекст неправильно освобождается - файлы с резервными ключиками не удалятся). И все это при том что в системном каталоге WINDOWS создается файл, в котором содержится информация где находятся файлы с публичными и секретными ключами (о нем будет подробно сказано ниже). Для сравнения работы через PGP API предоставлен листинг2.

Листинг 2. Пример использования PGPsdk через PGP API Var context : pPGPContext; keyFileRef : pPGPKeySet; defaultKeyRing : pPGPKeySet; foundUserKeys : pPGPKeySet; filter : pPGPFilter; countKeys : PGPUInt32; keyFileName : PChar; userID : PChar; inFileRef, outFileRef : pPGPFileSpec; inFileName, outFileName : PChar; Begin // Init от C++ context:=NIL; keyFileName:='pubring.pgp'; userID:=''; inFileName:='myInFile.txt'; outFileName:='myOutFile.txt.asc'; // Begin PGPCheckResult('sdkInit', PGPsdkInit); PGPCheckResult('PGPNewContext', PGPNewContext( kPGPsdkAPIVersion, context )); PGPCheckResult('PGPNewFileSpecFromFullPath', PGPNewFileSpecFromFullPath( context, keyFileName, keyFileRef )); PGPCheckResult('PGPOpenKeyRing', PGPOpenKeyRing( context, kPGPKeyRingOpenFlags_None, keyFileRef, defaultKeyRing )); PGPCheckResult('PGPNewUserIDStringFilter', PGPNewUserIDStringFilter(context, userID, kPGPMatchSubString, filter)); PGPCheckResult('PGPFilterKeySet', PGPFilterKeySet(defaultKeyRing, filter, foundUserKeys)); // Открываем файловые манипуляторы PGPCheckResult('PGPNewFileSpecFromFullPath', PGPNewFileSpecFromFullPath(context, inFileName, inFileRef)); PGPCheckResult('PGPNewFileSpecFromFullPath', PGPNewFileSpecFromFullPath(context, outFileName, outFileRef)); // // А вот здесь уже идет кодирование. // PGPCheckResult('PGPEncode', PGPEncode( context, [ PGPOEncryptToKeySet(context, foundUserKeys), PGPOInputFile(context, inFileRef), PGPOOutputFile(context, outFileRef), PGPOArmorOutput(context, 1), PGPOCommentString(context, PChar('Comments')), PGPOVersionString(context, PChar('Version 5.0 assembly by Evgeny Dadgoff')), PGPOLastOption(context) ] )); // // Освобождаем занимаемые ресурсы и контекст PGP // if (inFileRef<>NIL) then PGPFreeFileSpec(inFileRef); if (outFileRef<>NIL) then PGPFreeFileSpec(outFileRef); if (filter<>NIL) then PGPFreeFilter(filter); if (foundUserKeys<>NIL) then PGPFreeKeySet(foundUserKeys); if (defaultKeyRing<>NIL) then PGPFreeKeySet(defaultKeyRing); if (keyFileRef<>NIL) then PGPFreeKeySet(keyFileRef); if (context<>NIL) then PGPFreeContext(context); PGPsdkCleanup; End;


Здесь реализован пример из [9] со страницы 39. Функция PGPCheckResult позаимствована у Стивена из его примеров - принимает два параметра - строковую и код выполнения функции PGP API, если была ошибка - генерируется исключение и на экран выводится описание ошибки с именем функции (Очень помогает для ловли ошибок, а при вызове dll-библиотеки, тем более написанной на другом языке – помогает избавиться от Access violation).

Листинг 3. Функция PGPCheckResult. procedure PGPCheckResult(const ErrorContext: shortstring; const TheError: PGPError); var s : Array [0..1024] of Char; begin if(TheError<>kPGPError_NoError)then begin PGPGetErrorString(TheError, 1024, s); if(PGPGetErrorString(TheError, 1024, s) = kPGPError_NoError)then raise exception.create(ErrorContext + ' [' + IntToStr(theError)+'] : '+StrPas(s)) else raise exception.create(ErrorContext + ': Error retrieving error description'); end; end;
Там же у Стивена я нашел еще один проект - написанная на Delphi библиотека для VB, проект под названием SimplePGP (SPGP). Дело в том, что VB не может использовать библиотеку PGPsdk из-за ограничения импортирования библиотек dll [9, раздел FAQ]. Сам Стивен предложил мне добавить к проекту еще одну dll, тем самым забыть про PGP API, и использовать облегченную модель вызова функций криптований.

Сам интерфейс к доступу функциям выполнен не плохо, продуманно и вызов их не должен вызвать затруднений у Вас.

Открыв ее я подумал - а не убрать ли мне все эти "stdcall;export;" и просто присоединить библиотеку к ехе-файлу (ну не устраивает меня хитросплетение dll). Сказано сделано.


Сообщение окну


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

procedure WMMyMessage(var Msg : TMessage); message WM_MYMESSAGE;
здесь код сообщения определен, например, так: const WM_MYMESSAGE = WM_USER+XXXX;

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

Кстати, метод Synchronize(Method: TThreadMethod) класса TThread использует для общения с главным потоком программы именно оконное сообщение, посылаемое через SendMessage. При этом заданный в параметрах вызова Synchronize метод класса выполняется в контексте главного потока (main VCL thread), и его код является потокобезопасным (может обращаться к любым объектам VCL). Но (другая сторона медали) пока наш клиент в главном потоке занят фактически выполнением этого метода или другими делами (сообщения ставятся в очередь), сервер не может продолжить работу - он замер на вызове SendMessage. Часто это весьма нежелательно.



Сообщение потоку


Сообщение потоку посылается функцией PostThreadMessage, и для его получения поток не обязан иметь окно, достаточно содержать вызовы функций GetMessage или PeekMessage.