delphi (XE8 x64, 10, 7)

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#13 dyvniy » Пн, 25 апреля 2016, 11:35:29

Автогенерация кода
http://gurin.tomsknet.ru/dccusing.html
Спойлер
Использование компилятора Delphi в прикладных программах
Последнее обновление: 29 декабря 2003
Хотя в названии статьи тема выглядит довольно узко, я хотел бы рассказать не только об использовании dcc32.exe, но и о технологии, которой я дал условное название "многозвенное программирование", хотя вынести это название в заголовок статьи мне показалось неправильным. Какой смысл я вкладываю в термин многозвенное программирование? Начну издалека. Работу над более или менее большими программами можно разделить на два крупных этапа. Первый этап - это собственно разработка, которая включает формулировку технического задания, увязку требований заказчика, проектную фазу, итеративное уточнение структуры проекта, программирование, отладку и тестирование. Первый этап заканчивается выпуском первой версии и началом эксплуатации программы у заказчика (или у массы пользователей, если программа была разработана по собственной инициативе для распространения или продажи). Затем наступает этап сопровождения, который включает устранение обнаруживаемых ошибок, адаптацию к постоянно изменяющимся требованиям заказчика, ввод дополнительных возможностей, которые не были оговорены в исходном задании. Часто бывает так - за время сопровождения программа претерпевает настолько существенные изменения, что сопровождение ее становится делом гораздо более трудоемким и хлопотным, чем разработка.

Если первый этап достаточно хорошо поддерживается разнообразным программистским инструментарием, то второй этап в этом смысле поддержан значительно хуже. Главной целью многозвенного программирования как раз и является поддержка этапа сопровождения. В чем основная идея этого подхода? Рассмотрим простую схему:

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

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

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

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

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

Конечно, здесь есть и другая сторона медали - маркетинговые соображения. Если заказчик получит развиваемый инструмент, то разработчик может остаться в проигрыше. И чем лучше инструмент, тем менее вероятно, что заказчик будет оплачивать сопровождение программы. Здесь уже решать разработчику - или заниматься только сопровождением старых программ или высвобождать время для новых разработок.

Вопросы реализации
Вот собственно то, что я хотел сказать о технологии многозвенного программирования. Теперь практическая часть. Как можно реализовать такую технологию? Один из наиболее используемых вариантов - это встраивание в программу интерпретатора проблемно-ориентированного языка. Встраивание компилятора я встречал значительно реже. В своей программистской практике я использовал оба варианта. Достоинства интерпретатора очевидны, но также очевидны и недостатки. Общий принцип состоит в том, что интерпретатор либо каждый раз интерпретирует исходное представление программы на проблемно-ориентированном языке, либо порождает промежуточный код, исполняемый некоторой виртуальной машиной. Примеры - пакет Microsoft Office с языком VB for Applications, реализация языка Java. Сюда же относятся все скриптовые языки. Если требуется межплатформенная совместимость, то интерпретаторы, вероятно, будут лучшим вариантом. Если же более важны соображения эффективности, то компиляция оказывается более предпочтительным выбором. Общий принцип - берем описание задачи на проблемно-ориентированном языке, генерируем соответствующий код на некотором универсальном языке, компилируем его и выполняем.

В этом разделе статьи я расскажу о втором варианте - использование компилятора, а конкретно, dcc32.exe из поставки Delphi (хотя можно использовать любой другой быстрый и качественный компилятор для любого другого подходящего языка). Использование коммерческого компилятора может вызывать проблемы, связанные с лицензированием. Обойти их можно так: компилятор приобретается заказчиком (в составе самой простой версии Delphi - Desktop, стоимость которой входит в стоимость проекта). Другой вариант - это использование свободно-распространяемого компилятора для любого подходящего языка, например, С, С++, Pascal, Java. Пример хорошего бесплатного компилятора - bcc32.exe (разработка фирмы Borland), используемого в Borland C++ Builder.

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

1. Генерация кода
На основе исходного представления, которое формулирует технолог, нужно сгенерировать код для компиляции. Исходное представление может быть любым, в простейшем случае - это обычный текст. В процессе генерации кода наибольшее внимание нужно уделить диагностике ошибок. То есть, ошибки желательно выявить во время генерации кода и генерировать уже синтаксически правильный код. Для этого можно использовать любые доступные методы, вплоть до синтаксических анализаторов с рекурсивным спуском - такие анализаторы достаточно просты и описаны во многих книгах, например у Бьерна Страуструпа в "Язык программирования C++" (Третье издание). Если есть возможность, то желательно контролировать также семантическую правильность. Далее я буду рассматривать только те моменты, которые являются общими для всех задач без учета их специфики.

Генерировать исходный текст можно любым способом, например, просто посылая строки текста в файл. Более удобный способ, как мне кажется, это направление текста в строко-ориентированный поток. Такой поток предоставляет дополнительное удобство при диагностике ошибок. Библиотека DccUsing содержит два потоковых класса: TFileCompileOut и TStringCompileOut, которые порождаются от TCompileOut. Классы очень просты, их реализацию можно посмотреть в исходном файле библиотеки, поэтому я дам только обзор. Базовый класс имеет методы:

public
procedure IncLevel;
procedure DecLevel;
procedure AddSpace;
procedure AddLine(const aLine: String);
procedure AddLines(const aLines: array of String);
procedure AddFile(const aFileName: String);
procedure AddLineTemplate(const aLine: String;
const aArgs: array of String);
procedure AddLinesTemplate(const aLines, aArgs: array of String);
procedure AddFileTemplate(const aFileName: String;
const aArgs: array of String);
procedure AddPoint(aPoint: Integer);
function FindPoint(aLine: Integer): Integer;
property Level: Integer read FLevel;
property LinesCount: Integer read FLinesCount;
Первые три метода позволяют управлять форматированием кода. Хотя форматирование совсем не обязательно (код никто не читает), но дает удобства при отладке, а, кроме того, мне нравится, когда программа выглядит эстетично. IncLevel увеличивает отступ текста, DecLevel уменьшает, а AddSpace добавляет в поток пустую строку. Два следующих метода добавляют в поток соответственно строку и массив строк, а метод AddFile - весь указанный файл. Свойства позволяют узнать текущий уровень отступа и текущее число строк в потоке. Назначение методов AddPoint и FindPoint будет объяснено в разделе диагностики ошибок.

Методы AddLineTemplate, AddLinesTemplate и AddFileTemplate более сложны, чем предыдущие методы, представляют собой простые макропроцессоры и позволяют параметризовать генерируемый текст. Параметризующие аргументы - это массив строк, которые заменяют метасимволы в исходном тексте шаблона. Метасимволы выглядят так: {{X}}, где Х - это порядковый номер аргумента, начиная от 1. Макроподстановка производится без всякого учета лексики. Поэтому можно параметризовать все что угодно - идентификаторы, строки, комментарии, операторы и т.д. Например, если шаблон текста таков:

const
tFunc: array[0..5] of String = (
'function {{1}}.SortProc{{2}}(const a1, a2: {{2}}): Integer;',
'begin',
' if a2 > a1 then result := 1',
' else if a2 = a1 then result := 0',
' else result := -1;',
'end;'
);
то при использовании

c.AddLinesTemplate(tFunc,['TTestClass1','Integer']);
мы получим такой результат:

function TTestClass1.SortProcInteger(const a1, a2: Integer): Integer;
begin
if a2 > a1 then result := 1
else if a2 = a1 then result := 0
else result := -1;
end;
а при использовании

c.AddLinesTemplate(tFunc,['TTestClass2','String']);
такой:

function TTestClass2.SortProcString(const a1, a2: String): Integer;
begin
if a2 > a1 then result := 1
else if a2 = a1 then result := 0
else result := -1;
end;
Наследуемые классы переопределяют абстрактную процедуру записи строки в поток и имеют специфические методы. Класс TFileCompileOut специализируется на построчном выводе в файл:

public
constructor Create(const aFileName: String);
destructor Destroy; override;
property FileName: String read FFileName;
Конструктор принимает имя файла и открывает файл на чтение, а деструктор закрывает файл.

Класс TStringCompileOut хранит генерируемый текст в памяти:

public
procedure Clear;
procedure SaveToFile(const aFileName: String);
procedure SaveToOut(aOut: TCompileOut);
property Capacity: Integer ...
property Items[aIndex: Integer]: String ... default;
Методы класса позволяют очистить поток, сохранить поток в файле и добавить его к другому потоку. Свойства позволяют изменить резервируемый объем памяти для списка строк и получить доступ на запись и чтение строк по индексу. Общее число строк определяет наследуемое свойство LinesCount. Примеры использования этих классов смотрите в DccExamples.pas.

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

2. Компиляция
После того, как исходный код создан, требуется его откомпилировать. Компилятор dcc32 замечательно подходит для этой роли - он очень быстрый, качественный и объединяет в себе все, что необходимо для построения exe-файлов, dll-библиотек и пакетов. Размер файла dcc32.exe (версия 12.0, из Delphi 5) всего 545 Кб, ранние версии имеют еще меньший размер. К нему нужно добавить только три файла - rlink32.dll, sysinit.dcu и system.dcu (это минимум). Компилятор и указанные файлы можно разместить в подкаталоге прикладной программы, например, bin. Генерировать текст целесообразно в подкаталоге компилятора, например, bin\pas, чтобы использовать короткие пути файлов и не засорять каталог компилятора.

Для вызова dcc32.exe в библиотеке DccUsing определена функция ExecDcc32. Она устанавливает текущий каталог, создает файл для перехвата ошибок компиляции, вызывает компилятор, дожидается завершения компиляции и определяет наличие ошибок.

function ExecDcc32(const aDccDir, aOptions,
aProjectPath, aErrorPath: String;
aCheckPaths: Boolean = False): Boolean;
Функция принимает аргументы: aDccDir - каталог, в котором находится компилятор Dcc32, aOptions - опции компилятора (рекомендации по их использованию смотрите в файле DccUsing.pas), aProjectPath - путь файла проекта (обычно dpr), aErrorPath - путь файла, куда будут направляться сообщения об ошибках компиляции. Необязательный аргумент aCheckPaths позволяет разрешить или запретить контроль наличия каталога и файла dcc32.exe. Функция возвращает True, если компиляция была успешной и False в противном случае. Предупреждения (hints и warnings) ошибками не считаются - их выводом можно управлять с помощью опций -H и -W. Опуская детали, рассмотрим немного подробнее эту функцию:

// сохранение текущего каталога и установка нового
CurDir := GetCurrentDir;
if not SetCurrentDir(DccDir) then
raise Exception.Create(SCantChangeDir + DccDir);
try
hStdOut := INVALID_HANDLE_VALUE;
try
// установки атрибутов безопасности
with SecurAtt do begin
nLength := SizeOf(SecurAtt);
lpSecurityDescriptor := nil;
// разрешить наследование дочернему процессу
bInheritHandle := BOOL(True);
end;
// создание файла, в который будут направляться ошибки
hStdOut := CreateFile(PChar(aErrorPath), GENERIC_WRITE, 0,
@SecurAtt, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if hStdOut = INVALID_HANDLE_VALUE then
raise Exception.Create(SCantCreateFile + aErrorPath);
// заполнение структуры, специфицирующей создание процесса
ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
with StartupInfo do begin
cb := SizeOf(StartupInfo);
// скрывать окно компилятора и наследовать потоки ввода-вывода
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdOutput := hStdOut;
end;
// создать и стартовать процесс компилятора
s := 'dcc32.exe ' + aOptions + ' ' + aProjectPath;
if not CreateProcess('dcc32.exe', PChar(s), @SecurAtt, @SecurAtt,
BOOL(True), 0, nil, PChar(DccDir), StartupInfo, ProcessInfo) then
raise Exception.Create(SCantCreateProcess + 'dcc32.exe');
// ждать завершение компиляции неопределенное время
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
// получить результат компиляции
ResultCode := 0;
GetExitCodeProcess(ProcessInfo.hProcess, ResultCode);
result := ResultCode = 0;
finally
// закрыть файл ошибок
if hStdOut <> INVALID_HANDLE_VALUE then
CloseHandle(hStdOut);
end;
finally
// восстановить прежний каталог по умолчанию
SetCurrentDir(CurDir);
end;
Установка каталога компилятора, как текущего, позволяет не заботиться о мелочах, связанных с назначением путей. Компилятор направляет сообщения об ошибках в стандартный файл вывода. Для его перехвата создаем свой файл, дескриптор которого передаем компилятору. Для того, чтобы процесс компилятора мог наследовать дескриптор открытого файла, устанавливаем его атрибут наследования. При заполнении структуры StartupInfo указываем, что окно компилятора должно быть скрытым и порождаемый процесс должен наследовать стандартные потоки ввода-вывода. Атрибуты безопасности, передаваемые функции создания процесса, нужны для правильной работы в NT, в Windows 95-98 их можно было бы опустить. Функция CreateProcess сохраняет параметры процесса в структуре ProcessInfo - мы используем дескриптор процесса, чтобы передать его функции ожидания системного события - в данном случае, завершения процесса. С помощью GetExitCodeProcess получаем значение, которое возвращает компилятор. Если компиляция была успешной, то возвращается 0, иначе - ненулевое значение. Операции закрытия файла ошибок и восстановления предыдущего каталога произойдут независимо от возможных исключительных ситуаций по ходу функции ExecDcc32.

Компилятору, вместе с исходным файлом (файлами), нужно также передать файл проекта (dpr) и уточнить в опциях, что же будет результатом компиляции. Возможных вариантов много - GUI или консольное приложение, dll, пакет, ActiveX (наверное, есть еще варианты). Выбор вида компиляции связан со спецификой задачи, требованиями пользователя и вкусами разработчика. К этому вопросу я еще раз вернусь в разделе Исполнение кода.

3. Диагностика ошибок
Идеальный вариант - это генерация синтаксически и семантически правильного кода. Но проверка семантики в большинстве случаев вряд ли возможна, поэтому желательно генерировать, по крайней мере, синтаксически правильный код. В этом случае компиляция всегда будет успешной. Если проверка синтаксической корректности затруднительна или невозможна, то приходится полагаться на диагностику, которую сформирует компилятор. Конечно, давать эту диагностику технологу - это самый последний случай, когда уже ничего не остается. Более спокойный вариант - это извлечь из файла ошибок номера ошибочных строк и определить, чему они соответствуют в том описании, который сделал технолог. Для разбора файла ошибок, библиотека DccUsing содержит класс TParseDcc32Errors. Класс весьма прост, поэтому я только обрисую его интерфейс:

TCompileMessageStatus = (cmsNone, cmsHint, cmsWarning,
cmsError, cmsFatal);

public
procedure ParseFile(const aFileName: String);
function MessagesCount: Integer;
function StatusCount(aStatus: TCompileMessageStatus): Integer;
function MessageText(aIndex: Integer): String;
function MessageStatus(aIndex: Integer): TCompileMessageStatus;
function MessageFile(aIndex: Integer): String;
function MessageLine(aIndex: Integer): Integer;
TCompileMessageStatus перечисляет все возможные статусы ошибок. Процедура ParseFile выполняет разбор файла ошибок и сохраняет результат в своем приватном списке. Функция MessagesCount возвращает общее количество сообщений, а StatusCount - количество сообщений с заданным статусом. Оставшиеся 4 функции разбирают строку сообщения компилятора на составляющие - текст сообщения, статус, имя файла, в котором обнаружена ошибка и номер строки.

Вот теперь можно вернуться к необъясненным методам TCompileOut. Метод AddPoint добавляет в поток контрольную точку. Контрольная точка - это просто целое число, которое помечает уникальным номером начало некоторой части генерируемого кода и жестко связывается с номером строки. Контрольная точка может служить, например, индексом в таблице ошибок. Расставив при генерации кода такие точки-метки, мы можем локализовать место ошибки. Для поиска ошибки нужно повторить генерацию кода без вызова компилятора (чтобы опять сформировать выходной поток), а затем, для результирующего выходного потока, вызвать функцию FindPoint, передав ей номер ошибочной строки. Эта функция определит ближайшую точку ошибки. Если генерируется несколько файлов исходных кодов, то выбор ошибочного файла сделать с помощью функции, возвращающей имя файла - MessageFile.

4. Исполнение кода
Как я уже говорил, возможны различные варианты того, в какой вид будет скомпилирована задача, сформулированная технологом. Если технолог передает результаты своей работы конечному пользователю, то удобный вариант - exe-файл. Если технолог решает некоторую задачу и сразу же пользуется результатами решения, то сам факт компиляции должен быть для него полностью прозрачен (или максимально незаметен). Технолог работает в программе, которая сделана разработчиком и, по большому счету, ему совершенно безразлично, каким конкретно способом разработчик предоставляет возможность изменять функциональность программы. Существует несколько технологий построения гибко подгружаемых модулей, и они описаны в литературе. Я остановлюсь только на одной технологии - динамическая загрузка и выгрузка DLL. Если результирующий проект, который нужен технологу, содержит визуальные формы (а их можно генерировать как dfm-файлы), то вероятно, более предпочтительными будут пакеты.

Возможен также вариант, когда исполняемый код делится на две составляющие - исполняемое ядро (exe-файл) и подгружаемый модуль. Ядро создается разработчиком и динамически подключает модули, создаваемые технологом. Достоинство такого подхода в том, что работу с визуальными компонентами можно сосредоточить в ядре, а в DLL формировать только алгоритмическую часть задачи. Другое достоинство такого подхода - технолог может работать в мощной интегрированной среде, а конечному пользователю он передает только ядро и нужный модуль, скрывая от пользователя все технологические детали.

Для работы с DLL, в библиотеку DccUsing добавлен класс TDllWrap - простая оболочка, инкапсулирующая дескриптор загруженной DLL. Основные методы класса:

public
constructor Create(const aDllPath: String);
destructor Destroy; override;
function Execute(const aFunctionName: String;
const aInterface: Pointer): Pointer;
Конструктор Create просто сохраняет путь к файлу DLL и больше ничего не делает, деструктор Destroy выгружает DLL из памяти, если она была загружена. Основную работу делает метод Execute - он вызывает экспортируемую функцию DLL по имени и передает ей указатель на интерфейс вызывающей части. Экспортируемая функция возвращает интерфейс вызываемой части. Более подробно о взаимодействии вызывающей и вызываемой частей поговорим в следующем разделе, а пока рассмотрим реализацию метода Execute.

function TDllWrap.Execute(const aFunctionName: String;
const aInterface: Pointer): Pointer;
var
f: TDllFunction;
begin
if FDllInst = 0 then begin
if not FileExists(FDllPath) then
raise Exception.Create(SFileNotFound + FDllPath);
FDllInst := LoadLibrary(PChar(FDllPath));
if FDllInst = 0 then
raise Exception.Create(SCantLoadDll +
SysErrorMessage(GetLastError));
end;
f := TDllFunction(GetProcAddress(FDllInst,
PChar(aFunctionName)));
if not Assigned(f) then
raise Exception.Create(SCantFindFunction + aFunctionName);
result := f(aInterface);
end;
Вначале метод Execute контролирует - загружена ли DLL? и, если DLL еще не загружена, то она загружается. Если загрузка была успешной, то с помощью функции GetProcAddress получаем адрес экспортируемой функции по ее символическому имени (можно также использовать индекс). Если адрес функции успешно получен, то вызываем ее и передаем ей аргумент - указатель на вызывающий интерфейс. Функция возвращает указатель на вызываемый интерфейс. Из этой реализации видно, что вызывающая часть может обратиться с помощью метода Execute к нескольким различным функциям DLL или многократно к одной и той же функции - DLL будет загружена только один раз.

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

TDllFunction = function(aInterface: Pointer): Pointer; StdCall;
После вызова этой функции Master и Slave части взаимодействуют друг с другом через свои интерфейсы. В качестве интерфейса наиболее удобно использовать чистый абстрактный класс, например:

IMaster = class
public
procedure Method1; virtual; abstract;
.............
end;
Виртуальный абстрактный класс не содержит переменных, а все его методы - виртуальные и абстрактные. Декларация интерфейса включается в обе взаимодействующие части. Для реализации интерфейса создается класс, наследуемый от абстрактного интерфейса и переписывающий все его виртуальные методы. Интерфейсный объект Master-части конструируется и удаляется в основной программе. Интерфейсный объект Slave-части конструируется в экспортируемой функции DLL, а уничтожается в блоке finalization при выгрузке DLL или с помощью другой экспортируемой функции. Например:

uses
UnitIMaster, UnitISlave;

type
TSlaveObject = class(ISlave)
private
FMain: IMain;
public
constructor Create(aMain: IMain);
destructor Destroy; override;
procedure Method1; override;
............
end;

function CreateSlave(aInterface: Pointer): Pointer; stdcall;
function DestroySlave(aInterface: Pointer): Pointer; stdcall;

implementation

var
SlaveObject: TSlaveObject;

// Реализация TSlaveObject
............

function CreateSlave(aInterface: Pointer): Pointer;
begin
SlaveObject := TSlaveObject.Create(IMaster(aInterface));
result := SlaveObject;
end;

function DestroySlave(aInterface: Pointer): Pointer;
begin
SlaveObject.Free;
SlaveObject := nil;
result := nil;
end;

initialization
SlaveObject := nil;
finalization
SlaveObject.Free;

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

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

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

Редактор актеров

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

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

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

Редактор сценария

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

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

Статья написана специально для LinkКоролевства Delphi
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#14 dyvniy » Чт, 28 апреля 2016, 14:06:41

Прогресс на панели задач
http://teran.karelia.pro/articles/item_4405.html
Спойлер
Windows 7 taskbar и Delphi
Опубликовано 14.03.2010 г. 19:26
Для управления состоянием и значением прогресса текущего действия, отображаемого на значке панели задачи приложения в СО windows 7 используются COM интерфейс ITaskBarList3 описанный в ShlObj.pas (Delphi2010). Для использования данного функционала создадим новое приложение с одной формой MainForm, расположим на нем stateSelect (TRadioGroup) и progressBar (TTrackBar). Для удобства опишем тип-запись TTaskState с полями имя-значение:

Код: Выделить всё

type TTaskState = record
        name : string;
        value : byte;
     end;

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

Код: Выделить всё

const
    taskStates : array [0..4] of TTaskState = (
            (name : 'TBPF_NOPROGRESS';    value : TBPF_NOPROGRESS),
            (name : 'TBPF_INDETERMINATE'; value : TBPF_INDETERMINATE),
            (name : 'TBPF_NORMAL';        value : TBPF_NORMAL ),
            (name : 'TBPF_ERROR';         value : TBPF_ERROR),
            (name : 'TBPF_PAUSED';        value : TBPF_PAUSED)
        );

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

Код: Выделить всё

private
  taskBarList : ITaskBarList3;
 
  fState : integer;
  fProgress : integer;
 
  procedure setTaskState(newState:integer);
  procedure setTaskProgress(newValue : integer);

в разделе public объявим соответствующие свойства для управления состоянием и прогрессом.

Код: Выделить всё

property state : integer read fState write setTaskState;
property progress : integer read fProgress write setTaskProgress;

в событии создания формы FormCreate создадим COM объект для работы с с панелью задач Windows (ITaskBarList), проверим поддерживает ли ОС требуемый нам интерфейс ITaskBarList3 и заполним stateSelect значениями из массива taskStates.

Код: Выделить всё

procedure TMainForm.FormCreate(Sender: TObject);
var tbList : ITaskBarList;
    hr : HRESULT;
    state : TTaskState;
begin
    tbList := CreateComObject(CLSID_TaskBarList) as ITaskBarList;
    hr := tbList.QueryInterface(IID_ITaskBarList3,taskBarList);
    if hr <> S_OK then begin
        taskBarList := nil;
           tbList._Release;
    end;
 
    for state in TaskStates do
        stateSelect.Items.AddObject(state.name,TObject(state.value));
end;

В обработчике события OnChange stateSelect будем устанавливать состояние значка панели задач, в зависимости от выбранного пункта. Для этого определим обработчик события и метод записи свойства state следующим образом

Код: Выделить всё

procedure TMainForm.StateSelectClick(Sender: TObject);
begin
    self.state :=  stateSelect.ItemIndex;
end;
 
procedure TMainForm.setTaskState(newState: Integer);
begin
    if assigned(taskBarList) then
        taskBarList.SetProgressState(handle,taskStates[newState].value);
end;

Установим значения min/max progressBar'a в 0/100 и опишем обработчик события изменения его значения, а также метод изменения значения свойства progress


Код: Выделить всё

procedure TMainForm.progressBarChange(Sender: TObject);
begin
    self.progress := progressBar.Position;
end;
 
procedure TMainForm.setTaskProgress(newValue: Integer);
begin
    if assigned(taskBarList) then
        taskBarList.SetProgressValue(handle,newValue,100);
end
;

Ещё кнопки панели задач +иконки
http://www.webdelphi.ru/2010/09/ttaskbar-komponen ... oty-s-panelyu-zadach-windows7/
Спойлер
TTaskBar компонент для работы с панелью задач Windows7
29/09/2010 SeregaAltmer 35 Комментарии Компоненты Delphi

Windows 7 уже достаточно давно и прочно вошла в наши пк. Не смотря на то что до популярности XP ей пока далеко, обходить сторонной ее новые возможности не хочется.

Вот и решил разобраться на досуге с новыми для меня интерфейсами ITaskBarList..ITaskBarList4.

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

Однако работать с этими интерфейсами оказалось довольно просто. Но, как и предполагалось не удобно, особенно если программа будет активно пользоваться таскбаром.

Следствием, я сваял для этих интерфейсов небольшую и удобную «обертку» в виде компонента.

Название долго подбирать не стал — «TTaskBar«, хотя оно может еще поменятся.

На данный момент компонент обеспечивает работу со следующими элементами TaskBar-а:

TaskBar button;
Progress bar;
Overlay icons;
Thumbnail toolbar.

Изображение Taskbar-а на msdn

Все основные свойства компонента, на которых и держится манипуляция TaskBar-ом, вынесены в испектор объектов.

Свойство TaskBarButton, позволяет скрывать и отображать кнопку окна на панели задач.

Свойства ProgressMax, ProgressState и ProgressValue управляют отображением прогресса каких-либо выполняемых приложением задач. Свойство ProgressState может принимать следующие значения:

TBPF_NOPROGRESS — прогресс не отображается;
TBPF_INDETERMINATE — прогресс отображается с неопределенными пределами;
TBPF_NORMAL — отображается зеленая полоса прогресса;
TBPF_ERROR — отображается красная полоса прогресса;
TBPF_PAUSED — отображается желтая полоса прогресса.
Свойство ImageList, позволяет выбрать TImageList использующийся для отображения иконок.

Свойства OIconID и OIconDescription отвечают за отображение OverLay-ной иконки на Taskbar button-е. Свойство OIconID указывает на номер иконки в ImageList-е. Если такого номера не существует, иконка отображаться не будет.

Свойство Buttons.Active доступно только для чтения — показывает будет ли отображаться панель Thumbnail toolbar, если количество видимых кнопок больше нуля то равно True

Свойство Buttons.Count также только для чтения, — показывает количество кнопок со свойством Visivle = True.

Свойства Buttons.TaskButtonX.(Caption, Enabled, IconId, Visible) отвечают за отображение кнопок на Thumbnail toolbar-е.

Для отлавливания нажатий по кнопкам используется событие Buttons.TaskButtonX.OnClick.

Любые манипуляции с вышеописанными свойствами сразу отображаются на TaskBar-е, что гораздо удобнее чем прямое взаимодействие с интерфейсами.

По скольку в компоненте пока реализованы не все возможности таскбара Windows7, работа над компонентом еще не закончена. После доработки выложу компонент на BuBa-Group.ru.
Вложения
TTaskbar-v.1.31.zip
(9.94 КБ) 91 скачивание
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#15 dyvniy » Пт, 13 мая 2016, 12:41:25

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#16 dyvniy » Пт, 13 мая 2016, 14:42:55

dll
Работу с БД имеет смысл писать на делфи)
Потому что она не требует установки,работает в одном файле.
И вообще классная!
http://delphiru.ru/resources/26-creating-and-using-dll-from-delphi
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#17 dyvniy » Вт, 17 мая 2016, 15:49:07

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#18 dyvniy » Ср, 25 мая 2016, 13:34:41

Переопределение операторов в Delphi
http://delphisources.at.ua/publ/raznye/delphi_net ... regruzka_operatorov/57-1-0-315
Символ оператора Сигнатура метода Категория

Код: Выделить всё

Неявное преобразование   Implicit(a : type) : resultType;   Приведение
Явное преобразование   Explicit(a: type) : resultType;   Приведение
-   Negative(a: type) : resultType;   Унарный
+   Positive(a: type): resultType;   Унарный
Inc   Inc(a: type) : resultType;   Унарный
Dec   Dec(a: type): resultType   Унарный
not   LogicalNot(a: type): resultType;   Унарный
not   BitwiseNot(a: type): resultType;   Унарный
Trunc   Trunc(a: type): resultType;   Унарный
Round   Round(a: type): resultType;   Унарный
=   Equal(a: type; b: type) : Boolean;   Сравнение
<>   NotEqual(a: type; b: type): Boolean;   Сравнение
>   GreaterThan(a: type; b: type) Boolean;   Сравнение
>=   GreaterThanOrEqual(a: type; b: type): resultType;   Сравнение
<   LessThan(a: type; b: type): resultType;   Сравнение
<=   LessThanOrEqual(a: type; b: type): resultType;   Сравнение
+   Add(a: type; b: type): resultType;   Бинарный
-   Subtract(a: type; b: type) : resultType;   Бинарный
*   Multiply(a: type; b: type) : resultType;   Бинарный
/   Divide(a: type; b: type) : resultType;   Бинарный
div   IntDivide(a: type; b: type): resultType;   Бинарный
Mod   Modulus(a: type; b: type): resultType;   Бинарный
shl   ShiftLeft(a: type; b: type): resultType;   Бинарный
shr   ShiftRight(a: type; b: type): resultType;   Бинарный
and   LogicalAnd(a: type; b: type): resultType;   Бинарный
Or   LogicalOr(a: type; b: type): resultType;   Бинарный
xor   LogicalXor(a: type; b: type): resultType;   Бинарный
and   BitwiseAnd(a: type; b: type): resultType;   Бинарный
Or   BitwiseOr(a: type; b: type): resultType;   Бинарный
xor   BitwiseXor(a: type; b: type): resultType;   Бинарный
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 115 гостей