Каждое Устройство типа Program содержит простой компилятор для выполнения программ на языке, условно названном DAQ PASCAL. Этот компилятор не ставит целью как-либо конкурировать с другими версиями языков Pascal, он служит для строго определенных целей. Язык DAQ PASCAL – это узкоспециализированная версия компилятора языка Паскаль для решения задач сбора данных в системе CRW-DAQ. Его основным достоинством является простота и скорость создания программ для автоматизации измерений, “прозрачность” и ясность получаемых при этом программ, повышенная степень защищенности DAQ-программ от сбоев, а также наличие в системе CRW-DAQ полной среды разработки этих программ. Встроенный язык позволяет сильно ускорить разработку и отладку программ автоматизации измерений.
Система CRW-DAQ компилирует файл с программой на языке DAQ PASCAL в промежуточный P-код, который можно условно назвать DAQ-программой, который затем выполняется при помощи интерпретатора P-кода, который можно условно назвать виртуальной DAQ-машиной. Интерпретатор P-кода работает намного быстрее, чем интерпретаторы типа Basic, хотя и уступает машинному коду, который генерируют обычные компиляторы. Например, для PII-350 производительность составляет около 1.100.000 простых операторов Daq Pascal в секунду. Здесь следует заметить, что даже самый простой оператор Pascal на самом деле содержит довольно значительное число низкоуровневых команд процессора и поэтому накладные расходы на интерпретацию играют сколько-нибудь заметную роль только для самых элементарных операций. Для подавляющего числа задач производительности кода DAQ PASCAL оказывается достаточно. Немаловажным фактором является то, что программы на встроенном языке Daq Pascal могут быть отредактированы, скомпилированы и сразу же выполнены в рамках системы CRW-DAQ и не требуют отдельного компилятора или среды разработки.
Компилятор DAQ PASCAL не создает каких-либо промежуточных файлов, а сразу генерирует P-код программы в памяти из исходного текста при загрузке конфигурационного файла. Поэтому в качестве “исполняемой” программы хранятся исходные тексты на языке DAQ Pascal, обычно расположенные в каталоге ..\DaqPas.
Файлы с программами на языке DAQ PASCAL должны содержать разделенные на строки символы ASCII, строки не должны быть длиннее 241 символа. Модули в данной версии не поддерживаются. Допускаются включаемые файлы, то есть каждая DAQ-программа может быть разбита на несколько файлов - главный и включаемые. Комментарии помещаются в фигурные скобки {}. Вложение комментариев не допускается. Идентификаторы могут содержать буквы английского алфавита a…z,A…Z, цифры 0…9 и знак подчеркивания _, но могут начинаться только с буквы или символа _. Идентификаторы имеют в данной версии не более 40 значащих символов, причем заглавный/прописной регистр не играет роли. Однако в строковых константах и выражениях регистр символов играет роль.
Компилятор содержит следующие зарезервированные слова и операторы, которые не могут использоваться
как пользовательские идентификаторы:
and, array, begin, case, const, div, do, downto, else, end, file, for, function, goto, if, in, label, mod, nil, not, of, or, packed, procedure, program, record, repeat, set, then, to, type, until, var, while, with, '+', '-', '*', '/', ')', '=', ',', '[', ']', '~', '&', ';', '|'
В приведенном списке слова file, goto, in, label, nil, set не используются и введены только для совместимости со стандартом языка.
{$I FileName}
или
(*$I FileName*)
Для имени файла действуют такие правила:
Такая сложная система поиска включаемых файлов призвана облегчить разработку и сопровождение программ. Предполагается, что в подавляющем большинстве случаев будет указываться простое имя включаемого файла, без пути и расширения (которое, напомню, по умолчанию .INC). Если система найдет файл в текущем каталоге компилируемой программы, она включит его. Если этого файла нет, будет продолжен поиск по каталогам, заданным в [DAQ] SearchPath=..., затем в каталоге системной библиотеки и наконец в пути предыдущего по уровню включаемого файла. Такая система позволяет ссылаться на системные библиотечные файлы по короткому имени, в то же время системные библиотеки легко "перекрыть", если поместить их в путь поиска [DAQ] SearchPath=..., либо в текущий каталог программы.
При включении файла выполняется простая подстановка текста файла в месте включения. За процессом включения можно следить по отладочному выводу в *.LST файл в каталоге [DAQ] TempPath, который также доступен в панели свойств DAQ-устройств. Это позволяет точно выяснить, какой именно файл был включен в программу и увидеть полный текст программы, а также время, затраченное на его компиляцию.
Включение файлов имеет ограничения:
Создана стандартная библиотека включаемых файлов для прикладных программ DAQ PASCAL. Есть описание и шаблон программы, поясняющий, как правильно ее использовать. Предполагается, что постепенно большинство прикладных программ будут создаваться на основе этой библиотеки путем включения в программу нужных библиотек. Это резко (в 2-3 раза) сократит объем прикладного кода, а также понизит вероятность ошибок в прикладных программах за счет отделения стандартного, хорошо провереного кода от прикладного.
Сначала приведем шаблон для копирования:
{
[Compiler.Options] ; Size of table for ...
Compiler.dtabmax = 1024*16 ; -Data segment ( number of virtual machine data items )
Compiler.stabmax = 1024*4 ; -Strings ( number of available string var+const )
Compiler.dtabmin = 1024*1 ; Min stack space at start ( error if free space less )
Compiler.stabmin = 512*1 ; Min string space at start ( error if free space less )
Compiler.slenmax = 1024*1024*2 ; Max string length limit ( error at runtime if more )
[]
}
Здесь указаны наиболее часто употребляемые значения по умолчанию, умноженные на единицу - как заготовка.
Вместо *1 можно вставить свой множитель, можно вещественный.
А теперь обещанное описание.
При разработке компилятора и DAQ-машины было принято решение сделать внутренние таблицы компилятора и виртуальной DAQ-машины статическими, то есть не изменяющимися в процессе работы прикладной программы, однако конфигурируемыми. Это значит, что память для таблиц компилятора и DAQ-машины выделяется однократно, при загрузке DAQ-системы и компиляции и далее не меняется до завершения ее работы. При этом размеры таблиц можно менять через конфигурационные параметры.
Такой подход имеет недостатки и преимущества. Основной недостаток в том, что систему становится несколько сложнее конфигурировать, а в процессе компиляции или выполнения возможно появление сообщений об ошибках переполнения таблиц компилятора или DAQ-машины. При появлении таких ошибок надо увеличить размер соответствующих таблиц компилятора или DAQ-машины.
Основное достоинство статических таблиц - более высокая общая долговременная стабильность и живучесть системы. Систему со статическими таблицами можно сравнить с подводной лодкой, разделенной на множество переборок (в данном случае - параллельно выполняемых прикладных подпрограмм). Как известно, наличие многих переборок - один из основных способов добиться повышенной живучести судна при возникновении пробоин. Пробоина ведет к заполнению одного отсека, а не к затоплению всего судна. Так и тут. Если в одной из прикладных программ возникает ошибка, приводящая к "утечке ресурсов", статические таблицы рано или поздно заполнятся и "утечка ресурсов" прекратится, хотя бы и вероятной ценой отказа данной прикладной программы. Однако система в целом будет продолжать работу. Статические таблицы выполяют роль "переборок", изолирующих "отсеки".
Динамические таблицы, несмотря на все удобства для программиста, более опасны, подобно тому, как судно, не разделенное на переборки, менее живуче по отношению к пробоинам. Если в программе с динамическими таблицами возникает "утечка ресурсов", при длительной работе (а измерительные системы работают месяцами в непрерывном режиме) возможно исчерпание ресурсов всей системы и последующий сбой всей системы в целом.
Учитывая повышенные требования к стабильности измерительных систем, было принято решение использовать статические таблицы, несмотря на усложнение процедуры конфигурирования.
Размеры таблиц задаются в файле с исходным кодом программы. Для задания размеров таблиц в комментарии программы помещается секция [Compiler.Options], обязательно завершающаяся пустой секцией []. Например:
program demo;
{
[Compiler.Options]
Compiler.dtabmax = 4096
[]
}
begin
end.
Если размеры таблиц не найдены в файле с исходным кодом программы, их поиск продолжается
сначала в секции [Compiler.Options] в основном конфигурационном файле загружаемой
DAQ системы, а затем в секции [Compiler.Options] системного файла настроек
Crw32.ini. Это позволяет задавать прикладным программам опции "по умолчанию".
Если же размер таблицы явно нигде не описан, принимается разумное значение по умолчанию,
зашитое в программе (оно указано в следующем описании) и пригодное для большинства простых программ.
Все это сокращает трудозатраты на конфигурирование устройств.
Пример можно посмотреть в конце раздела.
Итак, какие же таблицы имеет компилятор и DAQ-машина? Вот они:
Compiler table overflow - identifiers
При возникновении этой ошибки надо увеличить значение itabmax.
Начиная с версии 17.02.2012 переменная Compiler.itabmax задает лишь начальное значение таблицы itab, которая может расти по мере необходимости. Это решение было принято потому что таблица itab заполняется в момент компиляции и не может расти в процессе работы программы, то есть не может снизить надежность работы программы. Поэтому ошибка переполнения itab не должна более возникать.
Compiler table overflow - procedures
При возникновении этой ошибки надо увеличить значение btabmax.
Начиная с версии 17.02.2012 переменная Compiler.btabmax задает лишь начальное значение таблицы btab, которая может расти по мере необходимости. Это решение было принято потому что таблица btab заполняется в момент компиляции и не может расти в процессе работы программы, то есть не может снизить надежность работы программы. Поэтому ошибка переполнения btab не должна более возникать.
Compiler table overflow - arrays
При возникновении этой ошибки надо увеличить значение atabmax.
Начиная с версии 17.02.2012 переменная Compiler.atabmax задает лишь начальное значение таблицы atab, которая может расти по мере необходимости. Это решение было принято потому что таблица atab заполняется в момент компиляции и не может расти в процессе работы программы, то есть не может снизить надежность работы программы. Поэтому ошибка переполнения atab не должна более возникать.
Compiler table overflow - reals
При возникновении этой ошибки надо увеличить значение rtabmax.
Начиная с версии 17.02.2012 переменная Compiler.rtabmax задает лишь начальное значение таблицы rtab, которая может расти по мере необходимости. Это решение было принято потому что таблица rtab заполняется в момент компиляции и не может расти в процессе работы программы, то есть не может снизить надежность работы программы. Поэтому ошибка переполнения rtab не должна более возникать.
Compiler table overflow - code segment
При возникновении этой ошибки надо увеличить значение ctabmax.
Начиная с версии 17.02.2012 переменная Compiler.ctabmax задает лишь начальное значение таблицы ctab, которая может расти по мере необходимости. Это решение было принято потому что таблица ctab заполняется в момент компиляции и не может расти в процессе работы программы, то есть не может снизить надежность работы программы. Поэтому ошибка переполнения ctab не должна более возникать.
Compiler table overflow - data segment
При возникновении этой ошибки надо увеличить значение dtabmax.
Compiler table overflow - strings
При возникновении этой ошибки надо увеличить значение stabmax.
Следует особенно подчеркнуть, что
Для оценки требуемых размеров таблиц надо учитывать, что каждая переменная, элемент массива или записи любого типа занимает одну ячейку сегмента данных. Сегмент данных можно контролировать при помощи функции stackavail. Кроме того, каждая строковая константа, переменная или элемент массива требует ячейки в таблице строк. Таблицу строк можно контролировать при помощи функции maxavail.
Надо также отметить, что задании размеров таблиц можно использовать арифметические выражения, например,
Compiler.dtabmax = 1024*8+256
Compiler.ctabmax = 8192*1.5
program demo;
{
[Compiler.Options]
Compiler.itabmax = 1024*2 ; Size of table for identifiers ( var, const, procedure, function etc )
Compiler.btabmax = 256*1 ; Size of table for blocks ( number of procedures and functions )
Compiler.atabmax = 64*1 ; Size of table for arrays ( number of arrays and records )
Compiler.rtabmax = 128*1 ; Size of table for real const ( number of real constants )
Compiler.ctabmax = 1024*16 ; Size of table for code segment ( number of virtual machine operators )
Compiler.dtabmax = 1024*16 ; Size of table for data segment ( number of virtual machine data items )
Compiler.stabmax = 1024*4 ; Size of table for strings ( number of available string var+const )
Compiler.dtabmin = 1024*1 ; Min stack space at startup ( generate error if free space less )
Compiler.stabmin = 512*1 ; Min string space at startup ( generate error if free space less )
Compiler.slenmax = 1024*1024*2 ; Max string length ( generate runtime error if string length more )
[]
}
begin
writeln('hello);
end.
Список основных типов DAQ-объектов:
DAQ-объекты идентифицируются именами и ссылками, а также имеют тип. В ряде функций Daq Pascal доступ к объектам осуществляется по имени, однако в большинстве случаев работа с объектами идет при помощи ссылок. Программа получает ссылку объекта по имени объекта и далее работает со ссылкой для быстрого доступа к нему - таков общий принцип работы с объектами в CRW-DAQ. Зачем нужно было делать такое разделение?
Имя объекта - это строка символов, которая служит для идентификации объектов в конфигурационном файле, а также именующая объект с точки зрения пользователя. Имя объекта существует, даже если самого объекта нет (программа не запущена). Поэтому имя объекта - его постоянный и неизменный идентификатор. Однако доступ к объекту по имени приводит к деградации производительности системы, так как доступ по имени связан с поиском объекта в таблице объектов, что занимает много времени. Для ускорения доступа и нужны ссылки.
Ссылка (reference) объекта - это число, которое служит для идентификации объекта с точки зрения исполнительной системы. Смысл ссылки известен только системе. Прикладной программе известно только следующее:
Тип объекта определяет, какие операции к нему применимы. Тип объекта также является постоянным атрибутом объекта, как и его имя. Тип объекта ref можно узнать вызовом refinfo(ref,'Type'). Для каждого типа объекта существует набор функций, которые к нему применимы. Например, теги обслуживаются функциями типа iGetTag,iSetTag и т.д.
Daq Pascal задуман как защищенная от сбоев среда для DAQ-систем. По этой причине в языке нет и не будет указателей, они небезопасны. Идентификация объектов CRW-DAQ ссылками позволяет повысить защиту программ. Система поддерживает таблицу ссылок для всех объектов программы, поэтому всегда можно проверить, существует ли объект с данной ссылкой, какой он имеет тип, имя и т.д. Для указателей такой возможности нет.
Благодаря высокой степени защиты, ошибочное использование ссылки в неверном контексте (скажем, вычисление crvlen(reffind('tag button')) не приведет к сбою системы, будет возвращено разумное значение (в данном примере - ноль). В худшем случае будет прекращено выполнение DAQ программы, но это все же лучше, чем сбой всей системы.
{работа с тегами}
tag:=findtag('button') {получить ссылку тега по имени}
tag:=reffind('tag button') {еще один способ сделать это}
if typetag(tag)=tag_type_int {проверить тип тега}
if IsSameText(refinfo(tag,'type'),'tag') {проверить что это тег}
i:=igettag(tag); {использование тега}
...
{работа с кривыми}
crv:=refai(0); {получить ссылку кривой по входу}
crv:=crvfind('temp1'); {получить ссылку кривой по имени}
crv:=reffind('curve temp1'); {еще один способ сделать это}
if IsSameText(refinfo(crv,'type'),'curve') {проверить что это кривая}
y:=crvx(crv,crvlen(crv)); {использование кривой}
...
{работа с устройствами}
dev:=reffind('device &driver'); {получить ссылку устройства по имени}
if IsSameText(refinfo(dev,'type'),'device') {проверить что это устройство}
r:=devsend(dev,'Hello'+EOL); {послать устройству сообщение}
...
{работа с задачами}
tid:=task_init('CmdLine=cmd.exe') {создать задачу и получить ссылку}
if IsSameText(refinfo(tid,'type'),'task') {проверить что это задача}
b:=task_run(tid) {запустить на исполнение}
b:=task_free(tid) {уничтожить задачу}
...
{работа с таймерами}
tm:=tm_new {создать таймер и получить ссылку}
if IsSameText(refinfo(tid,'type'),'task') {проверить что это таймер}
b:=tm_event(tm) {использовать для генерации событий}
b:=tm_free(tm) {уничтожить таймер}
...
Устройства описываются в конфигурационном файле и создаются системой при загрузке DAQ-конфигурации. Динамического создания устройств в процессе исполнения не предусмотрено.
Можно представить устройство CRW-DAQ как “черный ящик”, имеющий цифровые и аналоговые входы и выходы, который может по какому-то алгоритму преобразовывать данные, взяв нужную информацию из базы данных реального времени и поместив результат обработки в эту же базу данных. База данных реального времени - это список кривых и тегов, в которых хранятся измеряемые величины и вспомогательные переменные.
Кривые обычно поключаются к входам и выходам устройства, так что реально устройство обычно берет данные с входов и помещает результат на выходы. Устройства также могут использовать для хранения и обмена данными общие скалярные переменные - теги. Каждое устройство работает автономно, то есть не зависит явно от других устройств, так как выполняется в своем программном потоке. Входы и выходы устройств – это на самом деле массивы ссылок на кривые, которые, как и теги, хранятся в общей базе данных реального времени, описанной в секциях [DataStorage] и [TagList]. Под чтением входа или записью выхода понимается чтение или запись данных в кривую, на которую ссылается этот выход или выход. Таким образом, все потоки данных централизованы и проходят через общую базу данных, целостность которой поддерживает ядро CRW-DAQ.
Входы и выходы устройств делятся на цифровые и аналоговые. Это деление достаточно условно, так как и цифровые и аналоговые данные хранятся в одинаковых кривых в общей базе данных в виде чисел double (64-битные с плавающей точкой). Разница аналоговых и цифровых входов состоит в методах обработки данных, которую можно производить с данными на этом входе или выходе. Можно считать, что аналоговые входы и выходы ориентированы на гладкие непрерывные данные, в то время как цифровые – на дискретные целочисленные данные. Поэтому параметры, связанные с входами или выходами, различны:
Вызов устройств CRW-DAQ происходит периодически, каждое устройство работает в своем потоке. Частота опроса определяется файлом конфигурации, максимальная частота опроса определяется квантом времени системы и составляет порядка 100 герц под Windows-NT/2000/XP, что бывает достаточно для многих систем реального времени. Работа устройства фактически заключается в том, чтобы брать входные данные из общей базы данных (кривых и тегов), обрабатывать их и помещать результат в эту же базу данных.
Устройства CRW-DAQ не могут непосредственно вызывать друг друга или передавать друг другу какие-либо данные. Передача данных между устройствами возможна только через входы-выходы, то есть через кривые, так как входы и выходы связаны с кривыми, теги и сообщения. При обмене данными между устройствами устройство - “писатель” записывает данные в базу данных, а устройство - “читатель” читает их из базы данных, причем запись и чтение асинхронны, а запись в кривые буферизована через механизм событий (см. описание putev).
Набор свойств или атрибутов устройства CRW-DAQ определяется его типом. Однако есть минимальный набор атрибутов, присущих всем устройствам:
Кривые описываются в конфигурационном файле и создаются системой при загрузке DAQ-конфигурации. Динамического создания кривых в процессе исполнения не предусмотрено.
Система CRW-DAQ поддерживает базу данных (список) тегов. Каждый тег имеет:
Теги описываются в конфигурационном файле и создаются системой при загрузке DAQ-конфигурации, в секции [TagList]. Например:
[TagList]
StartBtn = Integer 0
Voltage = Real 0.0
WinName = String Кривые
Динамическое создание тегов в процессе исполнения также возможно
(createtag/freetag),
хотя надо пользоваться этим очень осторожно.
Обычно DAQ программы в начале выполнения инициализируют ссылку на тег по имени при помощи findtag, а затем используют эту ссылку для доступа к тегу, например, вызовом igettag/isettag. Использование ссылок позволяет сделать обращение к тегам весьма быстрым, в то время как имя тега используется для его идентификации при создании конфигураций и программ.
Тексты создаются динамически вызовом text_new.
См. также refinfo.
Задачи создаются вызовом task_init.
Программные потоки создаются системой для общесистемных целей (например, поток System.Uart обслуживает COM-порты), а также для каждого устройства, канала или порта - для его обслуживания. Как правило, потоки опрашиваются автоматически, по таймеру, с определенным периодом. Для досрочного пробуждения потоков служит функция devsend(ref,''). Досрочное пробуждение может потребоваться, например, для ускорения реакции на какие-то события.
Интерфейс DbApi дает универсальный доступ к распространенным СУБД, таким как Firebird, MySQL, MS SQL и другим - при условии установки необходимых клиентских драйверов.
В системе CRW-DAQ канальный ввод-вывод буферизован. Все функции pipe_xxxx для работы с каналами - неблокирующие, они просто помещают данные в FIFO буфер передатчика или берут их из FIFO буфера приемника. Собственно ввод-вывод осуществляет система в отдельных программных потоках.
Каналы создаются вызовом pipe_init.
Согласно Википедии, Хэш-таблица или хеш-таблица — это структура данных, реализующая интерфейс ассоциативного массива, а именно, она позволяет хранить пары (ключ, значение) = (key,value) и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу.
В библиотеке DaqPascal реализованы хеш-таблицы, которые позволяют хранить в качестве значений записи из трех элементов - вещественного числа data, целого числа link и строкового параметра para, поэтому каждому ключу соответствует запись (key, data, link, para). Кроме того, реализованные хеш-таблицы позволяют независимо менять значения данных (data,link,para).
Хеш-таблицы создаются вызовом hashlist_init.
Таймеры создаются вызовом tm_new.
Окна описываются в конфигурационном файле и создаются системой при загрузке DAQ-конфигурации. Динамического создания окон в процессе исполнения не предусмотрено.
Регулярные выражения создаются вызовом regexp_init.
См. также refinfo.
Конечные Автоматы создаются вызовом fsm_new.
См. также refinfo.
Прежде всего, планирование задач CRW-DAQ основано на механизмах планировщика потоков операционной системы. Поэтому полезно посмотреть файл об оптимизации Windows.
Программы Daq Pascal выполняются каждая в своем отдельном потоке. Потоки имеют различные приоритеты и частоты опроса. На частоту опроса также влияют внешние события. Для планирования опроса устройства CRW-DAQ в конфигурационном файле, в секции описания устройства задаются такие характеристики:
[DeviceList]
MyDevice = device software program
[MyDevice]
InquiryPeriod = aPeriod ; Задает номинальный период опроса
DevicePolling = aDelay, aPriority ; Задает период и приоритет потока опроса
Параметры aPeriod, aDelay, aPriority задают временные характеристики опроса устройства.
Времена указаны в миллисекундах.
Цикл опроса программных устройств CRW-DAQ можно иллюстрировать таким псевдокодом:
procedure DeviceThread.Execute; // Поток опроса устройства (Polling)
begin // Программных устройств (DAQPascal)
Priority:=aPriority; // Задаем приоритет потока
while not Terminated do begin // Выполняем цикл опроса с выходом по внешнему сигналу:
WaitForSingleObject(aWakeEvent,aDelay); // Ждем пробуждающее событие aWakeEvent или событие таймера
if GotEvents(aPeriod) then Polling; // Если есть события для обработки, выполняем опрос Polling
ResetWatchDogTimer; Statistics; // Сбрасываем Watchdog и накапливаем статистику
end;
end;
function GotEvents(aPeriod):Boolean; // Есть какие-нибудь события?
begin // Это может быть:
Result:=InquiryTimer.Event(aPeriod) // Событие таймера опроса с периодом aPeriod
or (ClickButton<>0) // Нажатие клавиатуры, сенсора на мнемосхеме
or (StdIn.Count>0) // Поступление данных в FIFO буфер консоли ввода
or aWakeEvent; // Пробуждение от внешнего события, например, devSend()
end; // Тогда TRUE - сигнал о наличии событий для обработки
Параметр aPeriod задает номинальный период опроса устройства Polling в миллисекундах.
Этот период, например, нужен для устройств, которые не имеют своего собственного программного потока.
Так, он определяет период опроса устройств ADAM, которые обслуживают обмен через шину RS-232/485
и выполняются в потоке, обслуживающем все устройства ADAM.
Следует учитывать, что (в силу свойств функций времени) aPeriod квантуется величиной
около 10 ms для одноядерных или 15 ms для многоядерных систем.
При этом, например, указание aPeriod=1 или aPeriod=10 оказываются эквивалентными.
Это может привести к тому, что опрос будет идти существенно реже ожидаемого.
Поэтому для программных устройств часто используется значение aPeriod=0.
В этом случае выполнение программы будет происходить при каждой активизации потока, связанного с устройством,
т.е. с периодом aDelay.
Если точнее, активизация потока программы будет происходить либо по таймеру с периодом aDelay, либо досрочно,
по событию WakeEvent, нажатию сенсора, поступлению данных в консоль ввода.
Во всяком случае, для устройств, которые работают по внешним событиям, то есть обрабатывают сообщения,
поступающие в консоль ввода, иногда есть смысл устанавливать aPeriod=0,
чтобы событие было обработано как можно скорее.
Обычно нет смысла использовать значения aPeriod > aDelay, так как в этом случае поток будет
активизироваться чаще, чем надо для опроса устройства.
В некоторых случаях, однако, имеет смысл использовать aPeriod > aDelay,
так как при каждой активизации поток, как минимум, сбрасывает сторожевой таймер ResetWatchDogTimer и набирает статистику.
Сторожевой таймер используется для регистрации подвисания потоков.
Поэтому, например, если устройство должно опрашиваться редко, скажем, раз в 10 секунд,
то установка значения aDelay=10000 привела бы к активизации сторожевого таймера
.
В этом случае лучше задать параметры aDelay=1000,aPeriod=10000.
Это значит, раз в секунду поток будет активизироваться и сбрасывать сторожевой таймер,
а раз в 10 секунд будет также выполняться опрос.
Параметр aDelay задает период активизации потока опроса устройства в миллисекундах. Этот параметр применим только для устройств, которые имеют свой собственный программный поток. С целью снижения загрузки процессора рекомендуется регулировать частоту опроса именно этим параметром, установив aPeriod=0. Тогда не будет ненужных для опроса активизаций потока устройства. Надо учитывать, что реальная частота опроса определяется (в обычном состояниии) квантом времени Windows, который составляет для Win-NT/2K/XP/7/8/10 около 10 ms для одноядерной и 15 ms для многоядерной системы. Это значит, при задании 1 или 10 миллисекунд период опроса будет один и тот же - 10 миллисекунд. При этом частота опроса может быть увеличена до 1 kHz путем задания специального таймера. Это можно сделать консольной командой Главной Консоли SetClockRes(p), где p - желаемый период опроса, ms. Эту команду можно вызвать и через функцию Eval:
Eval('@System @Async SetClockRes(1)'); // Установить период опроса Windows 1 ms
Параметр aPriority задает приоритет потока и влияет на гарантию его своевременной активизации. Параметр принимает значения
Сама процедура опроса Polling должна быть быстрой. Длительные процессы надо отслеживать не замкнутым циклом while ... do ..., а путем разбиения обработки на этапы, выполняемые при многократном входе в процедуру в разное время. Это требует введения переменных состояния измеряемого процесса, значение которых между вызовами сохраняется. Вызов WaitForSingleObject(aWakeEvent,aDelay) гарантирует, что в течение указанного времени поток не будет активизирован, если не возникает внешнего события aWakeEvent. Поток будет находиться в спящем состоянии, то есть не будет отнимать процессорное время, освобождая его для других потоков.
Событие aWakeEvent введено для досрочного выполнения программы в случае возникновения внешнего события. Таким событием может быть:
Windows обеспечивает, по крайней мере высокоприоритетным потокам, возобновление работы во время очередного кванта времени. При выборе времен надо учесть, что квант времени Windows в нормальном состоянии составляет около 10 ms для одноядерной и 15 ms для многоядерной системы. Это значит, при задании 1 или 10 миллисекунд период опроса будет один и тот же - 10 миллисекунд. При этом частота опроса может быть увеличена до 1 kHz путем задания специального таймера. Это можно сделать консольной командой Главной Консоли SetClockRes(p), где p - желаемый период опроса, ms.
Не следует делать высокоприоритетными все потоки устройств CRW-DAQ. Отнюдь не все устройства требуют жесткого времени опроса. Лучше дать им низкий приоритет, чтобы облегчить работу тех устройств, где режим времени действительно жесткий.
Система CRW-DAQ имеет консоль монитора ресурсов
,
через которую можно наблюдать за работой всех потоков программы.
[FastDevice] ; Обработка быстрых измерений
InquiryPeriod = 0 ; Опрос при каждой активизации потока, т.к. aPeriod=0
DevicePolling = 1, tpTimeCritical ; Частота активизации потока до 1 кГц
[HandlerDevice] ; Обработка сообщений/событий
InquiryPeriod = 1 ; Опрос при каждой активизации потока, т.к. aPeriod << aDelay
DevicePolling = 1000, tpNormal ; Раз в секунду или по событиям
[BackgroundDevice] ; Обработка данных в фоновом режиме
InquiryPeriod = 1 ; Опрос при каждой активизации потока, т.к. aPeriod << aDelay
DevicePolling = 100, tpIdle ; Низкий приоритет для фоновых операций
[SlowDevice] ; Обработка данных раз в минуту
InquiryPeriod = 60000 ; Задаем длительный период таймера опроса, aPeriod >> aDelay
DevicePolling = 1000, tpNormal ; Но раз в секунду все равно надо сбросить Watchdog
rem Найти hidcon.exe в библиотеке утилит, в списке путей поиска CRW_DAQ_SYS_PATH
for %%i in ( hidcon.exe ) do ( set hidcon=%%~$CRW_DAQ_SYS_PATH:i)
if -%hidcon% == - ( echo Не найден ) else ( echo Найден %hidcon%)
Кроме того, можно использовать эту переменную с помощью paramstr:
hidcon:=ParamStr('FileSearch hidcon.exe CRW_DAQ_SYS_PATH');
CRW_DAQ_SYS_LANG=RUSSIAN,CP1251,CP866,$419,$419,$419,$419
\ \ \ \ \ \ \__ GetUserDefaultLCID - идентификатор локали пользователя
\ \ \ \ \ \______ GetUserDefaultLangID - идентификатор языка пользователя
\ \ \ \ \__________ GetSystemDefaultLCID - идентификатор локали системы
\ \ \ \______________ GetSystemDefaultLangID - идентификатор языка системы
\ \ \__________________ OEM CodePage - кодовая страница консоли DOS
\ \________________________ ANSI CodePage - кодовая страница программ ANSI
\_______________________________ Язык интерфейса CRW-DAQ: (RUSSIAN,ENGLISH)
Позволяет дочерним программам узнать язык интерфейса процесса CRW32.EXE.
rem Найти hidcon.exe в списке путей поиска CRW_DAQ_CONFIG_PATH
for %%i in ( hidcon.exe ) do ( set hidcon=%%~$CRW_DAQ_CONFIG_PATH:i)
if "%hidcon%" == "" ( echo Не найден ) else ( echo Найден %hidcon%)
Кроме того, можно использовать эту переменную с помощью paramstr:
hidcon:=ParamStr('FileSearch hidcon.exe CRW_DAQ_CONFIG_PATH');
URL кодировка (от Universal Resource Location) была первоначально создана для кодирования имен Web. Однако ее можно успешно использовать для других случаев, когда надо передавать произвольные двоичные данные по текстовому каналу связи.
URL кодировка применяется в случаях когда
URL кодировка очень проста. Для кодирования текста в URL кодировку:
текст:
Строка 1
Строка 2+
Строка 3%
может быть записан строками
Строка 1%0d%0aСтрока 2%2b%0d%0aСтрока 3%25
или
Строка+1%0d%0aСтрока+2%2b%0d%0aСтрока+3%%
Заметьте, что обязательно надо заменять CR на %0d, LF на %0a, символ % на %% или %25, символ + на %2b, а пробел можно заменить на + или %20. Остальные символы нет необходимости менять, они будут прочитаны "как есть".
Замена пробелов на + или %20, табуляций на %09, хотя она необязательна, имеет то достоинство, что при этом строку, содержащую пробелы и табуляции, можно представить одним непрерывным словом, что иногда существенно (например, если слово служит элементом списка). Это позволяет передавать в утилиты, содержащие список параметров - слов, произвольные строки, не нарушая при этом логики разбора слов. По этой причине URL кодировка часто используется для задания списка параметров в файлах конфигураций.
Кодировку текста в URL кодировку можно сделать вызовом URL_Packed(Text) или URL_Encode(Text).
Исходный текст, заданный URL кодировкой можно получить вызовом URL_Decode(Text).
Декодер URL_Decode(Text) заменяет символ + на пробел, строку %% на символ %, строку %hh на символ chr(hh), где hh - шестнадцатеричный код символа. Никакие другие символы строки Text декодер не меняет. Поэтому URL-кодированные строки удобно применять в случае, когда надо передавать в процедуры\функции список строковых параметров, разделенных пробелами, но при этом сами параметры могут содержать пробелы. Если параметр не содержат пробелов и символов +,% - можно передавать его "как есть", это наиболее частый случай. Если указанные символы есть, применяется функция URL_Packed(Text) или URL_Encode(Text). Таким образом, в большинстве случаев можно передавать URL-строки без всякого изменения.
Надо иметь в виду, что изначально строки не инициализированы и не могут быть использованы для получения значения, пока им не будет что-нибудь присвоено. При попытке использования неинициализированной строки генерируется ошибка и выполнение Daq-Pascal программы завершается. Чтобы избежать этого, надо придерживаться таких правил:
Поскольку строки динамические, они занимают динамическую память. Чтобы избежать неоправданного использования памяти или утечки памяти, необходимо освобождать все строки после использования путем присвоения им пустой строки. Если этого не делать, возможно переполнение таблицы строк, после чего генерируется ошибка и выполнение Daq-Pascal программы завершается. Чтобы избежать этого, надо придерживаться таких правил:
program example;
var s1:string;
procedure DoSomething;
var s2:string;
begin
s2:=''; {initialize s2}
writeln(s2); {use s2}
s2:=''; {finalize s2}
end;
begin
if runcount=1 then begin
s1:=''; {initialize s1}
end else
if isinf(runcount) then begin
s1:=''; {finalize s1}
end else begin
writeln(s1); {use s1}
end;
end.
var c:char; b:boolean; i,j:integer; x:real; s:string;
begin
for i:=48 to 57 do begin c:=chr(i); writeln(i,'->',c); end;
i:=1; j:=2; b:=(i=j); {b=false}
i:=1; j:=1; b:=(i=j); {b=true}
for i:=1 to 100 do writeln(i);
x:=exp(pi/2); writeln(x);
s:='';
readln(s);
writeln('Read:',s);
s:='';
end;
type point=record x:real; y:real; end;
var p:point;
begin
p.x:=1;
p.y:=2;
writeln(p.x,p.y);
end;
type
point=record x:real; y:real; end;
points=array[0..9] of point;
var i:integer; p:points;
begin
for i:=0 to 9 do begin
p[i].x:=1;
p[i].y:=2;
writeln(p[i].x,p[i].y);
end;
end;
x:=(a+b)*c/d-e;
s:='one'+' '+'two';
z[i,j]:=x;
if( (i>1) and (i<10) ) then ...
Операторы begin .. end - операторные скобки, аналогично скобкам {} языка C. Они нужны для того, чтобы:
Операторные скобки begin и end, как и обычные скобки, всегда идут в паре. Нескомпенсированность операторных скобок (разное число begin и end) является ошибкой.
procedure max(a,b:integer; var c:integer);
begin
if a>b then begin
c:=a;
writeln(c);
end;
end;
program name; - Оператор объявления программы с именем name. Имя программы доступно в тексте программы через вызов функции progname.
В языке DAQ PASCAL программы имеют жесткую структуру:
program name;
const ... объявление констант ...
type ... объявление констант ...
var ... объявление переменных ...
procedure ... объявление процедур и функций ...
function ... объявление процедур и функций ...
begin
... тело программы ...
end.
Описания const, type, var могут отсутствовать,
однако их порядок фиксирован.
Описания процедур или функций могут чередоваться, но должны идти после констант, типов и переменных.
Особенностью DAQ программ является то, что
program test;
const
msg = 'c = ';
type
abc : record a,b,c:integer;
var
x : abc;
{Пример процедуры}
procedure pmax(a,b:integer; var c:integer);
begin
if a>b then c:=a else c:=b;
writeln(msg,c);
end;
{Пример рекурсивного вызова}
function gamma(n:integer):integer;
begin
if n=1 then gamma:=1 else gamma:=n*gamma(n-1);
end;
begin
x.a:=1;
x.b:=2;
pmax(x.a,x.b,x.c);
writeln(gamma(10));
end.
procedure p(arg); - Оператор объявления процедуры с именем p и списком параметров arg.
function f(arg):t; - Оператор объявления функции типа t с именем f и списком параметров arg. Функция обязательно должна содержать присвоение f:=...; иначе результат функции будет неопределенным.
Список параметров arg = [var] x1:t1; [var] x2:t2; ... содержит имена параметров x1,x2,... типа t1,t2,..., разделенные точкой с запятой. Параметры могут передаваться по значению и по ссылке. Параметры, передаваемые по значению, не содержат объявление var. При передаче этих параметров в стек записывается текущее значение параметра в момент вызова. Вся работа идет с этой локальной копией данных, так что значение внешней переменной не изменяется. Параметры, передаваемые по ссылке, содержат объявление var. При передаче этих параметров в стек записывается адрес переменной, где хранится параметр. Вся работа идет с внешней переменной, на которую ссылается адрес, поэтому значение переменной после вызова в общем случае изменяется.
Допустим рекурсивный вызов процедур и функций. Уровень рекурсии ограничен размером стека программы.
В языке DAQ PASCAL подпрограммы имеют жесткую структуру:
procedure p(x:real; var s:string);
const ... объявление констант ...
type ... объявление констант ...
var ... объявление переменных ...
procedure ... объявление процедур и функций ...
function ... объявление процедур и функций ...
begin
... тело подпрограммы ...
end;
Описания const, type, var могут отсутствовать,
однако их порядок фиксирован.
Описания "вложенных" процедур или функций могут чередоваться, но должны идти после констант, типов и переменных.
type t:expr; - Оператор объявления нового типа t, заданного выражением expr. Выражение содержит базовые типы char, integer, real, string, а также конструкции array, record, позволяющие создавать новые типы данных.
var x1:t1; x2:t2; ... - Оператор объявления переменных x1,x2,... типа t1,t2,.... Объявления разделяются точкой с запятой, возможно перечисление нескольких переменных одного типа, то есть x1,x2:t1. Возможно также неявное определение типа переменной, например:
var x,y:array[0..10] of integer;
const c1 = v1; c2 = v2; ... - Оператор объявления констант c1,c2,... со значением v1,v2,....
Тип константы определяется автоматически, по значению.
Объявления разделяются точкой с запятой.
Целочисленные константы могут задаваться
В языке DAQ PASCAL объявления констант, типов и переменных имеют жесткую структуру, см. program.
В языке DAQ PASCAL есть ряд предопределенных констант:
Имя Тип Значение Комментарий
===========================================================
true boolean истина и так ясно
false boolean ложь и так ясно
sizeofchar integer (1) размер char
sizeofreal integer (8) размер real
sizeofboolean integer (1) размер boolean
sizeofinteger integer (4) размер integer
daq_cmd_init integer (1) см. daqdllinit
daq_cmd_free integer (2) см. daqdllinit
daq_cmd_poll integer (3) см. daqdllinit
TAG_REF_MIN integer (runtime) нижний диапазон ссылок тегов
TAG_REF_MAX integer (runtime) верхний диапазон ссылок тегов
TASK_REF_MIN integer (runtime) нижний диапазон ссылок task
TASK_REF_MAX integer (runtime) верхний диапазон ссылок task
rfReplaceAll integer (1) см. StringReplace
rfIgnoreCase integer (2) см. StringReplace
QuoteMark char (") двойные кавычки, определенные в Unicode как "Quotation Mark"
Apostrophe char (') одинарные кавычки, определенныые в Unicode как "Apostrophe"
IsUnix boolean (runtime) работает OS Unix? (для поддержки многоплатформенности)
IsLinux boolean (runtime) работает OS Linux? (для поддержки многоплатформенности)
IsWindows boolean (runtime) работает OS Windows? (для поддержки многоплатформенности)
CpuBitness integer (runtime) разрядность CPU, 32/64 (для поддержки многоплатформенности)
SizeOfPointer integer (runtime) размер указателя, байт (для поддержки многоплатформенности)
defaultsystemcodepage integer (runtime) кодовая страница системы (для поддержки многоплатформенности)
CP_UTF8 integer (65001) кодовая страница UTF8
CP_NONE integer (65535) кодовая страница отсутствует
1251 integer (1251) кодовая страница windows-1251
866 integer (866) кодовая страница dos-866
Примечание:
1) Константы, помеченные как runtime, определяются на этапе выполнения.
2) Ряд других констант (например, EOL), реализованы в виде функций.
3) Ряд системных констант и параметров доступен через ParamStr.
4) Ряд констант введен для поддержки многоплатформенности. Например, IsUnix,IsWindows.
5) SizeOfPointer равно 4/8 на 32/64 битных платформах.
if c then a else b; - Оператор ветвления или условный оператор. Если логическое выражение c истинно, выполняется оператор a, иначе выполняется оператор b. В качестве выполняемых операторов можно использовать пустой оператор (нет оператора) или составной оператор begin .. end. Надо иметь в виду, что при вычислении логического выражения вычисления всегда идут полностью, например при выполнении if f(x) or g(x) then ... вызов g(x) произойдет, даже если f(x)=true, хотя это, строго говоря избыточно, так как значение выражения уже определено.
case n of n1:a1; n2:a2; .. end; - Оператор альтернативы (выбора) из списка значений. Выполняет операторы a1,a2,.., если значение целочисленного выражения n равно n1,n2,.. В качестве значения n1,n2,.. допустимо использовать перечисление (через запятую) вида n1,n2,n3. В Daq Pascal оператор case имеет особенности:
if a>b then x:=a else x:=b;
case i of
0,1,2,3,4,5,6,7,8,9: Make1;
10,11,12,13,14,15,16,17,18,19: Make2;
20: Make3;
end;
while с do p; - Оператор цикла с пред-условием. Выполняет оператор p, пока истинно условие c. Обычно оператор p содержит вычисления, влияющие на значение условия c, иначе выхода из цикла не произойдет. Заметим, что оператор p не будет выполнен ни разу, если условие c изначально не выполнено.
repeat p until c; - Оператор цикла с пост-условием. Выполняет оператор p, затем проверяет условие c. Если оно истинно, прерывает цикл, если ложно - продолжает итерации. Обычно оператор p содержит вычисления, влияющие на значение условия c, иначе выхода из цикла не произойдет. Заметим, что оператор p будет выполнен хотя бы один раз, даже если условие c изначально выполнено.
for n:=n1 to n2 do p; - Оператор восходящего цикла. Присваивает переменной цикла n значение n1, затем, пока n<=n2, выполняет в цикле оператор p, увеличивая счетчик цикла в конце каждой итерации на 1. Это аналог выражения
n:=n1; while n<=n2 do begin p; n:=n+1; end;
В случае n1>n2 оператор p не будет выполняться совсем.
for n:=n2 downto n1 do p; - Оператор нисходящего цикла. Присваивает переменной цикла n значение n2, затем, пока n>=n1, выполняет в цикле оператор p, меньшая счетчик цикла в конце каждой итерации на 1. Это аналог выражения
n:=n2; while n>=n1 do begin p; n:=n-1; end;
В случае n1>n2 оператор p не будет выполняться совсем.
for i:=1 to 10 do writeln(i);
for i:=10 downto 1 do writeln(i);
i:=1; while i<=10 do begin writeln(i); i:=i+1; end;
i:=1; repeat writeln(i); i:=i+1; until i=10;
var x,y:real;
begin
x:=random(-1,1);
y:=abs(x);
writeln('x=',x,' y=',y);
end;
x:=sqr(2); {x=4}
y:=sqrt(100); {y=10}
var i:integer;
begin
for i:=1 to 10 do writeln('i=',i,' odd=',odd(i));
end;
var i:integer;
begin
for i:=48 to 57 do writeln('i=',i,' chr=',chr(i));
end;
writeln(ord('a'));
if UpCase(StrFetch(s,1))='A' then writeln('Start from A');
if LoCase(StrFetch(s,1))='b' then writeln('Start from b');
writeln(succ('a')); {return 'b'}
writeln(pred('b')); {return 'a'}
i:=round(1.2); {i=1}
j:=round(1.6); {j=2}
i:=trunc(1.2); {i=1}
j:=trunc(1.6); {j=1}
x:=int(1.2); {x=1.0}
y:=int(1.6); {y=1.0}
x:=frac(1.2); {x=0.2}
y:=frac(1.6); {y=0.6}
x:=floor(1.2); {x=1.0}
y:=floor(1.6); {y=1.0}
x:=ceil(1.2); {x=2.0}
y:=ceil(1.6); {y=2.0}
x:=rad(30); {x=pi/6}
x:=deg(pi); {x=180}
x:=sin(pi/6); {x=0.5}
x:=cos(pi/3); {x=0.5}
x:=tan(pi/4); {x=1.0}
x:=asin(0.5); {x=pi/6}
x:=acos(0.5); {x=pi/3}
x:=atan(1.0); {x=pi/4}
x:=sinh(1);
x:=cosh(1);
x:=tanh(1);
v:=exp(1); {v=e}
v:=power(10,2); {v=100}
v:=ln(sqr(e)); {v=2}
v:=log(10,100); {v=2}
v:=ln(-1); {v=NAN}
v:=ln(0); {v=INF}
x:=_nan; if isnan(x) then writeln('x=NAN'); {yes}
x:=ln(-1); if isnan(x) then writeln('x=NAN'); {yes}
x:=ln(+1); if isnan(x) then writeln('x=NAN'); {no}
x:=_minusinf; if isinf(x) then writeln('x=INF'); {yes}
x:=_plusinf; if isinf(x) then writeln('x=INF'); {yes}
x:=1/0; if isinf(x) then writeln('x=INF'); {yes}
x:=1/1; if isinf(x) then writeln('x=INF'); {no}
if cref=_nil
then writeln('Curve is not initialized')
else writeln('Curve length = ',crvlen(cref));
x:=sin(pi/3);
x:=macheps;
i:=maxint;
x:=gamma(4); {x=3!=6}
x:=sign(+pi); {x=+1}
x:=sign(0); {x=0}
x:=sign(-pi); {x=-1}
x:=hypot(3,4); {x=5}
x:=random(-1,1);
x:=min(3,4); {x=3}
x:=max(3,4); {x=4}
if eq(3,4) then ... {false}
if ne(3,4) then ... {true}
if lt(3,4) then ... {true}
if le(3,4) then ... {true}
if gt(3,4) then ... {false}
if ge(3,4) then ... {false}
x:=inot(x); {побитно инвертировать}
i:=iand(j,15); {выделить 4 младших бита}
x:=rshift(x,+2); {сдвинуть на 2 разряда влево (умножить на 4)}
x:=rshift(x,-2); {сдвинуть на 2 разряда вправо (поделить на 4)}
if isbit(x,0) then {проверить наличие 0 бита}
if hasflags(mode,3) then {проверить наличие битов 0 или 1, входящих в маску 3}
procedure TestHtonl;
var hl,nl,hs,ns,ne:Integer;
begin
ne:=0; writeln('Test htonl/ntohl:');
hl:=Val('$D0C0B0A0'); nl:=htonl(hl); writeln(' $',hexl(hl):8,' $',hexl(nl):8,' - ',(hl=ntohl(nl)):1); ne:=ne+ord(hl<>ntohl(nl));
nl:=Val('$D0C0B0A0'); hl:=ntohl(nl); writeln(' $',hexl(hl):8,' $',hexl(nl):8,' - ',(nl=htonl(hl)):1); ne:=ne+ord(nl<>htonl(hl));
hs:=Val('$DCBA'); ns:=htons(hs); writeln(' $',hexw(hs):8,' $',hexw(ns):8,' - ',(hs=ntohs(ns)):1); ne:=ne+ord(hs<>ntohs(ns));
ns:=Val('$DCBA'); hs:=ntohs(ns); writeln(' $',hexw(hs):8,' $',hexw(ns):8,' - ',(ns=htons(hs)):1); ne:=ne+ord(ns<>htons(hs));
writeln(ne:1,' error(s) found');
end;
Вывод процедуры:
Test htonl/ntohl:
$D0C0B0A0 $A0B0C0D0 - true
$A0B0C0D0 $D0C0B0A0 - true
$ DCBA $ BADC - true
$ BADC $ DCBA - true
0 error(s) found
Все скалярные переменные в Daq Pascal, независимо от типа, занимают одну ячейку в стеке данных. Функция StackAvail возвращает число свободных ячеек стека данных, то есть элементов данных которые может создать программа динамически. Глобальные переменные, то есть переменные, объявленные в начале программы, получают место в стеке еще при компиляции, поэтому функция возвращает свободное место в сегменте данных, исключая глобальные переменные. Стек используется для хранения временных данных, локальных переменных при вызове функций и т.д. Для нормального функционирования программы в стеке должно быть достаточно свободного пространства. Если его мало, надо увеличить сегмент данных, как описано в разделе опции компилятора.
Все строки в Daq Pascal динамические, память под них выделяется по мере необходимости. Все строки хранятся в таблице строк, которую имеет каждая программа DAQ Pascal. Функция MaxAvail возвращает число свободных ячеек таблицы строк, то есть число строк, которые может создать программа. Для нормального функционирования программы в таблице строк должно быть достаточно свободного пространства. Если его мало, надо увеличить таблицу строк, как описано в разделе опции компилятора.
Как описано в разделе string, все используемые строки должны инициализироваться в начале программы и освобождаться в конце программы путем присвоения пустой строки. Неправильное использование строк будет приводить к ошибкам. Если попытаться прочитать неинициализированную строку, DAQ программа аварийно завершит работу. Если не освобождать строки, таблица строк будет постепенно заполняться, пока не произойдет ее переполнение. После переполнения DAQ программа также аварийно завершит работу.
Функция maxavail позволяет контролировать корректность работы со строками. При корректной работе со строками значение функции при старте программы (runcount=1) и при ее завершении (isinf(runcount)) совпадают. Несовпадение говорит о том, что не все строки освобождены и происходит "утечка ресурсов".
{Пример программы с контролем утечки строковых ресурсов}
program test;
var s,w:string; errors,errorcode,fixavail:integer; b,Ok:boolean;
procedure ClearStrings;
begin
s:='';
w:='';
if runcount=1 then fixavail:=maxavail;
if isinf(runcount) then
if maxavail<>fixavail then begin
writeln('String resource leak found!');
b:=fixerror(errorcode);
end;
end;
begin
if runcount=1 then begin
errors:=0;
errorcode:=registererr(devname);
ClearStrings;
Ok:=(errors=0);
if errors<>0 then fixerror(errorcode);
end else
if isinf(runcount) then begin
ClearStrings;
end else
if Ok then begin
if not eof then begin
readln(s);
s1:=extractword(1,s);
writeln(s1);
end;
end;
end;
{Подсчет числа пробелов в строке}
function numspaces(s:string):integer;
var i,n:integer;
begin
n:=0;
for i:=1 to length(s) do if s[i]=' ' then n:=n+1;
numspaces:=n;
end;
{ Обработка команд, начинающихся с @ }
procedure HandleCommand(var arg:string);
begin
if strFetch(arg,1)='@' then begin // А раньше для безопасной проверки надо было еще проверять длину строки
// Обработка команды
end;
end;
s:='1234567890';
t:=copy(s,1,length(s)); {t='1234567890'}
t:=copy(s,1,3); {t='123'}
t:=copy(s,4,255); {t='4567890'}
t:=copy(s,3,3); {t='345'}
t:=copy(s,33,10); {t=''}
i:=0;
repeat
i:=PosEx(Sub,Str,i+1);
if (i>0) then writeln('Found '+Sub+' at position '+Str(i));
until (i<=0);
В этом цикле поиск на каждой итерации начинается со следующей позиции после ранее найденной.
s:='1234567890';
p:=pos('34',s); {p=3}
p:=pos('0',s); {p=10}
p:=pos('a',s); {p=0}
p:=posex('a','abcdabcd',1); {p=1}
p:=posex('a','abcdabcd',2); {p=5}
procedure ProcessLines(Lines:String);
var i,p,n:Integer; Line:String;
begin
i:=0; p:=1; n:=0;
repeat
i:=PosEx(EOL,Lines,i+1);
if (i>0) then begin
Line:=Copy(Lines,p,i-p);
n:=n+1; Writeln(StrFmt('Line[%d] = ',n)+Line);
p:=i+Length(EOL);
end else begin
if (p<=Length(Lines)) then begin
Line:=Copy(Lines,p,Length(Lines)-p+1);
n:=n+1; Writeln(StrFmt('Line[%d] = ',n)+Line);
end;
end;
until (i<=0);
end;
s:=1; // Это начало буфера
p:=poseol(buf,s,0); // Ищем конец строки
if (p>0) then begin // Найден маркер EOL
line:=copy(buf,s,p-s); // Выделяем эту строку
s:=poseol(buf,p,1); // Идем в начало следующей строки
end else line:=buf; // Берем весь текст, если нет eol
... и так далее ...
Шаблон для построения цикла обработки буфера по строкам приведен ниже.
//
// Procedure to handle line number n.
//
procedure HandleLine(n:Integer; line:String);
begin
writeln('Line ',n:1,' = ',line);
end;
//
// Handle lines in buffer, return num.lines.
//
function HandleLines(var buf:String):Integer;
var p,s,n,l:Integer;
begin
p:=1;s:=1;n:=0;l:=Length(buf);
while (p>=1) and (p<=l) do begin
p:=PosEol(buf,s,0); // Find EOL
if (p>0) then begin // EOL found
HandleLine(n,Copy(buf,s,p-s));
s:=PosEol(buf,p,1); // Skip EOL
n:=n+1; // inc line counter
end else // handle last line
if (s<=l) then begin // Has tail
HandleLine(n,Copy(buf,s,l-s+1));
n:=n+1; // inc line counter
end;
end;
HandleLines:=n; // return number of lines
end;
%d = Десятичное целое число ( decimal ) например: %10d
%u = Десятичное число без знака ( unsigned ) например: %12u
%x = Шестнадцатеричное целое ( hexadecimal ) например: %8.8x
%m = Денежный формат ( money ) например: %10m
%f = Фиксированный вещественный ( fixed ) например: %11.5f
%e = Научный (с экспонентой) ( exponent ) например: %11.5e
%g = Автоматический выбор ( general ) например: %11.5g
%s = Строка ( string ) например: %-20.15s
Общий вид описателя формата следующий:
s:=str(1/3); {s='0.333333333333333'}
s:=str(0.3); {s='0.3' }
s:=strfix(1/3,11,4); {s=' 0.3333' }
s:=strfmt('%11.4f',1/3); {s=' 0.3333' }
s:=strfmt('pi=%7.5f',pi); {s='pi=3.14159' }
{
Get string like 2006.09.21-00:12:30
}
function GetDateTime(ms:Real):String;
begin
GetDateTime:=StrFmt('%4.4d.',ms2year(ms))
+StrFmt('%2.2d.',ms2month(ms))
+StrFmt('%2.2d-',ms2day(ms))
+StrFmt('%2.2d:',ms2hour(ms))
+StrFmt('%2.2d:',ms2min(ms))
+StrFmt('%2.2d',ms2sec(ms));
end;
{
Процедура для тестирования StrFmt().
}
procedure testStrFmt;
begin
writeln('testStrFmt');
// StrFmt(string, integer)
writeln(StrFmt('maxint = %14.8x',maxint));
// StrFmt(string temporary, integer)
writeln(StrFmt('maxint '+'= %14.12d',maxint));
// StrFmt(string, real)
writeln(StrFmt('pi = %11.5e',pi));
writeln(StrFmt('pi = %11.5f',pi));
// StrFmt(string temporary, real)
writeln(StrFmt('pi '+'= %11.5g',pi));
// StrFmt(string,string)
writeln(StrFmt('str = %20.14s','ComputerName'));
// StrFmt(string,string temporary)
writeln(StrFmt('str = %20.14s',ParamStr('ComputerName')));
// StrFmt(string temporary,string)
writeln(StrFmt('str = %20.14s','Host'+'Name'));
// StrFmt(string temporary,string temporary)
writeln(StrFmt('str '+'= %20.14s',ParamStr('HostName')));
// StrFmt(string, char)
writeln(StrFmt('char = %14.8s','a'));
// StrFmt(string temporary, integer)
writeln(StrFmt('char '+'= %s','b'));
//writeln(StrFmt('%q',pi)); // bug
//writeln(StrFmt('%g',pi));
end;
yyyy год полный как 2022
yy год короткий как 22
m месяц 1..12 как 4
mm месяц 01..12 как 04
mmm месяц (имя) как апр
mmmm месяц (имя) как апрель
d день 1..31 как 1
dd день 01..31 как 01
ddd день недели как пт
dddd день недели как пятница
h часы 0..23 как 3
hh часы 00..23 как 03
n минуты 0..59 как 3
nn минуты 00..59 как 03
s секунды 0..59 как 7
ss секунды 00..59 как 07
z миллисекунды 0..999 как 57
zzz миллисекунды 000..999 как 057
Например: s:=StrTimeFmt('yyyy-mm-dd hh:nn:ss.zzz',msecnow);
s:=StrTimeFmt('yyyy.mm.dd-hh:nn:ss.zzz',msecnow); // s = 2022.12.25-12:15:30.123
s:=hexb(15); {s='0F'}
s:=hexb(15*16); {s='F0'}
s:=hexw(15*256); {s='0F00'}
s:=hexl(15*65536.0); {s='000F0000'}
s:=inttostrbase(15,16,2); {s='0F'}
s:=inttostrbase(15,8,0); {s='17'}
i:=strtointbase('0F',16,0); {i=15}
i:=strtointbase('17',8,0); {i=15}
i:=strtointbase('$0F',0,0); {i=15}
i:=strtointbase('&17',0,0); {i=15}
i:=val('15'); {i=15}
i:=val('$F'); {i=15}
i:=val('abc'); {i=0}
r:=rval('15'); {r=15}
r:=rval('$F'); {r=15}
r:=rval('1.23'); {r=1.23}
r:=rval('abc'); {r=NAN}
s:=upcasestr('Case'); {s='CASE'}
s:=locasestr('Case'); {s='case'}
Первый способ:
s:=worddelims(''); // Сохранили текущие разделители
sNul(worddelims('|')); // Задали новые разделители
n:=WordCount('1|2|3'); // Поработали с ними
sNul(worddelims(s)); // Восстановили прежние разделители
Второй способ:
sNul(worddelims('|')); // Задали новые разделители
n:=WordCount('1|2|3'); // Поработали с ними
sNul(worddelims(dump(0))); // Восстановили стандартные разделители
Не забывайте делать это при работе с worddelims.
d:=worddelims(''); {Узнать текущий набор разделителей}
d:=worddelims(' ='); {Сохранить текущий и задать новый набор разделителей}
n:=wordcount('x = 123'); {n=2}
s:=extractword(1,'x = 123'); {s='x'}
s:=extractword(2,'x = 123'); {s='123'}
if extractword(1,s)='x' then x:=rval(extractword(2,s)); {простой интерпретатор}
s:='one "two three" four'; { строка содержит 4 слова или 3 фразы }
n:=PhraseCount(s); { 3 фразы }
p:=ExtractPhrase(2,s); { p='two three' }
b:=echo('x=1'+CRLF+'y=2'); {Напечатает y с новой строки - под Windows}
b:=echo('x=1'+LineEnding+'y=2'); {Напечатает y с новой строки - для всех OC}
b:=echo('x=1'+EOL+'y=2'); {Напечатает y с новой строки - для всех OC}
b:=echo(AdjustLineBreaks('x=1'+cr+'y=2'+lf)); {Напечатает y с новой строки}
s:=StringReplace('a1 b1 c1','1','2',rfReplaceAll+rfIgnoreCase); // s='a2 b2 c2'
writeln(AnsiQuotedStr('Привет мир.',QuoteMark)); // Напечатает "Привет мир."
writeln(AnsiDeQuotedStr('"Привет мир."',QuoteMark)); // Напечатает Привет мир.
writeln(AnsiDeQuotedStr('"Привет" мир.',QuoteMark)); // Напечатает Привет
writeln(AnsiSkipQuotedStr('"Привет" мир.',QuoteMark)); // Напечатает мир.
/V короткие опции в стиле Windows
/VERBOSE длинные опции в стиле Windows
//V короткие опции в стиле WScript
//VERBOSE длинные опции в стиле WScript
-v короткие опции в стиле Linux
-verbose длинные опции в стиле Linux
--verbose длинные опции в стиле GNU
При разборе опций допускаются все виды опций, а регистр не учитывается,
т.е. опции /help, /HELP, -help, -HELP будут эквивалентными.
В опциях также допускается параметр, отделенный знаком равно (=), например,
--inifile=params.ini.
{
Test 3.
Check Command Line parsing functions.
}
procedure Test3;
procedure Test(cmdline:String);
var args,arg,del:String; parnum,optnum:Integer;
begin
del:=WordDelims(' '+CRLF);
parnum:=0; optnum:=0;
args:=cmdline; arg:='';
writeln('Process Command Line: ',args);
while not IsEmptyStr(args) do begin
arg:=ExtractFirstParam(args,QuoteMark);
args:=SkipFirstParam(args,QuoteMark);
if IsOption(arg,'') then begin
optnum:=optnum+1;
writeln(' Found option[',optnum:1,']: ',arg);
if IsOption(arg,'-h,--help') then writeln(' Option is --help');
if IsOption(arg,'--verbose') then writeln(' Option is --verbose');
if IsOption(arg,'--filename') then writeln(' Option is --filename with param '+GetOptionValue(arg));
end else begin
writeln(' Found parameter[',parnum:1,']: '+AnsiQuotedIfNeed(arg,QuoteMark));
parnum:=parnum+1;
end;
end;
del:=WordDelims(del);
args:=''; arg:=''; del:='';
end;
begin
writeln;
writeln;
Test('demo /h');
Test('demo --help');
Test('demo --version');
Test('demo --verbose --filename=note.txt "Test is OK" "Life is good" Bye!');
end;
Результат вызова:
Process Command Line: demo /h
Found parameter[0]: demo
Found option[1]: /h
Option is --help
Process Command Line: demo --help
Found parameter[0]: demo
Found option[1]: --help
Option is --help
Process Command Line: demo --version
Found parameter[0]: demo
Found option[1]: --version
Process Command Line: demo --verbose --filename=note.txt "Test is OK" "Life is good" Bye!
Found parameter[0]: demo
Found option[1]: --verbose
Option is --verbose
Found option[2]: --filename=note.txt
Option is --filename with param note.txt
Found parameter[1]: "Test is OK"
Found parameter[2]: "Life is good"
Found parameter[3]: Bye!
Если метод how не равен одному из указанных, функция возвращает исходную строку data.
Ограничений на длину строки data нет.
При использовании преобразования ansi-utf8 и utf8-ansi появляется возможность преобразования
текста в Unicode. При этом следует учитывать, что длина строки может измениться, а некоторые символы
могут быть замещены "заглушками" (если текст Unicode не может быть преобразован в ANSI).
Кроме того, результат преобразования utf8-ansi может быть пустой строкой, если преобразование
невозможно из-за нарушения формата UTF8.
s:=strconv('oem ansi',s); // Convert DOS to Windows code page
s:=strconv('ansi oem',s); // Convert Windows to DOS code page
s:=strconv('ansi-utf8',s); // Convert ANSI to UTF8 code page
s:=strconv('utf8-ansi',s); // Convert UTF8 to ANSI code page
По перечисленным причинам для передачи двоичных данных по текстовому каналу эти двоичные данные лучше всего закодировать в BASE16 (синоним HEX), BASE32 (синоним NICE), BASE64 (синоним MIME), чтобы там были только отображаемые (печатные) символы, а при приеме декодировать обратно. Эти методы кодирования отличаются битностью (4,5,6 бит) и таблицами алфавита, то есть набора символов (16,32,64 символа), используемых для кодирования данных. Для Base32 есть несколько распространенных вариантов алфавитов (при этом алгоритм кодирования остается одинаковым).
Формальные описания алгоритмов кодирования Base-16/32/64 даны в документах: rfc4648.txt, rfc3548.txt, rfc2938.txt, crockford-base32-encoding.htm, human-oriented-base-32-encoding.txt.
Таблица сравнения алгоритмов
Алгоритм Битность Алфавит Коэффициент Регистр символов Генерация имен Передача текста Читабельность
base16 4 бита 16 символа в 2 раза Не чувстителен Пригоден Возможна Приемлемая
base32 5 бит 32 символа в 8/5 раз Не чувствителен Пригоден Возможна Хорошая
base64 6 бит 64 символа в 4/3 раз Чувствителен Не пригоден Возможна Плохая
Кроме кодирования, при передаче данных могут возникать вопросы шифрования, чтобы конфиденциальную информацию нельзя было прочитать, взяв ее из открытого канала связи. Шифрование заключается в кодировании текста с паролем или ключем k, так что без ключа прочитать исходную информацию нельзя. Надо понимать, что шифрование основано на том, что потенциальный взломщик не имеет доступа к ключу шифрования, иначе шифрование лишается смысла.
Шифрование не следует использовать без нужды, оно занимает много времени и создает лишние хлопоты. Шифрование имеет смысл для небольших пакетов критически важных данных - паролей, имен пользователей и т.д.
Другой проблемой является то, что в Daq Pascal нет указателей и поэтому нет прямого доступа к двоичным данным. Вместо этого есть функции dump,dump2i,dump2r, позволяющие преобразовать двоичное содержимое переменной в строку и наоборот. Поэтому при кодировании - декодировании двоичных данных эти функции также используются.
base16_encode(s) и его синоним hex_encode(s) кодирует строку текста s по алгоритму BASE16,HEX. Входная строка может содержать произвольный набор двоичных данных. На выходе после кодирования получается строка, содержащая только набор 16 отображаемых символов из алфавита: '0123456789ABCDEF'. Размер закодированной строки увеличивается в 2 раза по сравнению с исходным. Поскольку закодированная строка содержит только отображаемые (печатные) символы, ее можно без искажений хранить в текстовых файлах и передавать через текстовые каналы связи, записывать и считывать процедурами writeln,readln. Можно применять к строкам функции преобразования регистра locasestr,upcasestr, данные при этом не будут искажены. Данные в шестнадцатеричном формате поэтому можно хранить в виде переменных в конфигурационном файле. Надо не забывать, что длина данных при кодировании возрастает в 2 раза. Поскольку многие функции DaqPascal работают только с короткими строками длиной до 255 символов, не следует кодировать слишком длинные строки - желательно чтобы длина исходной строки не превышала 127 символов.
base16_decode(s) и его синоним hex_decode(s) декодирует строку текста s, закодированную по алгоритму BASE16,HEX. Входная строка может содержать произвольный набор двоичных данных, но при декодировании игнорируются все символы, кроме набора 16 отображаемых символов из алфавита: '0123456789ABCDEF' без учета регистра. Это значит, что закодированная строка может при приемо-передаче быть подвергнута некоторым преобразованиям, не влияющим на результат декодирования. Например, добавление или вставка пробелов и преобразование регистра символов не влияют на декодирование, так как эти символы игнорируются. Однако не допускается изменение порядка символов и т.д. Результатом декодирования является строка, содержащая исходные двоичные данные. Процесс кодирования/декодирования можно представить так:
1) s1 исходные данные
2) sx=hex_encode(s1) данные для передачи в канал
3) ...sx... передача данных через канал
4) s2=hex_decode(sx) восстановление, s2=s1
Имеет место тождество s=hex_decode(hex_encode(s)).
base32_encode(s) и его синоним nice_encode(s) кодирует строку текста s по алгоритму BASE32 (название nice "приятный" взято потому что при кодировании получается читабельный, т.е. приятный текст). Входная строка может содержать произвольный набор двоичных данных. На выходе после кодирования получается строка, содержащая только набор 32 отображаемых символов, входящих в алфавит, см. описание функции base32_alphabet(). Размер закодированной строки увеличивается примерно в 8/5=1.6 раза по сравнению с исходным. Поскольку закодированная строка содержит только отображаемые символы, ее можно без искажений хранить в текстовых файлах и передавать через текстовые каналы связи, записывать и считывать процедурами writeln,readln. Можно применять к строкам функции преобразования регистра locasestr,upcasestr, данные при этом не будут искажены. Данные в base32 формате поэтому можно хранить в виде переменных в конфигурационном файле. Надо не забывать, что длина данных при кодировании возрастает в 1.6 раза. Поскольку многие функции DaqPascal работают только с короткими строками длиной до 255 символов, не следует кодировать слишком длинные строки - желательно чтобы длина исходной строки не превышала 150 символов. Заметим, что Base32 очень хорошо подходит для генерации читабельных идентификаторов, имен файлов и других именованных объектов.
base32_decode(s) и его синоним nice_decode(s) декодирует строку текста s, закодированную по алгоритму BASE32. Входная строка может содержать произвольный набор двоичных данных, но при декодировании игнорируются все символы, кроме набора 16 отображаемых символов, содержащихся в алфавите (см. base32_alphabet) без учета регистра. Это значит, что закодированная строка может при приемо-передаче быть подвергнута некоторым преобразованиям, не влияющим на результат декодирования. Например, добавление или вставка пробелов и преобразование регистра символов не влияют на декодирование, так как эти символы игнорируются. Однако не допускается изменение числа и порядка символов и т.д. Результатом декодирования является строка, содержащая исходные двоичные данные. Процесс кодирования/декодирования можно представить так:
1) s1 исходные данные
2) sx=base32_encode(s1) данные для передачи в канал
3) ...sx... передача данных через канал
4) s2=base32_decode(sx) восстановление, s2=s1
Имеет место тождество s=base32_decode(base32_encode(s)).
base32_alphabet(a) возвращает текущий и (если указан) задает новый алфавит (набор символов кодирования)
а для кодирования по алгоритму BASE32.
Если задана пустая строка а, то функция не меняет алфавит, а только возвращает текущий алфавит.
Алфавит может быть задан в строке а непосредственно или по номеру/имени.
Если строка а имеет длину 32 символа, она задает алфавит непосредственно.
33-й символ в этом случае может также задать символ заполнения блока (обычно это = знак равенства).
Также доступно несколько распространенных алфавитов по номерам или именам (псевдонимам):
'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=' - алфавит 0 или base32 или rfc4648.3.
'0123456789ABCDEFGHIJKLMNOPQRSTUV=' - алфавит 1 или base32hex или rfc4648.4.
'0123456789ABCDEFGHJKMNPQRSTVWXYZ' - алфавит 2 или crockford.
'ybndrfg8ejkmcpqxot1uwisza345h769' - алфавит 3 или zooko или zbase32 или human или nice.
Текущий алфавит можно узнать или задать вызовом base32_alphabet('').
В качестве алфавита по умолчанию в DAQ Pascal принят алфавит 3 = zooko,
так как это самый читабельный из известных в Сети base32 алфавитов.
Название алфавита zooko берется от имени предложившего его автора (Zooko O'Whielacronx).
Этот автор также предложил псевдоним алфавита zbase32.
Псевдоним human происходит от характеристики human-readable (человечный,читабельный).
Псевдоним nice происходит от слова nice - приятный, удобный.
abc:=base32_alphabet('3'); сохранить текущий алфавит и задать алфавит 3=Zooko
d:=base32_encode(s) закодировать данные
s:=base32_decode(d) декодировать данные
sNul(base32_alphabet(abc)); восстановить исходный алфавит
//
// Test Base16/32/64 functions.
//
procedure TestBase32;
var a,a0,a1,a2,a3,s:string; i,n:Integer;
begin
n:=16; a:=base32_alphabet('');
sNul(base32_alphabet('0')); a0:=base32_alphabet(''); sNul(base32_alphabet('1')); a1:=base32_alphabet('');
sNul(base32_alphabet('2')); a2:=base32_alphabet(''); sNul(base32_alphabet('3')); a3:=base32_alphabet('');
for i:=0 to 16 do begin
s:=copy('ABCDEFGHIJKLMNOPQRSTUVWXYZ',1,i);
writeln(s:n,' ',Ord(s=base16_decode(base16_encode(s))):1,' ',hex_encode(s) :n*2,' ',base16_encode(s));
sNul(base32_alphabet(a0));
writeln(s:n,' ',Ord(s=base32_decode(base32_encode(s))):1,' ',nice_encode(s):n*2,' ',base32_encode(s));
sNul(base32_alphabet(a1));
writeln(s:n,' ',Ord(s=base32_decode(base32_encode(s))):1,' ',nice_encode(s):n*2,' ',base32_encode(s));
sNul(base32_alphabet(a2));
writeln(s:n,' ',Ord(s=base32_decode(base32_encode(s))):1,' ',nice_encode(s):n*2,' ',base32_encode(s));
sNul(base32_alphabet(a3));
writeln(s:n,' ',Ord(s=base32_decode(base32_encode(s))):1,' ',nice_encode(s):n*2,' ',base32_encode(s));
writeln(s:n,' ',Ord(s=base64_decode(base64_encode(s))):1,' ',mime_encode(s):n*2,' ',base64_encode(s));
end;
writeln('base32_alphabet(0): ',a0,' also named base32, rfc4648.3');
writeln('base32_alphabet(1): ',a1,' also named base32hex, rfc4648.4');
writeln('base32_alphabet(2): ',a2,' also named crockford');
writeln('base32_alphabet(3): ',a3,' also named zooko, human, nice, zbase32');
sNul(base32_alphabet(a)); a0:=''; a1:=''; a2:=''; a3:=''; a:=''; s:='';
end;
base64_encode(s) и его синоним mime_encode(s) кодирует строку текста s по алгоритму BASE64,MIME. Входная строка может содержать произвольный набор двоичных данных. На выходе после кодирования получается строка, содержащая только набор 64 отображаемых символов: 'a'..'z','A'..'Z','0'..'9','/','+'. Размер закодированной строки увеличивается примерно в 4/3 раза по сравнению с исходным. Поскольку закодированная строка содержит только отображаемые символы, ее можно без искажений хранить в текстовых файлах и передавать через текстовые каналы связи, записывать и считывать процедурами writeln,readln. Только не надо применять к строкам функции преобразования регистра locasestr,upcasestr, иначе данные будут искажены. Надо также не забывать, что длина данных при кодировании возрастает примерно в 4/3 раз. Поскольку многие функции DaqPascal работают только с короткими строками длиной до 255 символов, не следует кодировать слишком длинные строки - желательно чтобы длина исходной строки не превышала 190 символов.
base64_decode(s) и его синоним mime_decode(s) декодирует строку текста s, закодированную по алгоритму BASE64,MIME. Входная строка может содержать произвольный набор двоичных данных, но при декодировании игнорируются все символы, кроме набора 64 отображаемых символов: 'a'..'z','A'..'Z','0'..'9','/','+'. Это значит, что закодированная строка может при приемо-передаче быть подвергнута некоторым преобразованиям, не влияющим на результат декодирования. Например, добавление или вставка пробелов не влияют на декодирование, так как эти символы игнорируются. Однако не допускается изменение порядка символов, преобразование регистра и т.д. Результатом декодирования является строка, содержащая исходные двоичные данные. Процесс кодирования/декодирования можно представить так:
1) s1 исходные данные
2) sx=mime_encode(s1) данные для передачи в канал
3) ...sx... передача данных через канал
4) s2=mime_decode(sx) восстановление, s2=s1
Имеет место тождество s=mime_decode(mime_encode(s)).
url_packed(s) кодирует строку текста s по "нестрогому" алгоритму URL-encode. "Нестрогость" заключается в том, что набор "непечатных" символов уменьшен и отличается от стандартного (используемого в url_encode в соответствии со стандартом HTTP). Это значит, что в строку вносятся только минимальные изменения, в отличие от url_encode, которая кодирует также русские буквы и другие полезные символы. Кроме того, url_packed будет давать чаще всего более компактную строку, чем url_encode. Работает url_packed так:
url_encode(s) кодирует строку текста s по "строгому" алгоритму URL-encode. Этот метод кодирования соответствует стандарту, используемому протоколом HTTP для передачи адресов URL (Universal Resource Location) и других данных. Работает url_encode так:
url_decode(s) декодирует строку текста s,
закодированную по алгоритму URL-encode функциями
url_packed, url_encode.
Эта функция может применяться для декодирования данных HTTP - запросов,
таких как URL-адреса, запросы QUERY_STRING, содержимое форм CONTENT.
Интересно, что декодер вообще не меняет строки, в которых нет символов + и %.
Если точнее, + заменяется на пробел, %% на %, %hh на chr(hh),
а остальной текст передается без изменений.
Так, декодер не меняет имен файлов или тегов, ведь в них обычно нет символов + и %.
Поэтому многие функции используют URL-encode-кодированные
параметры. Например, оба вызова
filecopy(file1+' '+file2)
filecopy(URL_Packed(file1)+' '+URL_Packed(file2))
допустимы, если имена файлов не содержат пробелов, + и %.
Однако второй вызов позволяет копировать также и файлы с пробелами в именах.
crypt_encode(s,k) кодирует (шифрует) строку текста s с ключем k по алгоритму, заданному вызовами crypt_ctrl. Входная строка может содержать данные в форматах Bin,Hex,Mime. По умолчанию входная строка в формате Bin, то есть в исходной двоичной форме. Перед шифрованием данные декодируются в Bin формат. Затем применяется шифрование по алгоритму, заданному вызовами crypt_ctrl. В результате шифрования получается строка, которую можно безопасно передавать по открытому каналу связи, так как без знания ключа понять ее содержание затруднительно. Для восстановления исходной строки надо знать ключ (пароль) k и параметры шифрования. После шифрования строка кодируется для передачи по открытому каналу связи. Результат может выдаваться в форматах Bin,Hex,Mime. По умолчанию результат выдается в формате Mime.
crypt_decode(s) декодирует и дешифрует строку текста s, зашифрованную с ключем k по алгоритму, заданному вызовами crypt_ctrl. На входе дешифратора имеется зашифрованная строка, принятая по открытому каналу связи. Для ее восстановления надо знать ключ (пароль) k и параметры шифрования. Входная строка может содержать данные в форматах Bin,Hex,Mime. Поэтому строка сначала декодируется в Bin в формат. По умолчанию предполагается, что данные даны в формате Mime, симметрично с выходным форматом crypt_encode. Затем строка дешифруется с паролем k по алгоритму, заданному вызовами crypt_ctrl. Результат может выдаваться в форматах Bin,Hex,Mime. По умолчанию результат выдается в формате Bin, то есть восстанавливается исходная строка. Процесс кодирования/декодирования можно представить так:
1) s1 исходные данные
2) sx=crypt_encode(s1,k) данные для передачи в открытый канал
3) ...sx... передача данных через открытый канал
4) s2=crypt_decode(sx,k) восстановление, s2=s1
Имеет место тождество s=crypt_decode(crypt_encode(s,k),k).
crypt_ctrl(p) задает/читает параметры, используемые для шифрования,
по заданному выражению p.
Выражение x возвращает текущее значение параметра x.
Выражение x=y присваивает параметру x значение y
и возвращает новое значение x.
Выражение x=* присваивает параметру x значение по умолчанию
и возвращает новое значение x.
Можно задавать такие переменные (в таблице указаны значения по умолчанию):
Method BlockSize Key,bytes IV,bytes Modes
Blowfish 64 bit 1..56 8 ECB,CBC,CFB,OFB,OFBC
Gost 64 bit 32 8 ECB,CBC,CFB,OFB,OFBC
RC2 64 bit 1..128 8 ECB,CBC,CFB,OFB,OFBC
RC4 None 1..256 None Stream only
RC5 64 bit 1..256 8 ECB,CBC,CFB,OFB,OFBC
RC6 128 bit 1..256 16 ECB,CBC,CFB,OFB,OFBC
Обычно достаточно использовать значения по умолчанию.
При задании других параметров надо иметь в виду следующее.
Восстановление данных произойдет только при совпадении секретных паролей k1=k2
на стороне передатчика(1) и приемника(2), а также при при правильных настройках
параметров шифрования, то есть при одинаковых значениях параметров
Kind1=Kind2, Mode1=Mode2, IV1=IV2 и симметричных значениях форматов
Ei1=Do2, Eo1=Di2. Параметры шифрования могут передаваться по каналу открыто,
это не влияет на степень защиты.
Скрытым должен быть только секретный пароль k.
Группа функций GetMd5FromXXX
dump(x) извлекает двоичное содержимое x в виде строки. Длина строки будет равна размеру данных x, то есть 1 для boolean,char, 4 для integer, 8 для real. Заметим, что Real эквивалентно типу IEEE Double (вещественное 8 байт).
dump2b(s),dump2c(s),dump2i(s),dump2r(s) возвращает значение переменной, заданной своей двоичной строкой. Если длина строки больше размера данных данного типа, лишние байты игнорируются. Если длина строки меньше размера данных данного типа, недостающие байты зануляются.
Функции dump2f(s) и dumpf(r) работают с типом float или IEEE-754 Single (вещественное 4 байта). Поскольку в Daq Pascal нет типа float, в функцию dumpf(r) данные передаются в виде числа r типа Real. Функция преобразует число к типу float и возвращает его дамп. Наконец, функция dump2f(s) извлекает из строки s число float, преобразует в Real и возвращает его значение.
{ Программа - писатель. }
{ Кодирует двоичные данные и записывает в текстовый канал связи. }
program writer;
var x,y,z:real; i:integer; s:string;
begin
s:=dump(x)+dump(y)+dump(z)+dump(i); {binary dump of (x,y,z,i) record}
s:=mime_encode(s); {now dump converted to MIME}
writeln(s); {write it to text file...}
end;
{ Программа - читатель. }
{ Читает данные из текстового канала связи и декодирует. }
program reader;
var x,y,z:real; i:integer; s:string;
begin
readln(s); {read data from text file...}
s:=mime_decode(s); {decode data from MIME to binary}
x:=dump2r(copy(s,1,8)); {extract x value}
y:=dump2r(copy(s,9,8)); {extract y value}
z:=dump2r(copy(s,17,8)); {extract z value}
i:=dump2i(copy(s,25,4)); {extract i value}
end;
if size > 0 then hash:=hash mod size;Расчет хэша ведется по методу method. Этот параметр определяет алгоритм хеширования, т.е. способ вычисления хеша. В настоящее время в библиотеке есть 94 метода хеширования, включая много вариантов контрольных сумм CRC-8/16/32. Выбор метода определяется целью. Например, для индексирования строк лучше использовать быстрые методы, а для расчета контрольных сумм используются стандартизованные алгоритмы, хотя они работают несколько медленнее. Для индексации строк рекомендуется использовать быстрые методы 0..46 (метод 0 является основным). Для расчета контрольных сумм по разным алгоритмам используются методы 47..92.
// Вычисление контрольной суммы MODBUS/RTU
hid:=iValDef(ParamStr('HasherCode Crc16Modbus'),-1); // получить код хеш-функции CRC-16/MODBUS по имени
if hid<0 then Trouble('Алгоритм не найден'); // проверить допустимость кода хеш-функции
checksum:=HashIndexOf(data,0,hid); // вычислить контрольную сумму
Слово хэш происходит от английского hash - рубить, крошить, мешанина, путаница.
Это слово отражает цель хэш-функции - хорошенько перемешать символы ключа key таким образом,
чтобы разные строки ключей давали в качестве хэш-индекса числа, которые с высокой вероятностью не совпадают
и желательно равномерно распределены по диапазону значений.
Хеш-функции часто используются для ускорения поиска строк в ассоциативных массивах, а также в криптографии для
хранения или сравнения паролей, а также в качестве контрольных сумм для контроля целостности данных.
Например, вместо пароля может запоминаться или передаваться по сети его криптографическая хэш-сумма,
чтобы нельзя было узнать пароль даже перехватив сообщение.
К криптографическим хэш-функциям (таким как md5, crc32 и т.д.) предъявляются особые требования, в первую очередь
криптографической стойкости (невозможности восстановления ключа по значению хэша).
Однако такие хэш-суммы сложны и трудоемки в вычислениях.
Функция hashindexof реализует в основном не криптографические хэш-функции.
Это значит, что предпочтение отдается скорости вычисления хэша, а не его криптографической стойкости.
Быстро вычисляемый хэш используется в качестве индекса объекта со строковым ключем key в массиве размера size.
Функция hashindexof должна использоваться там, где криптографическая стойкость не важна, но требуется высокая скорость расчета хэша.
Её можно использовать для индексирования ассоциативных массивов или проверки обновления некритических данных
или метода расчета контрольной суммы, но не стоит использовать в качестве метода для хранения паролей.
Кроме алгоритмов индексирования строк в библиотеке алгоритмов есть также большой набор функций расчета контрольных сумм, такие как CRC-8/16/32 (много вариантов), HART, DCON и т.д. Эти алгоритмы в том или ином виде стандартизованы и применяются в различных протоколах хранения и приемопередачи данных. Эти алгоритмы можно использовать в драйверах соответствующих протоколов.
Ограничений на длину и содержимое строки key нет. Размер size должен быть неотрицательным или нулем. Значение method должно быть в диапазоне 0..93, рекомендуемый метод 0.
// Индексация строк
key:='ключ для поиска'; - взять ключ для поиска
hash:=hashindexof(key,1024,0); - вычислить хэш-индекс для таблицы data[0..1023] по алгоритму 0
data[hash]:=getdata(key); - использовать его для индексирования в таблице
// Вычисление контрольной суммы MODBUS/RTU
hid:=iValDef(ParamStr('HasherCode Crc16Modbus'),-1); // получить код хеш-функции CRC-16/MODBUS по имени
if hid<0 then Trouble('Алгоритм не найден'); // проверить допустимость кода хеш-функции
checksum:=HashIndexOf(data,0,hid); // вычислить контрольную сумму
// Получение списка имен хеш-функций
i:=0;
while (i<256) do begin
s:=ParamStr('HasherName '+Str(i));
if s='' then i:=256 else writeln(i:3,' ',s);
i:=i+1;
end;
//////////////////////// Алгоритмы хеширования строк:
0 RS основной алгоритм индексирования строк (используется в системе по умолчанию)
1 RS_W2 основной алгоритм для индексирования длинных имен (например для имен файлов)
2 RS_W4 основной алгоритм для индексирования длинных имен (например для имен файлов)
3 SDBM запасной алгоритм индексирования строк
4 SDBM_W2 запасной алгоритм для индексирования длинных имен
5 SDBM_W4 запасной алгоритм для индексирования длинных имен
6 SDBM_ASM
7 SDBM_SHF
8 ROT13 запасной алгоритм индексирования строк
9 ROT13_ASM
10 LY запасной алгоритм индексирования строк
11 FNV
12 FNV0
13 FNV1 запасной алгоритм индексирования строк
14 FNV1A запасной алгоритм индексирования строк
15 JENKINS
16 AP
17 ALSHA1
18 ALSHA2
19 MURMUR3
20 MURMUR3_ASM
21 BKDR131
22 BKDR31
23 BKDR1313
24 BKDR13131
25 BKDR131313
26 DJB
27 DJB_SHF
28 JS
29 LUA
30 H37
31 TDH17
32 TDPJW
33 PJW
34 PJW32
35 ELF
36 DEK
37 BP
38 LOSER
39 JCL
40 GOULBURN
41 ONEATTIME
42 SBOX
43 CRAP8
44 CRAPWOW
45 SUPERFAST
46 Murmur2
///////////////////////// Алгоритмы расчета контрольных сумм:
47 Crc8 см. crccalc.com, CRC RevEnv
48 Crc8Cdma2000 см. crc_calc, CRC
49 Crc8Darc
50 Crc8Dvbs2
51 Crc8Ebu
52 Crc8Icode
53 Crc8Itu
54 Crc8Maxim
55 Crc8Rohc
56 Crc8Wcdma
57 Crc16CcittFalse
58 Crc16Arc
59 Crc16AugCcitt
60 Crc16Buypass
61 Crc16Cdma2000
62 Crc16Dds110
63 Crc16DectR
64 Crc16DectX
65 Crc16Dnp
66 Crc16En13757
67 Crc16Genibus
68 Crc16Maxim
69 Crc16Mcrf4xx
70 Crc16Riello
71 Crc16T10dif
72 Crc16Teledisk
73 Crc16Tms37157
74 Crc16Usb
75 Crc16A
76 Crc16Kermit KERMIT protocol
77 Crc16Modbus MODBUS/RTU protocol
78 Crc16X25
79 Crc16Xmodem XMODEM protocol
80 Crc32
81 Crc32Bzip2
82 Crc32C
83 Crc32D
84 Crc32Jamcrc
85 Crc32Mpeg2 CRC-32/MPEG-2
86 Crc32Posix
87 Crc32Q
88 Crc32Xfer
89 ModbusLrc MODBUS/ASCII protocol
90 ModbusCrc MODBUS/RTU protocol
91 DCON DCON protocol
92 HART HART protocol
93 ZERO нулевой хеш (только для отладки\калибровки)
{
Demo for UTF8 functions.
Example: demo_utf8('Привет Мир!');
}
procedure demo_utf8(test:string);
var su,sa:string; i:Integer;
begin
su:=''; sa:='';
writeln('Execute utf8_demo("'+test+'"):');
su:=utf8_encode_ansi(test);
sa:=utf8_decode_ansi(su);
writeln('Test string = ',test);
writeln('UTF8 encode = ',su);
writeln('UTF8 decode = ',sa);
writeln('Test Status = ',(test=sa):1);
writeln('Test Length = ',length(test):1);
writeln('UTF8 Length = ',length(su):1);
writeln('Char Length = ',utf8_length(su):1);
writeln('UTF8 Upper = ',strconv('utf8-ansi',utf8_uppercase(ExtractWord(1,su))));
writeln('UTF8 Lower = ',strconv('utf8-ansi',utf8_lowercase(ExtractWord(2,su))));
writeln('Test Copy = ',utf8_decode_ansi(utf8_copy(su,2,utf8_length(su)-2)));
writeln('UTF8 BOM = ',utf8_bom);
for i:=1 to length(test) do begin
write(' Index ',i:3);
write(' Ansi ',StrFetch(test,i));
write(' Code ',Ord(StrFetch(test,i)):3);
write(' UTF8 ',utf8_copy(su,i,1):4);
write(' Code ',utf8_ord(su,i):5);
write(' Char ',utf8_chr(utf8_ord(su,i)):4);
write(' Test ',(StrFetch(test,i)=utf8_decode_ansi(utf8_chr(utf8_ord(su,i)))):1);
writeln;
end;
su:=''; sa:='';
end;
Ограничений на длину строк s1,s2 нет.
b1:=issametext('Var','VAR'); b2:=('Var'='VAR'); {true, false}
b1:=issametext('Var','Var'); b2:=('Var'='Var'); {true, true}
b1:=issametext('Var','xxx'); b2:=('Var'='xxx'); {false, false}
rex:=regexp_init(0,'/^[_a-zA-Z]+[_a-zA-Z0-9]*$/i'); // Создать объект - регулярное выражение
if IsLexeme(s,rex) then ... // Проверка выражения s
if regexp_test(rex,s) then ... // Проверка выражения s (эквивалент)
bNul(regexp_free(rex)); // Не забываем освободить регулярное выражение
Это расширение IsLexeme(..) позволяет использовать регулярные выражения единообразным способом
вместе с предопределенными константами, делая эту функцию универсальным инструментом синтаксического
анализа теста.
POSIX-класс Эквивалент (регулярные выражения) Значение
[:upper:] [A-Z] Символы верхнего регистра
[:lower:] [a-z] Символы нижнего регистра
[:alpha:] [[:upper:][:lower:]] Буквы (латинские)
[:digit:] [0-9] Цифры
[:xdigit:] [[:digit:]A-Fa-f] Шестнадцатеричные цифры
[:alnum:] [[:alpha:][:digit:]] Буквы и цифры
[:word:] [[:alnum:]_] Символы, образующие «слово»
[:punct:] [!"#$%&'()*+,-./:;<=>?@[\\]_`{|}~] Знаки пунктуации
[:blank:] [ \t] Пробел и табуляция
[:space:] [[:blank:]\v\r\n\f] Пробельные символы
[:cntrl:] [\x00-\x1F\x7F] Управляющие символы
[:graph:] [\x21-\x7E] Печатные символы
[:print:] [\x20-\x7E], т. е. [[:graph:] ] Печатные символы с пробелом
[:ascii:] [\x00-\x7F] Все символы ASCII (7 бит)
if islexeme('Var',lex_name) then writeln('arg is name');
Конфигурации DAQ - систем используют преимущественно относительные имена файлов в качестве файловых ссылок (file reference). Отосительные ссылки хороши тем, что позволяют конфигурировать систему, не заботясь о конкретном расположении каталога как самого пакета CRW-DAQ, так и конкретной DAQ системы.
Функция формирует имя файла по следующим правилам:
Система CRW-DAQ также использует при формировании имен указанные выше договоренности.
Пусть
текущий каталог c:\daq
конфигурация c:\daq\demo\config\test.cfg
пакет c:\Crw32exe\Crw32.exe
пользователь Somebody
Тогда:
daqfileref('','') = c:\daq
daqfileref('.','') = c:\daq\demo\config
daqfileref('..','') = c:\daq\demo
daqfileref('~','') = c:\Documents and Settings\Somebody
daqfileref('~~','') = c:\Crw32exe
daqfileref('c:\daq\clear','.exe') = c:\daq\clear.exe
daqfileref('c:\daq\clear.bat','.exe') = c:\daq\clear.bat
daqfileref('.\main.cfg','') = c:\daq\demo\config\main.cfg
daqfileref('~\params','.ini') = c:\Documents and Settings\Somebody\params.ini
daqfileref('..\bitmaps\button','.bmp') = c:\daq\demo\bitmaps\button.bmp
daqfileref('~~\Resource\Button','.bmp') = c:\Crw32exe\Resource\button.bmp
defaultextension - задает имя файла с расширением по умолчанию.
Если имя файла n имеет расширение, возвращает просто исходное имя файла.
Если имя файла n не имеет расширения, возвращает исходное имя файла с расширением e.
forceextension - задает имя файла с новым расширением.
Возвращает имя файла n с новым расширением e, независимо от расширения исходного файла.
Имена файлов имеют не более 255 символов.
s:=defaultextension('c:\Crw32exe\Crw32.exe','.ini'); {c:\Crw32exe\Crw32.exe}
s:=defaultextension('c:\Crw32exe\Crw32', '.ini'); {c:\Crw32exe\Crw32.ini}
s:=forceextension('c:\Crw32exe\Crw32.exe','.ini'); {c:\Crw32exe\Crw32.ini}
s:=forceextension('c:\Crw32exe\Crw32', '.ini'); {c:\Crw32exe\Crw32.ini}
defaultpath - задает имя файла с путем по умолчанию.
Если имя файла n имеет абсолютный путь, возвращает просто исходное имя файла.
Если имя файла n не имеет пути или имеет относительный путь,
возвращает имя файла p\n, то есть путь прибавляется к началу имени файла.
forcepath - задает имя файла с новым путем.
Возвращает имя файла n с новым путем p, независимо от пути исходного файла.
Имена файлов имеют не более 255 символов.
s:=defaultpath('c:\Crw32exe\Crw32.exe','c:\tmp'); {c:\Crw32exe\Crw32.exe}
s:=defaultpath('Crw32exe\Crw32.exe', 'c:\tmp'); {c:\tmp\Crw32exe\Crw32.exe}
s:=forcepath('c:\tmp','c:\Crw32exe\Crw32.exe'); {c:\tmp\Crw32.exe}
s:=forcepath('c:\tmp','Crw32exe\Crw32.exe'); {c:\tmp\Crw32.exe}
makerelativepath - возвращает относительное имя файла относительно опорного файла.
Если имя файла n имеет относительный путь, возвращает просто исходное имя файла.
Если имя файла n имеет абсолютный путь и этот путь имеет общее начало с путем
опорного файла, то функция возвращает имя файла относительно опорного файла b.
Если имя опорного файла неизвестно, а используется просто опорный каталог, допустимо указывать вместо имени файла маску "*", например, c:\tmp\*.
Имена файлов имеют не более 255 символов.
{начало пути c:\tmp удаляется из имени файла:}
s:=makerelativepath('c:\tmp\Crw32exe\Crw32.exe','c:\tmp\*'); {Crw32exe\Crw32.exe}
s:=makerelativepath('c:\tmp\Crw32exe\Crw32.exe','c:\tmp\tst\*'); {..\Crw32exe\Crw32.exe}
trim(s) - удаляет незначащие пробелы справа и слева.
trimleft(s) - удаляет незначащие пробелы только слева.
trimright(s) - удаляет незначащие пробелы только справа.
Ограничений на длину строки s нет.
s:=' string ';
s:=trim(s); { 'string' }
s:=trimleft(s); { 'string '}
s:=trimright(s); {' string' }
ord(';') задать разделитель строк ";" (вдобавок к EOL)
256*7 задать опции (case sensitive, no trim name, no trim value)
256*256*ord(_CR) задать символ CR в качестве признака "выражение не найдено"
Специально следует оговорить маркер "выражение не найдено" в битах 16..23 параметра mode.
Это символ, который возвращается в случае, если в буфере не найдено выражение "name=value" с заданным именем.
Этот случай следует отличать от ситуации, когда выражение найдено, но значение value отсутствует (равно пустой строке).
В обоих случаях функция (без маркера) вернула бы пустую строку. Маркер позволяет эти случаи различать.
В качестве маркера надо выбирать CR=chr(13) или LF=chr(10), которые в нормальном значении присутствовать не могут.
buff:='read=book'+EOL+'write=pen'; // Буфер строк с разделителем EOL
s:=cookiescan(buff,'Read',0); // book
buff:='read = book; write = pen'; // Буфер строк с разделителем ; и с пробелами
s:=cookiescan(buff,'Write',Ord(';')); // pen
writeln(StringOfChar('*',10)); => напечатает строку '**********'
procedure testDump;
var s:string; i,n:Integer; b:Boolean;
begin
n:=9;
writeln('testDump ',SizeOfBoolean,SizeOfChar,SizeOfInteger,SizeOfReal); // Размер типов
s:=StringOfChar(chr(0),SizeOfInteger*n); // Выделение памяти
for i:=0 to n-1 do b:=iSetDump(s,i*SizeOfInteger,i+1); // Запись в буфер
for i:=0 to n-1 do write(iGetDump(s,i*SizeOfInteger):5,', '); writeln; // Чтение из буфера
s:=StringOfChar(chr(0),SizeOfReal*n); // Выделение памяти
for i:=0 to n-1 do b:=rSetDump(s,i*SizeOfReal,i+1.0); // Запись в буфер
for i:=0 to n-1 do write(rGetDump(s,i*SizeOfReal):5:3,', '); writeln; // Чтение из буфера
end;
Кроме того, в программе есть глобальный (общий для всей программы) интерпретатор, который доступен также через системную консоль (окно ГЛАВНАЯ КОНСОЛЬ). Он может использоваться для хранения общих для всей системы переменных, для взаимодействия между DAQ-программами, а также для выполнения специфических операций системного характера.
eval(expr) вычисляет формульное выражение expr при помощи локального интерпретатора. Интерпретатор поддерживает единственный (вещественный) тип данных, константы, переменные, встроенные функции и команды, операторы +,-,*,/,^,скобки(). Например:
var x,y создает переменные х=0, y=0, возвращает 0
y=1 создает(если надо) и присваивает y=1, возвращает 1
y возвращает значение переменной y
(x+y*10)/2-sin(pi)^2 вычисляет выражение
r=@voice 123 выполняет команду @voice 123, возвращает результат в r
@system pid=@run cmd.exe выполняет команду pid=@run cmd.exe в системном калькуляторе
Более подробно узнать об интерпретаторе можно через меню
Инструменты/Калькулятор.
Поскольку каждая программа имеет свой экземпляр интерпретатора, пространства переменных у каждой программы разные, а одноименные переменные из разных программ никак не связаны. Есть только два исключения, позволяющие получать доступ к системному калькулятору через локальный.
eval('@global @echo %x') - распечатает локальную переменную x (работает подстановка)
eval('@system @echo %x') - распечатает системную переменную x
evar(name,data) присваивает переменной локального интерпретатора с именем
name значение data.
Если переменная с таким именем не существовала, она создается.
Функция возвращает true в случае успешного выполнения,
false в случае ошибки (например, если указано пустое имя).
Конечно, присвоение можно также делать и в выражении типа
r:=eval(name+'='+str(data));
Однако присвоение через evar выстрее и точнее, так как при этом нет
преобразования в строковый вид и обратно и поэтому нет ошибок округления.
global(expr) вычисляет формульное выражение expr при помощи системного интерпретатора, связанного с системной консолью. Этот вызов следует считать устаревшим, вместо него лучше использовать выражение
eval('@system expr') или
eval('@global expr')
В отличие от локального интерпретатора, пространство переменных, констант, функций у системного калькулятора общее, что позволяет использовать эти переменные для взаимодействия между разными DAQ-программами. Для изучения возможностей global можно использовать системную консоль, потому что она делает в точности то же, что и функция global. Есть только одна существенная разница, связанная с потоками. Многие команды системной консоли (все, связанные с рисованием или окнами) могут выполняться только в основном потоке программы. Такие команды, вызванные из других потоков, по умолчанию ничего не делают и молча возвращают ноль. В консольном режиме это получается автоматически, так как анализ консольного ввода идет в основном потоке. А вот при вызове функции global выполнение происходит в потоке DAQ - программы. Чтобы устранить проблему, используется команда @async, которая помещает следующую за ней строку в очередь для последующего выполнения в основном потоке программы. Выполнение асинхронных команд делается по таймеру, с частотой примерно 18 Hz. Вместо результата команда @async возвращает длину помещенной в очередь команды. Если надо узнать результат самой команды, надо использовать присвоение переменной, например:
r:=eval('@system @async pid=@run cmd.exe'); или просто
r:=eval('@async pid=@run cmd.exe');
В этом примере eval вернет длину команды "pid=@run cmd.exe",
а для того, чтобы узнать результат команды, надо проверять системную
переменную pid вызовом eval('@system pid').
Реально она изменится с задержкой, так как будет выполнена в другом потоке.
Отметим, что вызов @system @async эквивалентен просто вызову @async.
Кроме стандартных функций, системный калькулятор содержит такие полезные команды:
Кроме того, есть ряд предопределенных переменных и имен:
Длина строк expr,name ограничена 255 символами.
Интерпретаторы не следует употреблять без необходимости, так как скорость интерпретации на порядок ниже, чем скорость выполнения обычных DAQ программ.
--Примеры вычисления выражений:
b:=evar('x',pi);
r:=eval('x');
r:=eval('sin(x/3)');
r:=eval('x=1');
--Включить\выключить речевой синтезатор, сказать фразу
--Перед использованием надо установить spchapi.exe и lhttsrur.exe
--которые можно найти в меню Web\Home
r:=eval('@async @speech 1'); {включить}
r:=eval('@async @speak Привет, Мир!'); {сказать фразу}
r:=eval('@async @speech 0'); {выключить}
--Речевой систезатор поддерживает теги.
--Вот примеры основных тегов:
r:=eval('@speak \Rst\Сброс всех тегов на значения по умолчанию.');
r:=eval('@speak Пауза \Pau=1000\в речи на 1000 миллисекунд.');
r:=eval('@speak Громкость речи \Vol=65535\по максимуму \Vol=0\по нулям.');
r:=eval('@speak \Spd=100\Скорость речи 100 слов в минуту.');
r:=eval('@speak Тон (тембр) речи в герцах \Pit=100\низкий \Pit=200\высокий.');
r:=eval('@speak Читать \Pro=1\с интонацией или \Pro=0\без интонации (монотонно).');
r:=eval('@speak Читать слова \RmS=1\по буквам \rms=0\нормальными словами.');
r:=eval('@speak Подчеркнуть интонацией выражение \Emp\СЛЕДУЮЩЕГО слова.');
r:=eval('@speak Не надо подчеркивать интонацией \Dem\СЛЕДУЮЩЕЕ слово.');
--Узнать\включить\выключить быстрый таймер
t:=eval('@system @mmtimer'); {узнать}
r:=eval('@system @mmtimer 1'); {включить}
r:=eval('@system @mmtimer 0'); {выключить}
--Узнать размер полной\доступной физической\виртуальной памяти
r:=eval('@system @memory TotalPhys');
r:=eval('@system @memory AvailPhys');
r:=eval('@system @memory TotalVirtual');
r:=eval('@system @memory AvailVirtual');
--Узнать\задать макс\мин размер рабочей области памяти процесса
r:=eval('@system @memory max');
r:=eval('@system @memory max 1024*1024*64');
r:=eval('@system @memory min');
r:=eval('@system @memory min 1024*1024*16');
--Сохранить/установить/восстановить права доступа
r:=eval('@system @async SaveGuard=@gurd');
r:=eval('@system @async @gurd lock');
r:=eval('@system @async @gurd guest');
r:=eval('@system @async @gurd user');
r:=eval('@system @async @gurd root');
r:=eval('@system @async @gurd %SaveGuard');
--Подавить/разрешить диалог подтверждения при завершении CRW32
r:=eval('@system _Crw_Force_Exit_=1');
r:=eval('@system _Crw_Force_Exit_=0');
--Подавить/разрешить диалог подтверждения при остановке DAQ
r:=eval('@system _Daq_Force_Stop_=1');
r:=eval('@system _Daq_Force_Stop_=0');
--Подавить/разрешить диалог подтверждения при завершении DAQ
r:=eval('@system _Daq_Force_Exit_=1');
r:=eval('@system _Daq_Force_Exit_=0');
--Форсировать\отменить автостарт при следующей загрузке DAQ
r:=eval('@system _Daq_Force_Start_=1');
r:=eval('@system _Daq_Force_Start_=0');
--Выполнить "@run mspaint.exe" непосредственно или асинхронно, в основном потоке
--Системная переменная p будет содержать PID запущенной программы mspaint.exe
r:=eval('@system p=@run mspaint.exe');
r:=eval('@system @async p=@run mspaint.exe');
--Вложенная конструкция @async: будет выведено
-- Two
-- One
--так как выполнение One задержано вложенным вызовом @async
r:=eval('@system @async @async @echo One');
r:=eval('@system @async @echo Two');
--Спрятать/показать основное окно программы в трей
r:=eval('@system @async @view min FormCrw32');
r:=eval('@system @async @view max FormCrw32');
--Спрятать/показать окно DAQ SYSTEM
r:=eval('@system @async @view min FormDaqControlDialog');
r:=eval('@system @async @view norm FormDaqControlDialog');
--Спрятать/показать окно ГЛАВНАЯ КОНСОЛЬ
r:=eval('@system @async @view min FormConsoleWindow');
r:=eval('@system @async @view norm FormConsoleWindow');
--Спрятать/показать основное меню
r:=eval('@system @async @view hide FormCrw32.MainMenu');
r:=eval('@system @async @view show FormCrw32.MainMenu');
--Спрятать/показать панель инструментов
r:=eval('@system @async @view hide FormCrw32.ToolBar');
r:=eval('@system @async @view show FormCrw32.ToolBar');
--Спрятать/показать статусную строку
r:=eval('@system @async @view hide FormCrw32.StatusBar');
r:=eval('@system @async @view show FormCrw32.StatusBar');
--Выполнить команду File/Exit
r:=eval('@system @async @menu run FormCrw32.ActionFileExit');
--Выполнить команду Daq/Start
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStart');
--Выполнить команду Daq/Stop
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
--Выполнить команду Daq/Init (загрузка конфигурации)
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqInit');
--Выполнить команду Daq/Done (выгрузка конфигурации)
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStart');
--Выполнить перезагрузку конфигурации
--Фактически запускается второй экземпляр Crw32 и ему передается имя *.cfg файла
r:=eval('@system @async @run '+ParamStr('ProgName')+' '+ParamStr('DaqConfigFile'));
--Выполнить загрузку новой конфигурации
--Фактически запускается второй экземпляр Crw32 и ему передается имя *.cfg файла
r:=eval('@system @async @run '+ParamStr('ProgName')+ ' c:\daq\test.cfg');
--Выполнить команду завершения Windows
r:=eval('@system @async @run -Hide %ComSpec% /c shutdown -l'); - Выполнить Logout
r:=eval('@system @async @run -Hide %ComSpec% /c shutdown -r'); - Выполнить Restart
r:=eval('@system @async @run -Hide %ComSpec% /c shutdown -s'); - Выполнить Stop(Halt)
r:=eval('@system @async @run -Hide %ComSpec% /c shutdown -s -f'); - Форсировать Stop
r:=eval('@system @async @run -Hide %ComSpec% /c shutdown -s -t 15'); - Выполнить Stop через 15 сек
--Запустить и затем через секунду убить процесс
r:=eval('@system @async p=@run mspaint.exe');
r:=eval('@system @async sleep 1000');
r:=eval('@system @async @async @pid kill %p');
--Скрипт, который завершает работу конфигурации, а затем перезагружает конфигурацию
--и выполняет старт загруженной конфигурации DAQ
--Обратите внимание - права доступа временно повышаются до ROOT, а затем восстанавливаются
--Используется вложенный вызов @async, т.к. следующие команды должны выполняться с задержкой
--Перед выполнением команд окно FormDaqControlDialog активизируется т.к. у скрытого окна
--команды запрещены
r:=eval('@system @async _Daq_Force_Stop_=1');
r:=eval('@system @async _Daq_Force_Exit_=1');
r:=eval('@system @async SaveGuard=@gurd');
r:=eval('@system @async @gurd root');
r:=eval('@system @async @view max FormCrw32');
r:=eval('@system @async @view norm FormDaqControlDialog');
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
r:=eval('@system @async @run '+ParamStr('ProgName')+' '+ParamStr('DaqConfigFile'));
r:=eval('@system @async @sleep 500');
r:=eval('@system @async @async @view norm FormDaqControlDialog');
r:=eval('@system @async @async @menu run FormDaqControlDialog.ActionDaqStart');
r:=eval('@system @async @async @gurd %SaveGuard');
--Скрипт, который завершает работу конфигурации, выходит из программы
--и завершает работу Windows через 15 секунд
r:=eval('@system @async @gurd root');
r:=eval('@system @async _Daq_Force_Stop_=1');
r:=eval('@system @async _Daq_Force_Exit_=1');
r:=eval('@system @async _Crw_Force_Exit_=1');
r:=eval('@system @async @view max FormCrw32');
r:=eval('@system @async @view norm FormDaqControlDialog');
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqStop');
r:=eval('@system @async @menu run FormDaqControlDialog.ActionDaqDone');
r:=eval('@system @async @run -Hide %ComSpec% /c shutdown -s -t15');
r:=eval('@system @async @menu run FormCrw32.ActionFileExit');
--Скрипт, который прячет меню, панель инструментов, статусную строку, окно DAQ SYSTEM
r:=eval('@system @async @view hide FormCrw32.MainMenu');
r:=eval('@system @async @view hide FormCrw32.ToolBar');
r:=eval('@system @async @view hide FormCrw32.StatusBar');
r:=eval('@system @async @view min FormDaqControlDialog');
--Скрипт, который показывает меню, панель инструментов, статусную строку, окно DAQ SYSTEM
r:=eval('@system @async @view show FormCrw32.MainMenu');
r:=eval('@system @async @view show FormCrw32.ToolBar');
r:=eval('@system @async @view show FormCrw32.StatusBar');
r:=eval('@system @async @view norm FormDaqControlDialog');
Engine=1,Modifiers=-igmrsx,ExecMax=0,UseSubst=1,UseSlash=1,Greedy=1,SavePar=0Задание в шаблоне модификаторов (типа /Шаблон/igm) устанавливает явно указанные модификаторы в соответствии с заданными.
Engine=1,Modifiers=-igmrsx,ExecMax=0,UseSubst=1,UseSlash=1,Greedy=1,SavePar=0в которой можно записывать все перечисленные параметры кроме Engine.
ref:=regexp_init(regexp_def,'\s*input\s*'); // init regexp
sNul(regexp_ctrl(ref,'IgnoreCase=1')); // ignore case mode
inp:='Demo Input Line.'; // set data to process
if regexp_test(ref,inp) then writeln('Found.'); // check inp is match
if regexp_exec(ref,inp)>0 then begin // find all matches:
for i:=1 to regexp_matchnum(ref,0) do begin // for all matches
writeln('match ',i,' str ',regexp_matchstr(ref,i,0)); // print match[i] string
for j:=1 to regexp_matchnum(ref,i) do // for each submatch
writeln('submatch ',j,' = ',regexp_matchstr(ref,i,j)); // print submatch[j] of match[i]
end;
end;
rep:=' Test '; // set replacer
out:=regexp_replace(ref,inp,rep); // replace pattern to replacer
writeln(out); // out = 'Demo Test Line'
Примечание: из-за отсутствия единого стандарта на \-экранирование здесь принят минимальный набор правил, обеспечивающий взаимооднозначное преобразование строк (чтобы кодер/декодер работали без искажений). Некоторые последовательности (например, \e для символа ESC \x1B или \0 для символа NUL \x00) здесь не включены, т.к. они не везде поддерживаются или неоднозначно интерпретируются. Также отсутствует поддержка octal кодировки (\nnn) из-за неоднозначности интерпретации (этот метод кодирования считается устаревшим и не рекомендуется стандартом EcmaScript5, вместо него предлагается использовать hex-коды). При этом, например, \0 интепретируется здесь как символ цифры 0 (а не \x00). Для передачи нулевого символа (NUL \x00) используется hex-код. Кроме того, здесь не поддерживается кодирование символов Unicode (\unnnn), которое также можно заменить последовательностью hex-кодов. Кроме того, длина hex-кода принимается всегда равной 4 (\xnn), а не продолжается до появления любого нецифрового символа, как описывают некоторые руководства. Надо заметить, что последовательность (\n) интерпретируется всегда как (LF \x0A), хотя некоторые руководства утверждают, что (\n) может означать (CRLF). Последовательность (CRLF) здесь передается как (\r\n). Таким образом, данная реализация кодера генерирует код, который поймет большинство декодеров, но другие \-декодеры могут генерировать код, который данный декодер поймет неверно. Причина - в отсутствии единого стандарта \-кодирования и неоднозначной интерпретации правил кодирования в разных реализациях кодера/декодера. Поэтому лучше использовать данный кодер на обеих сторонах приема/передачи.
Функция backslash_encode(s) кодирует строку данных s.
Функция backslash_decode(s) декодирует строку данных s. При ошибке декодирования (типа неверного hex-кода) возвращает пустую строку.
Функция backslash_encoder_ctrl(p) управляет через команду p
способом кодирования backslash_encode.
Строка параметра p имеет вида name (чтение) или name=value (запись).
Функция возвращает предыдущее значение параметра (до изменения).
Допустимые имена параметров:
esclist - список дополнительных символов, экранируемых обратным слешем (\).
hexlist - список дополнительных символов, передаваемых в HEX-кодировке (\xnn).
Не забывайте восстанавливать значения по умолчанию после завершения кодирования.
По умолчанию списки esclist, hexlist пустые.
// список esclist задает дополнительные символы, экранируемые обратным слешем \
///////////////////////////////////////////////////////////////////////////////
s:=backslash_encoder_ctrl('esclist'); // прочитать список esclist
s:=backslash_encoder_ctrl('esclist=()/|'); // задать список esclist
s:=backslash_encode('xy(1)=ab/cd'); // s='xy\(1\)=ab\/cd'
s:=backslash_encoder_ctrl('esclist='); // очистить список esclist
// список hexlist задает дополнительные символы, экранируемые HEX-кодом \xnn
////////////////////////////////////////////////////////////////////////////
s:=backslash_encoder_ctrl('hexlist'); // прочитать список hexlist
s:=backslash_encoder_ctrl('hexlist=()/|'); // задать список hexlist
s:=backslash_encode('xy(1)=ab/cd'); // s='xy\x281\x29=ab\x2Fcd'
s:=backslash_encoder_ctrl('hexlist='); // очистить список hexlist
Алгоритм работает так.
C/C++ - style encoding with backslash (\) escaping.
Escape sequence Description Representation
\a audible bell byte 0x07 in ASCII encoding
\b backspace byte 0x08 in ASCII encoding
\t horizontal tab byte 0x09 in ASCII encoding
\n line feed byte 0x0a in ASCII encoding
\v vertical tab byte 0x0b in ASCII encoding
\f form feed byte 0x0c in ASCII encoding
\r carriage return byte 0x0d in ASCII encoding
\" double quote byte 0x22 in ASCII encoding
\' single quote byte 0x27 in ASCII encoding
\? question mark byte 0x3f in ASCII encoding
\\ backslash byte 0x5c in ASCII encoding
\xnn hexadecimal byte nn
Notes:
1) control chars \x00..\x1F,\x7F always passed as hex \xnn
2) any printable char (c) except of (a,b,t,n,v,f,r,",',?,\)
may be passed as \c (use esclist to apply this pass).
3) any char (c) may be passed as hex \xnn (use hexlist).
4) current version don`t use octal codes, use hex instead.
Списки esclist, hexlist, задаваемые backslash_encoder_ctrl,
позволяют экранировать дополнительный набор символов (кроме стандартных).
Это может быть удобно для хранения или передачи данных по каналам связи.
Например, если хочется передавать текст как одно слово (без пробелов),
можно использовать экранирование пробелов, поместив пробел в список hexlist.
Независимо от содержимого списков декодер backslash_decode
сумеет восстановить исходную строку.
s:=backslash_encode('Line1'+CRLF+'Line2'+CRLF); // s='Line1\r\nLine2\r\n'
s:=backslash_decode(s); // return original string
sNul(backslash_encoder_ctrl('hexlist= ')); // add space to hexlist
s:=backslash_encode('word1 word2'); // s='word1\x20word2'
Функция percent_decode(s) декодирует строку данных s.
При наличии ошибки в кодированной строке декодируется то, что возможно
(что соответствует шаблону %xx), а остальные символы остаются неизменными.
При ошибке декодирования (типа неверного hex-кода) функция возвращает
частично декодированную строку, но при этом увеличивается внутренний счетчик ошибок,
который можно прочитать вызовом percent_encoder_ctrl('Errors').
Этот счетчик можно использовать для обработки ошибок.
Ошибкой декодирования считается, если в закодированной строке найден "висящий"
символ процента %, за которым в двух следующих символах не содержится
корректный HEX-код %xx.
Строго говоря, "висящий" символ процента не может встречаться в закодированной строке.
При (правильной) кодировке символ процента должен быть преобразован в %25,
поэтому даже при наличии в исходной строке символов процента закодированная строка
будет корректно декодироваться.
Однако при "ручном" кодировании программисты могут не заметить "висящий" процент
и не заменить его корректным кодом. Отсюда и могут быть ошибки.
Функция percent_encoder_ctrl(p) управляет через команду p
способом кодирования percent_encode.
Строка параметра p имеет вид name (чтение) или name=value (запись).
Функция возвращает предыдущее значение параметра (до изменения).
Допустимые имена параметров:
Reserved - список защищенных символов, экранируемых процентом.
Errors - счетчик ошибок декодирования.
Не забывайте восстанавливать значения по умолчанию после завершения кодирования.
Это делвется вызовом percent_encoder_ctrl('reserved=');
По умолчанию набор Reserved соответствует стандарту RFC-3986,
а счетчик ошибок Errors равен нулю.
// список Reserved задает защищенные символы, заменяемые на %xx
///////////////////////////////////////////////////////////////////////////////
s:=percent_encoder_ctrl('Reserved'); // прочитать список Reserved
s:=percent_encoder_ctrl('Reserved=( )/|'); // задать список Reserved
s:=percent_encode('Это пример'); // s='Это%20пример'
s:=percent_encoder_ctrl('Reserved='); // сбросить Reserved к стандартному значению
// счетчик Errors содержит число ошибок декодирования
////////////////////////////////////////////////////////////////////////////
s:=percent_encoder_ctrl('Errors'); // прочитать счетчик
s:=percent_encoder_ctrl('Errors=0'); // прочитать и сбросить в ноль
Набор защищенных символов Reserved, задаваемый через percent_encoder_ctrl,
позволяют экранировать любой желаемый набор символов для передачи данных по каналам связи.
Например, если хочется передавать текст как одно слово (без пробелов),
можно использовать экранирование пробелов, поместив пробел в список Reserved.
Независимо от содержимого набора защищенных символов декодер percent_decode
сумеет восстановить исходную строку.
s:=percent_encode('Line1'+CRLF+'Line2'+CRLF); // s='Line1%0D%0ALine2%0D%0A'
s:=percent_decode(s); // return original string
Если указано имя файла без пути (только имя и расширение), прибавляется путь текущего каталога. В частности, fexpand('') возвращает просто текущий каталог.
Если указано имя файла с относительным путем, путь преобразуется в абсолютный относительно текущего каталога.
Если указано имя файла с абсолютным путем, путь нормализуется, приводится к каноническому виду. Например, если имя было 'c:\tmp\picture\2005\..\family.bmp', оно преобразуется к эквивалентному имени 'c:\tmp\picture\family.bmp'.
Имена файлов в каноническом виде можно сравнивать. То есть выражение IsSameText(FExpand(f1),FExpand(f2)) говорит о том, что f1 и f2 указывают на один файл.
Имена файлов имеют не более 255 символов.
s:=fexpand('c:\tmp\picture\2005\..\family.bmp'); {'c:\tmp\picture\family.bmp'}
s:=fexpand('c:\tmp\picture\.\family.bmp'); {'c:\tmp\picture\family.bmp'}
s:=fexpand(''); {текущий каталог}
s:=fexpand('data.txt'); {полный путь файла 'data.txt' в текущем каталоге}
addbackslash(s), addpathdelim(s) - добавляет в конец строки символ разделителя каталогов (для Windows это \ - обратный слеш), если его там нет. Применяется для формирования имен файлов. Предпочтительнее использовать addpathdelim(s), в названии которого используется мультиплатформенное название PathDelim.
dropbackslash(s), droppathdelim(s) - удаляет из конца строки символ разделителя каталогов (для Windows это \ - обратный слеш), если он там есть. Применяется для формирования имен каталогов. Предпочтительнее использовать droppathdelim(s), в названии которого используется мультиплатформенное название PathDelim.
Имена файлов имеют не более 255 символов.
s:=addbackslash('c:\tmp'); {'c:\tmp\' }
s:=addbackslash('c:\tmp\'); {'c:\tmp\' }
s:=dropbackslash('c:\tmp'); {'c:\tmp' }
s:=dropbackslash('c:\tmp\'); {'c:\tmp' }
s:=addbackslash('c:\tmp')+'file.txt'; {'c:\tmp\file.txt' }
extractfilepath(s) - выделяет путь файла, то есть каталог, где он расположен. Путем считается часть имени файла до последнего разделителя каталогов "/" или "\". Например, файл "c:\tmp\x.bmp" имеет путь "c:\tmp".
extractfilename(s) - выделяет имя файла, без каталога и расширения.
extractfileext(s) - выделяет расширение файла. Расширением считается часть имени файла начиная от последней точки. Например, файл "c:\tmp\x.bmp" имеет расширение ".bmp".
Имена файлов имеют не более 255 символов.
s:='c:\tmp\data.txt');
s:=extractfilepath(s); {'c:\tmp\' }
s:=extractfilename(s); {'data' }
s:=extractfileext(s); {'.txt' }
s:=addbackslash(extractfilepath(s))+extractfilename(s)+extractfileext(s);
iswildcard(s) - возвращает true, если строка содержит маску файла, заданную символами * или ?.
isrelativepath(s) - возвращает true, если строка содержит относительное имя файла, а не полное (абсолютное) имя файла.
hasextension(s) - возвращает true, если строка содержит имя файла с расширением.
direxists(s) - возвращает true, если существует каталог s.
fileexists(s) - возвращает true, если существует файл s.
Имена файлов имеют не более 255 символов.
b:=iswildcard('*.tx?'); {true}
b:=iswildcard('a.txt'); {false}
b:=isrelativepath('.\bin\bp.exe'); {true}
b:=isrelativepath('c:\bp\bin\bp.exe'); {false}
b:=hasextension('data.txt'); {true}
b:=hasextension('data'); {false}
fileerase(s) - удаляет файл с именем fname.
filerename(s) - переименовывает файл согласно аргументу s.
Аргумент содержит пару имен файлов s='СтароеИмя НовоеИмя',
разделенных пробелом или маркером EOL.
Имена файлов передаются в одной из трех форм:
filecopy(s) - копирует файл согласно аргументу s.
Аргумент содержит пару имен файлов s='Оригинал Копия',
разделенных пробелом или маркером EOL.
Имена файлов передаются таким же способом, как в функции filerename.
Рекомендуется использовать имена файлов в кавычках, разделенные пробелом.
Например:
filerename('"c:\Program Files\demo.txt" "c:\temp\demo.txt"')
Заключать имена файлов в кавычки можно вызовом AnsiQuotedStr(FileName,QuoteMark).
getfattr(fname) - возвращает атрибуты файла fname:
setfattr(fname,attr) - устанавливает новые атрибуты attr файла fname.
doserror - возвращает ошибку DOS при выполнении последней файловой операции. В CRW32 это системный вызов GetLastError.
mkdir(folder) - создает каталог folder. При надобности создает родительские каталоги, если они не существуют. Например, mkdir('c:\tmp\data') может создать каталог c:\tmp, а затем c:\tmp\data.
dirlist(txt,maxlevel,folder,pattern) - создает список файлов каталога и его подкаталогов по данному шаблону поиска. Возвращает в качестве значения аргумент txt, чтобы можно было использовать конструкции типа txt:=dirlist(text_new,0,'c:\daq','*.cfg').
createtempfile(template) - создает временный файл с уникальным именем по заданному шаблону template типа XXX.TMP, где XXX - префикс файла (3 символа), .TMP - расширение. Функция создает файл нулевой длины с уникальным именем и применяется для создания временных файлов для промежуточных операций с данными. После использования временные файлы надо удалять.
Имена файлов имеют не более 255 символов.
В текущей версии могут быть проблемы с именами файлов и каталогов, содержащими пробелы.
b:=fileerase('data.txt');
b:=filerename('from.txt to.txt');
b:=filecopy('from.txt to.txt');
i:=getfattr('data.txt');
b:=setfattr('data.txt',ior(i,$20));
b:=mkdir('c:\data');
s:=createtempfile('dat.tmp');
Пример чтения файлов *.cfg, *.bmp и *.crc из каталога DAQ:
dir:=paramstr('daqconfigpath')+'\..';
t:=dirlist(text_new,maxint,dir,'*.bmp,*.crc,*.cfg');
for i:=0 to text_numln(t)-1 do begin
s:=text_getln(t,i);
if s<>'' then if s[length[s]]<>'\' then writeln(s); {исключить каталоги}
end;
b:=text_free(t);
Результатом будет что-то вроде
D:\DAQ\TESTS\BITMAPS\LED12025.BMP
D:\DAQ\TESTS\BITMAPS\LED60X18.BMP
D:\DAQ\TESTS\CIRCUITS\HEATS.CRC
D:\DAQ\TESTS\CONFIG\TEST.CFG
D:\DAQ\TESTS\CONFIG\HEATDEMO.CFG
[&Demo]
Program = ..\Utility\Demo.exe
Script = ..\Utility\Test.cmd
...
exe:=AdaptFileName(ReadIni('[&Demo] Program'));
cmd:=AdaptFileName(ReadIni('[&Demo] Script'));
// Unix: exe = ../utility/demo cmd = ../utility/test.sh
// Windows: exe = ..\Utility\Demo.exe cmd = ..\Utility\Test.cmd
[&Demo]
Program = ../utility/demo
Script = ../utility/test.sh
...
exe:=AdaptFileName(ReadIni('[&Demo] Program'));
cmd:=AdaptFileName(ReadIni('[&Demo] Script'));
// Unix: exe = ../utility/demo cmd = ../utility/test.sh
// Windows: exe = ..\utility\demo.exe cmd = ..\utility\test.cmd
s:='c:\opt\crwdaq\CrwDaq.exe');
s:=adaptexefilename(s);
// Под Unix: /opt/crwdaq/crwdaq
// Под Windows: c:\opt\crwdaq\CrwDaq.exe
//Установка режима
m:=adaptfilenamemode(afnm_trim+afnm_delim+afnm_drive+afnm_lower+afnm_utf8+afnm_nodup);
//Установка режима по умолчанию
m:=adaptfilenamemode(-1);
Если строка arg состоит из слов w1, w2, w3, w4, w5, w5, w6, разделенных пробелом или знаком табуляции, то первое слово w1 задает имя параметра или функции, а w2...w6 – аргументы этой функции. Результат вызова paramstr в зависимости от w1 приведен в таблице:
| w1 | Описание |
|---|---|
| 0..9 |
Возвращает параметр командной строки, заданный номером w1. paramstr('0') возвращает имя запущенной программы. paramstr('1') возвращает первый аргумент и т.д. |
| [Compiler.Options] | Возвращает текущие настройки компилятора DaqPascal. Параметр w2 принимает значения: Compiler.itabmax, Compiler.btabmax, Compiler.atabmax, Compiler.rtabmax, Compiler.dtabmax, Compiler.dtabmin, Compiler.stabmax, Compiler.stabmin, Compiler.slenmax, которые описаны в разделе опции компилятора. |
| SessionNumber, SessionNb |
Возвращает номер текущей сессии crwdaq.
Это натуральный номер (начиная с 1),
используемый Менеджером Сессий
для идентификации экземпляров программы.
Функция не имеет параметров. |
| SessionName |
Возвращает имя текущей сессии crwdaq.
Это имя имеет вид crwdaq_Nb,
где Nb - номер сеанса.
Например, crwdaq_1.
Функция не имеет параметров. |
| SessionTitle |
Возвращает заголовок текущей сессии crwdaq.
Этот заголовок имеет вид имеет вид crwdaq#Nb/Pid@Host,
где Nb - номер сеанса, Pid - номер процесса, Host - имя компьютера.
Например, crwdaq#1/5146@crwbox.
Этот заголовок также является заголовком главного окна программы и хранится в переменной
окружения CRW_DAQ_SYS_TITLE.
Функция не имеет параметров. |
| HOMEDIR |
Возвращает путь домашнего каталога,
то есть каталог основной программы (CRW32.EXE).
Функция не имеет параметров. |
| STARTUPPATH |
Возвращает путь домашнего каталога,
то есть каталог основной программы (CRW32.EXE).
Функция не имеет параметров. |
| TEMPPATH |
Возвращает путь временных файлов CRW-DAQ. Он задается переменной Crw32.ini [System] TempDir = ... Обычно это каталог Crw32exe\Temp. Функция не имеет параметров. |
| DAQCONFIGPATH |
Возвращает каталог основного файла конфигурации CRW-DAQ.
Функция не имеет параметров. |
| DAQCONFIGFILE |
Возвращает имя основного файла конфигурации CRW-DAQ.
Функция не имеет параметров. |
| DAQDATAPATH |
Возвращает путь каталога данных DAQ. Он задается переменной [DAQ] DataPath = ... Обычно это каталог ..\Data. Функция не имеет параметров. |
| DAQBACKUPFILE |
Возвращает имя файла автосохранения CRW-DAQ. Он задается переменной [DAQ] BackFile = ... Функция не имеет параметров. |
| GETCURDIR GETCURRDIR |
Возвращает текущий каталог.
Функция не имеет параметров. |
| DEVICENAME |
Возвращает имя устройства CRW-DAQ. Необязательный параметр w2 задает номер устройства. Номера устройств начинаются с 0. Если устройства с данным номером нет, функций возвращает пустую строку. Таким образом, функция позволяет просмотреть всю таблицу устройств CRW-DAQ, при этом условием завершения цикла будет возврат пустой строки. Если параметр w2 не указан, функция возвращает имя текущего устройства CRW-DAQ. Это позволяет идентифицировать текущее устройство. |
| DEVICEMODEL |
Возвращает модель устройства CRW-DAQ. Необязательный параметр w2 задает номер устройства. Номера устройств начинаются с 0. Если устройства с данным номером нет, функций возвращает пустую строку. Таким образом, функция позволяет просмотреть всю таблицу устройств CRW-DAQ, при этом условием завершения цикла будет возврат пустой строки. Если параметр w2 не указан, функция возвращает модель текущего устройства CRW-DAQ. Это позволяет идентифицировать текущее устройство. |
| DEVICEFAMILY |
Возвращает семейство устройства CRW-DAQ. Необязательный параметр w2 задает номер устройства. Номера устройств начинаются с 0. Если устройства с данным номером нет, функций возвращает пустую строку. Таким образом, функция позволяет просмотреть всю таблицу устройств CRW-DAQ, при этом условием завершения цикла будет возврат пустой строки. Если параметр w2 не указан, функция возвращает семейство текущего устройства CRW-DAQ. Это позволяет идентифицировать текущее устройство. |
| PROGNAME |
Возвращает полное имя основной программы CRW32.EXE.
Функция не имеет параметров. |
| SYSINIFILE |
Возвращает полное имя основного INI файла программы, CRW32.INI.
Функция не имеет параметров. |
| DAQPROGRAM |
Возвращает имя текущей программы Daq-Pascal (то, что написано в program ...).
Функция не имеет параметров. |
| USERNAME | Возвращает имя текущего пользователя на компьютере, имя котогого задан параметром w2. Если параметр w2 не задан или это точка (т.е. localhost), то имеется в виду локальный компьютер. |
| COMPUTERNAME |
Возвращает имя компьютера.
Функция не имеет параметров. |
| HOSTNAME |
Возвращает сетевое имя компьютера. Небязательный параметр w2 принимает значения 0/1 (по умолчанию w2=0). При w2=0 возвращается короткое имя компьютера, обычно совпадающее с COMPUTERNAME, например, crwbox. При w2=1 возвращается полное имя компьютера, включающее домен, например, crwbox.abbey.ru. |
| USERDOMAIN |
Возвращает доменное имя по имени пользователя. Небязательный параметр w2 задает имя пользователя. Если имя пользователя не указано, возвращает доменное имя текущего пользователя. |
| USERLIST |
Возвращает список пользователей компьютера или домена. Функция имеет параметры w2..w5. Все параметры необязательные. Параметр w2 (по умолчанию w3=.) задает имя компьютера в URL-кодировке. По умолчанию это точка (т.е. localhost), обозначающая текущий компьютер. Использование URL-кодировки имени имеет свои достоинства. Например, для задания пустого имени достаточно указать + или %20.
Параметр w3 (по умолчанию w3=0) задает уровень детализации.
UserName AccountType OnOff Comment
oleg User On Пользователь по имени oleg
alex Root On Администратор по имени alex
Гость Guest Off Гость по имени Гость, отключен
Следует иметь в виду, что 1 уровень детализации работает
существенно медленнее, чем 0 и может привести
к подвисанию потока программы.
Поэтому использовать этот уровень детализации надо очень осторожно.
Параметр w4 (по умолчанию w4=$02) задает фильтр:
$01 = Enumerates local user account data on a domain controller.
$02 = Enumerates global user account data on a computer.
$04 = Enumerates proxy accounts.
$08 = Enumerates domain trust account data on a domain controller.
$10 = Enumerates workstation or member server account data on a domain controller.
$20 = Enumerates domain controller account data on a domain controller.
Параметр w5 (по умолчанию w5=10000) задает TimeOut в ms. |
| HOSTLIST |
Возвращает список серверов в сети. Функция имеет параметры w2..w6. Все параметры необязательные. Параметр w2 (по умолчанию w3=.) задает имя компьютера в URL-кодировке. По умолчанию это точка (т.е. localhost), обозначающая текущий компьютер. Использование URL-кодировки имени имеет свои достоинства. Например, для задания пустого имени достаточно указать + или %20. Параметр w3 задает имя домена в URL-кодировке. По умолчанию это пустая строка (то есть +), обозначающая текущий домен. Использование URL-кодировки имени имеет свои достоинства. Например, для задания пустого имени домена достаточно указать + или %20.
Параметр w4 (по умолчанию w4=0) задает уровень детализации.
HostName IP Flags Comment
crwbox 174.21.4.131 \WSt\Srv\WinNt\ Сервер DaqGroup
Следует иметь в виду, что 1 уровень детализации работает
существенно медленнее, чем 0 и может привести
к подвисанию потока программы.
Поэтому использовать этот уровень детализации надо очень осторожно.
Параметр w5 (по умолчанию w5=$FFFFFFFF=-1) задает фильтр:
Значение Flags Имя Расшифровка
$00000001 - WSt WORKSTATION All LAN Manager workstations
$00000002 - Srv SERVER All LAN Manager servers
$00000004 - SQL SQLSERVER Any server running with Microsoft SQL Server
$00000008 - DomCtrl DOMAIN_CTRL Primary domain controller
$00000010 - DomBack DOMAIN_BAKCTRL Backup domain controller
$00000020 - TimSrc TIME_SOURCE Server running the Timesource service
$00000040 - AFP AFP Apple File Protocol servers
$00000080 - Novell NOVELL Novell servers
$00000100 - DomMemb DOMAIN_MEMBER LAN Manager 2.x Domain Member
$40000000 - LocList LOCAL_LIST_ONLY Servers maintained by the browser.
$00000200 - Prn PRINT Server sharing print queue
$00000400 - Deal DIALIN Server running dial-in service
$00000800 - Unix UNIX Xenix server
$00004000 - MFPN MFPN Microsoft File and Print for Netware
$00001000 - WinNt NT Windows NT (either Workstation or Server)
$00002000 - WinFW WFW Server running Windows for Workgroups
$00008000 - SrvNt SERVER_NT Windows NT Non-DC server
$00010000 - PBrow POTENTIAL_BROWSER Server that can run the Browser service
$00020000 - BBrow BACKUP_BROWSER Server running a Browser service as backup
$00040000 - MBrow MASTER_BROWSER Server running the master Browser service
$00080000 - DomMast DOMAIN_MASTER Server running the domain master Browser
$80000000 - DomEnum DOMAIN_ENUM Primary Domain enumerator
$00400000 - Win9x WINDOWS Windows 95 or later
$FFFFFFFF All servers
При вызове указывается численная маска w5, показывающая, какие сервера интересуют.
Соответственно, у каждого сервера будут установлены свои флаги, указанные в символьном виде,
например, \WSt\WinNt\PBrow\MBrow\.
Параметр w6 (по умолчанию w6=10000) задает TimeOut в ms. |
| DOMAINLIST |
Возвращает список доменов в сети. Функция имеет параметры w2..w4. Все параметры необязательные. Параметр w2 (по умолчанию w3=.) задает имя компьютера в URL-кодировке. По умолчанию это точка (т.е. localhost), обозначающая текущий компьютер. Использование URL-кодировки имени имеет свои достоинства. Например, для задания пустого имени достаточно указать + или %20.
Параметр w3 (по умолчанию w3=0) задает уровень детализации.
DomainName HostName IP
. localhost 127.0.0.1
kouriakine localhost 174.21.4.33
abbey abbot 174.21.4.21
...
Следует иметь в виду, что 1 уровень детализации работает
существенно медленнее, чем 0 и может привести
к подвисанию потока программы.
Поэтому использовать этот уровень детализации надо очень осторожно.
Параметр w4 (по умолчанию w4=10000) задает TimeOut в ms. |
| IPADDRESS |
Возвращает IP адрес компьютера, имя которого задано параметром w2. Если параметр не указан, имеется в виду локальный компьютер. |
| MACADDRESS |
Возвращает MAC адрес сетевой карты компьютера, имя которого задано параметром w2. Если параметр не указан, имеется в виду локальный компьютер. |
| COMPORTLIST |
Возвращает список COM-портов на локальном компьютере, например, "COM1,COM2,COM3".
Функция не имеет параметров. |
| ADAMTRAFFIC |
Возвращает траффик устройств ADAM в формате TxPolls TxBytes RxPolls RxBytes.
TxPolls - счетчик запросов передатчика. TxBytes - счетчик переданных байтов передатчика. RxPolls - счетчик ответов, успешно полученных приемником. RxBytes - счетчик байтов, успешно полученных приемником. Параметр w2 задает источник: COM-порт (например, COM1) или имя конкретного устройства. Если источник не указан или указан источник * (звездочка=любой), то выдается суммарная статистика для всех устройств. |
| ColorCode | Возвращает по имени w2 цвета RGB код цвета $GGBBRR. Например ColorCode Red = $0000FF. |
| ColorName | Возвращает по RGB коду w2 цвета $GGBBRR его имя. Например ColorName $0000FF = Red. |
| CharsetCode | Возвращает по имени w2 кодировки символов её код. Например CharsetCode Russian = 204. |
| CharsetName | Возвращает по коду w2 кодировки символов её имя. Например CharsetName 204 = Russian. |
| PitchCode | Возвращает по имени w2 ширины фонта его код. Например PitchCode Default = 0. |
| PitchName | Возвращает по коду w2 ширины фонта его имя (Default/Fixed/Variable). Например PitchName 0 = Default. |
| HasherCode | Возвращает по имени w2 хеш-функции её кодовый номер, который можно использовать при вызове функции HashIndexOf Например HasherCode Crc16Modbus = 77. |
| HasherName | Возвращает по коду хеш-функции w2 имя алгоритма хеширования, cм. функцию HashIndexOf. Например HasherName 77 = Crc16Modbus. |
| DaqSysInfo |
Возвращает по идентификатору w2 значение параметра DAQ системы:
Tag.Count - Счетчик всех тегов
Curve.Count - Счетчик всех кривых
Device.Count - Счетчик всех устройств
Sensor.Count - Счетчик всех сенсоров
Tab_Window.Count - Счетчик всех окон Tab_Window
Tab_Window.DrawView.Count - Счетчик рисований окна
Tab_Window.MonitorCall.Count - Счетчик вызовов монитора окон
Tab_Window.MonitorDraw.Count - Счетчик рисований в мониторе окон
Curve_Window.Count - Счетчик всех окон Curve_Window
Curve_Window.DrawView.Count - Счетчик рисований окна
Curve_Window.DrawCurve.Count - Счетчик рисований кривых
Curve_Window.DrawPoint.Count - Счетчик рисований точек
Curve_Window.MonitorCall.Count - Счетчик вызовов монитора окон
Curve_Window.MonitorDraw.Count - Счетчик рисований в мониторе окон
Curve_Window.MonitorCurve.Count - Счетчик рисований кривых в мониторе
Curve_Window.MonitorPoint.Count - Счетчик рисований точек в мониторе
Spectr_Window.Count - Счетчик всех окон Spectr_Window
Circuit_Window.Count - Счетчик всех окон Circuit_Window
Circuit_Window.DrawView.Count - Счетчик рисований окна
Circuit_Window.MakeBitmap.Count - Счетчик создания изображений сенсора
Circuit_Window.DrawSensor.Count - Счетчик рисований (реальных) сенсора
Circuit_Window.PollSensor.Count - Счетчик опросов для обновления сенсора
Circuit_Window.TagEvalCall.Count - Счетчик вызовов формулы TagEval(v)
Circuit_Window.LedEvalCall.Count - Счетчик вызовов формулы LedEval(v)
Circuit_Window.PainterCall.Count - Счетчик вызовов сценариев Painter(v)
Circuit_Window.PainterApiCall.Count - Счетчик вызовов функций Painter
Circuit_Window.PainterApiDraw.Count - Счетчик рисований внутри Painter
Circuit_Window.MonitorCall.Count - Счетчик вызовов монитора окон
Circuit_Window.MonitorDraw.Count - Счетчик рисований в мониторе
Чтение счетчиков позволяет узнавать важную информацию о системе и мониторировать её производительность.
|
| SYSTEM |
Возвращает системные параметры, по категории (w2) и имени (w3): ParamStr('SYSTEM w2 w3')
Параметр w2 задает категорию: OS, RAM, PROCESS, METRICS, SCREEN. Параметр w3 задает имя интересующего параметра в данной категории.
|
| GUARD |
Возвращает текущий уровень доступа Lock, Guest, User, Root.
Функция не имеет параметров. |
| CURVENAME |
Возвращается имя кривой с номером w2 или пустая строка, если кривой с таким номером нет. Номера кривых начинаются с 0. Функция позволяет просмотреть всю таблицу кривых CRW-DAQ. |
| CURWINNAME |
Возвращается имя окна-кривых Curve_Window с номером w2 или пустая строка,
если окна с таким номером нет. Номера окон начинаются с 0. Функция позволяет просмотреть всю таблицу окон-кривых CRW-DAQ. |
| CIRWINNAME |
Возвращается имя окна-мнемосхем Circuit_Window с номером w2 или пустая строка,
если окна с таким номером нет. Номера окон начинаются с 0. Функция позволяет просмотреть всю таблицу окон-мнемосхем CRW-DAQ. |
| TABWINNAME |
Возвращается имя окна-таблицы Tab_Window с номером w2 или пустая строка,
если окна с таким номером нет. Номера окон начинаются с 0. Функция позволяет просмотреть все окна-таблицы CRW-DAQ. |
| SPEWINNAME |
Возвращается имя окна-спектра Spectr_Window с номером w2 или пустая строка,
если окна с таким номером нет. Номера окон начинаются с 0. Функция позволяет просмотреть всю таблицу окон-спектров CRW-DAQ. |
| MAINCONSOLE |
Возвращает имя главной консоли, это ГЛАВНАЯ КОНСОЛЬ или MAIN CONSOLE,
в зависимости от текущего языка.
Функция не имеет параметров. |
| КОНСОЛЬ CONSOLE |
Возвращает имя консоли для устройства с именем w2, это КОНСОЛЬ ... или CONSOLE ..., в зависимости от текущего языка. |
| GETENV | Возвращает переменную окружения с именем w2 для текущего процесса. |
| FILESEARCH |
Поиск файла с коротким (без пути) именем w2 в списке путей, содержащихся
в списке переменных окружения, заданных в параметре w3.
Возвращает полное имя найденного файла или пустую строку, если файл не найден. Имя файла w2 задано в URL-кодировке (обычные имена пишутся "как есть"). Список имен переменных окружения w3 для поиска также задан в URL-кодировке, что позволяет разделять переменные окружения в списке символом +. Если параметр w3 отсутствует, то по умолчанию задается параметр Path+CRW_DAQ_SYS_PATH+CRW_DAQ_CONFIG_PATH, то есть поиск осуществляется сначала в списке путей, заданных системной переменной Path (общесистемные пути Windows), затем в списке путей, заданных переменной CRW_DAQ_SYS_PATH (пути поиска утилит пакета CRW-DAQ), а затем в списке путей, заданных переменной CRW_DAQ_CONFIG_PATH (пути поиска текущей конфигурации CRW-DAQ). Каждая из переменных окружения в списке w3 должна содержать разделенный точкой с запятой список путей, по которым будет осуществляться поиск файла. Например:
// Поиск ping.exe в системных каталогах Windows
ping:=ParamStr('FileSearch ping.exe Path');
// Поиск rmtshare.exe в библиотеке утилит CRW-DAQ
rmtshare:=ParamStr('FileSearch rmtshare.exe CRW_DAQ_SYS_PATH');
// Поиск arj.exe во всех списках Windows и CRW-DAQ
arj:=ParamStr('FileSearch arj.exe');
|
| ADDSEARCHPATH |
Добавление пути поиска в переменную окружения.
В переменную окружения с именем w2, содержащую разделенный точкой с запятой
список путей поиска, добавляется путь, заданный в параметре w3.
Возвращает переменную окружения w2 после добавления пути или пустую строку,
если добывление прошло неудачно. Список не изменяется, если путь уже есть в списке.
Имя каталога может задаваться относительно оснновного конфигурационного файла.
Например:
// Добавить c:\temp в переменную Path с путями поиска Windows
s:=ParamStr('AddSearchPath Path c:\Temp');
|
| REMSEARCHPATH |
Удаление пути поиска из переменной окружения.
Из переменной окружения с именем w2, содержащей разделенный точкой с запятой
список путей поиска, удаляется путь, заданный в параметре w3.
Возвращает переменную окружения w2 после удаления пути или пустую строку,
если удаление прошло неудачно. Список не изменяется, если пути не было в списке.
Имя каталога может задаваться относительно оснновного конфигурационного файла.
Например:
// Удалить c:\temp из переменной Path с путями поиска Windows
s:=ParamStr('RemSearchPath Path c:\Temp');
|
| FEXPAND |
Возвращает полное имя файла по имени w2. Полное имя файла имеет полный путь и нормализовано. Например, имена со ссылками типа .\ и ..\ преобразуются в нормальное имя. Функция гарантирует, что имена файлов после вызова можно сравнивать, например:
IsSameText(ParamStr('fexpand c:\data\temp\..\x.dat'),ParamStr('fexpand c:\data\x.dat'))=True
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| GETEXEBYFILE |
Возвращает исполняемый файл программы, которая ассоциирована с открытием файла или документа по имени w2. Файл документа должен существовать, а имя файла должно быть полным (с путем и расширением). Например, для файла *.htm исполняемым файлом будет iexplore.exe, для файлов *.doc - Word.exe и т.д. Полученное имя исполняемого файла позволяет запустить этот файл с указанием исходного файла-документа в качестве параметра в командной строке. В результате появляется возможность открывать файлы документов при помощи внешних программ. Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| EXTRACTFILEPATH |
Возвращает каталог файла по его полному имени w2.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| EXTRACTFILENAME |
Возвращает только имя файла w2, без каталога и расширения.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| EXTRACTFILEEXT |
Возвращается расширение по полному имени файла w2.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| ADDBACKSLASH |
Добавляет, если надо, обратный слэш '\' в конец имени каталога w2.
Используется для формирования имен файлов.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| AdaptFileName |
Адаптирует имя файла, см. AdaptFileName.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| AdaptExeFileName |
Адаптирует имя исполняемого файла, см. AdaptExeFileName.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| AdaptDllFileName |
Адаптирует имя файла динамической библиотеки.
Сначала адаптирует имя файла, см. AdaptFileName.
Затем корректирует расширение (.dll/.so для Windows/Unix).
А также удаляет/добавляет префикс lib в именах библиотек для Windows/Unix.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| AdaptLnkFileName |
Адаптирует имя файла ярлыка Рабочего Стола.
Сначала адаптирует имя файла, см. AdaptFileName.
Затем корректирует расширение (.lnk/.desktop для Windows/Unix).
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| ReadShellLink |
Читает содержимое файла ярлыка Рабочего Стола в виде текста.
Файл должен иметь расширение (.lnk/.desktop для Windows/Unix).
Возвращает текст из строк (полей) вида Name=Value, разделенные EOL.
Исполняемая команда имеет имя поля Exec. Другие поля зависят от системы.
Под Windows доступны поля TargetPath, Arguments, WorkingDirectory,
WindowStyle, IconLocation, Hotkey, Description.
Под Unix поля соответствуют спецификации
.desktop
файлов.
Имя файла w2 задано в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| MAKERELATIVEPATH |
Возвращает относительный путь файла w2, заданный относительно файла w3.
Имена файлов w2, w3 заданы в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| FORCEPATH |
Задать (насильно) путь w2 к файлу w3.
Каталог результата берется из w2, а имя с расширением - из w3.
Имена файлов w2, w3 заданы в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| DEFAULTPATH |
Задать (если не задан) путь w3 к файлу w2.
Если имя файла w2 - полное (с каталогом), ничего не делается.
Если имя файла w2 - не полное (без каталога), каталог берется из w3.
Имена файлов w2, w3 заданы в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| FORCEEXTENSION |
Присвоить (насильно) файлу w2 расширение w3.
Каталог и имя файла результата берется из w2, а расширение - из w3.
Имена файлов w2, w3 заданы в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| DEFAULTEXTENSION |
Присвоить (если не задано) файлу w2 расширение w3.
Если имя файла w2 - полное (с расширением), ничего не делается.
Если имя файла w2 - не полное (без расширения), расширение берется из w3.
Имена файлов w2, w3 заданы в URL-кодировке. Это позволяет обрабатывать имена файлов, содержащие пробелы. |
| REGISTRY |
Прочитать из реестра Windows данные. Функция имеет обязательные параметры w2, w3, w4, которые заданы в URL-кодировке (это позволяет работать с произвольными строками). Параметр w2 (ROOTKEY) может принимать значения:
HKCR или HKEY_CLASSES_ROOT
HKLM или HKEY_LOCAL_MACHINE
HKCU или HKEY_CURRENT_USER
HKU или HKEY_USERS
HKCC или HKEY_CURRENT_CONFIG
HKDD или HKEY_DYN_DATA
HKPD или HKEY_PERFORMANCE_DATA
Параметр w3 (PATH) задает путь переменной реестра. Параметр w4 (NAME) задает имя переменной реестра.
Данные возвращаются в виде двоичной строки. Для строковых данных это просто строка,
для целых чисел (DWORD) и двоичных данных (BINARY) это dump.
|
| REGISTRY |
Записать в реестр Windows данные. Функция имеет обязательные параметры w2, w3, w4, w5 которые заданы в URL-кодировке (это позволяет работать с произвольными строками). Параметр w2 (ROOTKEY) может принимать значения:
HKCR или HKEY_CLASSES_ROOT
HKLM или HKEY_LOCAL_MACHINE
HKCU или HKEY_CURRENT_USER
HKU или HKEY_USERS
HKCC или HKEY_CURRENT_CONFIG
HKDD или HKEY_DYN_DATA
HKPD или HKEY_PERFORMANCE_DATA
Параметр w3 (PATH) задает путь переменной реестра. Параметр w4 (NAME) задает имя переменной реестра. Параметр w5 (DATA) задает записываемые данные. Для строковых переменных это просто строка, а для целых чисел (DWORD) и двоичных данных (BINARY) это что-то вроде url_packed(dump(123)).
Функция возвращает данные после записи (при успехе это будет просто DATA).
Данные возвращаются в виде двоичной строки. Для строковых данных это просто строка,
для целых чисел (DWORD) и двоичных данных (BINARY) это что-то вроде dump.
|
| GetSystemAssoc |
Возвращает тип файла, связанный с расширением w2.
Например, для .lm9 это VisualTech.DieselPascal.CrossMachine. |
| GetSystemFType |
Возвращает командную строку, открывающую тип файла, заданный в w2.
Например, для VisualTech.DieselPascal.CrossMachine это будет "C:\Program Files\VisualTech\DieselPascal\exewindows\CrossMachine.exe" "%1" %* |
| GetSystemAssocExe |
Возвращает исполянемый файл, связанный с расширением w2 (открывающий его).
Например, для .lm9 это C:\Program Files\VisualTech\DieselPascal\exewindows\CrossMachine.exe. |
| GetSystemFTypeExe |
Возвращает исполянемый файл, открывающий тип файла, заданный в w2.
Например, для VisualTech.DieselPascal.CrossMachine это C:\Program Files\VisualTech\DieselPascal\exewindows\CrossMachine.exe. |
| CreateGUID CreateClassID |
Генерирует и возвращает "глобально уникальный идентификатор" GUID (Globally Unique Identifier)
в формате, заданном словом w2, из списка (str, bin, hex, nice).
Если слово w2 не указано, принимается формат по умолчанию str.
CreateGUID str - возвращает GUID в виде строки канонического вида {6B7826DD-B281-4BA9-B32F-AF78A24DEDA6}. CreateGUID bin - возвращает GUID в виде строки с двоичным содержимым 16-байтного значения GUID. CreateGUID hex - возвращает GUID в виде строки вида DD26786B81B2A94BB32FAF78A24DEDA6 в кодировке hex_encode. CreateGUID nice - возвращает GUID в виде строки вида jc8gbwozo19w7cheja66q55e4y в кодировке nice_encode. |
| SpecFolder |
Возвращает системный каталог (special folder) с идентификатором w2.
Если слово w2 не указано или равно '*', возвращается список системных каталогов.
Слово w2 принимает одно из приведенных значений:
Пример:
paramstr('specfolder *') // Список
paramstr('specfolder desktop'); // Рабочий стол пользователя
DESKTOP = Папка пользователя Рабочий Стол
INTERNET =
PROGRAMS = Папка пользователя меню Программы
CONTROLS =
PRINTERS =
PERSONAL = Папка пользователя Мои Документы
FAVORITES = Папка пользователя Избранное
STARTUP = Папка пользователя Автозагрузка
RECENT = Папка пользователя Недавнее
SENDTO = Папка пользователя Послать...
BITBUCKET =
STARTMENU = Папка пользователя меню Пуск
MYDOCUMENTS =
MYMUSIC = Папка пользователя Музыка
MYVIDEO = Папка пользователя Видео
DESKTOPDIRECTORY = Папка пользователя Рабочий Стол
DRIVES =
NETWORK =
NETHOOD = Папка пользователя Сетевое Окружение
FONTS = Папка системная со шрифтами
TEMPLATES = Папка пользователя с шаблонами
COMMON_STARTMENU = Папка общая меню Пуск
COMMON_PROGRAMS = Папка общая меню Программы
COMMON_STARTUP = Папка общая меню Автозагрузка
COMMON_DESKTOPDIRECTORY = Папка общая Рабочий Стол
APPDATA = Папка пользователя для файлов приложений
PRINTHOOD =
LOCAL_APPDATA = Папка пользователя для настроек приложений
ALTSTARTUP =
COMMON_ALTSTARTUP =
COMMON_FAVORITES = Папка общая Избранное
INTERNET_CACHE =
COOKIES =
HISTORY =
COMMON_APPDATA = Папка общая для файлов приложений
WINDOWS = Папка системная где установлена Windows
SYSTEM = Папка системная Windows\System32
PROGRAM_FILES = Папка системная %ProgramFiles%
MYPICTURES = Папка пользователя Изображения
PROFILE = Папка пользователя домашняя (профиль)
SYSTEMX86 = Папка системная Windows\System32 для 32-разрядной системы
PROGRAM_FILESX86 = Папка системная %ProgramFiles(x86)%
PROGRAM_FILES_COMMON = Папка системная %CommonProgramFiles%
PROGRAM_FILES_COMMONX86 = Папка системная %CommonProgramFiles(x86)%
COMMON_TEMPLATES = Папка общая Шаблоны
COMMON_DOCUMENTS = Папка общая Документы
COMMON_ADMINTOOLS = Папка общая для команд администрирования
ADMINTOOLS = Папка пользователя для команд администрирования
CONNECTIONS =
COMMON_MUSIC = Папка общая Музыка
COMMON_PICTURES = Папка общая Изображения
COMMON_VIDEO = Папка общая Видео
RESOURCES = Папка системная с ресурсами
RESOURCES_LOCALIZED =
COMMON_OEM_LINKS =
CDBURN_AREA = Папка системная для создания CD/DVD
COMPUTERSNEARME =
|
| AnsiCodePage | Возвращает кодовую страницу ANSI (GetACP) для программ Windows. Для русской версии Windows это 1251. |
| OemCodePage | Возвращает кодовую страницу OEM (GetOEMCP) для программ DOS. Для русской версии Windows это 866. |
| AppFormBounds | Возвращает абсолютные экранные координаты Left,Top,Right,Bottom формы приложения, т.е. главного окна CRW-DAQ. |
| AppClientBounds | Возвращает абсолютные экранные координаты Left,Top,Right,Bottom клиентской области приложения, т.е. клиентской области главного окна CRW-DAQ, которое содержит в себе остальные окна. Координаты клиентской области приложения нужны потому, что в функциях WinDraw, WinSelect координаты окон задаются относительно клиентской области приложения, в то время как в функции ClickParams координаты объектов (мыши, окна мнемосхемы, сенсора) выдаются в абсолютных экранных координатах. |
| OleDbProviderNames | Возвращает список провайдеров OLEDB или ADO, т.е. компонентов для доступа к базам данных через интерфейс ADO. Программный интерфейс ADO используется в пакете для доступа к СУБД. Список провайдеров позволяет проверить наличие в системе нужного провайдера. |
| OdbcDriverNames | Возвращает список драйверов ODBC, т.е. библиотек для доступа к базам данных через интерфейс ODBC. Программный интерфейс ADO используется в пакете для доступа к СУБД. При этом провайдеры ADO могут использовать драйверы ODBC. Список драйверов позволяет проверить наличие в системе нужного драйвера. |
| MainInstance | Возвращает идентификатор (handle) экземпляра приложения. |
| MainThreadId | Возвращает идентификатор (id) главного потока приложения. |
| CurrentThreadId | Возвращает идентификатор (id) текущего потока программы. |
| CurrentProcessId | Возвращает инентификатор текущего процесса (PID). |
| ApplicationHandle | Возвращает инентификатор (handle) окна приложения. Как правило, это скрытое окно, нужное для обработки событий. |
| MainFormHandle | Возвращает инентификатор (handle) главной формы (окна) приложения. |
| Polling.UseMsgPump | Возвращает флаг (0/1) использования (Use) потоком опроса (Polling) очереди сообщений (Message Pump). Если это флаг установлен, то поток программы использует очередь сообщений (PeekMessage/DispatchMessage). Если задан параметр (w2=0/1), то задается новое значение режима опроса очереди сообщений. Очередь сообщений нужна в отдельных случаях, например, при использовании COM объектов (таких как ADO, см. DbApi). Использование очереди событий всеми потоками по умолчанию задается в параметре Crw32.ini [System] TPolling.DefMsgPump файла настроек. Режим очереди сообщений рекомендуется задавать однократно при старте программы. |
| Polling.ThreadID | Возвращает идентифиукатор потока опроса (Polling). |
| Polling.Delay | Возвращает задержку (delay) потока опроса (Polling) в миллисекундах (ms). Фактически этот параметр определяет период опроса потока, т.к. цикл опроса основан на задержке (засыпании) потока. После очередного опроса в цикле поток засыпает на заданную величину задержки, чтобы освободить процесссор для других задач. После (заданной) задержки поток просыпается и выполняет очередной опрос, обеспечивая периодический опрос. Поток может проснуться и досрочно, если случились важные события (нажатие кнопки, получение сообщения devsend и другие). При задании параметра (w2) в диапазоне 1..5000 можно динамически менять частоту потока опроса (Polling). |
| Polling.Priority | Возвращает приоритет потока опроса (Polling) из набора ( tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest, tpTimeCritical ). При задании параметра (w2) можно динамически менять приоритет потока опроса (Polling). |
| [:digit:] | Возвращает набор символов Posix [:digit:] - десятичных цифр '0123456789'. Наборы символов используются при анализе строк. |
| [:xdigit:] | Возвращает набор символов Posix [:xdigit:] - шестнадцатеричных цифр '0123456789ABCDEFabcdef'. Наборы символов используются при анализе строк. |
| [:upper:] | Возвращает набор символов Posix [:upper:], т.е. букв верхнего регистра 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. В набор включен только минимальный (латинский) алфавит, т.к. он одинаков во всех кодировках. Наборы символов используются при анализе строк. |
| [:lower:] | Возвращает набор символов Posix [:lower:], т.е. букв нижнего регистра 'abcdefghijklmnopqrstuvwxyz'. В набор включен только минимальный (латинский) алфавит, т.к. он одинаков для всех кодировок. Наборы символов используются при анализе строк. |
| [:blank:] | Возвращает набор символов Posix [:blank:], т.е. пустых символов (пробел и табуляция). Наборы символов используются при анализе строк. |
| [:space:] | Возвращает набор символов Posix [:space:], т.е. пробельные символы, включая (пробел SP, HT и VT табуляцию, символы разрыва строки CR,LF,FF). Наборы символов используются при анализе строк. |
| [:cntrl:] | Возвращает набор символов Posix [:cntrl:], т.е. управляющие символы. Это символы с кодами Chr(0)..Chr(32) и Chr(127). Символы [:blank:] и [:space:] входят в набор [:cntrl:]. Управляющие символы считаются неграфическими (не имеют начертания) и служат для управления потоком данных при использовании текстовых протоколов связи. Наборы символов используются при анализе строк. |
| [:alpha:] | Возвращает набор символов Posix [:alpha:], т.е. всех буквенных символов, включая [:upper:] и [:lower:]. Наборы символов используются при анализе строк. |
| [:alnum:] | Возвращает набор символов Posix [:alnum:], т.е. цифр и букв - [:digit:] и [:alpha:]. Наборы символов используются при анализе строк. |
| [:punct:] | Возвращает набор символов Posix [:punct:], т.е. символов пунктуации '!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~'. Наборы символов используются при анализе строк. |
| [:print:] | Возвращает набор символов Posix [:print:], т.е. печатных символов, включая [:alnum:], [:punct:] и пробел. Наборы символов используются при анализе строк. |
| [:graph:] | Возвращает набор символов Posix [:graph:], т.е. графических (имеющих начертание) символов, включая [:alnum:] и [:punct:]. Наборы символов используются при анализе строк. |
| [:word:] | Возвращает набор символов Posix [:word:], т.е. символов для слов (идентификаторов), включая [:alnum:] и символ подчеркивания '_'. Наборы символов используются при анализе строк. |
| FetchUriScheme w2 |
Выделяет из универсального адреса URI (w2) компонент scheme.
При этом декодирование pct-decode к компоненту не применяется,
при необходимости (если в строке есть символ процента) делайте это самостоятельно.
Универсальные адреса ресурсов URI согласно RFC-3986 имеют формат scheme://authority/path?query#fragment. |
| FetchUriAuthority w2 |
Выделяет из универсального адреса URI (w2) компонент authority.
При этом декодирование pct-decode к компоненту не применяется,
при необходимости (если в строке есть символ процента) делайте это самостоятельно.
Универсальные адреса ресурсов URI согласно RFC-3986 имеют формат scheme://authority/path?query#fragment. |
| FetchUriPath w2 |
Выделяет из универсального адреса URI (w2) компонент /path.
При этом декодирование pct-decode к компоненту не применяется,
при необходимости (если в строке есть символ процента) делайте это самостоятельно.
Обратите ввнимание, что ведущий символ-разделитель / включается в компонент /path.
Универсальные адреса ресурсов URI согласно RFC-3986 имеют формат scheme://authority/path?query#fragment. |
| FetchUriQuery w2 |
Выделяет из универсального адреса URI (w2) компонент query.
При этом декодирование pct-decode к компоненту не применяется,
при необходимости (если в строке есть символ процента) делайте это самостоятельно.
Универсальные адреса ресурсов URI согласно RFC-3986 имеют формат scheme://authority/path?query#fragment. |
| FetchUriFragment w2 |
Выделяет из универсального адреса URI (w2) компонент fragment.
При этом декодирование pct-decode к компоненту не применяется,
при необходимости (если в строке есть символ процента) делайте это самостоятельно.
Универсальные адреса ресурсов URI согласно RFC-3986 имеют формат scheme://authority/path?query#fragment. |
| FindServicePort w2 w3 | Ищет и возвращает номер порта для сетевой службы w2 и протокола w3. Например, службе ssh соответствует порт 22. По умолчанию (если не задан) используется протокол tcp. Таблица служб берется из файла /etc/services. |
| ServicePortName w2 w3 w4 | Ищет и возвращает имя сетевой службы для порта номер w2 и протокола w3. Например, порту 22 соответствует служба ssh. По умолчанию (если не задан) используется протокол tcp. Параметр w4 (равный 1 по умолчанию) задает номер имени (1 - основное имя, 2+ - псевдонимы). Таблица служб берется из файла /etc/services. |
| ServicePortInfo w2 w3 | Ищет и возвращает описание сетевой службы для порта номер w2 и протокола w3. Например, порту 22 соответствует описание ssh # SSH Remote login protocol. В описании сетевой службы идет список имен (псевдонимов) службы, и комментарий ( # ... ). По умолчанию (если не задан) используется протокол tcp. Таблица служб берется из файла /etc/services. |
| ServicePortList | Возвращает (большой) список известных сетевых служб. Таблица служб берется из файла /etc/services. |
| EtcServicesFile | Возвращает имя файла c таблицей сетевых служб. Таблица служб обычно находится в файле /etc/services или %SystemRoot%\system32\drivers\etc\services. |
| ListOf GetListOf |
Функция чтения (системных) таблиц с возможностью выборки данных по значениям ключей
в стиле SQL запросов.
Общий формат запроса: ListOf TABLE [where key1=value1,[key2=value2[,…]]] Здесь TABLE - имя таблицы, а дальше необязательный запрос, например:
s:=ParamStr('ListOf tables'); // Список доступных таблиц
s:=ParamStr('ListOf keys'); // Список ключей для выборки таблиц
s:=ParamStr('ListOf pids'); // Список всех работающих процессов
s:=ParamStr('ListOf pids where pid=4321'); // Процесс по PID
s:=ParamStr('ListOf pids where name=dns.exe'); // Процесс по имени
s:=ParamStr('ListOf colors where name=lime,format=hex'); // Код цвета по имени
s:=ParamStr('ListOf colors where code=$00ff00,format=str'); // Имя цвета по коду
Результат (таблица или выборка из неё) возвращается в виде текста из строк, разделенных EOL.
Формат вывода зависит от таблицы.
Формат при выборке из таблицы может отличаться от формата при выводе всей таблицы и зависит от ключей.
Формат можно уточнить в консоли командой @paramstr listof ....
Краткое описание:
-------------------------------------------------------------------------------
ParamStrListOf(arg) и ParamStr('ListOf '+arg) - получить список параметров из
запрошенной системной таблицы (например, pids - список работающих процессов).
-------------------------------------------------------------------------------
Вызов Таблица Ключи where key=value Комментарий
-------------------------------------------------------------------------------
ListOf tables Список доступных для чтения таблиц
ListOf keys (name) Список ключей для таблицы (name)
ListOf pids (pid,ppid,name) Список процессов PID,PPID,PRIO,NAME
ListOf processes (pid,ppid,name) Список п-в PID,PPID,PRIO,NAME,CMDLINE
ListOf modules (pid) Список модулей для процесса PID
ListOf windows (pid,class,title) Список окон на Рабочем Столе
ListOf threads (name) Список потоков Polling
ListOf tasks (tid,pid) Список задач task_xxx
ListOf colors (name,code,format) Список цветов с выборкой по коду/цвету
ListOf netifs (mode) Список сетевых интерфейсов
ListOf specfolders (name) Список специальных именованных каталогов
ListOf services (name,port,protocol) Список сетевых сервисов (портов tcp,udp)
ListOf comports (name,path) Список COM-портов
ListOf users (name) Список пользователей (users)
ListOf hosts (name) Список известных компьютеров (hosts)
ListOf groups (name) Список членства в группах (groups)
ListOf environs (name) Список переменных окружения
ListOf ftypes (name) Список ftype(Windows)/mime(Unix)
ListOf assocs (name) Список assoc(Windows/Unix)
ListOf daq_tags (name) Список тегов DAQ системы
ListOf daq_curves (name) Список кривых DAQ системы
ListOf daq_devices (name,family,model) Список устройств DAQ системы
ListOf daq_windows (name,type) Список окон DAQ системы
-------------------------------------------------------------------------------
Например:
s:=paramstr('ListOf pids where name=firebird');
s:=paramstr('ListOf colors where code=$FF0000');
-------------------------------------------------------------------------------
Пример работы функции:
///////////////////////////////////////////////////////////////////
// Выполнено в консоли &TESTBENCH демо-конфигурации demo_testbench:
///////////////////////////////////////////////////////////////////
// Список всех доступных таблиц
@paramstr listof tables
specfolders,colors,comports,tables,threads,services,keys,pids,processes,tasks,windows,netifs,modules
// Список ключей для всех доступных таблиц
@paramstr listof keys
pids keys: pid,ppid,name
processes keys: pid,ppid,name
modules keys: pid
windows keys: pid,class,title,mode
threads keys: name
tasks keys: tid,pid
colors keys: name,code,format
netifs keys: mode
specfolders keys: name
services keys: name,port,protocol
comports keys: name,path
users keys: name
hosts keys: name
groups keys: name
environs keys: name
ftypes keys: name
ftypes keys: name
daq_tags keys: name
daq_curves keys: name
daq_devices keys: name,family,model
daq_windows keys: name,type
keys keys: name
tables keys: name
// Процесс по имени
@paramstr listof pids where name=dns
793241, 1, 120, dns
// COM-порт по имени
@paramstr listof comports where name=com1
COM1 /dev/ttyUSB0 pl2303
// Имя цвета по коду
@paramstr listof colors where code=$00ff00,format=str
Lime
|
| DetectBlobImageType |
Функция DetectBlobImageType w2 w3 пытается определить (detect) тип изображения (image type)
для блоба (blob), заданного строкой w3 в кодировке w2. Кодировка w2 может быть
одной и списка b64,hex,pct,url. Предполагается, что блоб (из базы данных) передается в закодированном виде.
Функция пытается по ряду признаков (например, по сигнатуре) определить, может ли этот блоб содержать изображение.
Если признаки изображения обнаружены, возвращается строка, одна из списка bmp,gif,png,pbm,pgm,ppm,jpg,xpm,tif,pcx.
Например: s:=ParamStr('DetectBlobImageType b64 '+base64_encode(blob)); |
| SysLogSeverityList | Возвращает список кодов и имен уровней значимости (Severity) системного журнала (SysLog). |
| SysLogSeverityCode w2 | Возвращает числовой код уровня значимости (Severity) системного журнала (SysLog) по имени w2. |
| SysLogSeverityName w2 | Возвращает имя (строку идентификации) уровня значимости (Severity) системного журнала (SysLog) по его числовому коду w2. |
s:=paramstr('0'); {s='c:\crw32exe\crw32.exe'}
s:=paramstr('daqconfigfile'); {s='c:\daq\triton\config\triton.cfg'}
s:=paramstr('daqconfigpath'); {s='c:\daq\triton\config'}
s:=paramstr('daqdatapath'); {s='c:\daq\triton\data'}
s:=paramstr('defaultextension x .txt'); {s='x.txt'}
s:=paramstr('defaultextension x.bat .txt'); {s='x.bat'}
s:=paramstr('forceextension x.bat .txt'); {s='x.txt'}
s:=paramstr('userlist'); {s='alex'+CRLF+... on localhost}
s:=paramstr('userlist crwbox 1'); {s='alex Root On'+CRLF+'vinogradov User On'+CRLF+...}
s:=paramstr('registry HKCU Software\Microsoft\Windows\CurrentVersion\Explorer\Shell+Folders Desktop');
{s='c:\Documents and Settings\Alex\Рабочий Стол'}
s:=ParamStr('FileSearch ping.exe Path'); {s='c:\Windows\system32.ping.exe'}
s:=ParamStr('FileSearch gzip.exe CRW_DAQ_SYS_PATH'); {s="c:\Crw32exe\Resource\Tools\UnixUtils\add\bin\gzip.exe"}
s:=ParamStr('FileSearch arj.exe'); {s="c:\Crw32exe\Resource\Tools\UnixUtils\add\bin\arj.exe"}
s:=ParamStr('CreateGUID str'); {s="{6B7826DD-B281-4BA9-B32F-AF78A24DEDA6}"}
s:=ParamStr('CreateGUID hex'); {s="DD26786B81B2A94BB32FAF78A24DEDA6"}
s:=ParamStr('ListOf Colors where name=Lime,format=web'); {s="#00ff00"}
В случае, если строка состоит из трех слов
В случае, если строка состоит из двух слов
В случае, если строка состоит из одного слова
Функция читает строковое значение value – слово, следующее за именем переменной:
[Device]
s = SomeString
x = 1.5
s:=readini('s'); {s='SOMESTRING'}
x:=rval(readini('x')); {x=1.5}
В параметре txt передается ссылка на текст, куда будет записана секция. Поскольку функция возвращает этот параметр, допустима конструкция вида
txt:=readinisection(text_new,...);
в которой совмещается создание текста и чтение конфигурации.
В параметре how передается битовый флаг, влияющий на разбор конфигурации. Основные параметры такие:
В параметре cfg передается имя файла конфигурации. Если это имя - пустая строка, разбирается основной конфигурационный файл, что равносильно paramstr('DaqConfigFile'). Если имя не содержит расширения, применяется расширение по умолчанию .cfg. Если имя файла содержит относительный путь, считается, что путь указан относительно основного конфигурационного файла.
В параметре sec передается имя файла секции. Если это имя - пустая строка, разбирается секция данного устройства, что равносильно '['+devname+']'. Имя секции можно указывать без скобок, регистр роли не играет. Например, '[DAQ]' эквивалено 'Daq'.
var txt,i:integer;
txt:=readinisection(text_new,16,'','[Daq]');
for i:=0 to text_numln(txt)-1 do writeln(text_getln(txt,i));
b:=text_free(txt);
Вот основные виды применения переменных окружения:
s:=getenv('username'); {узнать имя пользователя}
b:=setenv('daq','c:\daq'); {задать переменную окружения daq=c:\daq}
s:=expenv('%daq%'); {узнать переменную daq через подстановку}
tid:=task_init(getcomspec+' /k set'); {подготовить вызов командного процессора}
b:=task_run(tid); {запустить командный процессор на выполнение}
b:=task_free(tid); {процесс унаследовал переменную daq}
writeln('Current process identifier is ',getpid);
ShowTooltip('text Demo preset stdOk btn1 Hello cmd1 "'
+ParamStr('HomeDir')+'\Resource\Shell\send2crwdaq.exe -p '+Str(getpid)+' -c @echo Hello"');
Эти функции дублируются в paramstr('DaqProgram'),paramstr('DeviceName'), но дают более короткий путь для получения указанных параметров.
[DeviceList]
&testdev = device software program
...
program test;
begin
writeln(progname); {'test'}
writeln(devname); {'&testdev'}
end.
Ссылка (reference) на объект - это положительное число, по которому система CRW-DAQ идентифицирует объект. Нулевая ссылка используется как признак несуществующиего объекта или ошибки. Не надо путать ссылку с указателем, то есть с адресом памяти, где расположен объект. В Daq Pascal указателей нет и не будет, так как указатели небезопасны, а Daq Pascal задуман как язык для сценариев измерений с высокой степенью защиты. Ссылки Daq Pascal больше похожи на Handle в Windows. Объекту сопоставляется ссылка, смысл которой известен только системе. Ссылки используются только для доступа к объектам через функции Daq Pascal и не имеют какого-либо другого смысла для клиента.
Система ведет реестр всех объектов программы, сопоставляя каждому объекту некое положительное целое число, по которому к нему можно быстро получить доступ. Это и есть ссылка, возвращаемая функцией reffind. Система знает, как по ссылке найти указатель объекта для работы с ним. Сами же указатели скрыты от клиента и недоступны ему. Работать с объектами пользователь CRW-DAQ может только через ссылки и функции Daq Pascal.
В отличие от указателей, ссылки гораздо более безопасны. Как правило, неверная ссылка не приведет к краху системы, а приведет к ошибке конкретной Daq программы, которую можно исправить и продолжить работу. Кроме того, в отличие от указателей, корректность ссылки всегда можно проверить. Вот признаки корректной ссылки:
В настоящей версии реализованы такие поисковые запросы:
tag:=reffind('Tag Button');
s:=refinfo(tag,'ClassName'); {'Tag'}
s:=refinfo(tag,'Type'); {'Tag'}
s:=refinfo(tag,'Name'); {'Button'}
b:=isettag(tag,0); {Использование ссылки}
dev:=reffind('Device &Main');
s:=refinfo(tag,'ClassName'); {'TDaqPascalDevice'}
s:=refinfo(tag,'Type'); {'Device'}
s:=refinfo(tag,'Name'); {'&Main'}
r:=devsend(dev,'Hello'); {Использование ссылки}
{
Enumerate tags,devices, curves, etc...
}
procedure EnumerateAll;
var t,i:integer; b:Boolean;
procedure EnumerateType(What:string);
begin
b:=text_addln(t,'--- '+What+' --- list:');
t:=enumerate(t,What);
end;
begin
t:=text_new;
EnumerateType('Tag');
EnumerateType('Task');
EnumerateType('Curve');
EnumerateType('Device');
EnumerateType('Polling');
EnumerateType('Window');
EnumerateType('Tab_Window');
EnumerateType('Curve_Window');
EnumerateType('Spectr_Window');
EnumerateType('Circuit_Window');
for i:=0 to text_numln(t)-1 do writeln(text_getln(t,i));
b:=text_free(t);
end;
Виртуальная консоль - это как бы два текстовых файла, один для чтения (консоль стандартного ввода - StdIn), другой для записи (консоль стандартного вывода - StdOut). Оговорка "как бы" не случайна - файл консоли может быть связан как с регулярным файлом (то есть дисковым или сетевым файлом операционной системы), так и с виртуальным файлом - буфером FIFO, который на самом деле файлом не является, а есть просто буфер в памяти, через который производится ввод данных, введенных в консольном окне или прием сообщений, посланных вызовом devsend или devpost, а также вывод в консольное окно. Оговорка "текстовых" тоже не случайна - все функции работы с консолью ориентированы на текстовую информацию, с разделением на строки, между которыми стоит разделитель crlf. Например, при чтении чисел предполагается, что числа представлены в символьном виде, в десятичной системе счисления.
Другой отличительной чертой консоли является то, что это файл с последовательным (а не произвольным) доступом. В сущности, консоль реализует дисциплину FIFO.
Наличие виртуального файла FIFO делает консоль одним из важнейших средств Daq Pascal для обмена сообщениями между разными программами Daq Pascal. Не забывайте, все программы Daq Pascal выполняются в отдельных программных потоках и нуждаются в механизме обмена данными. Консоль как раз и реализует очередь сообщений для обмена данными между Daq-программами.
Надо отметить, что в DAQ Pascal вообще поддерживается только один текстовый файл для ввода и один текстовый файл для вывода, то есть виртуальная консоль. Не следует считать это серьезным ограничением, так как программ в конфигурации может быть сколько угодно, каждая программа может заниматься своим файлом. Кроме того, файлы можно открывать и закрывать, если надо читать или записывать несколько файлов. Вообще, долго держать несколько открытых дисковых файлов одновременно - плохая практика. Например, при сбое питания данные в открытых файлах могут пропасть. Поэтому при необходимости записи порции данных в дисковый файл лучше открыть его, записать порцию данных и немедленно закрыть. В таком стиле можно работать с многими файлами, несмотря на то, что консоль всего одна.
echo(msg) выводит сообщение msg в окно Главная консоль.
Вывод буферизован через FIFO, поэтому управление возвращается
немедленно, без задержек.
Реальное отображение сообщения происходит асинхронно, по таймеру, в другом
потоке программы.
То есть вызов echo не помешает высокоприоритетному измерительному
потоку продолжать выполнение.
Функция вернет False, если буфер FIFO переполнен и не может
принять сообщение.
Аналогичный результат будет, если открыть виртуальный консольный файл
с именем CON: при помощи append('CON:').
В этом случае консольный вывод направляется в окно Главная консоль.
reset(fname) открывает файл консольного ввода.
Функция возвращает ноль при успехе или ненулевой код ошибки.
Функция закрывает консольный файл ввода, если он был открыт,
и открывает для ввода виртуальный файл с именем fname.
rewrite(fname) создает файл консольного вывода.
append(fname) открывает файл консольного вывода.
Функции возвращают ноль при успехе или ненулевой код ошибки.
Функции закрывают консольный файл вывода, если он был открыт,
и открывают для вывода виртуальный файл с именем fname.
Различие функций в том, что rewrite всегда создает новый файл,
уничтожая существующий (или очищая FIFO),
в то время как append открывает существующий файл для записи
в конец этого файла (или сохраняет содержимое FIFO).
read(var1,..varN) - чтение переменных
var1..varN без учета crlf.
readln(var1,..varN) - чтение переменных
var1..varN с учетом crlf.
Обе процедуры делают чтение переменных из текстовых файлов, отличие в том,
что read читает переменные до конца файла, игнорируя разделители crlf,
а readln читает до конца строки, то есть до появления crlf.
Обычно текст разбит на строки и используется readln
(см. также eoln).
Переменные var1..varN могут иметь тип char,
integer,real,string.
Надо заметить, что числовые переменные считываются не как двоичные данные,
а как данные в символьном представлении, в десятичной системе счисления.
Эта особенность связана с тем, что консоль - текстовый файл, там все данные
предполагаются символьными.
Если надо передавать двоичные данные, можно воспользоваться функциями
кодирования, например, dump,
mime_encode/decode, hex_encode/decode.
write(out1,..outN) - запись выражений
out1..outN без завершающего crlf.
writeln(out1,..outN) - запись выражений
out1..outN с завершающим crlf.
Обе процедуры делают запись списка выражений в текстовый файл, отличие в том,
что write не записывает в конце разделитель строки crlf,
а writeln записывает.
Обычно текст разбит на строки и используется writeln.
Выражения out1..outN могут иметь тип char,
integer,real,string.
Это могут быть константы, переменные, результаты функций или формульных выражений.
Каждый элемент списка может также сопровождаться спецификатором формата:
out:w
или
out:w:d
out - выводимое выражение
w - минимальная ширина поля вывода
d - число цифр после запятой (для вещественных чисел)
например:
writeln('Значение pi=',pi:7:4); { Значение pi= 3.1459 }
ioresult - возвращает и сбрасывает флаг ошибки консольного ввода-вывода.
Возвращает ноль при отсутствия ошибок ввода-вывода.
Возвращает ненулевое значение (код ошибки) при обнаружении ошибки ввода-вывода.
При чтении-записи файлов могут возникать различные ошибки ввода-вывода.
При этом система устанавливает флаг ошибки ввода-вывода и блокирует все
операции ввода-вывода до сброса флага (при блокировке все операции ввода-вывода
игнорируются).
Единственный способ сбросить флаг ошибки - прочитать статус.
Поэтому, по идее, после каждой операции ввода-вывода (или группы операций)
надо вызывать ioresult.
eof - возвращает и признак "конец файла" ("end of file")
консоли ввода.
В случае FIFO концом файла считается пустой буфер ввода.
Поскольку данные в буфере могут появиться как с клавиатуры, так и через
сообщения devmsg, состояние eof может
в любой момент измениться.
Поскольку чтение файла при достижении конца файла считается ошибкой,
при чтении данных из консоли всегда надо проверять eof.
Обычно цикл чтения данных из консоли выглядит примерно так:
while (ioresult=0) and not eof do begin
readln(s);
... do something with s ...
end;
eoln - возвращает и признак "конец строки" ("end of line")
консоли ввода.
Конец строки - это появление во входном потоке разделителя строк
CrLf.
eoln применяется при чтении данных c помощью read,
чтобы фиксировать достижение конца строки.
Это бывает нужно в случае, если число читаемых переменных
в строке неизвестно, например:
while not eoln do begin
read(data[i]);
i:=i+1;
end;
При чтении данных c помощью readln конец строки учитывается
автоматически, однако при этом число переменных в строке должно быть
известно.
Последнее замечание: если работа с регулярными файлами может вызвать при операциях ввода-вывода блокировку потока, то есть приостановку выполнения потока на некоторое время, при работе с FIFO все вызовы - не блокирующие, то есть программа всегда немедленно получает управление после выполнения ввода-вывода. Это связано с тем, что ввод-вывод в FIFO - это просто пересылка данных в памяти, которая выполняется быстро или не выполняется вообще. Кстати, при необходимости записи данных в регулярный файл из критичного по времени потока лучше всего создать программу - сервер, которая будет принимать от клиента сообщения в консоль ввода и записывать их в файл через консоль вывода. Таким образом, при правильной организации работа с консолью в системе CRW-DAQ никак не противоречит принципам организации RealTime систем.
Пример взаимодействия программ через консольную очередь сообщений.
Клиент в цикле формирует сообщения (просто значение счетчика вызовов).
Сервер читает сообщения из консоли и выводит их в дисковый файл,
а также в консольное окно.
[DeviceList]
&client = device software program
&server = device software program
[&server]
StdInFifo = 64 ; сделаем буфер ввода побольше...
StdOutFifo = 64 ; сделаем буфер вывода побольше...
...
program client;
var b:boolean; s:string;
begin
s:=str(runcount); {формирование сообщения серверу}
b:=devmsg('&server '+s+CrLf); {посылка сообщения серверу}
writeln(s); {вывод в консольное окно}
end.
...
program server;
var b:boolean; i:integer; s:string;
begin
{Start actions}
if runcount=1 then begin
b:=echo('Hello, user '+ParamStr('UserName')); {вывод в Главную Консоль}
writeln('Hello, user '+ParamStr('UserName')); {вывод в локальную консоль}
end else
{Stop actions}
if isinf(runcount) then begin
b:=echo('Goodbye, user '+ParamStr('UserName')); {вывод в Главную Консоль}
writeln('Goodbye, user '+ParamStr('UserName')); {вывод в локальную консоль}
end else
{Polling}
begin
if ioresult<>0 {проверить статус ввода-вывода}
then b:=echo('I/O error found!'); {сообщить об ошибке в Главную Консоль}
while not eof do begin {пока есть данные в FIFO ввода}
readln(s); {прочитать строку из FIFO ввода}
if rewrite('c:\server.log')=0 {открыть файл для вывода}
then writeln(s); {вывод в файл}
if append('') {закрыть файл=открыть FIFO}
then writeln(s); {вывод в консольное окно}
end;
end;
end.
f_reset(fname,mode) - функция открытия существующего файла. Закрывает файл, если он был открыт. Открывает существующий файл с именем fname, в режиме mode (0-ReadOnly,1-WriteOnly,2-Read/Write). Возвращает 0 при успехе или ненулевой код ошибки, как IOResult.
f_rewrite(fname,mode) - функция создания файла. Закрывает файл, если он был открыт. Создает новый файл именем fname, в режиме mode (0-ReadOnly,1-WriteOnly,2-Read/Write). Если файл существовал, он уничтожается перед созданием нового файла. Возвращает 0 при успехе или код ошибки, как IOResult.
f_read(n) - функция чтения файла. Читает из файла n байт и возвращает прочитанное в виде строки дампа (двоичных данных). Длина строки результата равна числу прочитанных байтов. Возвращает статус через функцию IOResult. Для анализа прочитанных данных можно использовать функции dump2i и т.д.
f_write(data) - функция записи файла. Записывает данные, заданные строкой дампа data в файл. Возвращает число записанных байтов. Возвращает статус через функцию IOResult. Двоичные данные для записи можно получить функцией dump.
f_size - размер файла. Возвращает текущий размер открытого файла или -1 при ошибке. Возвращает статус через функцию IOResult.
f_seek(pos) - функция позиционирования файла. Позиционирует указатель открытого файла в положение pos, если p >=0 , или -1 при ошибке. При позиционировании 0 соответствует началу файла, f_size - концу. Возвращает позицию указателя после выполнения операции. Вызов f_seek(-1) просто вернет текущую позицию указателя. Возвращает статус через функцию IOResult.
f_close - закрывает файл, если он был открыт. Файл также автоматически закрывается при остановке программы или при открытии другого файла. Возвращает статус через функцию IOResult.
program Example;
var x,y:real; i,io:integer; fname,s:string;
begin
fname:='c:\test.bin';
if fileexists(fname)
then io:=f_reset(fname,2)
else io:=f_rewrite(fname,2);
if io=0 then begin
s:=dimp(i)+dump(x)+dump(y);
if f_write(s) <> length(s) then writeln('Error ',IoResult);
writeln('File size ',f_size);
if f_seek(0)<>0 then then writeln('Error ',IoResult);
s:=f_read(4+8+8);
if length(s)=20 then begin
i:=dump2i(Copy(s,1,4));
x:=dump2i(Copy(s,5,8));
i:=dump2i(Copy(s,13,8));
end;
end else begin
writeln('Error ',io);
b:=f_close;
end;
end.
procedure TestBufferRW;
var fname,buff:string;
begin
fname:=''; buff:='';
writeln('Test ReadFileToBuffer/WriteBufferToFile:');
fname:=AdaptFileName(ExpEnv('$CRW_DAQ_SYS_TMP_DIR/mymodules.txt'));
if FileExists(fname) then bNul(FileErase(fname));
buff:=ParamStr('ListOf modules');
//writeln(buff);
if WriteBufferToFile(fname,buff,0)>0
then writeln('Luck write '+fname)
else writeln('Fail write '+fname);
if fileexists(fname) then begin
buff:=ReadFileToBuffer(fname,0,0); // Read all file
if (buff<>'')
then writeln('File readback:',EOL,buff)
else writeln('Error read file '+fname);
writeln('File properties:',EOL,GetFileProperties(fname,''));
end;
fname:=''; buff:='';
end;
Действие сообщения и возвращаемый результат зависит от типа устройства - приемника. В настоящее время:
Надо также иметь в виду, что посылка сообщения devsend устройству - приемнику приводит к его досрочному пробуждению, то есть к активизации потока устройства, даже если время опроса по таймеру еще не пришло. Можно даже посылать пустое сообщение devsend, чтобы только "разбудить" устройство - приемник, не посылая ему данных. После посылки сообщения devsend функция awakeflag устройства-приемника вернет true при очередном опросе. Это признак того, что устройство-приемник было "разбужено" досрочно посылкой сообщения devsend.
Выбор функции devsend или devpost зависит от задачи. Если после посылки сообщения требуется немедленная обработка со стороны приемника, то следует использовать синхронное сообщение devsend, чтобы досрочно пробудить устройство-приемник для выполнения обработки сообщения. Однако при большой частоте сообщений есть риск создания большой нагрузки для процессора из-за частого переключения контекста потоков. Если сообщения передается часто, содержат много данных, а время задержки их обработки приемником не критично, то лучше использовать асинхронные сообщения devpost. В этом случае сообщения будут записываться в очередь приемника без его досрочного пробуждения, что снижает частоту переключения потоков и уменьшает нагрузку на процессор. Приемник будет работать в привычном для него темпе, заданном таймером. При этом задержка между посылкой сообщения и его обработкой будет определяться периодом опроса устройства-приемника.
Таким образом, функция devsend применяется для уменьшения задержки между посылкой сообщения и обработкой данных приемником - за счет возможного роста частоты переключения потоков и нагрузки процессора. А функция devpost, наоборот, применяется для уменьшения частоты переключения потоков и нагрузки процессора - за счет возможного увеличения задержки между посылкой сообщения и обработкой данных приемником. Какой путь выбрать - зависит от задачи.
Длина посылаемого сообщения может быть любой, допустимой типом string.
Однако рекомендуется посылать сообщения, в которых строки, разделенные переводом строки
CrLf, не длиннее 200-250 символов, так как многие функции обработки строк
ориентированы на строки длиной не более 255 символов.
Функция devsend в случае пустой строки данных принимает также ссылки
на поток (polling), задачу (task), канал (pipe),
которые создаются вызовом pipe_init или возвращаются вызовом reffind.
В этом случае поведение функции следующее:
{Server send message:}
dev:=reffind('Device &Client');
if devsend(dev,'Hello'+CrLf)=0 then writeln('Fifo overflow');
{Client handle messages:}
while not eof do begin
readln(s);
if IoResult=0
then writeln('Server talk: ',s)
else writeln('Error');
end;
ref:=RefFind('Polling System.Uart'); // Получить ссылку на поток драйвера COM-порта
r:=devSend(ref,''); // Досрочно пробудить этот поток
devmsg = devsend(reffind('Device dev'),'msg')
devsendmsg = devsend(reffind('Device dev'),'msg')
devpostmsg = devpost(reffind('Device dev'),'msg')
где вместо ссылки устройства используется его имя.
Есть отличия от devsend и devpost:
Таким образом, devmsg, devsendmsg, devpostmsg удобно применять в случае редких сообщений небольшого размера. При большом потоке сообщений devsend и devpost предпочтительнее.
{Server send message:}
if devmsg('&Client Hello'+CrLf)=0 then writeln('Fifo overflow');
{Client handle messages:}
while not eof do begin
readln(s);
if IoResult=0
then writeln('Server talk: ',s)
else writeln('Error');
end;
Примечание: В прикладном коде рекомендуется использовать функции-"обертки" из стандарной библиотеки, такие как trouble, problem, success. При прямом использовании будьте внимательны к выбору уровня значимости severity, т.к. неверный выбор этого параметра приводит либо к игнорированию события, либо к "спаму", т.е. избыточному "информационному шуму", который будет только мешать работе.
Ещё прочитайте про системный журнал syslog.if SysLogNotable(sev_info) then n:=SysLogNote(sev_info,'Это демонстрационное событие.');Рекомендуется всегда проверять уровень значимости событий перед записью, чтобы исключить ненужную работу программы.
procedure TestSysLog;
var severity,code,leng:Integer; name:String;
begin
name:='';
severity:=sev_Notify;
name:=ParamStr('SysLogSeverityName '+Str(severity));
code:=Val(ParamStr('SysLogSeverityCode '+name));
writeln('Имя уровня значимости ',name);
writeln('Код уровня значимости ',code);
if SysLogNotable(severity)
then writeln('Уровень ',severity,' ЗНАЧИМЫЙ')
else writeln('Уровень ',severity,' НЕ_ЗНАЧИМЫЙ');
leng:=SysLogNote(severity,'Это тестовая запись в Журнал.');
if (leng>0)
then writeln('Событие записано в журнал')
else writeln('Событие проигнорировано');
name:='';
end;
Тексты идентифицируются ссылками, см. refinfo.
var txt:integer;
txt:=text_new;
b:=text_addln(txt,'Add line');
b:=text_putln(txt,0,'Put line 0');
b:=text_insln(txt,0,'Insert line 0');
while text_numln(ref)>0 do text_delln(txt,text_numln(txt)-1); {clear text}
b:=text_free(txt);
Каждая DAQ-программа имеет счетчик вызовов программы с момента старта DAQ. При первом вызове программы, который происходит при старте DAQ системы, runcount=1. Затем при каждом последующем вызове программы значение runcount приращивается на 1. Наконец, при остановке DAQ системы программа вызывается со значением runcount=+INF, что можно проверить вызовом isinf(runcount).
Указанное поведение активно используется для организации вычислений.
Обычно:
program example;
var Ok:Boolean;
begin
if runcount=1 then begin
writeln('Start program');
Ok:=true;
end else
if isinf(runcount) then begin
writeln('Stop program');
end else
if Ok then begin
writeln(’Poll program, RunCount is ', runcount:1:0);
end;
end.
var t:real;b:boolean;
begin
t:=msecnow;
b:=sleep(1);
writeln('Sleep ',msecnow-t,' ms');
end;
Значение функции носит рекомендательный характер, оно просто говорит, что, вероятно, недавно произошло внешнее событие (одно из перечисленных). Программа пользователя может предпринять шаги для выяснения источника досрочного пробуждения и выполнить нужную обработку данных. Например, можно проверить clickbutton на предмет нажатия сенсора или eof на предмет приема сообщения.
Рекомендательный характер функции связан с тем, что сигнал пробуждения может возникнуть как до, так и после обработки сигнала, вызвавшего пробуждение (хотя и с меньшей вероятностью). Дело в том, что внешнее событие может возникнуть уже после активизации потока программы. Программа, получив управление, может уже обработать возникший сигнал, а флаг пробуждения остается. Это приведет к досрочной активизации программы на следующем кванте времени планировщика. В этом случае вознет "ложная тревога", однако с точки зрения скорости реакции все же лучше лишний раз проверить буферы ввода, чем упустить время при обработке события.
Поэтому не следует думать, что отсутствие сигнала пробуждения обязательно говорит об отсутствии внешних событий - они могли произойти уже после активизации потока. Нельзя также сказать совершенно точно, что присутствие сигнала пробуждения говорит о наличии необработанных внешних событий - эти события могли произойти уже после активизации потока и уже могут быть обработаны.
Надо также учитывать, что функция не регистрирует число событий, вызвавших пробуждение. Приход одного или десяти сообщений даст один результат - однократное пробуждение потокf. Можно твердо сказать только одно - если функция awakeflag вернула true, то с момента завершения предыдущего опроса программы что-то произошло.
if awakeflag then writeln('Awake');
Если указан flag=true, то функция выполняет также "принудительный" сброс Watchdog.
В обычной ситуации срабатывает системный сброс Watchdog, который делается автоматически.
Системный сброс Watchdog происходит в начале и конце каждого системного цикла опроса потока
перед вызовом программы и после её завершения.
Однако при длительных операциях требуется выполнять принудительный (досрочный) сброс.
Сброс Watchdog уведомляет систему, что работа потока программы продолжается нормально, т.е. поток не "завис".
После сброса Watchdog поток программы получает ещё один интервал времени для выполнения работы,
прежде чем для него сработает сторожевой таймер.
Этот интервал времени определяется максимальным значением из timeout (по умолчнию 0)
и значения системного времени ожидания, которое задается в диалоге СТОРОЖЕВОЙ ТАЙМЕР.
Функция wdt_timeout(timeout) возвращает старое и задает новое время ожидания для сторожевого таймера данного устройства. Время ожидания задается в миллисекундах. Нулевое значения timeout=0 соответствует значению по умолчанию. Время ожидания сторожевого таймера является наибольшим из времени ожидания для сторожевого таймера данного устройства и общесистемного времени ожидания, заданного в диалоге Сторожевой Таймер.
Функции Сторожевого Таймера применяются в трех случаях.
Terminated:=False;
while not Terminated do begin
DoSomeWork;
if wdt_reset(false)>3000 then Terminated:=true;
end;
В данном случае выполняется некоторая циклическая работа (DoSomeWork),
без сброса Watchdog, но не дольше 3 секунд в сумме (что меньше системного времени ожидания Сторожевого Таймера),
после чего цикл прерывается до следующего вызова программы в системном цикле опроса.
В этом примере время выполнения программы будет ограничено, поэтому сработает автоматический сброс Сторожевого Таймера.
Terminated:=False;
while not Terminated do begin
DoSomeWork;
if wdt_reset(true)>15000 then Terminated:=true;
end;
В данном случае выполняется некоторая циклическая работа (DoSomeWork),
со сбросом Watchdog после каждой итерации, но не дольше 15 секунд в сумме,
после чего цикл прерывается до следующего вызова программы в системном цикле опроса.
В этом примере время выполнения программы в цикле будет больше стандартного времени ожидания Сторожевого Таймера,
поэтому необходимо применять принудительный сброс Сторожевого Таймера.
if NewDataBase then begin // Если надо создать новую БД
bNul(wdt_reset(true)); // Сбрасываем Watchdog
tm:=wdt_timeout(30000); // Задаем большой таймаут
CreateNewDataBase; // Не спеша создаем новую БД
bNul(wdt_reset(true)); // Сбрасываем Watchdog
iNul(wdt_timeout(tmt)); // Восстанавливаем таймаут
end; // Сделано.
... // Работаем дальше...
В этом примере требуется задать timeout, потому что длительно выполняемая операция CreateNewDataBase
не позволяет сбросить Watchdog длительное время. Поэтому требуется уведомить систему о том, что в течение
этого времени поток ожидает завершения операции, а не просто "висит".
Система не будет фиксировать ошибку Сторожевого Таймера,
пока запрошенный лимит времени не будет исчерпан.
Теперь более подробно о работе Сторожевого Таймера.
Во-первых, в целях повышения надежности измерительных систем и улучшения диагностики опроса потоков,
система CRW-DAQ имеет сторожевой таймер (WatchDog Timer или просто Watchdog).
Цель сторожевого таймера состоит в том, чтобы диагностировать "подвисание" потоков программы.
В цикле опроса потока DAQ-системы, непосредственно перед очередным выполнением программы
Daq Pascal, система сбрасывает Watchdog и запоминает время этого сброса (назовем такой
сброс "системным", так как он генерируется системой автоматически).
При нормальном выполнении программы Watchdog будет сбрасываться каждый квант времени,
определяемый периодом опроса DevicePolling устройства и квантом времени операционной системы.
Если в программе Daq Pascal возникает задержка (блокировка потока или длительный цикл вычислений),
то сбос Watchdog будет задерживаться.
Специальный сторожевой поток системы CRW-DAQ периодически взводит Watchdog для каждого
потока программы и отслеживает, сколько времени Watchdog потока остается взведенным.
Если это время превысило критическое (обычно около 5 секунд), значит за это время поток
не имел возможности сбросить Watchdog, то есть скорее всего "повис".
При этом в окне появляется окно с изображением
предупреждающим о подвисании программы.
Цикл опроса DAQ-устройства можно представить в виде такого псевдокода:
DeviceThread.Execute; {функция потока устройства}
begin
while not Terminated do begin {цикл опроса}
t:=msecnow; {время сброса watchdog}
ResetWatchDogTimer; {сброс watchdog таймера}
if InquiryPeriod.Event {если подошло время опроса}
then ExecuteDaqPascalProgram; {то выполнить программу Daq Pascal}
TimeOfLastWatchdogReset:=t; {время последнего сброса watchdog}
WaitForSingleObject(event,DevicePolling); {ожидать цикл опроса или событие}
end;
end;
Функция wdt_reset может быть представлена таким псевдокодом:
function wdt_reset(flag:boolean):real;
begin
wdt_reset:=TimeOfLastWatchdogReset; {вернуть время системного сброса watchdog}
if flag then ResetWatchDogTimer; {если надо, принудительно сбросить watchdog}
end;
При нормальном выполнении программы процедура ExecuteDaqPascalProgram будет выполняться
быстро, а Watchdog будет сбрасываться примерно через каждые DevicePolling миллисекунд.
Если при выполнении программы возникла задержка (блокировка потока или длительные вычисления),
то сброса Watchdog не произойдет и система выдаст предупреждение.
Обратите внимание, поскольку программа выполняется по таймеру InquiryPeriod, а поток - по таймеру DevicePolling, то период опроса DAQ-программы и период опроса потока могут отличаться (хотя период опроса программы ме может быть меньше, чем потока). Таким образом, выражение
msecnow-wdt_reset(false)дает именно период опроса потока. Заметим, что период опроса программы и потока совпадают, если InquiryPeriod <= DevicePolling. По этой причине часто в конфигурации устанавливается InquiryPeriod=1, при котором период опроса DAQ Pascal программы и потока заведомо совпадают. Однако иногда имеет смысл делать опрос программы значительно реже, чем потока. Если программа должна выполняться раз в минуту, поток все же должен активизироваться раз в секунду, хотя бы для того, чтобы сбросить Watchdog. Сравните:
program polling;
var ms,lastms:real;
begin
ms:=msecnow;
writeln('Период опроса потока = ',ms-wdt_reset(false),' ms');
writeln('Период опроса программы = ',ms-lastms,' ms');
lastms:=ms;
end.
Функция wdt_reset может применяться в следующих целях:
while msecnow-wdt_reset(false) < timeout do Something;
{Get time of last polling}
dt:=msecnow-wdt_reset(false);
writeln('Last device polling was ',dt,' msec ago');
{Sleep 10 second. WatchDog Timer warning disabled.}
while msecnow-wdt_reset(true) < 10000 do b:=sleep(1);
{Sleep 10 second. WatchDog Timer warning enabled.}
while msecnow-wdt_reset(false) < 10000 do b:=sleep(1);
var t:real;
begin
t:=msecnow;
writeln('Time is ',t);
end;
if cpu_start then begin
DoSomething;
t:=cpu_clock;
writeln('DoSomething takes ',t,' CPU clocks');
end else writeln('cpu_clock is not supported');
function cpu_count:Integer - возвращает число логических процессоров в системе. Это могут быть
function cpu_start:Boolean - сбрасывает счетчик тактов в ноль (сброс происходит программно, а не аппаратно). Возвращает False, если счетчик тактов процессора недоступен. Функция используется в паре с cpu_clock для измерения тактов процессора. Необходимо периодически делать сброс счетчика, чтобы избежать его переполнения.
function cpu_clock:Real - возвращает счетчик тактов процессора, прошедших с последнего вызова cpu_start. Надо учитывать, что сам вызов функции тоже занимает несколько сот тактов и делать соответствующую коррекцию. Если функция возвратила 0, счетчик тактов недоступен.
function cpu_mhz:Real - возвращает частоту тактов процессора, в мегагерцах. Это измеренная в момент старта программы оценка, она может быть не вполне точной, так как надежного способа точно узнать частоту процессора не существует, ее можно измерить по счетчику тактов и кварцевым часам типа mksecnow. Если функция возвратила 0, счетчик тактов недоступен.
Теперь обсуждение.
Во-первых, функции, несомненно полезные, они позволяют измерять очень
малые интервалы времени и оценивать производительность программ прямо
в тактах процессора. Вызов функции достаточно быстрый, в отличие от
mksecnow.
Однако использование функции cpu_clock имеет ограничения, не позволяющие считать ее очень надежной.
По перечисленным причинам применение cpu_clock ограничено случаями когда
program demo;
var i:Integer; t:Real;
begin
writeln('System has ',cpu_count,' processors (CPUs).');
writeln('CPU frequency is ',cpu_mhz,' MegaHertz.');
{on multiprocessor systems set process affinity}
if cpu_count>1 then i:=pidaffinity(0,1);
if cpu_start then begin
DoSomething;
t:=cpu_clock;
writeln('Procedure DoSomething takes ',t,' CPU clocks.')
end;
end.
Квант времени системного таймера исключительно важен для CRW-DAQ. В этой системе обычно опросом устройств и управлением занимаются высокоприоритетные потоки, которые активизируются с частотой системного таймера, быстро выполняют процедуру опроса и затем засыпают до следующего кванта времени. Поэтому именно частота переключения потоков, заданная квантом времени системного таймера, определяет временные характеристики системы сбора данных.
Функции GetClockRes, SetClockRes позволяют определить и задать некоторые параметры системного таймера и тем самым влияют на временные характеристики системы сбора данных, как и системы в целом. Все времена в этих функциях измеряются в [ms] (миллисекундах) и имеют квант (гранулярность) 100 ns. Обе функции возвращают ноль, если возникает какая-то ошибка. Например, если отменяется таймер, который не был разрешен, возвращается ноль. Кроме того, функции не работают в Windows-9x, поэтому там они всегда возвращают ноль. Впрочем, время Windows-9x прошло, к тому же для этой стистемы квант времени - понятие прпктически бесполезное, так как вытесняющая приоритетная мультизадачность там реализована из рук вон плохо.
GetClockRes(What) возвращает параметры системного таймера:
InquiryPeriod = 0
DevicePolling = 1, tpTimeCritical
Если указать ненулевое значение InquiryPeriod, частота опроса потока
не будет выше стандартной частоты (порядка 100 Герц), т.к. квант функций
измерения времени равен стандартному кванту порядка 10 ms, а работа
InquiryPeriod основана на вызове msecnow.
Ну а высокий приоритет необходим для повышения стабильности частоты опроса потока.
SetClockRes(new_res) задает или отменяет новую частоту системного таймера. Положительное (разрешающее) значение таймера задает новую частоту таймера, а отрицательное (запрещающее) отменяет ее и возвращает стандартное значение. Функция возвращает новое актуальное значение таймера, подобно GetClockRes(3).
Важно понимать, что возвращаемое SetClockRes значение может существенно отличаться от заданного в аргументе желаемого значения. Ведь системный таймер - общесистемный ресурс и на него влияют запросы многих процессов. Система работает так, чтобы удовлетворять работе наиболее быстрых процессов. Например, если один процесс активизировал таймер с квантом 1 ms (например, вызвал SetClockRes(1)), а затем второй процесс активизировал таймер с квантом 5 ms (например, вызвал SetClockRes(5)), то функция вернет значение 1 ms, так как системный таймер должен удовлетворить требованиям первого процесса.
Другими словами, вызов GetClockRes(new_res) с положительным (разрешающим) значением аргумента может только уменьшить квант таймера (повысить частоту опроса). Если запрошенный квант больше текущего (частота опроса ниже текущей), значение кванта не изменится. Однако факт запроса будет зафиксирован, что важно при отмене таймера (см. далее).
Другой особенностью является то, что вызов SetClockRes с отрицательным (запрещающим) значением аргумента отменяет запрос на таймер для данного процесса, но не для всей системы. Численное значение запрещающего кванта не играет роли, но рекомендуется использовать такое же значение, как и при разрешении таймера, например,
SetClockRes(+5); ... SetClockRes(-5);
Система действительно отменяет таймер, только когда все процессы, запросившие таймер,
отменили запрос или завершились.
Например, в приведенном выше примере, если второй процесс (с квантом 5 ms)
вызвал SetClockRes(-5) для отмены таймера, квант времени останется равным
1 ms, пока первый процесс не отменит его или завершит работу.
Как показывают наблюдения, в рамках одного процесса очереди запросов на таймер не создается (длина очереди равна 1). Например, если вызвать SetClockRes(+5), затем SetClockRes(+2), то вызов SetClockRes(-2) отменяет таймер, хотя было два запроса. Однако рекомендуется соблюдать стековый порядок разрешения/отмены таймера (так, на будущее).
Наконец, надо напомнить, что в CRW-DAQ работает много потоков, каждый из них может разрешить/запретить таймер. Чтобы не создавать хаос, рекомендуется разрешать таймер в каком-то одном потоке (то есть в одной программе Daq Pascal).
Подробнее об измерении времени смотри описание msecnow.
program demo;
begin
writeln('Standard timer quantum is ',GetClockRes(0),' ms');
writeln('Maximal timer quantum is ',GetClockRes(1),' ms');
writeln('Minimal timer quantum is ',GetClockRes(2),' ms');
writeln('Actual timer quantum is ',GetClockRes(3),' ms');
writeln('Now set clock about 1 ms (1000 Hz).');
if SetClockRes(+1)=0 then writeln('Error!');
writeln('Actual timer quantum is ',GetClockRes(3),' ms');
writeln('Now disable fast clock, go back to standard value.');
if SetClockRes(-1)=0 then writeln('Error!');
writeln('Actual timer quantum is ',GetClockRes(3),' ms');
end;
[DAQ]
TimeBase = 13.05.1999-18:35:00 ; Базовое время Day.Month.Year-Hour:Min:Sec
Время time измеряется в единицах timeunits миллисекунд, которое
задается (в секундах) в переменной TimeUnit в секции [DAQ],
например для измерения времени в минутах надо написать:
[DAQ]
TimeUnit = 60 ; Единица измерения времени time,[секунд]
Некая путаница связана с тем, что в конфигурации TimeUnit задается
в секундах, а в программе timeunits заданы в миллисукундах.
Это сделано потому, что в конфигурации секунды использовать удобнее,
а в программе основной функцией измерения времени является все же msecnow.
Ноль времени timebase часов DAQ-системы, время msecnow в миллисекундах от Р.Х., время time и единицы измерения timeunits связаны соотношением:
msecnow = timebase + time * timeunits
Данные, записываемые в кривые и отображаемые при работе системы на графиках, обычно измеряются по часам DAQ, через функцию time, так как это время наиболее удобно пользователю для наблюдения. Времена time обычно выражены в привычных для пользователя единицах (секунды, минуты, часы, сутки...). Работать с ними удобно, числа получаются разумные. Однако надо учитывать, что по времени DAQ нельзя узнать астрономическое (календарное) время без знания начала отсчета и единиц измерения. Поэтому данные разных сеансов работы DAQ, записанные в единицах времени time, трудно "сшивать", так как они отсчитаны от разных моментов времени. Правда, этот момент зафиксирован в паспорте кривой, но каждый раз сдвигать данные неудобно.
Время msecnow в миллисекундах от Р.Х. - более подходящая единица для сохранения данных в файлы, так как данные потом не надо сшивать, ведь они заданы в одной временной шкале. Однако пользователю работать с астрономическим временем не очень-то удобно, слишком большие числа получаются. Поэтому разумнее всего отображать данные в единицах time, а хранить в единицах msecnow.
Астрономическое время tms от Р.Х. связано с временем tDAQ по часам time формулой
tms = timebase + tDAQ * timeunits
b:=putao(0,time,data); {запись данных в кривую}
x:=getai_xn(0); y:=getai_yn(0); {извлечение данных из кривой}
ms:=timebase+x*timeunits; {перевод времени кривой в ms}
writeln(ms,' ',y); {запись в файл}
ms:=msecnow;
writeln('Now ',ms2day(ms),'.',ms2month(ms),'.',ms2year(ms),'/',
ms2hour(ms),':',ms2min(ms),':',ms2sec(ms));
{результат типа 12.6.2005/12:15:33}
Таймеры хорошо приспособлены к асинхронной обработке данных, когда некие периодические действия выполняются в разных вызовах программы обработки. Именно этот способ организации вычислений является основным рабочим режимом для систем реального времени, к которым относятся DAQ-программы.
Группа содержит 11 функций:
program timer;
var tm:real; b:boolean; n:integer;
begin
{start measure}
if runcount=1 then begin
writeln('Start timer test program');
tm:=tm_new; {create new timer & add intervals 1,2 3 sec & start }
if not( tm_addint(tm,1000) and
tm_addint(tm,2000) and
tm_addint(tm,3000) and
tm_start(tm) )
then begin
writeln('Timer failure!');
b:=tm_free(tm);
tm:=0;
end;
end else
{stop measure}
if isinf(runcount) then begin
if tm<>0 then b:=tm_free(tm);
tm:=0;
writeln('Stop timer test program');
end else
{polling}
if tm<>0 then begin
if tm_event(tm) then begin {event happened?}
n:=tm_curint(tm); {read interval №}
{do something...}
if n=0 then writeln('interval 0 finished');
if n=1 then writeln('interval 1 finished');
if n=2 then writeln('interval 2 finished');
end;
end;
end.
[DataStorage]
ai#0 = Curve 0 100 black 15 1
ai#1 = Curve 0 100 black 15 1
di#0 = Curve 0 100 black 15 1
ao#0 = Curve 0 100 black 15 1
do#0 = Curve 0 100 black 15 1
[DeviceList]
Adc = device software program
[Adc]
AnalogInputs = 2
Link AnalogInput 0 with curve ai#0
Link AnalogInput 1 with curve ai#1
DigitalInputs = 1
Link DigitalInput 0 with curve di#0 bit 0
AnalogOutputs = 1
Link AnalogOutput 0 with curve ao#0
DigitalOutputs = 1
Link DigitalOutput 0 with curve do#0
for i:=0 to numais-1 do writeln('ai#',i,' -> ',crvname(refai(i)));
for i:=0 to numdis-1 do writeln('di#',i,' -> ',crvname(refdi(i)));
for i:=0 to numaos-1 do writeln('ao#',i,' -> ',crvname(refao(i)));
for i:=0 to numdos-1 do writeln('do#',i,' -> ',crvname(refdo(i)));
[DataStorage]
ai#0 = Curve 0 100 black 15 1
di#0 = Curve 0 100 black 15 1
ao#0 = Curve 0 100 black 15 1
do#0 = Curve 0 100 black 15 1
[DeviceList]
Adc = device software program
[Adc]
AnalogInputs = 1
Link AnalogInput 0 with curve ai#0
DigitalInputs = 1
Link DigitalInput 0 with curve di#0 bit 0
AnalogOutputs = 1
Link AnalogOutput 0 with curve ao#0
DigitalOutputs = 1
Link DigitalOutput 0 with curve do#0
var ref,x,y:real;
ref:=refai(0);
x:=crvx(ref,1);
y:=crvy(ref,1);
Сначала собственно описание функций упаковки событий.
putev(what,chan,time,data,data1) записывает в буфер FIFO устройства событие общего вида (what,chan,time,data,data1). Все остальные вызовы функций упаковки событий эквивалентны вызову этой функции с различными параметрами.
putao(chan,time,data) записывает событие (time,data) в аналоговый буфер FIFO, для последующей записи в кривую AnalogOutput[Chan]->(time,data). Эквивалентно вызову putev(4,chan,time,data,0). Вызов putao применяется обычно для записи аналоговых (непрерывных) сигналов.
putdo(chan,time,data) записывает событие (time,data) в цифровой буфер FIFO, для последующей записи в кривую DigitalOutput[Chan]->(time,data). Эквивалентно вызову putev(5,chan,time,data,0). Вызов putdo применяется обычно для записи цифровых (дискретных) сигналов.
Все три функции возвращают true при записи события в FIFO или false при переполнении FIFO. При переполнении FIFO также регистрируется ошибка DAQ. При обнаружении переполнения FIFO надо увеличить размер буферов FIFO в конфигурационном файле (переменные AnalogFifo, DigitalFifo).
Теперь немного теории.
Система CRW-DAQ поддерживает два стиля работы с кривыми - синхронный и асинхронный. Синхронный метод - работа с кривыми напрямую - дает больше возможностей, но он существенно сложнее. Асинхронный метод - работа через буфер событий - существенно проще, позволяет прикладной программе сбора данных не заботиться о стандартной, рутинной обработке данных, а также их отображении на экране.
При синхронной работе с кривой надо получить ссылку кривой (например, вызовом refai,..,reffind), и работать с кривой через функции типа crvx, crvy, crvput и т.д. При этом работа с кривой идет напрямую, а для корректной работы в многопоточной среде кривую надо к тому же блокировать и разблокировать (crvlock/crvunclock). Кроме того, надо самостоятельно заботиться об отображении кривой на экране после ее обработки. Правда, синхронная работа с кривой идет без задержек буферизации, а обработка делается в точности так, как задано в прикладной программе. Поэтому синхронная работа с кривой применяется при обработке больших блоков данных, а также при сложной или нестандартной обработке с кривой. Однако для стандартной, рутинной обработки данных, полученных в процессе измерений, синхронная обработка слишко громоздка.
Асинхронный стиль работы связан с механизмом событий, а также их буферизацией и диспетчеризацией. Механизм событий - упаковка каждого факта измерений в некую стандартную запись для последующей буферизации и диспетчеризации. Буферизация заключается в том, что запись данных в кривую идет не напрямую, сразу в момент измерений, а накапливается в буфере FIFO в виде событий для последующей асинхронной обработки. Событие, как правило, соответствует факту измерения сигнала, то есть получению известного значения y в определенный момент времени x. Каждое устройство имеет два буфера FIFO - аналоговый и цифровой, размер которых (в элементах) задаются переменными AnalogFifo, DigitalFifo в секции описания устройства. Диспетчеризация - это процедура, которая периодически обращается к FIFO, извлекает оттуда данные, выполняет их обработку по некоторому стандартному алгоритму, параметры которого задаются в конфигурации и затем записывает обработанные данные в кривые. Достоинство такого подхода в том, что при этом не надо заботиться о рутинных процедурах типа блокировки кривой, стандартной обработки данных и т.д. Кроме того, при асинхронной обработке обновление данных на экране идет автоматически. Асинхронная обработка обычно применяется для добавления в кривые небольших порций данных, в процессе измерений. Надо также отметить, что буферизация идет в измерительном потоке, при вызове функций упаковки событий, а диспетчеризация и отображение данных идут в других программных потоках, поэтому функции упаковки событий всегда работают быстро и не приводят к блокировке измерительного потока.
Необходимость введения механизма событий связана с тем, что набор данных может идти в разных режимах:
Таким образом, сбор данных в системе CRW-DAQ обычно построен на механизме событий. Это значит, что данные по мере поступления обычно не сразу записываются в кривые, а упаковываются в некоторые записи - события и буферизуются через FIFO. Событие имеет структуру, понятную из его описания, взятого из исходного текста программы, из файла _daqevnt.pas, входящего в дистрибутив:
TDaqEvent=record {запись - событие DAQ}
What : Integer; {класс события}
Chan : Integer; {канал события}
Time : Double; {время события}
Data : Double; {данные события (основные)}
Data1 : Double; {данные события (дополнительные)}
end;
Событие можно обозначать кратко как запись (what,chan,time,data,data1).
Класс события What содержит слово, биты которого влияют на его обработку и диспетчеризацию. Биты имеют такой смысл:
Канал события Chan влияет на диспетчеризацию события. Фактически, это номер аналогового/цифрового выхода, куда передается событие при диспетчеризации.
Время события Time фиксирует момент, когда оно зарегистрировано. Это время необходимо, ведь за счет буферизации время обработки события может не совпадать со временем его возникновения, особенно если события генерирует аппаратный драйвер. Обычно время извлекается функцией time. Во всяком случае, время динамического события должно изменяться монотонно. Это значит, что при записи ряда событий время каждого события должно быть больше предыдущего. Алгоритм диспетчеризации существенно использует условие монотонности времени. Если какое-то событие нарушает условие монотонности времени, оно игнорируется.
Данные Data,Data1 зависят от класса события, как описано в поле What. Для динамических событий Data - это значение сигнала в момент Time. Для спектрометрических событий Data - это канал, Data1 - приращение в канале.
Диспетчеризация состоит в том, что по имеющейся в событии информации (класс, канал, время данные) определяется, в какую кривую из базы данных его надо записать и затем, после дополнительных проверок и преобразований, идет добавление данных в эту кривую, а также ее прорисовка на экране, если это необходимо. При этом все операции обработки и отображения данных идут в других потоках программы и не задерживают выполнение потока устройства, сгенерировавшего событие. Класс события определяет способ добавления данных: для динамических событий в конец кривой должна добавляться очередная точка, а для спектрометрических событий должен инкрементироваться канал, номер которого задан в событии.
{Упаковка данных АЦП}
x:=time;
y:=GetADC;
b:=putao(0,x,y); {эквивалентно b:=putev(4,0,x,y,0);}
{Упаковка данных DI}
x:=time;
y:=GetDI;
b:=putdo(0,x,y); {эквивалентно b:=putev(5,0,x,y,0);}
{Упаковка события спектрометрии: инкремент канала y на 1 и запись в AnalogOutput 1}
x:=time;
y:=GetChan;
b:=putao(8,1,x,y,1);
Функция numcals возвращает число калибровок данного устройства. Калибровки имеют номера в диапазоне 0..numcals-1.
Функция refcalibr(i) возвращает ссылку на калибровку номер i или ноль, если калибровки с таким номером нет. Сама калибровка задается в конфигурации устройства ссылкой на файл калибровки (*.cal), в котором содержится массив точек калибровки, а также описание метода аппроксимации этих точек.
Функция calibr(i,dat,par) преобразует данные dat с параметром par по калибровке номер i. Например, если dat - термоЭДС термопары, par - температура холодного спая, а калибровка - термопарная таблица, то функция вернет температуру в градусах.
Теперь немного теории.
Каждое устройство может иметь массив калибровок. Число калибровок устройства задается типом устройства, а для программных устройств - переменной Calibrations в секции описания устройств.
Калибровка - некоторое преобразование данных, например, переводящее милливольты АЦП тензодатчика в атмосферы давления. От обычной функции калибровка отличается тем, что она определяется не столько аналитически (формулой), сколько эмпирически (набором точек калибровки). Обычно используется та или иная аппроксимация массива точек калибровки некоторой параметрической функцией по методу наименьших квадратов (МНК).
Начнем с понятия точек калибровки. Пусть имеется величина x, измеряемая непосредственно, например, данные АЦП. Пусть также известно, что величина y зависит только от x и, возможно, параметра z, который также измеряется либо считается известным (параметр может и отсутствовать). Обычно предполагается, что зависимость y от x,z может быть описана некоторой параметрической функцией y=Fy(x,z,p) с неизвестными параметрами p, которые определяются процедурой МНК. Для определения функции Fy делается калибровочное измерение. Для этого проводится ряд измерений величины x, при этом величина y одновременно измеряется альтернативным методом, который считается достоверным и достаточно точным. Следует подчеркнуть этот принципиальный момент: калибровка всегда основана на применении альтернативного метода измерения (сравнения с эталоном). Если эталонный метод неточен, калибровка тоже будет неточной. Вторым принципиальным моментом является то, что калибровочные измерения должны охватывать как можно более широкий диапазон значений величины y. Это не всегда осуществимо, однако сужение диапазона точек калибровки снижает ее точность. Результатом калибровочного измерения является массив точек (xi,yi,zi),i=1..N, причем значения xi и yi измерены одновременно, xi - непосредственно, а yi - альтернативным методом. Значения параметра zi, если он есть, считаются известными или измеряются непосредственно. Это и есть массив точек калибровки.
В отличие от многих других пакетов, в которых под калибровкой понимаются коэффициенты полиномов или другие вторичные величины, полученные аппроксимацией массива точек калибровки, в CRW-DAQ в качестве данных для калибровки хранится исходный массив точек калибровки, так как только этот массив является опытным фактом. Метод аппроксимации, напротив, рассматривается как гипотетический факт, то есть как гипотеза, которая может быть и неверной. Поэтому файл калибровки хранит, наряду с массивом точек калибровки, описание метода аппроксимации. Если описание работает плохо, его можно заменить другим, не проводя повторных измерений, так как массив точек калибровки уже имеется. Принято также, что все неизвестные параметры p, нужные для аппроксимации, вычисляются автоматически при загрузке калибровки. На практике эти промежуточные параметры никогда явно не использовались, они нужны только для внутренних вычислений и полностью "спрятаны" за вызовом функции calibr.
Рассмотрим пример конфигурирования устройства, которое преобразует милливольты термоЭДС из кривой mvT1, подключенной к 0 входу устройства и температуру холодного спая TCJ с 1 входа с использованием калибровки с номером 0.
[DeviceList]
&T_CONV = device software program
[&T_CONV]
Comment = Термопарные преобразования
InquiryPeriod = 10
DevicePolling = 10, tpNormal
ProgramSource = ..\daqpas\_mv2pu.pas
AnalogInputs = 2
AnalogOutputs = 1
Calibrations = 1
Link AnalogInput 0 with curve mvT1
Link AnalogInput 1 with curve TCJ
Link AnalogOutput 0 with curve T1 tolerance 0.05 0.001 history 100
Calibration#0 = ..\calibr\hral.cal U(mV) T(C) Tcj Line HrAl 0 10
Здесь
[U(mV)-T(C) calibration] ; Заголовок калибровки
FitMethod = Polynom ; Метод аппроксимации
TransformX = Line ; Линеаризующее преобразование по X
TransformY = HRAL ; Линеаризующее преобразование по Y
Power = 1 ; Степень полинома
Center = 0 ; Сдвиг
Scale = 1 ; Масштаб
Bounds = -7 55 ; Диапазон изменения аргумента
Data U(mV) T(C) Weight Tcj
-6.458 -270 1 0
0 0 1 0
54.875 1372 1 0
End Data
Notice Text
Калибровка хромель-алюмель
End Notice Text
Здесь описание калибровки помещено в секцию [U(mV)-T(C) calibration].
Обратите внимание, что имена аргумента U(mV) и значения T(C) входят в имя секции.
Переменная FitMethod задает метод аппроксамации, в данном случае это полиномиальная
аппроксимация по методу наименьших квадратов.
Переменные TransformX, TransformY задают линеаризующие преобразования,
в данном случае Line означает тождественное преобразование, HRAL - преобразование по термопарной
таблице хромель-алюмель.
Переменная Power задает степень полинома, используемого для аппроксимации.
Переменные Center, Scale задают линейное преобразование аргумента t=(x-Center)/Scale,
которое применяется для нормализации диапазона аргумента. Обычно они выбираются так, чтобы новая переменная
менялась в диапазоне [0..1].
Далее, в разделе Data ... End Data содержится таблица точек калибровки.
Таблица состоит из столбцов.
Столбцы должны идти в том же порядке, как названия соответствующих величин
U(mV), T(C), Weight, Tcj.
Здесь Weight - статистический вес точки.
Аппроксимация делается так. Сначала к массиву точек (xi,yi,zi) применяются линеаризующие преобразования по X, Y: x'i=Tx(xi,zi), y'i=Ty(yi,zi). Предполагается, что после этих преобразований линеаризованные переменные x', y' зависят друг от друга примерно линейно и могут быть описаны полиномом некоторой степени. Затем линеаризованная переменная x' преобразуется в нормализованную переменную t'=(x'-Center)/Scale. Затем массив точек (t',y') аппроксимируется полиномом p(t') степени Power по МНК. Эти коэффициенты запоминаются для дальнейших вычислений.
При вычислении калибровки calibr(0,x,z) аргумент x преобразуется в t'=(Tx(xi,zi)-Center)/Scale. Затем вычисляется y'=p(t'). Затем применяется обратное линеаризующее преобразование y=T-1y(y',z).
{чтение АЦП, преобразование по калибровке и запись события}
if refcalibr(0)<>0 then begin
t:=time;
dat:=getADC;
par:=getColdJunktion;
y:=calibr(0,dat,par);
b:=putao(0,t,y);
end;
getai(n,t) - аппроксимация значения кривой c аналогового входа номер n в момент времени t. Аналоговые входы ориентированы на обработку непрерывных сигналов, поэтому при аппроксимации используется сглаживание, указанное в файле конфигурации в конструкции
Link AnalogInput n with curve c smoothing w p k1 k2
Здесь
getdi(n,t) - аппроксимация значения кривой с цифрового входа номер n в момент времени t. В отличие от getai, в getdi используется интерполяция кусочно-постоянной функцией, так как цифровые входы ориентированы на дискретные сигналы.
getai_n(n) - возвращает число точек кривой, подключенной к аналоговому входу номер n. Возвращает ноль, если нет кривой или точек.
getdi_n(n) - возвращает число точек кривой, подключенной к цифровому входу номер n. Возвращает ноль, если нет кривой или точек.
getai_xn(n) - координата x последней точки кривой, подключенной к аналоговому входу номер n. Возвращает ноль, если нет кривой или точек на ней.
getdi_xn(n) - координата x последней точки кривой, подключенной к цифровому входу номер n. Возвращает ноль, если нет кривой или точек на ней.
getai_yn(n) - координата y последней точки кривой, подключенной к аналоговому входу номер n. Возвращает ноль, если нет кривой или точек на ней.
getdi_yn(n) - координата y последней точки кривой, подключенной к цифровому входу номер n. Возвращает ноль, если нет кривой или точек на ней.
getai_xi(n,i) - координата x точки номер i кривой, подключенной к аналоговому входу номер n. Возвращает ноль при отсутствии кривой или ошибке диапазона индекса.
getai_xi(n,i) - координата x точки номер i кривой, подключенной к цифровому входу номер n. Возвращает ноль при отсутствии кривой или ошибке диапазона индекса.
getai_yi(n,i) - координата y точки номер i кривой, подключенной к аналоговому входу номер n. Возвращает ноль при отсутствии кривой или ошибке диапазона индекса.
getai_yi(n,i) - координата y точки номер i кривой, подключенной к цифровому входу номер n. Возвращает ноль при отсутствии кривой или ошибке диапазона индекса.
getai_par(n,id) - возвращает значение параметра аналогового входа номер n с идентификатором id:
getao_par(n,id) - возвращает значение параметра аналогового выхода номер n с идентификатором id:
getdi_par(n,id) - возвращает значение параметра цифрового входа номер n с идентификатором id:
getdo_par(n,id) - возвращает значение параметра цифрового входа номер n с идентификатором id:
if getai_n(0)>0 then begin
x:=getai_xn(0);
y:=getai_yn(0);
b:=putao(0,x,y);
end;
aimap(i,n) - битовая карта наличия кривых на аналоговых входах (i…i+n-1). Соответствующий бит номер j выставлен, если к входу i+j подключена кривая. Функция применяется для быстрой проверки наличия подключенных к аналоговым входам кривых.
dimap(i,n) - битовая карта наличия кривых на цифровых входах (i…i+n-1). Соответствующий бит номер j выставлен, если к входу i+j подключена кривая. Функция применяется для быстрой проверки наличия подключенных к цифровым входам кривых.
aomap(i,n) - битовая карта наличия кривых на аналоговых выходах (i…i+n-1). Соответствующий бит номер j выставлен, если к выходу i+j подключена кривая. Функция применяется для быстрой проверки наличия подключенных к аналоговым выходам кривых.
domap(i,n) - битовая карта наличия кривых на цифровых выходах (i…i+n-1). Соответствующий бит номер j выставлен, если к выходу i+j подключена кривая. Функция применяется для быстрой проверки наличия подключенных к цифровым выходам кривых.
diword(i,n) - битовая карта входных битов (i..i+n-1). Эта функция рассматривает цифровые данные на входе устройства как массив битов, формирующийся из отдельных битов кривых, подключенных к цифровым входам. Если конфигурация устройства содержит конструкцию
Link DigitalInput i with curve с bit j или
Link DigitalInput i with curve с inverted bit j
то для формирования бита номер i берется последняя точка кривой c,
округляется до 32-битного целого и извлекается бит номер j этого числа.
Обратите внимание, что функция getdi_yn(i) позволяет получать доступ
ко всем битам последней точки кривой c, в то время как diword использует
только один бит из каждой кривой.
Основное применение функции – быстрое формирование логических сигналов из отдельных битов входных данных.
Во многих случаях это позволяет формировать логические сигналы без дополнительного программирования.
if dimap(0,1)>0 then ... к DigitalInput 0 подключена кривая
if dimap(0,16)>0 then ... к DigitalInput 0..15 подключены кривые
i:=outport(addr,dimap(diword(8,8))); вывод в порт байта (8 бит) с входов 8..15

DAQ-СИСТЕМА есть индикаторы состояния системы:
Когда все в порядке, нормальное состояние кнопки зеленое.
Это значит, не было зарегистрировано новых ошибок.
Если в DAQ системе возникает ошибка, состояние меняется на желтое.
Это состояние длится одну секунду, и если новых ошибок не возникло, оно переходит в красное.
Если новых ошибок не возникает, через секунду после последней ошибки состояние
переходит в красное.
fixerror(n) - фиксирует ошибку номер n. Номер n лежит в диапазоне 0…255. Вызывается для оповещения пользователя об ошибке в программе, чтобы можно было анализировать работу программы.
registererr(s) - регистрирует в системе новый вид ошибок с текстом сообщения s. Возвращает код зарегистрированной ошибки, который можно использовать в функции fixerror. При запуске программы следующие коды ошибок регистрируются автоматически:
geterrcount(n) - возвращает счетчик ошибок с кодом n. В зависимости от кода n:
if runcount=1 then begin
errorcode:=registererr(devname);
end else begin
if ErrorFound then b:=fixerror(errorcode);
writeln('Total errors:',geterrcount(-2));
writeln('Device errors:',geterrcount(errcorcode));
end;
Функция возвращает false, если очередь сообщений переполнена.
Строка s должна содержать список сообщений, которые будут озвучены. Каждое сообщение - это либо целое число в диапазоне -999999..+999999, либо имя библиотечного *.wav файла. Имена файлов указываются без расширения и пути. Числа также автоматически преобразуются в набор библиотечных *.wav файлов. Библиотечные *.wav файлы системы CRW-DAQ задаются в Crw32.ini [System] SoundLibrary. Библиотечные *.wav файлы конкретной DAQ системы задаются в секции [DAQ] SoundLibrary. Эти переменные ссылаются на каталог, где находятся *.wav файлы.
Функция озвучивания работает асинхронно, не прерывая выполнения вызывающего потока. Сообщения помещаются в очередь для дальнейшего озвучивания. По этой причине озвучивание сообщений может несколько отставать от момента вызова voice, ведь в очереди уже могут быть ожидающие сообщения.
В окне

DAQ-СИСТЕМА
есть индикатор звука:
.
Он позволяет включать/выключать озвучивание сообщений DAQ.
Командой
в панели управления CRW-DAQ в верхней части окна программы можно вызвать окно Управление звуком.
В нем можно отключить звук, а также посмотреть и попробовать библиотеку звуков.
if clickbutton>0 then begin
if IsSameText(clicksensor,'Button1') then b:=voice('click 1');
if IsSameText(clicksensor,'Button2') then b:=voice('click 2');
end;
action(s) - вызов метода action для списка устройств, заданного в строке s в виде названий устройств. Конкретное, действие action зависит от типа устройства. Например, устройства Dialog, ConstGenerator, BitSetGenerator отображают диалог на экране, а устройства других типов ничего не делают.
clear(s) - вызов метода clear для списка устройств, заданного в строке s в виде названий устройств. Метод clear в основном относится к устройствам – драйверам и должен делать очистку устройства. Для обычных устройств этот метод ничего не делает.
cleardevice(s) - вызов метода cleardevice для списка устройств, заданного в строке s в виде названий устройств. Метод cleardevice в основном относится к устройствам – драйверам и должен делать аппаратную очистку устройства. Для обычных устройств этот метод ничего не делает.
start(s) - вызов метода start для списка устройств, заданного в строке s в виде названий устройств. Этот метод приводит к началу работы устройства, если оно еще не работает. Функция считается устаревшей, не рекомендуется ее использовать. Вместо этого рекомендуется использовать сообщения devmsg, devsend.
stop(s) - вызов метода stop для списка устройств, заданного в строке s в виде названий устройств. Этот метод приводит к остановке работы устройства, если оно еще работает. Функция считается устаревшей, не рекомендуется ее использовать. Вместо этого рекомендуется использовать сообщения devmsg, devsend.
clearcurve(s) - очистка кривых, заданных в виде списка имен в строке s. Для динамических кривых очистка приводит к удалению точек кривой, а для статических кривых (то есть спектров) – к обнулению значений y всех точек кривой. В списке можно также задавать длину истории для последующих кривых. Например:
b:=clearcurve('100,crv1,200,crv2,crv3');
Следует иметь в виду, что функция выпролняется асинхронно, в другом потоке программы, а не сразу.
Функция считается устаревшей, не рекомендуется ее использовать.
Вместо этого рекомендуется использовать crvdel.
savecrw(s) - выполняет сохранение кривых в архивный файл формата *.CRW. Строка s имеет формат s='fname winname wintitle winlable curvelist'.
If not savecrw('c:\daq\data.crw Data_Window ^CTitle^N^L_Y_Units ^RX_Units_^N^CLabel [CurvesToSave]')
then writeln('Error!');
[CurvesToSave]
CurveList = curve1, curve2, curve3
if clicksensor='PRESET' then b:=action('&PresetDialog'); {вызов диалога}
В версии CRW16 функции позволяют выполнять обработку прерываний.
irqinit(irq) устанавливает обработчик прерывания номер irq, в частности, 0 - прерывание таймера, каждые 55 ms. При этом программа активизируется только при возникновении прерывания.
irqfree - снимает обработчик прерывания. При этом программа вызывается в обычном цикле опроса.
isisrnow - возвращает true, если программа вызвана из обработчика прерывания. В обработчике прерывания надо действовать осторожно, чтобы не "повесить" программу. В частности, все файловые или другие длительные операции должны быть вынесены за пределы обработчика. Допустимы только асинхронные вызовы, такие как putev или echo.
if runcount=1 then begin
if not irqinit(0) then writeln('Error!');
end else
begin
if isrnow then b:=echo('IRQ 0 Runcount=',runcount);
end;
Возможно также задание для поиска в виде s=name win,
где name - имя кривой, win - имя окна.
Если win = *, то поиск идет в активном окне.
Если win = **, то поиск в самом верхнем окне для кривых.
Если name = *, то берется активная кривая в окне.
Кривая должна быть объявлена в секции [DataStorage]. Например:
[DataStorage]
ai#0 = Curve 0 100 black 15 1
ref:=crvfind('ai#0');
Результат имеет тип real, так как на старых системах у типа integer
был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости
тип сохранен.
var ref,x,y:real;
ref:=crvfind('temperature');
x:=crvx(ref,crvlen(ref));
y:=crvy(ref,crvlen(ref));
c:=refai(0);
name:=crvname(c);
При работе с кривыми рекомендуется использовать потокобезопасный способ, основанный на технике блокировок crvlock/crvunlock.
Ссылка и результат имеют тип real, так как на старых системах у типа integer был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости тип сохранен.
for i:=1 to round(crvlen(refai(0))) do writeln(crvx(refao(0),i),crvy(refao(0),i));
При работе с кривыми рекомендуется использовать потокобезопасный способ, основанный на технике блокировок crvlock/crvunlock.
Ссылка и индекс имеют тип real, так как на старых системах у типа integer был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости тип сохранен.
var ref,x,y:real; b:boolean;
ref:=refai(0);
if crvlock(ref) then begin
x:=crvx(ref,crvlen(ref));
y:=crvy(ref,crvlen(ref));
b:=crvunlock(ref);
end;
При работе с кривыми рекомендуется использовать потокобезопасный способ, основанный на технике блокировок crvlock/crvunlock.
Ссылка имеет тип real, так как на старых системах у типа integer был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости тип сохранен.
y:=crvget(refai(0),x);
Вызов crvput(ref,i,x,y) задает точке с индексом i кривой ref новое значение x,y. Индекс (номер) точки может быть в диапазоне 1..crvlen(ref).
Вызов crvins(ref,i,x,y) вставляет или добавляет точку с индексом i кривой ref и значением x,y. Индекс (номер) точки для вставки может быть в диапазоне 1..crvlen(ref). При значении i>crvlen(ref) (например, i=maxint) точка добавляется в конец кривой. Функция возвращает число вставленных точек (0 или 1).
Вызов crvdel(ref,i,n) удаляет n точек кривой ref, начиная с индекса i. Индекс (номер) точки может быть в диапазоне 1..crvlen(ref). Функция возвращает число удаленных точек.
При работе с кривыми рекомендуется использовать потокобезопасный способ, основанный на технике блокировок crvlock/crvunlock.
Ссылка и индекс имеют тип real, так как на старых системах у типа integer был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости тип сохранен.
Задать последней точке кривой значения x,y
Блокировка гарантирует, что между вызовами crvlen и crvput кривая не изменится
c:=refao(0);
if crvlock(c) then begin
b:=crvput(c,crvlen(c),x,y);
b:=crvunlock(c);
end;
Добавить в конец кривой точку x,y
r:=crvins(c,maxint,x,y);
Вставить в начало кривой точку x,y
r:=crvins(c,1,x,y);
Удалить все точки кривой
r:=crvdel(c,1,maxint);
При работе с кривыми рекомендуется использовать потокобезопасный способ, основанный на технике блокировок crvlock/crvunlock.
Ссылка и результат имеют тип real, так как на старых системах у типа integer был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости тип сохранен.
i:=round(crvwhere(refai(0),x));
При работе с кривыми рекомендуется использовать потокобезопасный способ, основанный на технике блокировок crvlock/crvunlock.
Ссылка имеет тип real, так как на старых системах у типа integer был слишком малый диапазон. В текущей системе в этом нет необходимости, однако для совместимости тип сохранен.
f:=crvinteg(refai(0),0,1);
Вызов crvlock(ref) блокирует, а crvunlock(ref) разблокирует кривую ref.
Эти вызовы идут всегда парно (сбалансированно), как begin и end.
Вызов crvlock(ref) возвращает true, если блокировка прошла успешно.
Если crvlock(ref) вернул false, то либо ссылка ref некорректна,
либо переполнен стек блокировок (см. далее).
Вызов crvunlock(ref) возвращает true, если разблокировка прошла успешно.
Если crvunlock(ref) вернул false, то либо ссылка ref некорректна,
либо блокировка не была выполнена.
Использование блокировок всегда идет по шаблону:
if crvlock(crv) then begin
... действия с кривой crv ...
b:=crvunlock(crv);
end;
В целях защиты программ DAQ Pascal от подвисаний при ошибках в программе, DAQ система
имеет внутренний стек блокировок, так что при несбалансированности вызовов crvlock/crvunlock
программа DAQ Pascal не повиснет, система восстановит баланс, а затем сгенерирует ошибку времени
выполнения и вы увидите печальную физиономию
.
Это, конечно, не оправдывает неряшливого использования блокировок, хотя и спасает от подвисаний при отладках.
Внутренний стек блокировок имеет ограничение, максимум 8 блокировок одновременно. Не следует считать это серьезным ограничением, так как обычно используются одиночные блокировки, даже две одновременные блокировки - экзотика, к тому же опасная (чреватая взаимной блокировкой потоков, известной как DeadLock).
Следует стремиться, чтобы время блокировки ресурсов было минимально возможным. Дело в том, что при длительной блокировке другие потоки, использующие тот же ресурс, не смогут нормально функционировать, они будут приостановлены (заблокированы) до освобождения ресурса.
Наконец, применение блокировок излишне, если
Небезопасное извлечение последней точки кривой:
x:=crvx(c,crvlen(c));
y:=crvy(c,crvlen(c));
Между вызовами чтения x и y другой поток может добавить точки в кривую,
так что x и y теоретически могут быть взяты из разных точек кривой,
хотя вероятность такого события невелика.
Безопасное извлечение последней точки кривой:
c:=refai(0);
if crvlock(c) then begin
x:=crvx(c,crvlen(c));
y:=crvy(c,crvlen(c));
b:=crvunlock(c);
end;
Каждая кривая, помимо данных, имеет паспорт, то есть текст, сопровождающий или комментирующий эту кривую. Паспорт может содержать список дополнительных переменных, описывающих условия измерений, например, в виде текстовых выражений типа "name=value". Группа вышеуказанных функций дает полный доступ к паспорту кривой.
Ссылку на кривую можно получить функциями reffind, refai, refao, refdi, refdo, crvfind.
var ref:real;
ref:=refai(0);
for i:=0 to crvnumln(ref)-1 do writeln(crvgetln(ref,i));
b:=crvputln(ref,0,'Put line 0');
b:=crvinsln(ref,0,'Insert line 0');
b:=crvaddln(ref,'Add line');
while crvnumln(ref)>0 do crvdelln(ref,crvnumln(ref)-1); {clear comment}
findtag(name)
- ищет в базе данных тег по имени name.
В случае успеха возвращает ссылку на тег.
В случае неудачи, если тег не найден, возвращает ноль.
При поиске тегов имена тегов не чувствительны к регистру.
Теги создаются обычно при загрузке DAQ, согласно описаниям в секции [TagList].
Для работы с тегом надо сначало узнать его ссылку вызовом findtag.
Обычно это делается один раз, в начале программы, потому что поиск ссылки - не очень быстрая операция.
Полученная ссылка на тег используется затем всеми остальными функциями для чтения/записи значения тега,
проверки его имени и типа.
Использование ссылки (вместо имени) для доступа к тегу позволяет резко ускорить работу системы.
Ссылки на теги лежат в диапазоне, заданным константами
TAG_REF_MIN..TAG_REF_MAX.
Эти константы позволяют организовать цикл по всем тегам,
при этом надо проверять тип тега (typetag).
createtag(name,typ) или inittag(name,typ) - находит существующий или создает новый тег по имени name типа typ. В случае успеха возвращает ссылку на тег. В случае неудачи возвращает ноль. Неудача возможна по двум причинам:
freetag(tag) - уничтожает тег с данной ссылкой. Эту функцию надо использовать с большой осторожностью, и применять ее следует только к тегам, которые были созданы динамически вызовом createtag. Не следует уничтожать статические теги, созданные при загрузке DAQ, это может нарушить работу DAQ - программ, ведь программы обычно используют статические теги для своих нужд. Большинство DAQ-программ инициализируют ссылки на статические теги в начале программы и затем полагаются на их неизменность, то есть на то, что они не уничтожаются и не создаются вновь. Это позволяет существенно ускорить выполнение программ, однако требует корректной работы с функциями createtag/freetag.
typetag(tag) - возвращает тип тега:
nametag(tag) - возвращает имя тега или пустую строку, если тега с такой ссылкой не существует. Имена тегов не чувствительны к регистру.
igettag(tag) - возвращает значение Integer тега tag. Если ссылка некорректна (несуществующий тег или тег другого типа), возвращается ноль. При ошибке типа тега также фиксируется ошибка в DAQ системе.
isettag(tag,i) - записывает в Integer тег tag значение i. Если ссылка некорректна (несуществующий тег или тег другого типа), возвращается false. При ошибке типа тега также фиксируется ошибка в DAQ системе.
rgettag(tag) - возвращает значение Real тега tag. Если ссылка некорректна (несуществующий тег или тег другого типа), возвращается ноль. При ошибке типа тега также фиксируется ошибка в DAQ системе.
rsettag(tag,r) - записывает в Real тег tag значение r. Если ссылка некорректна (несуществующий тег или тег другого типа), возвращается false. При ошибке типа тега также фиксируется ошибка в DAQ системе.
sgettag(tag) - возвращает значение String тега tag. Если ссылка некорректна (несуществующий тег или тег другого типа), возвращается ноль. При ошибке типа тега также фиксируется ошибка в DAQ системе.
ssettag(tag,s) - записывает в String тег tag значение s. Если ссылка некорректна (несуществующий тег или тег другого типа), возвращается false. При ошибке типа тега также фиксируется ошибка в DAQ системе.
taglock(lock) - блокирует/разблокирует доступ к тегам для других потоков. Вызов taglock(true) блокирует доступ и инкрементирует счетчик блокировок. Вызов taglock(false) разблокирует доступ и декрементирует счетчик блокировок. Функция возвращет текущий счетчик блокировок после выполнения инкремента/декремента. Блокировка применяется в особых случаях, когда надо выполнить атомарные (неделимые) операции над одним или несколькими тегами, чтобы другие потоки не нарушили логику работы алгоритма. Из соображений производительности следует максимально сокращать объем кода внутри заблокированной секции кода. В противном случае другие потоки могут оказаться на долгое время заблокированными при ожидании доступа к тегам. Функцию всегда надо применять парными вызовами, кратковременно блокируя, а затем разблокируя доступ. Например:
procedure IncTag(tag:Integer); // Атомарный инкремент тега
var i:Integer; b:Boolean;
begin
i:=taglock(true); // Блокировка
b:=iSetTag(tag,iGetTag(tag)+1); // Инкремент тега
i:=taglock(false); // Разблокировка
end;
Если баланс блокировок-разблокировок нарушается, фиксируется фатальная ошибка в DAQ-программе.
Поэтому функцию надо использовать очень осторожно.
iAtomicTagOp(tag,op,i) - выполняет атомарную (неделимую) операцию op с тегом tag и операндом i.
Значение op Операция Аналог
+ сложение iSetTag(tag, iGetTag(tag) + i)
- вычитание iSetTag(tag, iGetTag(tag) - i)
* умножение iSetTag(tag, iGetTag(tag) * i)
/ div iSetTag(tag, iGetTag(tag) div i)
% mod iSetTag(tag, iGetTag(tag) mod i)
& AND iSetTag(tag, iAnd(iGetTag(tag), i))
| OR iSetTag(tag, iOr(iGetTag(tag), i))
^ XOR iSetTag(tag, iXor(iGetTag(tag), i))
< сдвиг влево iSetTag(tag, iShift(iGetTag(tag), i))
> сдвиг вправо iSetTag(tag, iShift(iGetTag(tag), -i))
Отличие от приведенного в таблице аналога состоит в том, что операция выполненяется в защищенном блоке
TagLock(true); ... TagLock(false); , что гарантирует невмешательство других потоков
в процесс вычислений во время выполнения операции.
Например:
b:=iAtomicTagOp(tag,'+',1); // Атомарный инкремент тега
эквивалентно вызову
i:=TagLock(true); // Блокировка
b:=iSetTag(tag,iGetTag(tag)+1); // Инкремент тега
i:=TagLock(false); // Разблокировка
Зачем нужны атомарные операции?
Для начала заметим, что доступ к тегам, как и всем другим объектам в DaqPascal защищен и потокобезопасен.
Это значит, что разные потоки (а их бывает много) могут безопасно считывать и записывать данные, не мешая друг другу.
Другими словами, операции чтения iGetTag(tag) и записи iSetTag(tag,value) являются атомарными (неделимыми).
Однако некоторые операции над данными тегов не являются атомарными. Это возникает в случае, если операция предполагает
чтение value:=iGetTag(tag), изменение значения value, а затем запись результата iSetTag(tag,value).
Поскольку чтение и запись - две разные операции, другой поток может вмешаться в промежутке между чтением и записью
и изменить значение тега, в результате чего может произойти потеря данных.
Рассмотрим пример с двумя потоками, которые работают с битами целого числа.
Допустим, Поток 1 хочет установить 0-й бит целого тега tag,
а Поток 2 - установить 1-й бит. Эти биты соответствуют значениям 1 и 2.
Возможна такая ситуация:
Время | Значение тега | Поток 1 | Поток 2
------|---------------|------------------------------------|-----------------------------------
| начальное | хочет выполнить | хочет выполнить
| значение 0 | iSetTag(tag,iOr(iGetTag(tag),1)) | iSetTag(tag,iOr(iGetTag(tag),2))
------|---------------|------------------------------------|-----------------------------------
0 | 0 | вычисляет iOr(iGetTag(tag),1) = 1 | спит
1 | 0 | засыпает | просыпается
2 | 0 | спит | вычисляет iOr(iGetTag(tag),2) = 2
3 | 2 | спит | вычисляет iSetTag(tag,2)
4 | 2 | просыпается | засыпает
5 | 1 | вычисляет iSetTag(tag,1) | спит
Результат = 1 - а должно быть 1+2 = 3, то есть 1-й бит (2) потерян из-за конфликта потоков
Описанная выше ошибка может возникать довольно редко, но она возможна.rAtomicTagOp(tag,op,r) - выполняет атомарную (неделимую) операцию op с тегом tag и операндом r.
Значение op Операция Аналог
+ сложение rSetTag(tag, rGetTag(tag) + r)
- вычитание rSetTag(tag, rGetTag(tag) - r)
* умножение rSetTag(tag, rGetTag(tag) * r)
/ деление rSetTag(tag, rGetTag(tag) / r)
^ степень rSetTag(tag, Power(rGetTag(tag), r))
например: b:=rAtomicTagOp(tag,'+',1); - атомарный инкремент
Отличие от приведенного в таблице аналога состоит в том, что операция выполненяется в защищенном блоке
TagLock(true); ... TagLock(false); , что гарантирует невмешательство других потоков
в процесс вычислений во время выполнения операции.
gettagcolor(tag) - возвращает значение атрибута Color тега tag.
settagcolor(tag,c) - задает значение (с) атрибута Color тега tag.
gettagparam(tag) - возвращает значение атрибута Param тега tag.
settagparam(tag,p) - задает значение (p) атрибута Param тега tag.
gettagtimer(tag) - возвращает значение атрибута Timer тега tag.
settagtimer(tag,t) - задает значение (t) атрибута Timer тега tag.
Каждый тег имеет набор атрибутов (color, param, timer), которые доступны независимо от типа тега.
Атрибуты предназначены для хранения локальных пользовательских данных, ассоциированных с тегом.
Кроме того, атрибуты служат для взаимодействия с графической системой при использовании
сценариев Painter.
Сценарии рисования Painter позволяют прочитать атрибуты связанного с сенсором тега
с помощью вызовов
LinkedTagColor(),
LinkedTagParam(),
LinkedTagTimer().
Это позволяет передавать графической системе дополнительные параметры для рисования сложных изображений,
в дополнение к значению тега.
Атрибут цвета (color) может использоваться, например, для задания цвета сенсора.
Атрибут параметра (param) может использоваться, например, для передачи сенсору какого-то дополнительного параметра.
Атрибут метки времени (timer) может использоваться для действий, связанных с отсчетом времени, например,
для того, чтобы сигнализировать критическую задержку (timeout).
[TagList]
StartButton = integer 0
program TagDemo;
var
b : Boolean; { Temporary }
Ok : Boolean; { Program initialization is Ok? }
errors : Integer; { Program error counter }
errorcode : Integer; { Error code for this device }
tagStart : Integer; { Start button }
{
Initialize and check tag.
}
procedure InitTag(var tag:Integer; name:String; typ:Integer);
begin
tag:=findtag(name);
if (typ>0) and (typetag(tag)<> typ) then errors:=errors+1;
end;
begin
{
Initialization actions on Start
}
if runcount=1 then begin
{
Initialize errors & strings, clear variables.
}
errors:=0;
errorcode:=registererr(devname);
{
Initialize tags.
}
InitTag(tagStart,'StartButton',1);
{
Is it Ok?
}
if errors<>0 then b:=fixerror(errorcode);
Ok:=(errors=0);
end else
{
Finalization actions on Stop.
}
if isinf(runcount) then begin
b:=iSetTag(tagStart,0);
end else
{
Data acquisition actions on Poll.
}
if Ok then begin
{
If Start button clicked, similate data
}
if igettag(tagStart)<>0 then begin
b:=putao(0,time,(1+sin(time))*0.01);
b:=putao(1,time,(1+sin(time))*0.02);
end;
end;
end.
Цикл обработки событий, вариант 1: Цикл обработки событий, вариант 2:
if ClickWhat<>0 then while ClickWhat<>0 do begin
repeat if ClickButton=1 then
if ClickButton=1 then ... Обработка .......
... Обработка ....... iNul(ClickRead);
until ClickRead=0; end;
Client pseudo-code: Server pseudo-code:
// Получаем клик, передаем серверу: // Клиентский клик записываем в очередь:
if ClickWhat<>0 then begin if ReceiveFromClient(data) then
data:=ClickParams('')+CRLF+ if ClickWrite(data) then Writeln('Client click received.');
'NewValue=123.45'+CRLF+ // Обрабатываем события в обычном цикле обработки событий:
'Time='+Str(Time)+CRLF; if ClickWhat<>0 then
SendToServer(data); repeat
end; if ClickWrote>0 then begin // Это событие просимулировано
writeln('Клиент прислал NewValue='+ClickParams('NewValue'));
end;
until ClickRead=0;
Длина присоединяемых к исходному событию данных (cookies) не должна быть очень большой.
В настоящей версии максимальная длина cookies сотавляет около 3500 символов.
f:=ClickFilter(511); // При инициализации задаем фильтр событий
a:=ClickAwaker(511); // а также флаги "пробуждения по событию"
... // В данном случае разрешаем все события
procedure HandleClicks; // Процедура обработки пользовательских событий
begin // Вызывается в цикле опроса ApplicationPoll
if ClickWhat<>0 then // Что-то случилось?
repeat // Начинаем цикл обработки событий...
if ClickWhat=1 then begin // Нажатие кнопки мыши?
if ClickButton=1 then begin // Нажатие левой кнопки?
if ClickSensor='DEMO' then ... // Выполняем обработку нажатия сенсора
... // И так далее
end; //
end; //
if ClickWhat=3 then begin // Движение мыши?
writeln('Mouse move to: '+ClickParams('Where')); // Печатаем координаты мыши
... // И так далее
end;
if ClickWhat=6 then begin // Поворот колесика мыши?
writeln('Mouse wheel: '+ClickParams('Wheel')); // Печатаем приращение колесика
... // И так далее
end;
until ClickRead=0; // Читаем следующие события, пока они есть
end;
Одним из основных элементов для организации интерфейсов в системе CRW-DAQ являются мнемосхемы. Мнемосхемы описываются при помощи *.CRC-файлов (от слова circuit). Мнемосхема представляет собой окно с изображением некоторой схемы, взятое из *.BMP-файла, на которой расположены сенсоры - чувствительные области для отображения и управления. Каждый сенсор – это поименованный объект, который содержит набор *.BMP-изображений, ссылку на кривую или тег и на устройство CRW-DAQ, а также горячую клавишу.
Изображение сенсора меняется в зависимости от значения кривой или тега, который с ним связан. Обновление изображений сенсоров ядро CRW-DAQ делает автоматически, по мере изменения состояния связанных с сенсорами кривых или тегов, поэтому заботиться об этом специально не надо. С точки зрения программы интерфейса как бы и нет – есть только набор переменных – тегов, значения которых надо правильно устанавливать.
Более сложной является анимация мнемосхемы. Под анимацией (“оживлением”) мнемосхемы понимается то, что с каждым сенсором связывается некоторое действие или процедура, выполняемая по нажатию правой и левой кнопок мыши на сенсоре, а также при нажатии горячей клавиши shortcut. Для анимации сенсора (например, Button) через устройство - программу (например, &Animate) на DAQ PASCAL надо выполнить следующие действия (смотри пример ниже):
Link sensor Button with device &Animate tag ButtonTag shortcut KeyShortcut
Одной из основных функций обработки пользовательского ввода является clickbuttont, без нее практически не обходится ни одна программа Daq Pascal, связанная с интерфейсом пользователя. Функция возвращает виртуальный код нажатой клавиши и обычно используется как индикатор, что что-то произошло, например, нажатие кнопки мыши на одном из сенсоров или нажатие связанной с сенсором горячей клавиши. Функция возвращает чаще всего следующие значения:
В программе анимации мнемосхемы всегда надо сначала вызывать clickbutton, чтобы узнать, был ли нажат какой-либо из сенсоров, и лишь при значении clickbutton>0 следует начинать анализ того, какой именно сенсор был нажат. Это позволяет исключить лишние проверки, что повышает скорость работы программ и способствует снижению загрузки цикла обработки данных.
Файл конфигурации:
[Windows]
Circuit = Circuit_Window
[Circuit]
Link sensor Button with device &Animate tag ButtonTag shortcut Alt+F5
[TagList]
ButtonTag = integer 0
[DeviceList]
&Animate = device software program
[&Anamate]
ProgramSource = Animate.pas
Файл программы Animate.pas:
program Animate;
var b:boolean;
begin
if clickbutton>0 then begin
if clickbutton=str2shortcut('Alt+F5') then writeln('Нажато Alt+F5');
writeln('Все параметры нажатия:',clickParams(''));
if (clicksensor='BUTTON') then begin
if igettag(clicktag)<>0 then begin
b:=isettag(clicktag,0);
b:=voice(‘кнопка отпущена’);
end else begin
b:=isettag(clicktag,1);
b:=voice(‘кнопка нажата’);
end;
end;
end;
end.
Рекомендуется вызывать clicktag только после проверки clickbuttont, так как вызов
clickbuttont существенно быстрее.
Подробнее о мнемосхемах смотри clickbutton.
Функция дает доступ к большому числу перечисленных ниже параметров, однако ее вызов довольно медленный. Поэтому настоятельно рекомендуется вызывать clickparams только после проверки clickbutton, так как вызов clickbutton существенно быстрее.
s:=URL_Decode(clickparams('Hint')); // Текст справки Hint
В качестве имен можно применять, например:
Scroll_Lock Caps_Lock Right_Shift Sys_Req BkSp Tab
Num_5 Enter Shift Ctrl Alt Esc Space PgUp PgDn End
Home Left Up Right Down Ins Del
0 1 2 3 4 5 6 7 8 9
A B C D E F G H I J
K L M N O P Q R S T
U V W X Y Z Num_* Num_+ Num_- Num_Del
F1 F2 F3 F4 F5 F6 F7 F8 F9 F10
F11 F12 F13 F14 F15 F16 F17 F18 F19 F20
F21 F22 F23 F24 Pause ; = , - .
` ' [ ] / \
в комбинации с регистровыми клавишами Alt+, Ctrl+, Shift+.
Надо заметить, что с мышью связаны специальные виртуальные коды, возвращаемые clickbutton, которые также поддерживаются функциями str2shortcut,shortcut2str, хотя они и не являются клавиатурными клавишами. Это такие виртуальные коды:
if clickbutton>0 then begin
writeln(shortcut2str(clickbutton));
if clickbutton=str2shortcut('LButton') then LeftMouseButtonClick;
if clickbutton=str2shortcut('RButton') then RightMouseButtonClick;
if clickbutton=str2shortcut('Alt+F1') then Help;
...
end;
Вызов winshow может также создать и открыть консольное окно устройства - программы:
winshow('Консоль name')
где name - имя устройства.
Функция выполняются не сразу в момент вызова, а помещает команду на выполнение в FIFO команд, то есть реально выполняется позже, в низкоприоритетном программном потоке рисования, что является важным требованием системы реального времени. Отсюда следует, что функция
Пример программы, открывающей свою консоль:
b:=winshow('Консоль '+devname);
b:=windraw('Консоль '+devname+'|Top=100|Left=200');
WinDraw('WindowName|Fast|Attribute=Value|Attribute=Value|...)
где
WindowName - имя окна, которое надо (пере)рисовать
Fast - необязательный режим быстрого рисования (см. описание ниже)
Attribute - атрибут (команда), который надо изменить (исполнить)
Value - значение атрибута или аргументы команды
Чтобы уменьшить число прорисовок, рекомендуется все атрибуты передавать в одном вызове или использовать режим Fast.
В то же время существует ограничение на длину строки аргументов (около 245 символов),
поэтому возможны ситуации, когда из-за длинного списка аргументов вызов придется разбивать
на несколько более коротких.
b:=WinDraw('Demo Window|Fast|Eval=(ItemColor=clRed)|DrawSensor=Item'); или
b:=WinDraw('Demo Window|Fast|Eval=(ItemColor=clRed)|UpdateSensor=Item');
1) находит окно (мнемосхемы) Demo Window
2) вычисляет в этом окне выражение (ItemColor=clRed)
3) рисует или обновляет сенсор с именем Item (предполагается, что цветом ItemColor)
4) окно полностью не перерисовывается, потому что включен режим Fast
b:=WinDraw('Demo.Plot|Fast|LegendX=Ось абсцисс|LegendY=Ось ординат');
1) находит окно (график кривых) Demo.Plot
2) меняет надписи на осях X, Y, заданные LegendX, LegendY
3) окно полностью не перерисовывается, потому что включен режим Fast
Атрибуты рисования имеет вид Имя=Значение. Окна разных типов могут иметь разный набор атрибутов.
b:=windraw('Ip4.Viewer|Reload=Ip4.Bitmap');
// Фиксация положения окон:
b:=windraw('DemoPlot|Left=0|Options=-Left'); // Задать и зафиксировать горизонтальное положение окна
b:=windraw('DemoPlot|Top=50|Options=-Top'); // Задать и зафиксировать вертикальное положение окна
// Скроллинг мнемосхем:
b:=windraw('DemoPlot|Scroll=100;*'); // Установить горизонтальный сдвиг 100, вертикальный не менять
b:=windraw('DemoPlot|Scroll=*;150'); // Установить вертикальный сдвиг 150, горизонтальный не менять
b:=windraw('DemoPlot|Scroll=100;50'); // Установить горизонтальный сдвиг 100, вертикальный 150
b:=windraw('DemoPlot|Scroll=xmin;ymin'); // Сдвинуться в левый верхний угол (это начальное положение)
b:=windraw('DemoPlot|Scroll=xmin;ymax'); // Сдвинуться в левый нижний угол (пролистать вниз до конца)
b:=windraw('DemoPlot|Scroll=xmax;ymin'); // Сдвинуться в правый верхний угол (пролистать вправо до конца)
b:=windraw('DemoPlot|Scroll=xmax;ymax'); // Сдвинуться в правый нижний угол (пролистать вниз и вправо)
b:=windraw('DemoPlot|Scroll=xpos+10;ypos+20'); // Сдвинуться на 10 пикселей вправо и на 20 вниз
[Circuit.StartupScript] initialization
alarm=10 initial value of "alarm" parameter
...
TagEval(v) = gt(v,alarm) tag evaluation depends on "alarm"
...
b:=windraw('Demo.Control|Eval=(alarm=100)'); change value of "alarm"
Из соображений производительности число вызовов windraw('..|Eval=..') для изменения параметров
интерпретатора следует сократить до минимума,
то есть делать вызов только при действительном изменении параметров.
При этом, во избежание перерисовки всего окна, можно использовать режим Fast.
Функция выполняются не сразу в момент вызова, а помещает команду на выполнение в FIFO команд, то есть реально выполняется позже, в низкоприоритетном программном потоке рисования, что является важным требованием системы реального времени. Отсюда следует, что функция
b:=windraw('Температурные_кривые'+
'|Top=100|Left=200|Width=600|Height=400'
'|LegendY=^CTemperature in box^N^L {Celsius}'+
'|LegendX=^RHours ^N^CTime'+
'|Range=max(x1,xmax);y1;xmax+x2;ymax+y2'+
'|Roi=roix1;roiy1;_nan;_nan'+
'|SelectCurve=T#1'+
'|SaveCrw=..\data\tmp.crw');
b:=windraw('Температурные_кривые|Options=-Min,-Max,-Close,-Width,-Height');
b:=windraw('Температурные_кривые|SaveBmp=24bit,c:\data\image.bmp');
b:=windraw('Spectrum|LoadSpd=..\Data\demo.spd');
Функция выполняются не сразу в момент вызова, а помещает команду на выполнение в FIFO команд, то есть реально выполняется позже, в низкоприоритетном программном потоке рисования, что является важным требованием системы реального времени. Отсюда следует, что функция
Пример программы, закрывающей свою консоль:
b:=winhide('Консоль '+devname);
Функция выполняются не сразу в момент вызова, а помещает команду на выполнение в FIFO команд, то есть реально выполняется позже, в низкоприоритетном программном потоке рисования, что является важным требованием системы реального времени. Отсюда следует, что функция
Пример программы, активизирующей свою консоль:
b:=winshow('Консоль '+devname);
b:=winselect('Консоль '+devname);
При построении трехмерной поверхности рекомендуется по возможности использовать регулярную прямоугольную сетку (x,y), так как в этом случае рисование на порядок быстрее.
Допустимые атрибуты opt такие:
Функция выполняются не сразу в момент вызова, а помещает команду на выполнение в FIFO команд, то есть реально выполняется позже, в низкоприоритетном программном потоке рисования, что является важным требованием системы реального времени. Отсюда следует, что функция
procedure show3d(nx,ny:integer; x1,y1,x2,y2:real; formula:string);
var ix,iy:integer; x,y,z:real;
begin
if add3d(_nan,0,0) then begin
for ix:=0 to nx-1 do
for iy:=0 to ny-1 do begin
x:=x1+(x2-x1)*ix/(nx-1);
y:=y1+(y2-y1)*iy/(ny-1);
b:=evar('x',x);
b:=evar('y',y);
z:=eval(formula);
b:=add3d(x,y,z);
end;
b:=plot3d(nx,ny,x1,y1,x2,y2,
'Caption=Plot3d'+
'|Title=Заголовок|Legend=Легенда'+
'|top=100|left=100|width=600|height=400'+
'|scalex=1|scaley=1|scalez=1'+
'|phi=60|psi=85'+
'|replace=1');
b:=plot3d(0,0,0,0,0,0,'');
end else writeln('Could not plot now!');
end;
specmark(win) - функция возвращает текущий маркер спектрометрического окна с именем win.
specmarkl(win) - функция возвращает левый маркер спектрометрического окна с именем win.
specmarkr(win) - функция возвращает правый маркер спектрометрического окна с именем win.
specroil(win) - функция возвращает левый маркер ROI спектрометрического окна с именем win.
specroir(win) - функция возвращает правый маркер ROI спектрометрического окна с именем win.
Вычисление интеграла по ROI:
a:=specroil('Spectr');
b:=specroir('Spectr');
integ:=crvinteg(refai(0),a,b);
Проблема с модальными диалогами в том, что программа Daq Pascal выполняется в своем потоке и не может (да и не должна) напрямую вызывать какие-либо диалоги. Ведь в этом случае поток программы будет приостановлен на время выполнения диалога, что делать нельзя. Ведь неправильно приостанавливать поток измерительной DAQ-программы во время редактирования, так как редактирование - длительная операция, а измерения идут непрерывно. Поэтому логика работы диалогов DAQ Pascal другая - асинхронная. Или, фактически, логика Конечного Автомата (Finite State Machine). При этом алгоритм работы автомата основан на анализе переменных состояния в режиме опроса. Функция EditState служит для быстрого получения общего (сводного) состояния, а функция Edit('?..') - для получения наиболее подробного состояния.
Когда редактор свободен (EditState=0 или EditStateReady), программа может инициировать диалог. При инициировании с диалогом связывается некий идентификатор, который при завершении диалога используется для интерпретации результатов. Инициирование диалога состоит в следующем: программа при помощи вызовов edit формирует задание для редактирования и продолжает выполнение. В цикле опроса анализируется статус редактора, а само редактирование выполняется асинхронно, в другом потоке, в соответствии с заданием. После инициирования задания на редактирование через некоторое время, после ввода пользователем данных и нажатия Ok или Cancel, появится статус готовности ответа (EditState=1 или EditStateDone). По этому статусу можно читать результат редактирования. При чтении результата используется идентификатор завершившегося диалога, что позволяет различать разные диалоги, если в программе их несколько. Такова, вкратце, логика редактирования в DAQ Pascal.
function editstate:integer - возвращает статус редактирования. Это комбинация флагов:
function edit(params:string):string - основная функция редактирования. Редактирование получается за счет многократного вызова этой функции с разными аргументами. Перед вызовом edit надо проверять editstate.
Вызов edit('') используется, для начальной очистки редактора, при этом происходит сброс флагов 1,4,8 редактора, флаг 2 сбрасывается только самим редактором.
При вызове edit(params) с непустым значением params<>'', работа функции определяется первым символом params[1]:
В нулевой строке результата ?ans 0 возвращается строка Name=Result, где Name - идентификатор диалога, который был указан при создании диалога, Result - статус редактирования, это одна из констант:
Другие строки результата (?ans 0, ?ans 1,...),
если они нужны, определяются типом диалога.
Типы диалога такие:
if editstate=0 then begin
if pos('?',edit('(Нет связи!')
+edit(' Проверьте провода!')
+edit('@set Form.Left '+ExtractWord(1,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit('@set Form.Top '+ExtractWord(4,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit(')Warning NetworkWarning'))>0
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
if IsSameText(edit('?ans 0'),'NetworkWarning=1') then {Ok} else
if IsSameText(edit('?ans 0'),'NetworkWarning=2') then {Cancel};
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
edit('@set ListBox.Font Name:PT_Mono\Size:10\Color:Black\Style:[Bold]') - задать шрифт списка меню
В описании координат LLL, TTT
и размеров WWW, HHH используется число,
вслед за которым может следовать ключевое слово relative (относительно)
и указание идентификатора окна и (возможно) идентификатора визуального элемента в этом окне,
относительно которого указывается координата или размер.
Если ключевое слово relative не указано,
координаты или размеры задаются в абсолютных единицах (пикселях на экране).
При указании относительных координат их значения корректируются с учетом расположения указанного окна или элемента.
При указании относительных размеров размер берется в процентах от указанного окна или элемента.
edit('@set Form.Left 200') - указание абсолютной координаты на экране
edit('@set Form.Width 80 relative Screen') - ширина окна 80% относительно ширины экрана
edit('@set Form.Top 20 relative Desktop') - координата относительно Рабочего Стола (зависит от настроек Windows)
edit('@set Form.Left 30 relative DemoEdit PaintBox') - координата относительно окна с заголовком DemoEdit, элемента PaintBox
edit('@set Form.Width 40 relative *::FormCrw32') - ширина 40% относительно ширины главного окна пакета
edit('@set Form.Height 50 relative TFormCrw32::*') - высота 50% относительно высоты главного окна пакета
if (ClickButton=1) then
if IsSameText(ClickSensor,'Demo') then begin
if editstate=0 then begin
if pos('?',edit('(Выбор термопары')
+edit(' Задайте термопару')
+edit(' Термопара 1') + edit('>@termocouple 1') // Здесь задается текст
+edit(' Термопара 2') + edit('>@termocouple 2') // пунктов меню, а также
+edit(' Термопара 3') + edit('>@termocouple 3') // пользовательские команды
+edit('@set Panel.Font Name:PT_Mono\Size:12\Color:Maroon\Style:[Bold]')
+edit('@set ListBox.Font Name:PT_Mono\Size:24\Color:Navy\Style:[Bold]')
+edit('@set Form.Left '+ExtractWord(1,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit('@set Form.Top '+ExtractWord(4,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit('@set Form.Width 300')+edit('@set Form.Height 230')
// Другие варианты задания размеров:
//+edit('@set Form.Width 40 relative Desktop')+edit('@set Form.Height 50 relative Screen')
//+edit('@set Form.Width 50 relative '+ClickParams('Window'))+edit('@set Form.Height 50 relative '+ClickParams('Window'))
//+edit('@set Form.Width 40 relative *::FormCrw32')+edit('@set Form.Height 50 relative TFormCrw32::*')
+edit(')MenuList MENU_TC 2'))>0
// Здесь 2 - номер текущего пункта, начиная с 0
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
end;
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin // Если редактирование завершено
if IsSameText(edit('?ans 0'),'MENU_TC=1') // Если статус результата = 1 = OK
then n:=val(edit('?ans 1')) else n:=-1; // Узнать номер n выбранного пункта
s:=Trim(edit('?cmd '+Str(n))); // Извлечь пользовательскую команду s
if Pos('@',s)=1 then b:=devpostmsg(devname+' '+s+CRLF); // Если она есть, послать её в консоль
s:=edit(''); // Сброс редактора в исходное состояние
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
if editstate=0 then begin
if pos('?',edit('(Выбор термопар')
+edit(' Задайте термопары')
+edit(' Термопара 1')
+edit(' Термопара 2')
+edit(' Термопара 3')
+edit(' Термопара 4')
+edit(' Термопара 5')
+edit('@set Panel.Font Name:PT_Mono\Size:12\Color:Green\Style:[Bold]')
+edit('@set ListBox.Font Name:PT_Mono\Size:14\Color:Magenta\Style:[Bold]')
+edit('@set Form.Left 200 relative DemoEdit PaintBox')
+edit('@set Form.Top 250 relative DemoEdit PaintBox')
+edit('@set Form.Width 300')+edit('@set Form.Height 220')
+edit(')SelectionList SEL 0 5'))>0
// Здесь 0 - текущий пункт, 5 - битовая маска выбранных пунктов
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
if IsSameText(edit('?ans 0'),'SEL=1') then
for i:=1 to val(edit('?ans Count'))-1 do
writeln('Select element № ',edit('?ans '+str(i)));
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
if editstate=0 then begin
if pos('?',edit('(Выбор термопар')
+edit(' Задайте используемые термопары')
+edit(' Термопара 1')
+edit(' Термопара 2')
+edit(' Термопара 3')
+edit(' Термопара 4')
+edit(' Термопара 5')
+edit('@set Panel.Font Name:PT_Mono\Size:12\Color:Green\Style:[Bold]')
+edit('@set ListBox.Font Name:PT_Mono\Size:14\Color:Magenta\Style:[Bold]')
+edit('@set Form.Left 200 relative DemoEdit PaintBox')
+edit('@set Form.Top 250 relative DemoEdit PaintBox')
+edit('@set Form.Width 300')+edit('@set Form.Height 220')
+edit(')CheckBoxList SEL 0 5'))>0
// Здесь 0 - текущий пункт, 5 - битовая маска отмеченных флажками пунктов
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
if IsSameText(edit('?ans 0'),'SEL=1') then
for i:=1 to val(edit('?ans Count'))-1 do
writeln('Select element № ',edit('?ans '+str(i)));
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
if editstate=0 then begin
if pos('?',edit('(Отредактируйте комментарий')
+edit(' Эта калибровка')
+edit(' снята при температуре 25°C.')
+edit('@set Editor.Font Name:PT_Mono\Size:14\Color:Blue\Style:[Bold]')
+edit(')TextEdit EDIT_NOTE'))>0
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
if IsSameText(edit('?ans 0'),'EDIT_NOTE=1') then
for i:=1 to val(edit('?ans Count'))-1 do
writeln('Line ',i,' -> ',edit('?ans '+str(i)));
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
На выходе:
if editstate=0 then begin
if pos('?',edit('(Отредактируйте таблицу')
+edit(' Temperature|10|20|30')
+edit(' Pressure|100|200|300')
+edit('@set StringGrid.Font Name:PT_Mono\Size:14\Color:Red\Style:[Bold]')
+edit(')StringGridEdit EDIT_TAB'))>0
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
s:=worddelims('|');
if IsSameText(edit('?ans 0'),'EDIT_TAB=1') then
for i:=1 to 3 do begin
T[i]:=val(ExtractWord(i+1,edit('?ans 1')));
P[i]:=val(ExtractWord(i+1,edit('?ans 2')));
end;
s:=worddelims(s);
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
На выходе:
if pos('?',edit('(Please open file(s)')
+edit(' c:\daq\*.cfg')
+edit(' Text files (*.txt)|*.txt|')
+edit(' Config files (*.cfg)|*.cfg|')
+edit(' All files (*.*)|*.*|')
+edit('@set Form.Left '+ExtractWord(1,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit('@set Form.Top '+ExtractWord(4,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit(')FileOpenDialog FOPEN'))>0
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
if IsSameText(edit('?ans 0'),'FOPEN=1') then
for i:=1 to val(edit('?ans Count'))-1 do
writeln('Selected file ',edit('?ans '+str(i)));
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
На выходе:
if pos('?',edit('(Please select directory')
+edit(' '+ParamStr('DaqConfigPath'))
+edit(' sdAllowCreate, sdPerformCreate, sdPrompt')
+edit('@set Form.Left '+ExtractWord(1,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit('@set Form.Top '+ExtractWord(4,ClickParams('Bounds'))+' relative '+ClickParams('Window')+' PaintBox')
+edit(')SelectDirectoryDialog SELDIR'))>0
then writeln('Ошибка инициирования диалога!');
end else writeln('В настоящий момент редактирование недоступно!');
Пример анализа результата:
{редактирование завершено?}
if editstate=1 then begin
if IsSameText(edit('?ans 0'),'SELDIR=1') then
writeln('Selected directory ',edit('?ans 1'));
s:=edit('');{Сброс}
end;
{найдена ошибка?}
if iand(editstate,8)<>0 then begin
writeln('Dialog error found!');
s:=edit('');{Сброс}
end;
Другой пример (программа редактирования строки):
program editstr;
var s:string; x:real;
begin
{инициирование диалога при нажатии сенсора EDIT_X}
if (clickbutton=1) and (clicksensor='EDIT_X') then
if editstate=0 then begin
if pos('?',edit('(Отредактируйте переменную:')
+edit(' X=|10')
+edit(')StringGridEdit EDIT_X'))>0
then writeln('Не могу инициировать диалог!');
end;
{анализ результата}
if editstate=1 then begin
if extractword(1,edit('?ans 0'))='EDIT_X' then begin
if extractword(2,edit('?ans 0'))='1' then x:=rval(copy(edit('?ans 1'),4,255));
s:=edit(''); {сброс}
end;
end;
{сброс по ошибке};
if editstate=8 then s:=edit('');
end.
Напомним, что система CRW-DAQ имеет систему безопасности (guard), которая опирается на понятие уровня доступа. В каждый момент времени система имеет некий текущий уровень доступа, который определяет набор доступных для оператора возможностей, причем смена уровней доступа защищена паролями.
В настоящее время предусмотрены такие уровни доступа:
Функция guard_check(level) сравнивает текущий уровень доступа с уровнем, заданным строкой level, и возвращает
if guard_check('user') < 0 then ... отказать в правах уровня User
if guard_check('user') >=0 then ... предоставить права уровня User
if clickbutton=1 then begin
if clicksensor='START' then
if guard_check('root') < 0
then writeln('You need Root access level to START!')
else Start;
if clicksensor='STOP' then
if guard_check('user') < 0
then writeln('You need User access level to STOP!')
else Stop;
end;
Группа функций comopen, comclose, comcount, comspace, comread, comwrite ориентирована на работу с портом последовательной связи, то есть связи с дисциплиной доступа типа FIFO. Функции поддерживают работу в дуплексном режиме, то есть с возможностью одновременной передачи данных в двух направлениях. В первую очередь это RS-232 для связи с измерительными устройствами и именованные каналы (NamedPipe) для сетевого обмена по Ethernet. Эта группа функций пригодна в принципе и для RS-485, однако, поскольку RS-485 поддерживает только полудуплексный режим работы (одновременно допустима только передача в одном направлении), а также имеет адресацию устройств, драйвер должен сам обеспечивать корректную реализацию полудуплексного режима и адресации всех устройств на шине. Но вообще лучше работать с RS-485 через функции Adam_XXX.
Программа Daq Pascal может обслуживать только один COM-порт. Для обслуживания нескольких портов параллельно запускается несколько DAQ-программ. Либо используются каналы связи, см. функции pipe_xxxx. При необходимости совместной работы параллельные программы обмениваются данными через теги, кривые или сообщения. Заметим, что возможна одновременная работа с RS-232 и RS-485, так как RS-485 имеет свой набор функций Adam_XXX.
Надо иметь в виду, что именованные каналы доступны только под Windows-NT/2K/XP/7/8/10/11. При сетевом обмене один экземпляр должен быть сервером, другой клиентом (возможно, на другой машине). Параметры порта считываются из указанной в строке s секции в файле конфигурации CRW-DAQ, например,
b:=comopen('[SerialPort-COM2]');
или
b:=comopen('[PipeServer]');
Надо учитывать, что процедура comopen монопольно захватывает порт, не проверяя, занят ли он другими устройствами. По этой причине нельзя подключать на этот порт другие устройства DAQ, иначе будет конфликт. Поэтому, в частности, нельзя использовать процедуры comXXX для того же порта, к которому подключены устройства серии ADAM или другое устройство program, использующее этот же порт. Однако можно бесконфликтно использовать устройства серии ADAM и Program на разных портах.
Следует иметь в виду, что все функции COM-порта реализованы асинхронно. Есть два FIFO-буфера, через которые принимаются и передаются данные, приемник и передатчик. Собственно чтение-запись осуществляет отдельный программный поток драйвера порта. При записи данных в порт данные немедленно помещаются в буфер передатчика и программа продолжает выполнение. Параллельно работающая программа драйвера порта читает данные из буфера передатчика и передает их в порт. Данные, прочитанные драйвером из порта, складываются в буфер приемника, откуда программа их забирает. Таким образом, во всех случаях вызовы функций являются неблокирующими, то есть не приводят к приостановке потока программы.
При реализации драйверов устройств RS-232 также необходимо стремиться к асинхронной работе программы. Пусть, например, устройству посылается сообщение msg и в течение 100 миллисекунд ожидается ответ из 3 символов. Самая простая реализация:
t:=msecnow;
b:=comwrite(msg);
while (comcount<3) and (msecnow-t<100) do;
ans:=comread(3);
приведет к тому, что процессор в течение 100 миллисекунд будет занят
ненужными проверками в цикле ожидания, а поток будет стоять без дела.
Загрузку процессора можно устранить при помощи Sleep:
t:=msecnow;
b:=comwrite(msg);
while (comcount<3) and (msecnow-t<100) do b:=Sleep(1);
ans:=comread(3);
однако и в этом случае поток программы будет заблокирован и ничего полезного сделать не сможет.
Правильным подходом будет введение переменной состояния, например, state. При записи данных в порт переменная состояния устанавливается в 1, при получении ответа или при превышении времени ожидания сбрасывается в 0.
var t:real; state:integer;
begin
if state=0 then begin
t:=msecnow;
b:=comwrite(msg);
state:=1;
end else
if state=1 then begin
if comcount=3 then begin
ans:=comread(3);
state:=0;
end;
if msecnow-t>=100 then begin
writeln('timeout');
state:=0;
end;
end;
DoSomethingElse;
end;
Программа, конечно, несколько усложнилась, так как теперь запрос и ответ обрабатываются
в разных вызовах программы, в разное время.
Однако теперь программа никогда не блокируется, нет никаких задержек и циклов ожидания.
Поэтому, независимо от состояния запроса, при каждом входе программа будет выполнять
процедуру DoSomeThingElse, то есть делать что-то полезное помимо обмена данными
с портом.
Таков общий подход к построению DAQ программ, который можно обозначить термином
асинхронное программирование.
Конфигурирование сетевого канала (PipeLine).
[SerialPort-COM1] ; Разные варианты канала связи: PipeLine = Com Port 1 Baudrate 9600 Parity NONE DataBits 8 StopBits 1 ; Обычный COM порт PipeLine = Pipe DEMO Polling 4 Priority tpHighest ListenPeriod 100 ; Именованный канал DEMO (сервер) PipeLine = Pipe .\DEMO Polling 4 Priority tpHighest ListenPeriod 100 ; Именованный канал DEMO (клиент) PipeLine = Tcp Port 1234 Server 1 Polling 4 Priority tpHighest ; Сокет TCP:1234 (сервер) PipeLine = Tcp Port 1234 Client localhost Polling 4 Priority tpHighest ; Сокет TCP:1234 (клиент)
Конфигурирование именованного канала (NamedPipe).
Конфигурирование RS-232/485.
Примеры описаний порта:
[SerialPort-COM2] ; Секция описания COM2-порта
Port = COM2 ; Идентификатор COM-порта номер 2
BaudRate = 115200 ; Скорость передачи, бод
Parity = NONE ; Проверка на четность: NONE/ODD/EVEN/MARK/SPACE
DataBits = 8 ; Число бит данных: 5/6/7/8
StopBits = 1 ; Число стоп-бит: 1/1.5/2
XonXoff = 0 ; Использовать ли протокол Xon-Xoff: 0/1
BufSize = 8 ; Размер буфера fifo в килобайтах
DcbFlags = $3004 ; Флаги, задающие режим COM-порта: в данном случае CTS/RTS
[PipeServer] ; секция описания именованного канала (сервер)
PipeName = postbox ; идентификатор канала
FifoSize = 16 ; размер буфера, КБ
PipePolling = 10, tpNormal ; поток опроса канала
TimeOut = 1000 ; тайм-аут для установки связи, миллисекунд
ListenPeriod = 100 ; период опроса (accept), миллисекунд
[PipeClient] ; секция описания именованного канала (клиент)
PipeName = host\postbox ; компьютер\канал
FifoSize = 16 ; размер буфера, КБ
PipePolling = 10, tpNormal ; поток опроса канала
TimeOut = 1000 ; тайм-аут для установки связи
ListenPeriod = 100 ; период опроса (connect), миллисекунд
[TcpServer] ; Секция описания сетевого сокета (сервер)
PipeLine = tcp port 1234 server 1 polling 4 priority tpHighest
[TcpClient] ; Секция описания сетевого сокета (клиент)
PipeLine = tcp port 1234 client localhost polling 4 priority tpHighest
Пример терминала RS-232:
Файл _RS232.cfg
[DeviceList]
&RS232 = device software program
[&RS232]
Port = SerialPort-COM1 ; Секция описания порта
ProgramSource = _RS232 ; Программа
[SerialPort-COM1] ; описание COM-порта номер 1
Port = COM1 ; идентификатор COM-порта номер 1
BaudRate = 9600 ; скорость передачи, бод
Parity = NONE ; проверка на четность - NONE, ODD, EVEN
DataBits = 8 ; число бит данных
StopBits = 1 ; число стоп-бит
XonXoff = 0 ; использовать ли протокол XON/XOFF
BufSize = 8 ; размер буфера fifo в килобайтах
DcbFlags = 0 ; дополнительные флаги RS-232, при использовании CTS/RTS/DTR/DTS
Файл _RS232.pas:
program _RS232;
var
b,Ok:Boolean; errors,errorcode,n:Integer; inp,out:string;
begin
{
Initialization actions on Start
}
if runcount=1 then begin
errors:=0;
errorcode:=registererr(devname);
writeln(devname+': start');
inp:='';
out:='';
if not comopen('['+readini('Port')+']') then begin
writeln(devname+': Could not open COM port!');
errors:=errors+1;
end;
b:=winshow('Консоль '+devname);
if errors<>0 then b:=fixerror(errorcode);
Ok:=(errors=0);
end else
{
Finalization actions on Stop
}
if isinf(runcount) then begin
writeln(devname+': stop');
inp:='';
out:='';
b:=comclose;
end else
{
Actions on Poll
}
if Ok then begin
if not eof then begin
readln(out);
if comspace>length(out)+1 then begin
if comwrite(out+chr(13))
then writeln('SEND:':9,' ',out)
else writeln('SEND ERROR');
end else begin
writeln('Tx overflow.');
b:=comclear;
end;
end;
if comcount>0 then inp:=inp+comread(comcount);
n:=pos(chr(13),inp);
if n>0 then begin
writeln('RECEIVED:':9,' ',copy(inp,1,n-1));
inp:='';
end;
if ioresult<>0 then b:=fixerror(errorcode);
end;
end.
adam_status возвращает статус устройства Adam_Slot в сети RS-485:
adam_get(what) - эта функция в зависимости от параметра what возвращает:
adam_request(request,timeout) - эта функция возбуждает запрос request, который передается в устройство Adam_Slot для последующей передачи в сеть RS-485, когда это станет возможным. Абсолютное значение timeout определяет время ожидания ответа на запрос в миллисекундах, причем если timeout=0, используется стандартное время, а если отрицательное - подавляется генерация ошибки TimeOut.
adam_reqtime - возвращает время передачи последнего запроса в сеть RS-485. Фактически это время вызова adam_request.
[SerialPort-COM1]
Port = COM1
BaudRate = 115200
Parity = NONE
DataBits = 8
StopBits = 1
XonXoff = false
[DeviceList]
&Slot18 = device adam adam_slot
[&Slot18]
InquiryPeriod = 40
Port = 1
Address = $08
UsesCheckSum = 1
[DeviceList]
&Adam18 = device software program
[&Adam18]
InquiryPeriod = 40
DebugMode = 3
ProgramSource = .\_slot18
Adam_Slot_Device = &Slot18
program _slot18;
const { Коды adam_status: }
rs_NotAvail = 0; { RS-485 недоступен, вероятно, неверен Adam_Slot_Device }
rs_NoRequest = 1; { Запрос очищен - можно слать новый запрос Adam_Request }
rs_WaitQueue = 2; { Запрос послан в слот, ждем его передачи на линию RS }
rs_WaitAnswer = 3; { Запрос послан, передан на линию RS, ждем ответа с линии }
rs_Answer = 4; { Пришел ответ Adam_Get('Answer') на запрос }
rs_TimeOut = 5; { TimeOut - не пришел ответ в течение заданного срока }
var b:boolean; i:integer; s,addr:string;
procedure raiserequest;
begin
i:=i+1;
s:='#'+addr+str(i mod 8);
writeln('Request: ',s);
if not adam_request(s+chr(13),100) then writeln('Error');
end;
begin
if runcount=1 then begin
s:='';
addr:=adam_get('address');
writeln('Address > ',adam_get('address'));
writeln('Port > ',adam_get('port'));
end else
if isinf(runcount) then begin
writeln('STOP');
end else
begin
case adam_status of
rs_NotAvail : writeln('COM port is not available.');
rs_NoRequest : raiserequest;
rs_WaitQueue : writeln('Wait queue...');
rs_WaitAnswer : writeln('Wait answer...');
rs_Answer : begin
writeln('Request > ',adam_get('request'));
writeln('Time > ',adam_reqtime:14:8);
writeln('Answer > ',adam_get('answer'));
raiserequest;
end;
rs_TimeOut : begin
writeln('TimeOut');
raiserequest;
end;
end;
end;
end.
Функции inportb, inportw, inportl предназначены для чтения данных из порта ввода.
Функции outportb, outportw, outportl предназначены для записи данных в порт вывода. Все три функции возвращают значение записываемых данных data.
Во всех случаях данные передаются через число Integer, только используется в нем 1, 2 или 4 младших байта.
addr:=val('$300');
di:=inportw(addr);
data:=GetData;
do:=outportw(addr+2,data);
for tid:=TASK_REF_MIN to TASK_REF_MAX do
if (task_ref(tid)<>0) then writeln('TASK ',tid,' has PID ',task_pid(tid));
Библиотека включает 20 функций:
if task_wait(tid,0) then // Если процесс работает
if task_kill(tid,1,0,1000) then // Пробуем убить процесс "мягко" (через WM_CLOSE) в течение 1000 ms
Success('Kill(1) PID '+str(task_pid(tid))); // Если получилось, то пишем сообщение
if task_wait(tid,0) then // Если процесс все еще работает
if task_kill(tid,0,0,1000) then // Пробуем убить процесс "жестко" (через Terminate) в течение 1000 ms
Success('Kill(0) PID '+str(task_pid(tid))); // Если получилось, то пишем сообщение
При использовании метода завершения 1 (WM_CLOSE) следует иметь в виду код возврата.
Если консольная задача завершается через ^C (CTRL-C),
что эквивалентно посылке консольному окну WM_CLOSE,
то кодом возврата процесса будет
STATUS_CONTROL_C_EXIT = -1073741510; // Код завершения при CTRL-C = $C000013AСм. например NTSTATUS Error Code List.
i:=shellexecute('open | notepad.exe | c:\readme.txt | c:\ | SHOWNORMAL');
i:=shellexecute('open | notepad.exe | c:\readme.txt | .');
i:=shellexecute('explore | | | c:\paslib | showmaximized');
i:=shellexecute('open | readme.htm');
Надо отметить также, что вызов shellexecute возвращает управление
сразу после создания процесса.
Кроме того, поскольку функция не возвращает никакую ссылку на процесс,
отследить его дальнейшее выполнение нельзя.
Можно также сказать, что вызов shellexecute эквивалентен последовательности
вызовов функций task_xxx, например:
Вызов
i:=shellexecute('open | notepad.exe | c:\readme.txt | c:\ | SHOWNORMAL');
аналогичен последовательности
tid:=task_init('');
s:=task_ctrl(tid,'CmdLine=notepad.exe c:\readme.txt');
s:=task_ctrl(tid,'HomeDir=c:\');
b:=task_run(tid);
b:=task_free(tid);
Удобство shellexecute в первую очередь в том, что можно открывать документы,
не заботясь о поиске программы просмотра для этого типа файлов.
Система сама найдет нужную программу.
Альтернативой является вызов типа paramstr('GetExeByFile c:\readme.htm')
для получения имени программы для данного типа документов и затем запуск
этой программы с именем документа в командной строке.
Этот путь сложнее, хотя возможности его больше.
Вызов shellexecute дает более короткий путь для простых случаев.
Pid,ParentPid,Threads,Priority,FileName
где:
Особо надо сказать о запуске задач от имени другого пользователя. Поскольку давать пользователю право запускать программы от имени администратора означает рисковать возможностью взлома системы, предприняты усиленные меры защиты. Разумеется, для запуска задачи программе нужен аккаунт, то есть имя пользователя, имя домена и пароль. Разумеется также, что прописывать аккаунт в программе напрямую нельзя, иначе пользователь может прочитать текст программы и узнать пароль администратора. Выход состоит в том, чтобы открыто хранить ключ, то есть зашифрованный аккаунт, причем шифровать ключ надо так, чтобы этот ключ можно было использовать только для запуска конкретной программы и ни для чего больше.
Для генерации ключа используется вызов
tid:=task_init('c:\Crw32exe\Crw32.exe');
key:=task_ctrl(tid,'Encrypt='+'User'+CRLF+'Domain'+CRLF+'Password');
Разумеется, генерация ключа делается "дома", на недоступной для пользователя машине,
так как пароль в этот момент прописывается явно.
Для генерации ключа предварительно должно быть указано полное имя файла,
с полным путем и расширением.
Файл должен быть именно тот, который будет в рабочей версии, так как ключ привязан
к конкретному файлу и без него никак не может быть использован.
После генерации ключ заносится как константа в текст программы.
Из этой абракадабры пользователь ничего не поймет.
В качестве имени домена можно использовать как имя сетевого домена, так и имя локального компьютера, либо явно, либо в виде точки. Точка в качестве имени домена означает учетную запись на локальном компьютере. Это удобно, так как позволяет не заботиться об имени локальной машины. Пустое имя домена разрешает поиск учетной записи как на локальном компьютере, так и в известных системе доменах. На практике чаще всего будет использоваться либо точка, либо имя сетевого домена.
Для запуска программы на исполнение используется вызов:
tid:=task_init('c:\Crw32exe\Crw32.exe');
s:=task_ctrl(tid,'Account='+key);
tid:=task_run(tid);
Ключ заносится как константа в текст программы.
Из этой абракадабры пользователь ничего не поймет.
Для исполнения должно быть указано полное имя файла, с полным путем и расширением.
Файл должен быть именно тот, который использовался при генерации пароля, так как ключ
привязан к конкретному файлу и без него никак не может быть использован.
В отличие от запуска программ под текущим аккаунтом, где допустимы короткие имена файлов, при запуске под другим аккаунтом имя исполняемого файла должно быть обязательно ПОЛНЫМ, то есть включать полный путь, имя файла и расширение файла, иначе программа не сможет правильно расшифровать ключ. Ведь ключ привязан к конкретному файлу. Такая привязка обязательно нужна, чтобы нельзя было подменить исполняемый файл другим файлом и получить таким образом права администратора.
Не забудьте также, что при запуске под другим пользователем этот пользователь должен иметь права на чтение и запуск исполняемого файла и на использование других необходимых ресурсов. Не буду на этом останавливаться, это общие вопросы, не зависящие от конкретной программы.
Разумеется, если защита пароля не нужна (для отладок), можно просто писать:
tid:=task_init('c:\Crw32exe\Crw32.exe');
s:=task_ctrl(tid,'Account='+task_ctrl(tid,'Encrypt='+'User'+CRLF+'Domain'+CRLF+'Password'));
tid:=task_run(tid);
но в конечном варианте этого следует избегать.
Пример программирования: DEMO_TASK.
1)Вызвать программу и пусть ее живет...
tid:=task_init(getcomspec+' /c example.bat');
b:=task_run(tid);
b:=task_free(tid);
2)Вызвать программу, ждать 10 секунд и убить, если не завершилась сама.
tid:=task_init(getcomspec+' /c example.bat');
b:=task_run(tid);
if task_wait(tid,10000) then task_kill(tid,0,0,1000);
b:=task_free(tid);
3)Вызвать программу c обменом данными по каналу и установкой приоритетов...
tid:=task_init('');
s:=task_ctrl('CmdLine=c:\example.exe');
s:=task_ctrl('HomeDir=c:\');
s:=task_ctrl('StdInPipeSize=1000');
s:=task_ctrl('StdOutPipeSize=1000');
s:=task_ctrl('StdInPriority=tpTimeCritical');
s:=task_ctrl('StdOutPriority=tpTimeCritical');
s:=task_ctrl('ThreadPriority=tpTimeCritical');
s:=task_ctrl('ProcessPriority=RealTime');
b:=task_run(tid);
if task_wait(tid,0) then writeln('task is running');
if task_rxcount>0 then writeln('Received:',task_recv(tid,100));
if task_txspace>100 then writeln('Sent:',task_send(tid,s),' bytes');
b:=task_free(tid);
4)Вызвать программу от имени другого пользователя...
tid:=task_init('');
{--задание программы, должно предшествовать генерации ключа}
{--имя файла программы должно быть полным}
s:=task_ctrl('AppName=c:\Crw32exe\Crw32.exe');
{--генерация ключа для запуска файла, делается на этапе отладки}
{--ключ уникален для данного исполняемого файла и аккаунта}
{--Encrypt=USER+CRLF+DOMAIN+CRLF+PASSWORD}
key:=task_ctrl('Encrypt='+'Alex'+CRLF+'.'+CRLF+'eklmn');
{--в рабочем режиме ключ подставляется как константа}
key:=task_ctrl('Account='+key);
b:=task_run(tid);
b:=task_free(tid);
5)Показать список выполняемых процессов...
procedure ShowTaskList;
var t,i:Integer; b:Boolean;
begin
writeln('Pid,ParentPid,Threads,Priority,FileName:');
t:=pidlist(text_new);
for i:=0 to text_numln(t)-1 do writeln(text_getln(t,i));
b:=text_free(t);
end;
Результатом будет что-то вроде:
Pid,ParentPid,Threads,Priority,FileName:
0,0,1,0,[System Process]
4,0,58,8,System
400,4,3,11,smss.exe
580,400,11,13,csrss.exe
612,400,19,13,winlogon.exe
656,612,16,9,services.exe
...
3416,1752,12,8,CRW32.exe
2948,3416,6,8,IEXPLORE.EXE
2984,3416,1,8,cmd.exe
3328,3416,1,8,cmd.exe
6)Убить все процессы по известному имени exe-файла...
{
Kill process by given *.exe file name.
}
function KillPidByName(exe:string):Integer;
var t,i,n,pid:Integer; b:Boolean;
function npos(c:char;n:integer;s:string):integer;
var i,j:integer;
begin
j:=0;
npos:=0;
for i:=1 to length(s) do
if s[i]=c then begin j:=j+1; if j=n then npos:=i; end;
end;
begin
n:=0;
exe:=trim(exe);
if length(exe)>0 then begin
t:=pidlist(text_new);
for i:=0 to text_numln(t)-1 do
if IsSameText(exe,copy(text_getln(t,i),npos(',',4,text_getln(t,i))+1)) then begin
pid:=val(extractword(1,text_getln(t,i)));
if pid<>0 then n:=n+pidkill(pid,0,0);
end;
end;
b:=text_free(t);
KillPidByName:=n;
end;
n:=KillPidByName('cmd.exe'); {Убить все экземпляры cmd.exe, вернуть число убитых}
Канал связи с идентификатором pid может иметь один или несколько потоков pipe_stream(pid,i), через которые осуществляется ввод-вывод. Большинство каналов сами и являются потоками, то есть для них выполняется
pipe_count(pid) = 1
pipe_stream(pid,0) = pid
Только сервер tcp не является потоком (через него нельзя делать ввод-вывод),
но он имеет много потоков, каждый из которых соответствует соединению с каким-либо клиентом.
Библиотека включает 20 функций:
pid:=pipe_init('task cmd')
эквивалентно
pid:=task_init('cmd')
task_ctrl(pid,'Display=0')
task_ctrl(pid,'TxPipeSize=16384')
task_ctrl(pid,'RxPipeSize=32768')
После создания канала надо не забыть запустить задачу вызовом pipe_run(pid).
После завершения работы с каналом надо не забыть его закрыть вызовом pipe_free(pid).
Пример:
pid:=pipe_init('task c:\daq32\demo.exe'); консольная задача
pid:=pipe_init('pipe test'); серверный именованный канал
pid:=pipe_init('pipe crwbox\test'); клиентский именованный канал
pid:=pipe_init('tcp port 1000 server 2'); TCP порт 1000, сервер на 2 клиента
pid:=pipe_init('tcp port 1000 client 192.168.0.1'); TCP порт 1000, клиент подключается к заданному IP адресу
pid:=pipe_init('com port 2 baudrate 115200'); COM порт 2, скорость 115200
После создания канала не мешает вызвать pipe_run для запуска задачи. Идентификатор канала pid далее используется во всех остальных функциях для работы с каналами, его можно также использовать в качестве ссылки при вызове refinfo.
if pipe_connected(pid)>0 then
for i:=0 to pipe_count(pid)-1 do
if pipe_connected(pipe_stream(pid,i))>0 then begin
incomes:=pipe_recv(pipe_stream(pid,i),255);
sent:=pipe_send(pipe_stream(pid,i),outcomes);
end;
if pipe_connected(pid)>0 then
for i:=0 to pipe_count(pid)-1 do
if pipe_connected(pipe_stream(pid,i))>0 then begin
incomes:=pipe_recv(pipe_stream(pid,i),255);
sent:=pipe_send(pipe_stream(pid,i),outcomes);
end;
Надо заметить, что потоки серверного канала не являются самостоятельными единицами,
их не надо закрывать вызовом pipe_free, так как они закрываются автоматически
вместе с серверным каналом.
Особо надо сказать о работе с каналами tcp server. Дело в том, что канал этого типа сам по себе не может служить для чтения-записи данных, то есть вызов типа pipe_send(pid,data) для него будет неверным. Вместо этого надо использовать вызовы pipe_send(pipe_stream(pid,i),data) с индексом i от 0 до pipe_count(pid)-1. Каждый поток соответствует клентскому подключению. Наличие нескольких потоков pipe_stream(pid,i) позволяет серверу обслуживать несколько клиентских подключений одновременно. При этом обработка может зависеть от адреса клиента, который можно узнать вызовом
pipe_ctrl(pipe_stream(pid,i),'PeerIP') адрес удаленного клиента номер i
pipe_ctrl(pipe_stream(pid,i),'PeerName') имя машины удаленного клиента номер i
В общем случае работа с каналом строится примерно так:
TCP Server:
if runcount=1 then begin
srv:=pipe_init('tcp port 1000 server 2');
end else
if isinf(runcount) then begin
b:=pipe_free(srv);
end else
begin
if pipe_connected(srv)>0 then
for i:=0 to pipe_count(srv)-1 do begin
pid:=pipe_stream(srv,i);
if pipe_connected(pid)>0 then begin
if pipe_rxcount(pid)>0
then writeln(' Receive ',pipe_recv(pid,255),
' from remote host ',pipe_ctrl(pid,'PeerName'));
if HasDataToSend then
if pipe_send(pid,GetDataToSend)=0 then Error;
end;
end;
end;
TCP Client:
if runcount=1 then begin
pid:=pipe_init('tcp port 1000 client localhost');
end else
if isinf(runcount) then begin
b:=pipe_free(pid);
end else
begin
if pipe_connected(pid)>0 then begin
if pipe_rxcount(pid)>0
then writeln(' Receive ',pipe_recv(pid,255),
' from remote host ',pipe_ctrl(pid,'PeerName'));
if HasDataToSend then
if pipe_send(pid,GetUserDataToSend)=0 then Error;
end;
end;
Заметьте, что клиенту не обязательно использовать циклы и вызовы
pipe_count, pipe_stream, так как для него всегда
pipe_count(pid)=1 и pipe_stream(pid,0)=pid.
Пример программирования: DEMO_PIPE.
1)Ввод/вывод данных из консольной программы...
pid:=pipe_init('task ping /t crwbox');
if pipe_run(pid) then {Ok} else {Error};
....
if pipe_rxcount(pid)>0 then writeln(pipe_recv(pid,255));
if HasDataToSend then
if pipe_send(pid,GetData)=0 then {Error};
....
b:=pipe_free(pid);
2)Ввод/вывод данных из именованного канала...
pid:=pipe_init('pipe crwbox\test');
if pipe_run(tid) then {Ok} else {Error};
....
b:=pipe_free(pid);
3)Ввод/вывод данных из порта TCP/IP (server)...
srv:=pipe_init('tcp port 1000 server 2');
if pipe_run(srv) then {Ok} else {Error};
....
if pipe_connected(srv)>0 then
for i:=0 to pipe_count(srv)-1 do begin
pid:=pipe_stream(srv,i);
if pipe_connected(pid)>0 then begin
if pipe_rxcount(pid)>0
then writeln(' Receive ',pipe_recv(pid,255),
' from remote host ',pipe_ctrl(pid,'PeerName'));
if HasDataToSend then
if pipe_send(pid,GetUserDataToSend)=0 then Error;
end;
end;
...
b:=pipe_free(srv);
1) @cmd arg - Команда cmd с аргументами arg. Анализируется, например, с помощью функции GotCommand(Data,cmd,arg).
2) Name=Value - Выражение присвоения параметру Name значение Value. Анализируется, например, с помощью CookieScan(Data,Name,0).
Двоичные данные можно передавать через текстовый канал связи с помощью кодирования,
например, в HEX.
1)Сервер:
... Инициализация ...
ipc:=easyipc_init('test',''); // Для клиента указывается только basename - в данном случае test.
... В цикле опроса ...
b:=easyipc_poll(ipc); // обязательно надо вызывать процедуру опроса канала
if val(easyipc_ctrl(ipc,'connected'))>0 then // если подключен и есть данные
if val(easyipc_ctrl(ipc,'rxlength'))>0 then writeln(easyipc_recv(ipc,maxint));
if HasDataToSend then if not easyipc_send(ipc,data) then {Error};
... Завершение ...
b:=easyipc_free(ipc);
1)Клиент:
... Инициализация ...
ipc:=easyipc_init('.\test',''); // Для клиента указывается hostname\basename - в данном случае .\test
... В цикле опроса ...
b:=easyipc_poll(ipc); // обязательно надо вызывать процедуру опроса канала
if val(easyipc_ctrl(ipc,'connected'))>0 then // если подключен и есть данные
if val(easyipc_ctrl(ipc,'rxlength'))>0 then writeln(easyipc_recv(ipc,maxint));
if HasDataToSend then if not easyipc_send(ipc,data) then {Error};
... Завершение ...
b:=easyipc_free(ipc);
daqdllinit(name) - загружает DLL библиотеку по имени *.DLL файла name. В случае успеха возвращает некую отличную от нуля ссылку id, которая может быть использована только для вызова функций daqdllfree и daqdllcall. Если файл не найден или не содержит функции CRW32_PLUGIN, загрузки DLL не происходит и возвращается 0. Сразу после загрузки DLL файла DAQ-программа должна вызвать daqdllcall(id,DAQ_CMD_INIT) для того, чтобы библиотека могла себя корректно инициализировать. Если вызов вернул false, инициализация не прошла и библиотеку надо завершать и выгружать.
daqdllfree(id) - выгружает DLL со ссылкой id из памяти. Перед выгрузкой DAQ-программа должна вызвать daqdllcall(id,DAQ_CMD_FREE) для того, чтобы библиотека могла себя корректно завершить.
daqdllcall(id,cmd) - вызывает DLL со ссылкой id на выполнение. Функция возвращает true в случае успешного выполнения или false в случае ошибки в коде DLL. Параметр - команда cmd указывает библиотеке, что надо делать. Код DLL программы получает эту команду вызовом CrwApi.DaqApi.DaqCommand. Все остальные данные для работы код DLL библиотеки получает из тегов и кривых при помощи вызова функций CrwApi.
Есть стандартные команды, как описано в файле _CRWAPI.PAS:
// Команды DaqApi.DaqCommand
Daq_Cmd_Init = 1; // Команда "Начало работы"
Daq_Cmd_Free = 2; // Команда "Завершение работы"
Daq_Cmd_Poll = 3; // Команда "Опрос устройства"
Daq_Cmd_User = $1000; // Начало пользовательских команд
Стандартными являются команды:
Библиотеки пишутся на Delphi, встроенном в CRW-DAQ, по специальным правилам.
Для компиляции библиотек надо открыть *.DPR файл в меню Файл/Открыть, указав тип файла "Программа".
В этом окне будет кнопка компиляции
.
Краткая сводка самых элементарных правил:
function CRW32_PLUGIN(TheCrwApi:TCrwApi):Integer; StdCall;Функция возвращает 0 при успешном выполнении или -1 при ошибке.
Обычно библиотеки подключаются через стандартную DAQ программу _DLLWRAP.PAS. Эту программу можно использовать как шаблон для создания других программ для работы с DLL.
См. также: описание технологии создания DLL в файле DEMO_RFA-1229.doc, а также примеры из каталога Demo.
Программа - оболочка на DAQ PASCAL для вызова DLL:
{
*********************************************************************
Назначение: Интерфейс для вызова DLL из DAQ Pascal.
Пример конфигурирования:
[DeviceList]
&DRIVER = device software program
[&DRIVER]
ProgramSource = ..\DAQPAS\_DLLWRAP.PAS ; Program source file
DLL_FILE_PATH = ..\DAQPAS\DRIVER.DLL ; DLL driver
..... ; Other information
*********************************************************************
}
program DLLWRAP;
var
hDll:Integer; { Ссылка на DLL }
{
Процедура освобождения DLL
}
procedure DLL_FREE(var hDll:Integer);
var b:Boolean;
begin
if hDll<>0 then begin
b:=daqdllcall(hDll,Daq_Cmd_Free);
b:=daqdllfree(hDll);
hDll:=0;
end;
end;
{
Процедура пытается загрузить DLL с заданным именем файла.
Возвращает ненулевую ссылку DLL или ноль при ошибке.
}
function DLL_INIT(DllPath:String):Integer;
var b:Boolean; hDll:Integer;
begin
hDll:=daqdllinit(DllPath);
if hDll<>0 then if not daqdllcall(hDll,Daq_Cmd_Init) then DLL_FREE(hDll);
if hDll=0 then b:=fixerror(registererr('Fail load '+DllPath+' !'));
DLL_INIT:=hDll;
end;
{
Процедура опроса DLL.
}
procedure DLL_POLL(hDll:Integer);
var b:Boolean;
begin
if hDll<>0 then b:=daqdllcall(hDll,Daq_Cmd_Poll);
end;
begin
{
При старте загружаем DLL
}
if runcount=1 then hDll:=DLL_INIT(readini('DLL_FILE_PATH')) else
{
При останове удаляем DLL
}
if isinf(runcount) then DLL_FREE(hDll) else
{
В цикле опроса вызываем DLL
}
if hDll<>0 then DLL_POLL(hDll);
end.
Простейший код DLL может выглядеть так:
LIBRARY DEMO_DRIVER;
{$I _sysdef}
uses ShareMem, SysUtils, Windows, Math, Classes, mmsystem, ShellApi, _CrwApi;
type
PDemoDriver = ^TDemoDriver; // Pointer to user driver data
TDemoDriver = object // General user driver object
public
CrwApi : TCrwApi; // Points to CrwApi interface
dllFilePath : ShortString; // Executable DLL file path
OwnDevice : Integer; // Device reference
public
procedure DAQ_CMD_INIT(TheCrwApi:TCrwApi); // Driver initialization
procedure DAQ_CMD_FREE; // Driver finalization
procedure DAQ_CMD_POLL; // Driver polling loop
procedure DAQ_CMD_FAIL; // Driver failure handler
end;
procedure TDemoDriver.DAQ_CMD_INIT(TheCrwApi:TCrwApi);
begin
FillChar(Self,sizeof(Self),0);
CrwApi:=TheCrwApi;
with CrwApi,SysApi,GuiApi,DaqApi do begin
RedirectStdIn(Input);
RedirectStdOut(Output);
dllFilePath:=readini('DLL_FILE_PATH');
OwnDevice:=FindObjectRef('Device','');
Echo(Format('%s: Initializing %s ...',[DeviceName(OwnDevice),dllFilePath]));
end;
end;
procedure TDemoDriver.DAQ_CMD_FREE;
begin
if Assigned(CrwApi) then
with CrwApi,SysApi,GuiApi,DaqApi do begin
Echo(Format('%s: Finalizing %s ...',[DeviceName(OwnDevice),dllFilePath]));
DaqDataSheet(0);
end;
end;
procedure TDemoDriver.DAQ_CMD_POLL;
begin
if Assigned(CrwApi) then
with CrwApi,SysApi,GuiApi,DaqApi do begin
Echo('Dll is running!');
end;
end;
procedure TDemoDriver.DAQ_CMD_FAIL;
begin
if Assigned(CrwApi) then
with CrwApi,SysApi,GuiApi,DaqApi do
RAISE EDaqApi.Create(Format('%s: wrong DaqCommand=%d!',[DeviceName(OwnDevice),DaqCommand]));
end;
function CRW32_PLUGIN(TheCrwApi:TCrwApi):Integer; StdCall;
var DemoDriver:PDemoDriver;
begin
Result:=0;
with TheCrwApi,SysApi,GuiApi,DaqApi do
try
if Target <> ForDataAcquisition then RAISE EDaqApi.Create('DemoDriver: Invalid Target!');
DemoDriver:=DaqDataSheet(sizeof(DemoDriver^));
if not Assigned(DemoDriver)
then RAISE EDaqApi.Create('DemoDriver: Out of memory!');
if Assigned(DemoDriver.CrwApi) and (DemoDriver.CrwApi<>TheCrwApi)
then RAISE EDaqApi.Create('DemoDriver: Invalid CrwApi.');
case DaqCommand of
DAQ_CMD_INIT: DemoDriver.DAQ_CMD_INIT(TheCrwApi); // Initialize DemoDriver driver
DAQ_CMD_FREE: DemoDriver.DAQ_CMD_FREE; // Finalize DemoDriver driver
DAQ_CMD_POLL: DemoDriver.DAQ_CMD_POLL; // General DemoDriver data acquisition loop
else DemoDriver.DAQ_CMD_FAIL; // Detected DemoDriver driver failure
end;
except
on E:Exception do Result:=-1;
end;
end;
exports CRW32_PLUGIN name CRW32_PLUGIN_ID;
begin
end.
Согласно Википедии, Хэш-таблица или хеш-таблица — это структура данных, реализующая интерфейс ассоциативного массива, а именно, она позволяет хранить пары (ключ, значение) = (key,value) и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу.
В библиотеке DaqPascal реализованы хеш-таблицы, которые позволяют хранить в качестве значений записи из трех элементов - вещественного числа data, целого числа link и строкового параметра para, поэтому каждому ключу соответствует запись (key, data, link, para). Кроме того, реализованные хеш-таблицы позволяют независимо менять значения данных (data,link,para) по ключу key.
Библиотека включает 12 функций:
// Создать хеш-таблицу, чувствительную к регистру
// символов и выбрать функцию хеширования номер 3
hid:=hashlist_init(1+256*3);
procedure TestHashList(MaxIter:Integer);
var hid,i,numerr,link:Integer; key,para:String; data:Real; b:Boolean;
begin
numerr:=0;
hid:=HashList_Init(0);
for i:=1 to MaxIter do begin
key:='Data_'+Str(i);
b:=HashList_SetData(hid,key,+i);
b:=HashList_SetLink(hid,key,-i);
b:=HashList_SetPara(hid,key,Str(2*i));
end;
b:=HashList_SetPara(hid,'abc','def');
b:=HashList_SetPara(hid,'ghi','jkl');
b:=HashList_SetPara(hid,'mno','pqr');
b:=HashList_SetPara(hid,'stu','vwx');
b:=HashList_SetPara(hid,'yz0','123');
b:=HashList_SetPara(hid,'456','789');
if HashList_Count(hid)<>MaxIter+6 then numerr:=numerr+1;
writeln('Count = '+Str(HashList_Count(hid)));
for i:=0 to HashList_Count(hid)-1 do begin
key:=HashList_GetKey(hid,i);
writeln(Str(i)+' '+key+' = '+Str(HashList_GetData(hid,key))+', '
+Str(HashList_GetLink(hid,key))+', '+HashList_GetPara(hid,key));
end;
b:=HashList_Delete(hid,'abc');
b:=HashList_Delete(hid,'stu');
if HashList_Count(hid)<>MaxIter+4 then numerr:=numerr+1;
writeln('Count = '+Str(HashList_Count(hid)));
for i:=0 to HashList_Count(hid)-1 do begin
key:=HashList_GetKey(hid,i);
writeln(Str(i)+' '+key+' = '+Str(HashList_GetData(hid,key))+', '
+Str(HashList_GetLink(hid,key))+', '+HashList_GetPara(hid,key));
end;
writeln('IndexOf(data_0) = '+Str(HashList_IndexOf(hid,'data_0')));
writeln('IndexOf(data_1) = '+Str(HashList_IndexOf(hid,'data_1')));
for i:=1 to MaxIter do begin
key:='Data_'+Str(i);
data:=HashList_GetData(hid,key); if data<>+i then numerr:=numerr+1;
link:=HashList_GetLink(hid,key); if link<>-i then numerr:=numerr+1;
para:=HashList_GetPara(hid,key); if para<>Str(2*i) then numerr:=numerr+1;
end;
writeln(Str(numerr)+' error(s) found');
b:=HashList_Free(hid);
end;
Библиотека включает следующие функции:
Дерево принадлежности элементов FSM:
Manager Менеджер FSM
|_Parameter Параметры менеждера (int,float,string)
|_Domain Домены менеджера
|_Parameter Параметры домена
|_Class,Object Классы или объекты, принадлежащие домену
| |_Parameter Параметры класса или объекта
| |_State Состояния объекта, принадлежащие классу/объекту
| | |_Parameter Параметры состояния
| | |_Action Действия, принадлежащие состоянию
| | |_Parameter Параметры вызова действия
| |_Function Функции принадлежащие классу/объекту
| |_Parameter Параметры вызова функции
|_ObjectSet Наборы (группы) объектов в домене
|_Parameter Параметры набора объектов
Независимо от глубины иерархии ссылки (ref), корневой элемент
(менеджер) доступен вызовом fsm_root(ref).
#PARAM параметр Менеждера
DOMAIN имя Домена
DOMAIN#PARAM параметр Домена
DOMAIN::OBJECT путь Объекта/Класса/Набора
DOMAIN::OBJECT#PARAM параметр Объекта
DOMAIN::OBJECT/STATE путь Состояния/Функции
DOMAIN::OBJECT/STATE#PARAM параметр Состояния
DOMAIN::OBJECT/STATE/ACTION путь Действия
DOMAIN::OBJECT/STATE/ACTION#PARAM параметр Действия
Например:
DEMO::LOGGER/READY/START#RUN_NUM
содержит путь параметра RUN_NUM действия START
состояния READY объекта LOGGER в домене DEMO
Пути объектов используются при глобальном поиске (fsm_find)
элементов в Менеджере (с любой глубиной иерархии).
Имя параметра - Комментарий (применимо для ... )
* - список всех свойств, разделенный CRLF (для всех )
*= - список всех свойств, доступных для записи (writable) (для всех )
state - текущее состояние объекта, аналог fsm_get/set_state (для class/object )
initial_state - начальное состояние объекта (для class/object )
dead_state - состояние мертвого объекта (для class/object )
name - имя объекта, аналог fsm_name (для всех )
path - путь объекта, аналог fsm_path (для всех )
type - тип объекта, в виде строки вида FsmDomain (для всех )
classname - имя класса объекта в виде строки вида TFsmDomain (для всех )
catalog - каталог Менеджера, т.е. список путей всех элементов (для всех )
cookie - поле для произвольных данных в виде списка name=value (для контейнеров )
body - поле для хранения кода (для контейнеров )
color - поле для хранения цвета состояния (для контейнеров )
defaultcolor - поле для хранения цвета по умолчанию (для manager )
associated - поле для флага /associated (для class/object )
declaration - декларация элемента (object: LOGGER /associated) (для всех )
is_of_class - поле для хранения свойства is_of_class (для object/objectset)
unionlist - поле для списка объектов, входящих в набор (для objectset )
unionmode - поле для флага union (для objectset )
visible - поле для свойства видимости (!visible:) (для контейнеров )
Примечание: (серым/синим) показаны свойства, доступные для (чтения/записи)
Например: s:=fsm_ctrl(obj,'state=READY'); // установить для объекта obj состояние READY
При использовании вызова вида fsm_ctrl(obj,'state=READY') следует иметь в виду,
что это вполне допустимый метод установки состояния объекта (obj) по имени состояния,
но он работает несколько медленнее, чем вызов fsm_set_state(obj,state) с установкой
состояния объекта (obj) по ссылке состояния (state).
Поэтому из соображений удобства можно использовать fsm_ctrl(obj,'state=...'),
а из соображений производительности следует запоминать ссылки состояний в локальных переменных,
чтобы затем использовать вызов fsm_set_state(obj,state).
procedure FsmTest1;
var fsm,i:Integer;
begin
fsm:=fsm_new; // create FsmManager
i:=fsm_add(fsm,fsm_type_domain,'DEMO'); // add domain DEMO
i:=fsm_add(fsm,fsm_type_domain,'TEST'); // add domain TEST
for i:=0 to fsm_count(fsm,fsm_type_domain) // for all domains
do writeln(fsm_name(fsm_items(fsm,fsm_type_domain,i))); // print domain name
bNul(fsm_free(fsm)); // free FsmManager
end;
Доступные для перечисления типы зависят от элемента.
Например, в домене можно перечислять объекты, в объекте - состояния,
а в состоянии - действия.
procedure FsmTest2;
var fsm,dom,par,i:Integer;
begin
fsm:=fsm_new; // create FsmManager
dom:=fsm_add(fsm,fsm_type_domain,'DEMO'); // add domain DEMO
par:=fsm_add(dom,fsm_type_int,'NUMBER'); // add domain parameter int NUMBER
bNul(fsm_set_iparam(par,123)); // set parameter value
par:=fsm_add(dom,fsm_type_float,'VOLT'); // add domain parameter float VOLT
bNul(fsm_set_fparam(par,pi)); // set parameter value
par:=fsm_add(dom,fsm_type_string,'USER'); // add domain parameter string USER
bNul(fsm_set_sparam(par,paramstr('username'))); // set parameter value
for i:=0 to fsm_count(dom,fsm_type_parameter)-1 do begin // for all domain parameters
par:=fsm_items(dom,fsm_type_parameter,i); // reference of parameter[i]
write(fsm_name(par)); // print parameter name
if fsm_type(par)=fsm_type_int then writeln(' = ',fsm_get_iparam(par)); // int value
if fsm_type(par)=fsm_type_float then writeln(' = ',fsm_get_fparam(par)); // float value
if fsm_type(par)=fsm_type_string then writeln(' = ',fsm_get_sparam(par)); // string value
end;
bNul(fsm_free(fsm)); // free FsmManager
end;
Скалярные параметры могут иметь любые контейнерные элементы, но сами параметры не содержат других элементов.
Число параметров в контейнерах не ограничено.
fsm:=fsm_new; // Создать Менеджер
dom:=fsm_add(fsm,fsm_type_domain,'EGP'); // Создать Домен EGP в менеджере
obj:=fsm_add(dom,fsm_type_object,'UPS'); // Создать Объект UPS в домене EGP
sta:=fsm_add(obj,fsm_type_state,'OFF'); // Создать Состояние OFF в объекте UPS
act:=fsm_add(sta,fsm_type_action,'SWITCH_ON'); // Создать Действие SWITCH_ON в состоянии OFF
par:=fsm_add(act,fsm_type_float,'VOLTAGE'); // Создать Параметр VOLTAGE в Действии SWITCH_ON
bNul(fsm_set_fparam(par,12.5)); // Задать значение 12.5 для параметра VOLTAGE
xxx:=fsm_add(dom,fsm_type_state,'ERROR'); // !!! Ошибка, нельзя создать состояние в Домене!!!
Элементы, созданные вызовом fsm_add, не требуется освобождать,
т.к. они принадлежат Менеджеру FSM и освобождаются вместе с ним.
obj:=fsm_find(fsm,fsm_type_object,'EGP::UPS'); // Найти объект UPS в домене EGP менеджера fsm
... в цикле опроса ...
if (fsm_name(fsm_get_state(obj))='OFF') // Если состояние OFF
then sta:=fsm_set_state(obj,'ON'); // то задать состояние ON
другой вариант, существенно более быстрый, но более сложный:
staON:=fsm_find(obj,fsm_type_state,'ON'); // Найти и запомнить ссылку состояния ON
staOFF:=fsm_find(obj,fsm_type_state,'OFF'); // Найти и запомнить ссылку состояния OFF
... в цикле опроса ...
if (fsm_get_state(obj)=staOFF) // Если состояние OFF
then sta:=fsm_set_state(obj,staON); // то задать состояние ON
сложность второго подхода в том, что он требует сохранять ссылки состояний.
Функция fsm_set_state(ref,state) является полиморфной, т.е. принимает аргументы разных типов.
В качестве аргумента (state) можно указать как имя нового состояния, так и его ссылку.
Работа со ссылкой идет ощутимо (в 3-4 раза) быстрее, но и при использовании имени производительность
тоже достаточно высока и приемлема для подавляющего числа задач.
sta:=fsm_get_state(fsm_find(fsm,fsm_type_object,'EGP::UPS')); // текущее состояние объекта UPS в домене EGP
... в цикле опроса ...
if (fsm_name(sta)='OFF') // Если текущее состояние OFF
then sta:=fsm_set_state(fsm_parent(sta),'ON'); // то задать новое состояние ON
ссылку объекта можно не хранить, а использовать вместо этого вызов fsm_parent(sta)
При анализе состояний следует помнить, что все имена элементов FSM (кроме менеджера) хранятся в верхнем регистре,
поэтому для сравнения имен можно использовать не только IsSameText(), но и прямое сравнение строк, при этом
имена состояний надо задавать в верхнем регистре.
Привязка:
tag:=findtag('COUNTER'); // Поиск тега COUNTER
iNul(fsm_link(fsm,'tagCOUNTER='+Str(tag))); // Запись (привязка) тега COUNTER под именем 'tagCOUNTER'
Использование:
tag:=fsm_link(fsm,'tagCOUNTER'); // Чтение тега по имени 'tag'
bNul(iSetTag(tag,123)); // Использование тега
Функция fsm_link используется для хранения связанных ссылок в runtime,
когда чтение ссылок производится часто (в цикле опроса), а запись - редко (при старте).
Использование связанных ссылок позволяяет резко сократить число статических переменных,
т.к. многие данные можно хранить в связанных ссылках элементов FSM,
а также позволяет хранить динамические данные (например, загружаемые из файла)
вместе с элементами FSM.
Модификация по событию:
obj:=fsm_find(fsm,'DEMO::LOGGER'); // Находим объект DEMO::LOGGER
sta:=fsm_set_state(obj,'LOGGING'); // Задаем ему новое состояние LOGGING
iNul(fsm_modified(obj,1)); // Маркируем объект как модифицированный
Использование в цикле опроса:
if fsm_modified(fsm,0)>0 then // Если Конечный Автомат модифицирован
for i:=0 to fsm_count(fsm,fsm_type_domain)-1 do begin // то в цикле по доменам:
dom:=fsm_items(fsm,fsm_type_domain,i); // берем домен
if fsm_modified(dom)>0 then // проверяем, был ли он модифицирован
for j:=0 to fsm_count(dom,fsm_type_object)-1 do begin // если был, то в цикле по объектам
obj:=fsm_items(dom,fsm_type_object,j); // берем объект
if fsm_modified(obj,0)>0 then begin // проверяем, был ли он модифицирован
writeln('Object '+fsm_name(obj)+' was modified.'); // и обрабатываем его (в данном случае - печатаем)
end;
end;
end;
Функция fsm_modified используется для удобной и эффективной организации
цикла обработки обработки событий, связанных с элементами FSM.
if IsLexeme(name,fsm_name_rule(fsm_type_domain))
then dom:=fsm_add(fsm,fsm_type_domain,name)
else writeln('Invalid domain name '+name);
Функция fsm_name_rule используется для синтаксического анализа имен,
связанных с элементами FSM,
с помощью функции IsLexeme.
Вызов Время вызова Комментарий
fsm_get_state(obj) 0.125 mks Чтение состояния по ссылке
fsm_set_state(obj,state) 0.250 mks Задание состояния по ссылке
fsm_set_state(obj,name) 0.950 mks Задание состояния по имени
fsm_ctrl(obj,'state') 1.250 mks Чтение состояния по имени через fsm_ctrl
fsm_ctrl(obj,'state='+key) 2.500 mks Задание состояния по имени через fsm_ctrl
fsm_find(obj,name) 0.790 mks Локальный поиск элемента по имени
fsm_find(fsm,path) 1.550 mks Глобальный поиск элемента по пути
fsm_link(fsm,name) 0.470 mks Чтение привязанной ссылки по имени
fsm_modified(fsm,0) 0.078 mks Чтение/сброс счетчика модификаций
Это очень грубые оценки, полученные при размерностях порядка (dom[3]*obj[10]*state[10]*act[10],name[10]), но они отражают основные закономерности.
Наиболее быстрая, "дешевая" по времени работа происходит с готовыми ссылками (но при этом надо позаботиться об их сохранении).
Чтение/изменение состояния по имени удобно, но ощутимо "дороже", т.к. работа со строками вообще идет медленнее,
к тому же состояние приходится искать по имени в списке дочерних элементов.
Сравнительно "дорогим" является также поиск элементов, причем "локальный" поиск по имени идет быстрее, чем глобальный поиск по пути.
Эти соображения следует учитывать при программировании, отдавая предпочтение тому или иному способу работы в зависимости от задачи.
Важно, однако, отметить, что при работе со ссылками скорость FSM примерно соответствует максимально возможной для программ
на языке DaqPascal (для сравнения, вызов msecnow занимает примерно 0.1 mks).
Другими словами, скорость FSM вполне достаточна для решения задач, которые вообще можно решать средствами DaqPascal.
{
Test 8.
Check FSM functions.
}
procedure Test8;
var n:Integer; ms:Real;
procedure Timing(who,arg:String; nn:Integer);
begin
n:=nn;
if (who='') then ms:=msecnow else begin
ms:=msecnow-ms;
writeln(who,' ',ms*1000/n:1:3,' mks, ',ms*1e6/n/length(arg):1:3,' ns/char, ',ms:1:0,' ms');
end;
end;
procedure FsmTest1;
var fsm,dom,i:Integer;
begin
Success(GetDateTime(msecnow)+' => FSM Test1.');
fsm:=fsm_new; // create FsmManager
dom:=fsm_add(fsm,fsm_type_domain,'DEMO'); // add domain DEMO
dom:=fsm_add(fsm,fsm_type_domain,'TEST'); // add domain TEST
for i:=0 to fsm_count(fsm,fsm_type_domain) // for all domains
do writeln(fsm_name(fsm_items(fsm,fsm_type_domain,i))); // print domain name
bNul(fsm_free(fsm)); // free FsmManager
end;
procedure FsmTest2;
var fsm,dom,par,i:Integer;
begin
Success(GetDateTime(msecnow)+' => FSM Test2.');
fsm:=fsm_new; // create FsmManager
dom:=fsm_add(fsm,fsm_type_domain,'DEMO'); // add domain DEMO
par:=fsm_add(dom,fsm_type_int,'NUMBER'); // add domain parameter int NUMBER
bNul(fsm_set_iparam(par,123)); // set parameter value
par:=fsm_add(dom,fsm_type_float,'VOLT'); // add domain parameter float VOLT
bNul(fsm_set_fparam(par,pi)); // set parameter value
par:=fsm_add(dom,fsm_type_string,'USER'); // add domain parameter string USER
bNul(fsm_set_sparam(par,paramstr('username'))); // set parameter value
for i:=0 to fsm_count(dom,fsm_type_parameter)-1 do begin // for all domain parameters
par:=fsm_items(dom,fsm_type_parameter,i); // reference of parameter[i]
write(fsm_name(par)); // print parameter name
if fsm_type(par)=fsm_type_int then write(' = ',fsm_get_iparam(par)); // int value
if fsm_type(par)=fsm_type_float then write(' = ',fsm_get_fparam(par)); // float value
if fsm_type(par)=fsm_type_string then write(' = ',fsm_get_sparam(par)); // string value
writeln;
end;
bNul(fsm_free(fsm)); // free FsmManager
end;
procedure FsmTest3(ndom,nobj,nsta,nact,npar:Integer);
var fsm,dom,obj,sta,act,par,idom,iobj,ista,iact,ipar,ref,nerr,i:Integer; key:String;
begin
key:='';
Success(GetDateTime(msecnow)+' => FSM Test3.');
nerr:=0;
fsm:=fsm_new;
if not IsSameText(fsm_name(fsm),'FsmManager') then nerr:=nerr+1;
for idom:=1 to ndom do begin
dom:=fsm_add(fsm,fsm_type_domain,StrFmt('domain_%d',idom));
if (fsm_type(dom)<>fsm_type_domain) then nerr:=nerr+1;
if (fsm_type(fsm_root(dom))<>fsm_type_manager) then nerr:=nerr+1;
if (fsm_type(fsm_parent(dom))<>fsm_type_manager) then nerr:=nerr+1;
for iobj:=1 to nobj do begin
obj:=fsm_add(dom,fsm_type_object,StrFmt('object_%d',iobj));
if (fsm_type(obj)<>fsm_type_object) then nerr:=nerr+1;
if (fsm_type(fsm_root(obj))<>fsm_type_manager) then nerr:=nerr+1;
if (fsm_type(fsm_parent(obj))<>fsm_type_domain) then nerr:=nerr+1;
for ista:=1 to nsta do begin
sta:=fsm_add(obj,fsm_type_state,StrFmt('state_%d',ista));
if (fsm_type(sta)<>fsm_type_state) then nerr:=nerr+1;
if (fsm_type(fsm_root(sta))<>fsm_type_manager) then nerr:=nerr+1;
if (fsm_type(fsm_parent(sta))<>fsm_type_object) then nerr:=nerr+1;
for iact:=1 to nact do begin
act:=fsm_add(sta,fsm_type_action,StrFmt('action_%d',iact));
if (fsm_type(act)<>fsm_type_action) then nerr:=nerr+1;
if (fsm_type(fsm_root(act))<>fsm_type_manager) then nerr:=nerr+1;
if (fsm_type(fsm_parent(act))<>fsm_type_state) then nerr:=nerr+1;
end;
end;
end;
end;
//
key:=StrFmt('state_%d',nsta);
if not IsSameText(fsm_name(fsm_get_state(obj)),'STATE_1') then nerr:=nerr+1;
if not IsSameText(fsm_name(fsm_set_state(obj,key)),key) then nerr:=nerr+1;
if not IsSameText(fsm_name(fsm_set_state(obj,'STATE_1')),'STATE_1') then nerr:=nerr+1;
if not IsSameText(fsm_name(fsm_set_state(obj,StrFmt('state_%d',nsta))),key) then nerr:=nerr+1;
if not IsSameText(fsm_name(fsm_set_state(obj,'STATE_1')),'STATE_1') then nerr:=nerr+1;
//
//writeln(trim(fsm_ctrl(fsm,'catalog')));
writeln('last action: ',fsm_path(fsm_ref(act)));
key:=StrFmt('domain_%d',ndom)+'::'+StrFmt('object_%d',nobj)+'/'+StrFmt('state_%d',nsta)+'/'+StrFmt('action_%d',nact);
if not IsSameText(fsm_path(act),key) then nerr:=nerr+1;
ref:=fsm_find(fsm,fsm_type_any,key); if (ref<>act) then nerr:=nerr+1;
//
Timing('',key,1000*20);
for i:=1 to n do ref:=fsm_find(fsm,fsm_type_any,key);
Timing('fsm_find path',key,n);
//
key:=StrFmt('action_%d',nact);
ref:=fsm_find(sta,fsm_type_any,key); if (ref<>act) then nerr:=nerr+1;
Timing('',key,1000*100);
for i:=1 to n do ref:=fsm_find(sta,fsm_type_any,key);
Timing('fsm_find name',key,n);
//
key:=StrFmt('state_%d',nsta);
ref:=fsm_set_state(obj,sta); if (ref<>sta) then nerr:=nerr+1;
ref:=fsm_get_state(obj); if (ref<>sta) then nerr:=nerr+1;
Timing('',key,1000*1000);
for i:=1 to n do ref:=fsm_get_state(obj);
Timing('fsm_get_state fast',key,n);
Timing('',key,1000*500);
for i:=1 to n do ref:=fsm_set_state(obj,sta);
Timing('fsm_set_state fast',key,n);
//
key:=StrFmt('state_%d',nsta);
ref:=fsm_find(obj,fsm_type_state,fsm_ctrl(obj,'state='+key)); if (ref<>sta) then nerr:=nerr+1;
ref:=fsm_find(obj,fsm_type_state,fsm_ctrl(obj,'state')); if (ref<>sta) then nerr:=nerr+1;
Timing('',key,1000*50);
for i:=1 to n do ref:=Ord(fsm_ctrl(obj,'state')<>'');
Timing('fsm_get_state slow',key,n);
Timing('',key,1000*50);
for i:=1 to n do ref:=Ord(fsm_ctrl(obj,'state='+key)<>'');
Timing('fsm_set_state slow',key,n);
Timing('',key,1000*50);
for i:=1 to n do ref:=fsm_set_state(obj,key);
Timing('fsm_set_state strn',key,n);
//
key:='tag';
Timing('',key,1000*100);
writeln('Link ',fsm_link(obj,'tag=123'):1);
for i:=1 to n do ref:=fsm_link(obj,key);
Timing('fsm_link',key,n);
//
obj:=fsm_find(fsm,fsm_type_object,'domain_2::object_3');
sta:=fsm_find(obj,fsm_type_state,'state_4');
iNul(fsm_modified(sta,1));
writeln(fsm_path(sta),' ',fsm_modified(sta,-1):1,' ',fsm_modified(obj,-1):1,' ',fsm_modified(fsm,-1):1);
if fsm_modified(fsm,0)>0 then
for idom:=0 to fsm_count(fsm,fsm_type_domain)-1 do begin
dom:=fsm_items(fsm,fsm_type_domain,idom);
if fsm_modified(dom,0)>0 then
for iobj:=0 to fsm_count(dom,fsm_type_object)-1 do begin
obj:=fsm_items(dom,fsm_type_object,iobj);
if fsm_modified(obj,0)>0 then begin
writeln('Object '+fsm_path(obj)+' was modified.');
end;
end;
end;
//
key:='fsm_modified';
Timing('',key,1000*1000);
for i:=1 to n do ref:=fsm_modified(obj,0);
Timing('fsm_modified',key,n);
//
bNul(fsm_free(fsm));
writeln(nerr:1,' error(s) found in Test3.');
key:='';
end;
begin
FsmTest1;
FsmTest2;
FsmTest3(2,3,10,10,3);
end;
-- ПРИЛОЖЕНИЕ------DbApi------ДВИЖКИ-----ПРОВАЙДЕРЫ-----ДРАЙВЕРЫ------ БАЗЫ ДАННЫХ
-------------- ___ MSDASQL --- Driver 1 ------ Interbase/Firebird
| DAQ-system | |
| Application | -- DbApi ------ ADO ---- MSDASQL --- Driver 2 ------ MS SQL Server, и так далее
| DaqPascal | | | |
|_____________| | | |___ Provider N --- Driver N ------ MySQL, и так далее
| |
| |
| | ___ IB ------------------- Interbase/Firebird
| | |
| \-- SQLDB ---- ODBC -- драйверы ODBC -- много типов БД
| |
| |___ SQLite3 ------------------- SQLite
| |
| ---- PQ ------------------- PostgreSQL, и так далее
|
| ___ Firebird ------------------- Interbase/Firebird
| |
\-- ZEOS------- ODBC -- драйверы ODBC -- много типов БД
|
|___ SQLite ------------------- SQLite
|
---- PostgreSQL ------------------- PostgreSQL, и так далее
arg:='Engine=ADO;Provider=LCPI.IBProvider.5.Free;User Id=SYSDBA;Password=masterkey;ctype=win1251;location=localhost:c:\demo\test.fdb;';
if db_create(arg) then writeln('Создана БД c:\demo\test.fbd') else writeln('Ошибка создания БД.');
arg:='Engine=SQLDB;Provider=IB;User=SYSDBA;Password=masterkey;Charset=utf8;Database=c:\demo\test.fdb;';
if db_create(arg) then writeln('Создана БД c:\demo\test.fbd') else writeln('Ошибка создания БД.');
Этот пример создает БД в указанном месте. Если файл БД уже есть, функция вернет ошибку.
// Для движка ADO, Firebird и провайдера MSDASQL с драйвером ODBC от IBPhoenix:
con:=db_connection(db_engine_ado,'Provider=MSDASQL;DRIVER=Firebird/InterBase(r) driver;'
+'DBNAME=localhost:employee;UID=SYSDBA;PWD=masterkey;');
// Для движка ADO, Firebird и провайдера IBProvider:
con:=db_connection(db_engine_ado,'Provider=LCPI.IBProvider.5.Free;ctype=win1251;'
+'Location=localhost:employee;User ID=SYSDBA;Password=masterkey;');
// Для движка SQLDB, Firebird и провайдера IB:
con:=db_connection(db_engine_sqldb,'Provider=IB;ctype=utf8;'
+'Location=/opt/crwdaq/demo/demo_data/employee.fdb;User ID=SYSDBA;Password=masterkey;');
// Для движка SQLDB, Firebird и провайдера IB:
con:=db_connection(db_engine_sqldb,'Provider=IB;Charset=utf8;'
+'DBNAME=SYSDBA:masterkey@localhost:/opt/crwdaq/demo/demo_data/employee.fdb;');
При задании строки подключения следует учитывать, что разные провайдеры могут использовать разные
имена для одних и тех же параметров.
Формат DB URI:
provider://[userspec@][hostspec][/dbname][?paramspec][#comment]
где userspec это:
user[:password]
где hostspec это:
[host][:port]
где paramspec это:
name=value[&...]
Например:
// Для движка SQLDB, Firebird и провайдера IB:
uri:='IB://SYSDBA:masterkey@localhost/employee.fdb?Charset=utf8&PageSize=4096#Example';
con:=db_connection(db_engine_sqldb,uri);
scheme://authority/path?query#fragmentВ терминах стандартного URI:
buff:=db_ctrl(dbo,'Properties=Connection.Database;Connection.UserName;Connection.Password');
dbs:=CookieScan(buff,'Connection.Database',Ord(';')); writeln('Database: ',dbs);
uid:=CookieScan(buff,'Connection.UserName',Ord(';')); writeln('UserName: ',uid);
pwd:=CookieScan(buff,'Connection.Password',Ord(';')); writeln('Password: ',pwd);
Properties - это своего рода "портал", дающий доступ к большому числу внутренних свойств подключения.
Использовать его надо осторожно.
Connection.EngineId=2
Connection.EngineName=SQLDB
Connection.EngineVersion=SQLDB LCL 3.6.0.0 FPC 3.2.2
Connection.Provider=IB
Connection.Connected=1
Connection.Database=c:\opt\crwdaq\demo\demo_data\employee.fdb
Connection.UserName=SYSDBA
Connection.Password=masterkey
Connection.HostName=
Connection.Catalog=
Connection.CharSet=utf-8
Connection.Role=
Connection.ReadOnly=1
Connection.Options=[]
Connection.ConnOptions=[sqSupportParams,sqEscapeRepeat,sqSupportReturning,sqSequences]
Connection.ConnectionInfo="Firebird","030000","WI-V6.3.10.33601 Firebird 3.0","fbclient.dll",""
Connection.KeepConnection=0
Connection.IsSQLBased=0
Transaction.Active=1
Query.Active=0
Query.Options=
Query.SQLText=select * from employee
Query.TableName=employee
Query.UpdateMode=upWhereKeyOnly
Query.SchemaType=stNoSchema
Query.RowsAffected=42
Query.Filter=
Query.Filtered=0
Query.IndexName=
Query.StatementType=stSelect
DataSource.State=dsInactive
Connection.SaveNullSubs=(NULL)
Connection.TableNames=COUNTRY,CUSTOMER,DEPARTMENT,EMPLOYEE,EMPLOYEE_PROJECT,JOB,PHONE_LIST,PROJECT,PROJ_DEPT_BUDGET,SALARY_HISTORY,SALES
Connection.Params.DIALECT=3
Connection.Params.PAGESIZE=8192
Connection.EngineId=2
Connection.EngineName=SQLDB
Connection.EngineVersion=ZEOS ZEOS 8.0.0-release LCL 3.6.0.0 FPC 3.2.2
Connection.Provider=interbase
Connection.Connected=1
Connection.Database=c:\opt\crwdaq\demo\demo_data\employee.fdb
Connection.UserName=SYSDBA
Connection.Password=masterkey
Connection.HostName=
Connection.Port=0
Connection.Catalog=
Connection.CharSet=utf8
Connection.LibLocation=
Connection.ClientVersion=3.0.10
Connection.ServerVersion=3.0.10
Connection.KnownCodePages=ASCII,BIG_5,CYRL,DOS437,DOS850,DOS852,DOS857,DOS860,DOS861,DOS863,DOS865,EUCJ_0208,GB_2312,ISO8859_1,KSC_5601,NEXT,NONE,OCTETS,SJIS_0208,UNICODE_FSS,WIN1250,WIN1251,WIN1252,WIN1253,WIN1254,DOS737,DOS775,DOS858,DOS862,DOS864,DOS866,DOS869,ISO8859_2,ISO8859_3,ISO8859_4,ISO8859_5,ISO8859_6,ISO8859_7,ISO8859_8,ISO8859_9,ISO8859_13,WIN1255,WIN1256,WIN1257,WIN1258,KOI8R,KOI8U,UTF8,CP943C,GBK,TIS620,GB18030
Connection.AutoCommit=1
Connection.ReadOnly=0
Connection.Ping=1
Connection.PingServer=1
Transaction.Active=0
Query.Active=0
Query.SQLText=select * from employee
Query.TableName=employee
Query.RowsAffected=0
Query.Filter=
Query.Filtered=0
Query.IndexName=
Query.StatementType=stSelect
DataSource.State=dsInactive
Connection.SaveNullSubs=(NULL)
Connection.TableNames=COUNTRY,CUSTOMER,DEPARTMENT,EMPLOYEE,EMPLOYEE_PROJECT,JOB,MON$ATTACHMENTS,MON$CALL_STACK,MON$CONTEXT_VARIABLES,MON$DATABASE,MON$IO_STATS,MON$MEMORY_USAGE,MON$RECORD_STATS,MON$STATEMENTS,MON$TABLE_STATS,MON$TRANSACTIONS,PHONE_LIST,PROJECT,PROJ_DEPT_BUDGET,RDB$AUTH_MAPPING,RDB$BACKUP_HISTORY,RDB$CHARACTER_SETS,RDB$CHECK_CONSTRAINTS,RDB$COLLATIONS,RDB$DATABASE,RDB$DB_CREATORS,RDB$DEPENDENCIES,RDB$EXCEPTIONS,RDB$FIELDS,RDB$FIELD_DIMENSIONS,RDB$FILES,RDB$FILTERS,RDB$FORMATS,RDB$FUNCTIONS,RDB$FUNCTION_ARGUMENTS,RDB$GENERATORS,RDB$INDEX_SEGMENTS,RDB$INDICES,RDB$LOG_FILES,RDB$PACKAGES,RDB$PAGES,RDB$PROCEDURES,RDB$PROCEDURE_PARAMETERS,RDB$REF_CONSTRAINTS,RDB$RELATIONS,RDB$RELATION_CONSTRAINTS,RDB$RELATION_FIELDS,RDB$ROLES,RDB$SECURITY_CLASSES,RDB$TRANSACTIONS,RDB$TRIGGERS,RDB$TRIGGER_MESSAGES,RDB$TYPES,RDB$USER_PRIVILEGES,RDB$VIEW_RELATIONS,SALARY_HISTORY,SALES,SEC$DB_CREATORS,SEC$GLOBAL_AUTH_MAPPING,SEC$USERS,SEC$USER_ATTRIBUTES
Connection.Params.DIALECT=3
Connection.Params.PAGESIZE=8192
Connection.Params.codepage=utf8
Connection.Params.RawStringEncoding=DB_CP
Большинство свойств понятны по названию.
О них можно почитать в документации
ADO,
SQLDB,
ZEOS.
Однако некоторые свойства нуждаются в комментариях.
if db_active(con) then begin
rst:=db_execute(con,'select * from DemoTable');
if db_active(rst) then
while not db_eof(rst) do begin
// Print current table row as string
writeln(Trim(db_ctrl(rst,'GetString')));
bNul(db_movenext(rst));
end;
end;
.
{
Test 12.
Database routines.
}
procedure Test12;
var con,rst,ern:Integer;
function FormatRow(rst:Integer):String;
var id,res:String; i,tp:Integer;
begin
id:=''; res:='';
for i:=0 to db_fieldscount(rst)-1 do begin
id:=db_fieldsnames(rst,i); tp:=db_fieldstypes(rst,id);
res:=res+' '+id+'='+Str(tp)+','+db_fieldsasstring(rst,id,'r','')+';';
end;
FormatRow:=res; id:=''; res:='';
end;
begin
ern:=0;
Success('Test12: Check Database routines.');
con:=db_connection(db_engine_ado,'Provider=MSDASQL;DRIVER=Firebird/InterBase(r) driver;'
+'DBNAME=localhost:employee;UID=SYSDBA;PWD=masterkey;');
if (con=0) then ern:=ern+1 else begin
Success('ADO version '+db_ctrl(con,'Version'));
if db_open(con,adConnectUnspecified) then begin
Success('Database opened');
if db_begintrans(con)>0 then begin
Success('Transaction started, execute...');
rst:=db_execute(con,'select first 10 COUNTRY.* from COUNTRY',adCmdText);
if (rst<>0) then begin
Success('Got data records, read data:');
if db_active(rst) then
if db_fieldscount(rst)>0 then
while not db_eof(rst) do begin
writeln(FormatRow(rst));
bNul(db_movenext(rst));
end;
end else ern:=ern+1;
if db_committrans(con) then Success('Transaction done');
end else ern:=ern+1;
bNul(db_close(con));
end else ern:=ern+1;
bNul(db_free(con));
end;
if (ern=0)
then Success('Success.')
else Problem(StrFmt('%d error(s) found',ern));
writeln;
end;
Писатель:
shm_iop(shm,COUNTER,'+',1); // инкремент счетчика ДО записи
shm_rop(shm,ANYDATA,'W',data); // запись данных
shm_iop(shm,COUNTER,'+',1); // инкремент счетчика ПОСЛЕ записи
Читатель:
c1:=shm_iop(shm,COUNTER,'+',0); // атомарное чтение счетчика ДО чтения данных
data:=shm_rop(shm,ANYDATA,'R',0); // чтение данных
c2:=shm_iop(shm,COUNTER,'+',0); // атомарное чтение счетчика ПОСЛЕ
dirty:=(c1<>c2) or odd(c1); // флаг "грязных" данных (коллизия)
if dirty // если данные грязные
then writeln('попробуйте позже') // ингорируем их
else Handle(data); // хорошие данные обрабатываем
Достоинством функций shm_xxx является то, что они доступны и работают
одинаково в DaqPascal,Delphi,FreePascal
на платформах Windows/Unix.
Это облегчает создание и взаимодействие серверов и клиентов, написанных
на разных языках.
Использовать стандартную библиотеку надо c шаблоном прикладной программы template.pas.
Самый простой способ правильно использовать библиотеку StdLibrary - просто следовать шаблону template.pas и следующим рекомендациям.
Вместо: используйте:
WEB_Reply('<b>'+GetDateTime(GetMidnight(ms))+'</b>); mid:=GetMidnight(ms);
WEB_Reply('<b>'+GetDateTime(mid)+'</b>);
Замечено, что такие сложные вызовы создают риск утечки памяти (строк).
Это одна из проблем, которые предстоит решить в будущем.
Теперь описание супермодуля StdLibrary.
_con_StdLibrary.inc - файл описания констант супермодуля StdLibrary.
Супермодуль инкапсулирует все константы модулей библиотеки. Своих констант он не содержит.
_typ_StdLibrary.inc
- файл описания типов супермодуля StdLibrary.
Его включение в программу (пока) необязательно, но рекомендуется (на будущее).
В этом файле определены некоторые стандартные типы, часто используемые в прикладном ПО.
В настоящее время в нем определен тип TTagRef,
как наиболее часто используемый в прикладных программах.
Тип TTagRef -
запись,
содержащая ссылку на тег (tag),
а также ряд ассоциированных (связанных) с тегом полей:
номера кривых аналоговых/цифровых входов/выходов (nai,nao,ndi,ndo),
целочисленные ссылки (ref, crv, tid, pid, cid, key,
lnk,obj) на ассоциированные (связанные с тегом) объекты,
а также пользовательские вещественные данные (dat,val,tim),
которые могут использоваться для обработки или обновления тегов.
Тип TTagRef
часто используется в прикладных программах потому,
что теги обычно существуют не сами по себе, а связаны с другими
объектами (входами/выходами, кривыми, окнами и т.д.).
В стандартную библиотеку внесен наиболее общий (полный) набор ассоциированных
с тегом данных из встречавшихся на практике (это удобно).
Минусом такого множества полей может быть повышенное использование памяти.
При необходимости можно увеличить сегмент данных (Compiler.dtabmax)
через опции компилятора.
В файле стандартных типов также определен
тип TStatSum2D -
запись,
содержащая 2D статистические суммы, см. модуль
StdStatSum.
_fun_StdStatSum.inc
- файл функций для статистических расчетов (включается опционально, при необходимости).
В модуле определены функции
TStatSum2D_Reset,
TStatSum2D_Init,
TStatSum2D_Add,
TStatSum2D_Calculate.
См. описание
_man_StdStatSum.htm.
В файле стандартных типов также определен
тип TVector - тип для хранения динамических массивов.
В настоящее время это строка String, но это может измениться в будущем.
Лучше расматривать TVector как "непрозрачный" абстрактный тип.
_var_StdLibrary.inc - файл описания переменных супермодуля StdLibrary.
Супермодуль инкапсулирует все переменные модулей библиотеки. Своих переменных он не содержит.
_fun_StdLibrary.inc - файл описания функций супермодуля StdLibrary.
Супермодуль инкапсулирует все функции модулей библиотеки. А также содержит собственные процедуры.
procedure ClearStdLibrary;
procedure InitStdLibrary;
procedure FreeStdLibrary;
procedure PollStdLibrary;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса супермодуля в рамках шаблона
прикладной программы template.pas.
procedure StdIn_DefaultHandler(var Data,cmd,arg:String);
- обработчик консольных команд "по умолчанию".
Вызывается в конце процедуры StdIn_Processor, после обработки всех пользовательских команд.
Обеспечивает обработку некоторого набора стандартных консольных команд.
В настоящее время в него входят следующие команды:
@Help Показать справку. Справка прикладной программы находится в [@Help].
@DebugFlags n Установить режим отладочного вывода n=1+2+4+8, где флаги разрешают:
1 = Success(информационные сообщения);
2 = Trouble(выводится сообщение и генерируется ошибка), Problem(только сообщение);
4 = ViewExp(сообщения о выводе=экспорте, например, в канал или порт);
8 = ViewImp(сообщения о вводе=импорте, например, из канала или порта).
16 = Details(сообщения отладочного характера)
@Eval e Вычислить выражение e в интерпретаторе данного устройства, например, @Eval 2+2
Для вычисления используется экземпляр калькулятора текущей программы DaqPascal,
то есть доступны все переменные и функции, которые доступны для функции Eval().
Например, @Eval @echo Привет выведет Привет в системную консоль.
Это очень мощная функция, но пользоваться ей надо весьма осторожно.
@SysEval e Вычислить выражение e в Главной Консоли. Эквивалентно @Eval @System @Async e.
Помещает выражение e в буфер ввода Главной Консоли для последующего асинхронного исполнения.
Команда @SilentEval влияет на то, будет ли выражение вычисляться "молча" (@silent)
или будет выводиться эхо. По умолчанию режим эхо включен.
Например: @SysEval @run cmd (запускает cmd.exe)
@SilentEval n Задать режим 0/1=эхо/тихий режим для команды @SysEval.
В эхо режиме @SysEval e выполняется как @Eval @System @Async e.
В тихом режиме @SysEval e выполняется как @Eval @System @Async @Silent e.
По умолчанию используется эхо режим.
@Async cmd Выполнить команду cmd асинхронно, т.е. поместить в буфер ввода консоли данного устройства.
Команда будет выполнена позже, после обработки всех команд, уже находящихся в буфере.
@Speak s Произносит фразу s с помощью речевого синтезатора через сервер &SpeakSrv.
Фактически посылает сообщение серверу &SpeakSrv. Например: @Speak Hello world.
@Voice s Издает звук s. Эквивалент вызова Voice(s). Например: @Voice Click
@DevMsg dev msg Посылает сообщение msg устройству dev. Например: @DevMsg &CronSrv @Speak Hello
@DevSend dev msg Посылает сообщение msg устройству dev. Например: @DevSend &CronSrv @Speak Hello
@WinHide win Спрятать окно с именем win.
@WinShow win Показать окно с именем win.
@WinDraw win|opt Перерисовать окно win с (необязательными) опциями opt.
@WinSelect win Показать окно с именем win и выделить его, поместив вперед и дав фокус ввода.
@LoadIni Загружает параметры из INI файла. см. описание функции custominirw.
@SaveIni Сохраняет параметры в INI файл. см. описание функции custominirw.
Если команде передаются аргументы, они передаются функции custominirw.
Например: @SaveIni ..\Config\Custom.ini [CustomParameters] [CustomParameters.TagList] ..\Data\Custom
@CpuProfiler Показать Help по команде @CpuProfiler для вывода ститистики о производительности.
@CpuProfiler Start p v m s Стартовать профайлер с параметрами p v m s, по умолчанию 1 60 7 0
p - период опроса профайлера, секунд, по умолчанию 1
v - период вывода отчетов, секунд, по умолчанию 60
после вывода отчета счетчики зануляются, стартует новый цикл набора
нулевой период подавляет вывод и соответсвенно рестарт профайлера
m - режим вывода отчетов профайлера, по умолчанию 7=1+2+4
1=заголовок, 2=статистика загрузки, 4=счетчики
нулевой режим подавляет вывод отчетов и рестарт профайлера
s - тестовый режим стресс-нагрузки, %, по умолчанию 0 %
создает искусственную нагрузку потока программы s %
используется только для стресс-тестирования программы
при различных загрузках CPU
@CpuProfiler View Досрочный вывод отчета, не дожидаясь очередного периода отчетов.
Можно задать нулевой период отчетов, подавив его периодический вывод
с рестартом, а статистику смотреть командой View.
@CpuProfiler Stop Останавливает профайлер, чтобы он не отнимал время процессора.
По умолчанию профайлер остановлен, его можно стартовать только
консольной командой.
@Cron cmd Посылает команду cmd в консоль сервера &CronSrv для последующего исполнения.
Например:
@cron @run -hide dns.exe Запуск процесса через Cron
@cron @winshow DAQ-SYSTEM Показать окно с именем DAQ-SYSTEM
@StartupScript Выполнение сценария StartupScript. Сценарий задается в секции,
на которую указывает переменная StartupScript в описании устройства.
Например
[&Device]
StartupScript = [&Device.StartupScript]
...
[&DemoDevice.StartupScript]
@Command1 arg1
@Command2 arg2
...
@FinallyScript Выполнение сценария FinallyScript. Сценарий задается в секции,
на которую указывает переменная FinallyScript в описании устройства.
Например
[&Device]
FinallyScript = [&Device.FinallyScript]
...
[&DemoDevice.FinallyScript]
@Command1 arg1
@Command2 arg2
...
@ParamStr arg Вызывает ParamStr(arg) и печатает результат в консоль устройства. Полезно для справки.
@Remote arg Команда @Remote предназначена для создания распределенных систем на основе DIM сервера.
Она выполняет команду (arg) удаленно, через вызов Dim_GuiConsoleSend(DevName,arg).
Команда передается удаленному серверу через сеть DIM и извлекается на стороне сервера
вызовом Dim_GuiConsoleRecv(DevName,''), примерно как показано в примере:
procedure GUI_POLL;
var s:String;
begin
s:='';
DIM_GuiClickBuff:='';
if ClickWhat<>0 then
repeat
// Copy GUI click to DIM buffer.
DIM_GuiClickBuff:=DIM_GuiClickCopy;
// Handle MouseDown/KeyDown
if (ClickWhat=cw_MouseDown) or (ClickWhat=cw_KeyDown) then begin
// Handle Left mouse button click
if (ClickButton=VK_LBUTTON) then begin
// Handle local clicks
if ClickIsLocal then begin
......
end;
//
// Handle remote clicks comes from DIM via @DimGuiClick message.
// @DimGuiClick default handler decode and write events to FIFO,
// so we can find it as clicks and can handle it in usual way.
//
if ClickIsRemote then begin
//
// Handle remote console commands...
//
s:=Dim_GuiConsoleRecv(DevName,'');
if LooksLikeCommand(s) then DevSendCmdLocal(s);
......
end;
end;
end;
until (ClickRead=0);
......
s:='';
end;
@Local arg Работает аналогично @Async arg, т.е. выполняет команду (arg) асинхронно.
Эта команда сделана как парная к команде @Remote.
@Tooltip arg Инициирует всплывающее сообщение (tooltip), заданное аргументами (arg).
Например:
@tooltip text "Желаю вам успеха" preset stdNotify delay 15000
@OpenConsole dev Открывает окно консоли устройства с заданным именем (dev).
Если имя пустое, открывается консоль текущего устройства.
Например:
@OpenConsole
@OpenConsole &CronSrv
@BrowseHelp f Открывает HTM справку, заданную файлом с именем (f) и с расширением по умолчанию (.htm).
Путь файла может быть задан относительно основного конфигурационного файла.
Если файл не задан, его имя берется из параметра конфигурации [DAQ] HelpFile.
Например:
@BrowseHelp
@BrowseHelp ..\Help\index.htm
@DimServiceUp s Обработчик сообщения об установлении связи DIM сервера с именем сервиса s.
Действием по умолчанию является печать сообщения в консоль.
@DimServiceDie s Обработчик сообщения о разрыве связи DIM сервера с именем сервиса s.
Действием по умолчанию является печать сообщения в консоль.
@Reports t msg Процедура "докладывает", т.е. выводит сообщение (msg) типа (t).
t - тип сообщения: Trouble,Problem,Success,Succeed,ViewImp,ViewExp,Details,Disturb,Alerter,CritErr,Fatally
msg - сообщение: Текст, который передается в сообщении при вызове процедуры, на которую указывает тип.
Следует отметить, что для каждой стандартной команды @XXXX в библиотеке есть переменная cmd_Std_XXXX,
которая инициализируется в начале с помощью RegisterStdInCmd и содержит код команды.
_con_StdErrors.inc - файл описания констант модуля StdErrors.
Константы acc_Deny (доступ закрыт), acc_Guest (гостевой доступ), acc_User (пользовательский доступ), acc_Root (полный доступ), acc_List (список уровней доступа), acc_DenyList (список всех уровней доступа), acc_webList (список уровней доступа Web) - служат для идентификации уровня доступа и проверки прав доступа, используется в серверах DimSrv, WebSrv.
Константы MinInt (минимальное целое), MinReal (минимальное положительное вещественное), MaxReal (максимальное вещественное) - определяют системно-зависимые величины - диапазоны основных типов.
Константа VitalErrCode содержит код критических ошибок. Это ошибка исполнения программы DaqPascal, возникающая из-за программных исключений - деления на ноль, ошибки индексации массивов, переполнения стека и т.д.
Константа HangsErrCode содержит код ошибок сторожевого таймера (WATACHDOG DEADLINE). Это ошибка исполнения программы DaqPascal, возникающая из-за недопустимо длительных задержек - замкнутых циклов, длительного ожидания событий и т.д.
_var_StdErrors.inc - файл описания переменных модуля StdErrors.
Переменные Ok (статус стартовой инициализации), Errors (счетчик ошибок стартовой инициализации), ErrorCode (кодовый номер ошибки данного устройства) - используются для обработки ошибок. Счетчик Errors, в начале равный нулю, инкрементируется при выполнении стартовой секции, если возникли существенные ошибки (Trouble). Если по завершении стартовой инициализации (Starting) счетчик равен нулю, функция проверки (CheckStdErrors) устанавливает переменную Ok в True, разрешая опрос программы (Polling). Если на этапе инициализации возникла ошибка, опрос программы блокируется.
Переменные FixmSecNow (начальное время mSecNow), FixMaxAvail (начальное значение таблицы свободных строк), FixStackAvail (начальное значение стека свободной памяти) - фиксируют начальные значения системных счетчиков, чтобы при старте и завершении программы проверить, достаточно ли ресурсов и правильно ли они используются. Сравнение счетчиков в начале (Starting) и конце (Stopping) работы позволяет найти "утечки" ресурсов и сгенерировать ошибку, если найдены проблемы.
Переменная
FatalMaxAvail (фатальное значение MaxAvail)
задает критическое значение счетчика свободных строк (MaxAvail), при достижении которого прикладная программа
генерирует ошибку и останавливается с выводом сообщения в главную консоль.
Исчерпание таблицы строк - один из видов критических сбоев, после которых программу надо останавливать.
При недостатке свободного места в таблице строк исполнение программы становится проблематичным.
См. подробнее PostMortalWill.
При загрузке конфигурации переменная FatalMaxAvail считывается (до первого успеха) из секций:
[&DeviceName], [DAQ], Crw32.ini [DaqSys].
Это позволяет задавать значения как индивидуально каждому устройству, так и всем устройствам сразу.
Значение по умолчанию (в файле Crw32.ini [DaqSys]) равно 128.
Переменные
FixVitalBugs (начальное значение счетчика критических ошибок),
FatalVitalBugs (фатальное значение счетчика критических ошибок)
используются для остановки программы при возникновении критических ошибок, типа программных исключений из-за деления на ноль,
ошибок индексации массивов и т.д. Критические ошибки (дословно vital bugs, жизненно важные сбои) возникают при невозможности
дальнейшего исполнения программы Daq Pascal по причине серьезной ошибки. При этом исполнение программы прерывается
до следующего кванта времени (RunCount), фиксируется ошибка номер VitalErrCode.
При накоплении критического числа таких ошибок дальнейшее исполнение программы прекращается,
с выводом диагностики и исполнением "завещания".
См. подробнее PostMortalWill.
При загрузке конфигурации переменные FatalVitalBugs, FatalHangsBugs считываются (до первого успеха) из секций:
[&DeviceName], [DAQ], Crw32.ini [DaqSys].
Это позволяет задавать значения как индивидуально каждому устройству, так и всем устройствам сразу.
Значения по умолчанию (в файле Crw32.ini [DaqSys]) равны 1, 1.
Переменные FixHangsBugs (начальное значение счетчика ошибок Watchdog Deadline), FatalHangsBugs (фатальное значение счетчика ошибок Watchdog Deadline) используются для остановки программы при подвисании, то есть возникновении длительных периодов времени, когда программа не подает признаков жизни. Под признаками жизни понимается либо завершение очередного цикла исполнения программы, либо сброс стророжевого таймера (wdt_reset). Типичная ситуация, когда это может возникнуть - вход в бесконечный цикл из-за программной ошибки. Либо слишком долгое ожидание каких-то событий. Система снабжена сторожевым таймером Watchdog, который выполняется в отдельном высокоприоритетном потоке Daq.Watchdog и проверяет, когда программы в последний раз сбросили Watchdog. Сброс Watchdog происходит автоматически, при завершении исполнения программы на очередном цикле, а также программно, при вызове wdt_reset. Если со времени сброса прошло более, чем WatchdogDeadline миллисекунд (переменная задается в секции описания устройства и по умолчанию равна 60000, то есть минуте), то фиксируется WATCHDOG DEADLINE и исполнение программы принудительно прерывается до следующего кванта времени (RunCount), фиксируется ошибка номер HangsErrCode. При накоплении критического числа таких ошибок дальнейшее исполнение программы прекращается, с выводом диагностики и исполнением "завещания". См. подробнее PostMortalWill.
Переменная PostMortalWill (дословно - посмерная воля или завещание) задает действия, выполняемые при остановке программы в случае критических сбоев, см. FatalMaxAvail, FatalVitalBugs и FatalHangsBugs. В этой длинной строке содержится текст "завещания", разделенный на строки с помощью EOL, в которых задаются посмертные сообщения (DevMsg) и команды Eval в формате
&DeviceName arguments - задает сообщение DevMsg(...)
@CommandName argiments - задает команду Eval(...)
Например:
procedure InitApplication;
begin
FatalMaxAvail:=128; // Минимальный размер таблицы строк. Это значение по умолчанию.
FatalVitalBugs:=128; // Предельное число критических сбоев. Это значение по умолчению.
PostMortalWill:='@system @async @run -hide msg * "I am dead."'+EOL
+'@system @async @compile device '+DevName+EOL
+'&CronSrv @Warning I am dead.';
end;
procedure PollApplication;
begin
writeln(1 div 0); // Фатальная ошибка - целочисленное деление на ноль!
end;
В приведенном примере в процедуре опроса Poll есть фатальная ошибка - целочисленное деление на ноль.
После 128 попыток выполнить программу будет зафиксировано предельное число критических ошибок
времени исполнения (vital bugs - дословно жизненно важные сбои). При этом программа будет остановлена,
веведена диагностика, а также будет исполнено её "завещание" - в данном случае будет выполнена команда
Eval('@system @async @run -hide msg * "I am dead."') - запуск команды msg для вывода сообщения
Eval('@system @async @compile device '+DevName) - перезапуск программы путем перекомпиляции
DevMsg('&CronSrv @Warning I am dead.') - посылка сообщения серверу &CronSrv
В зависимости от задачи в "завещании" можно указать, например, перезапуск самой сбойной программы
или всей DAQ системы, включение блокировок или отключение аппаратуры и т.д.
При загрузке конфигурации переменная PostMortalWill считывается (последовательно, до певого успеха):
Типичное описание PostMortalWill имеет вид:
[DefaultPostMortalWill]
@system @async @run -hide unix tooltip-notifier head "Attension:" text "%time%: CRW-DAQ device $DeviceName$ critical error." preset stdError delay 60000
@system @async @compile device $DeviceName$
[]
Разумеется, конкретная программа может также задавать значение PostMortalWill в InitApplication.
Переменные StartingDevMsg, StoppingDevMsg задают сообщения, которые посылаются в консоль в блоке Starting (при старте программы) и в блоке Stopping (при завершении программы). По умолчанию обе переменные пустые, то есть сообщения не посылаются. Задание значений StartingDevMsg:='@StartupScript';, StoppingDevMsg:='@FinallyScript'; позволяет вызывать стартовый и завершающий сценарий, например:
procedure InitApplication;
begin
StartingDevMsg:='@StartupScript'; Задать стартовое сообщение в консоль
StoppingDevMsg:='@FinallyScript'; Задать завершающее сообщение в консоль
StdIn_ToStopping:=MaxInt; Разрешить обработку завершающих консольных команд
end;
...
В результате (при условии что в StdIn_Processor вызывается StdIn_DefaultHandler)
будет выполнен StartupScript на старте и FinallyScript при завершении.
_fun_StdErrors.inc - файл описания функций модуля StdErrors.
function Starting:Boolean;
function Stopping:Boolean;
function Polling:Boolean;
function CheckStdErrors:Boolean;
procedure PostStartingDevMsg;
procedure PostStoppingDevMsg;
- обеспечивают поддержку шаблона прикладной программы
template.pas.
Starting указывает на то, что программа выполняет этап начальной инициализации при старте.
Если в процессе инициализации не было фатальных ошибок (Trouble), то функция CheckStdErrors
устанавливает флаг Ok для разрешения опроса программы Polling.
Stopping указывает на то, что программа выполняет этап финального освобождения ресурсов при остановке системы.
PostStartingDevMsg посылает в консоль сообщение из переменной StartingDevMsg.
PostStoppingDevMsg посылает в консоль сообщение из переменной StoppingDevMsg.
Подробнее см. _std_main.inc
function CompareGuards(g1,g2:String):Integer;
- сравнивает уровни доступа g1 (реальный) и g2 (эталонный), возвращается результат -1 (отказать), 0 (уровень достаточен),
+1 (уровень выше эталона). Например,
if CompareGuards(g,'user')<0 then AccessDenied else WelcomeToUserLevel;Уровень доступа может быть guest,user,root, а также deny при отказе в доступе.
procedure cNul(c:Char);
procedure rNul(r:Real);
procedure sNul(s:String);
procedure bNul(b:Boolean);
procedure iNul(i:Integer);
- эти функции реализуют "устройство Nul" для основных типов DaqPascal.
Их удобство заключается в том, что с ними можно не заводить временные переменные для результатов, которые часто и не нужны.
Сравните два текста:
procedure Long; procedure Short;
var b:Boolean; begin
begin bNul(Echo('Привет'));
b:=Echo('Привет'); end;
end;
Вторая запись очевидно короче, и не надо заводить временные переменные для результатов, которые все равно не используются.
Поскольку при использовании результатов обычно вызывается if () then ..., то и в этом случае временные
переменные не нужны. Присвоение будет идти только тогда, когда результат в самом деле нужен.
Отрицательным моментом тут является некоторое снижение производительности (затраты на вызов пустой процедуры).
Поэтому процедуры лучше использовать там, где производительность не играет критической роли.
function IsRefTag(ref:Integer):Boolean;
function IsRefCurve(ref:Integer):Boolean;
function IsRefDevice(ref:Integer):Boolean;
function IsRefWindow(ref:Integer):Boolean;
function IsRefTimer(ref:Integer):Boolean;
function IsRefText(ref:Integer):Boolean;
function IsRefTask(ref:Integer):Boolean;
function IsRefPipe(ref:Integer):Boolean;
function IsRefTcp(ref:Integer):Boolean;
function IsRefCom(ref:Integer):Boolean;
- эти функции проверяют ссылку "ref" для основных типов объектов DaqPascal, см. RefInfo(..).
Их рекомендуется вызывать перед использованием ссылок для содержательной работы.
Например:
if IsRefTag(dev) then Success(TagName(tag)) else Trouble('Invalid tag reference');
if IsRefDevice(dev) then i:=DevSend(dev,msg) else Trouble('Invalid device reference');
procedure ClearStdErrors;
procedure InitStdErrors;
procedure FreeStdErrors;
procedure PollStdErrors;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdStrings.inc - файл описания констант модуля StdStrings.
Константы _NUL, _SOH, _STX, _ETX, _EOT, _ENQ, _ACK, _BEL, _BS, _HT, _LF, _VT, _FF, _CR, _SO, _SI, _DLE, _DC1, _DC2, _DC3, _DC4, _NAK, _SYN, _ETB, _CAN, _EM, _SUB, _ESC, _FS, _GS, _RS, _US, _SP, _DEL - определяют коды управляющих (непечатных) символов стандартной таблицы ASCII и Unicode. Для преобразования кодов в символы и обратно используйте chr и ord. Управляющие коды очень часто используются для реализации протоколов связи. Поэтому желательно использовать их стандартные названия, а не цифры.
Константы _Space, _Exclamation, _DoubleQuote, _NumberSign, _DollarSign, _PercentSign, _Ampersand, _SingleQuote, _LeftParent, _RightParent, _Asterisk, _PlusSign, _Comma, _MinusSign, _Dot, _Slash, _Colon, _SemiColon, _LessSign, _EqualsSign, _GreaterSign, _Question, _AtSign, _LeftBracket, _BackSlash, _RightBracket, _Caret, _Underscore, _GraveAccent, _LeftCurly, _PipeSign, _RightCurly, _Tilde, _CurrencySign, _TripleDot, _Dagger, _DoubleDagger, _eurosign, _PerMilleSign, _Bullet, _Trademark, _SectionSign, _Copyright, _NotSign, _DegreeSign, _PlusMinus, _MicroSign, _PilcrowSign, _NumeroSign - определяют специальные печатные символы (пунктуации, математические и другие) таблицы ASCII и Unicode. Эти константы полезно использовать в программах, так как обилие кавычек делает программу трудно читаемой. Например, понять запись
s:=s+_SingleQuote+_DoubleQuote; все-таки легче чем s:=''''+'"'А это далеко не самый запутанный случай, с которыми приходится иметь дело. А такие символы, как знак микро или троеточие с клавиатуры так просто и не введешь. Поэтому использование мнемонических констант для специальных знаков может сильно помочь и сделать программу более понятной.
Константы cw_Nothing, cw_MouseDown, cw_MouseUp, cw_MouseMove, cw_KeyDown, cw_KeyUp cw_MouseWheel, cw_MouseWheelDown, cw_MouseWheelUp, - определяют коды возврата функции ClickWhat и других функций, возвращающих тип пользовательских событий (от мыши, клавиатуры).
Константа cw_NameList - содержит список имен возможных значений ClickWhat в виде строки. Имя можно получить в виде Name:=ExtractWord(1+ClickWhat,cw_NameList); What:=WordIndex(Name,cw_NameList)-1;
Константы VK_LBUTTON, VK_RBUTTON, VK_CANCEL, VK_MBUTTON, VK_BACK, VK_TAB, VK_CLEAR, VK_RETURN, VK_SHIFT, VK_CONTROL, VK_MENU, VK_PAUSE, VK_CAPITAL, VK_ESCAPE, VK_SPACE, VK_PRIOR, VK_NEXT, VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN, VK_SELECT, VK_PRINT, VK_EXECUTE, VK_SNAPSHOT, VK_INSERT, VK_DELETE, VK_HELP, VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, VK_F11, VK_F12, VK_NUMLOCK, VK_SCROLL, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU - определяют виртуальные коды клавиатуры, они же коды возврата функции ClickButton. При нажатии регистровых клавиш (Ctrl,Alt,Shift,NumLock,CapsLock) эти клавиши модифицируются по известным правилам, см. str2shortcut.
Константы risModeUpper, risModeLower, risModeTrimL, risModeTrimR, risModeRemComm, risModeDefault, risModeDefCase, risAlterSecRef, risAlterEnvRef, risModeAlter - определяет режимы readinisection и readinialter.
_var_StdStrings.inc - файл описания переменных модуля StdStrings.
Переменные ProgramSourcePas (имя файла *.PAS программы), ProgramSourceDir (каталог *.PAS программы) - содержат полное имя и каталог файла с исходным кодом текущей исполняющейся программы DAQ Pascal. Хотя эти имена можно всегда прочитать из переменной ReadIni('ProgramSource'), полезно иметь их в готовом виде.
Переменная WHEEL_DELTA содержит шаг приращения колесика мыши и используется при обработке события MouseWheel. После старта переменная получает стандартное для Windows значение, равное 120.
_fun_StdStrings.inc - файл описания процедур и функций модуля StdStrings. Это строковые функции общего назначения.
function IsEmptyStr(s:String):Boolean;
проверяет, является ли строка пустой. Строка считается пустой, если она не содержит символы, отличные от пробелов.
function IsNonEmptyStr(s:String):Boolean;
проверяет, является ли строка непустой. Строка считается непустой, если она содержит символы, отличные от пробелов.
function TailStr(S:String; Pos:Integer):String;
возвращает часть строки S ("хвост"), начиная с позиции Pos до конца строки.
function RightStr(S:String; Count:Integer):String;
возвращает правую часть строки S длиной (не более чем) Count последних символов.
function LeftStr(S:String; Count:Integer):String;
возвращает левую часть строки S длиной (не более чем) Count первых символов.
function StrAheadOf(S:String; Delim:Char):String;
возвращает часть строки S перед (первым найденным) символом Delim, либо пустую строку, если нет такого символа в строке.
function StrAfterOf(S:String; Delim:Char):String;
возвращает часть строки S после (первого найденного) символа Delim, либо исходную строку, если нет такого символа в строке.
function ContainsStr(AText,ASubText:String):Boolean;
возвращает флаг, если в строку AText входит подстрока ASubText.
Сравнение строк идет с учетом регистра.
function StartsStr(ASubText,AText:String):Boolean;
возвращает флаг, если строка AText начинается с подстроки ASubText.
Сравнение строк идет с учетом регистра.
function EndsStr(ASubText,AText:String):Boolean;
возвращает флаг, если строка AText заканчивается подстрокой ASubText.
Сравнение строк идет с учетом регистра.
function ContainsText(AText,ASubText:String):Boolean;
возвращает флаг, если в строку AText входит подстрока ASubText.
Сравнение строк идет без учета регистра.
function StartsText(aSubText,aText:String):Boolean;
возвращает флаг, если строка AText начинается с подстроки ASubText.
Сравнение строк идет без учета регистра.
function EndsText(aSubText,aText:String):Boolean;
возвращает флаг, если строка AText заканчивается подстрокой ASubText.
Сравнение строк идет без учета регистра.
function TrimDef(s,def:String):String;
возвращает строку Trim(s) с удалением незначащих пробелов, если строка (s) является НЕпустой.
Возвращает значение по умолчанию Trim(def), если строка (s) является пустой.
Т.е. возвращает непустую строку (s), либо значение по умолчанию (def).
function LastPos(sub,str:String):Integer;
находит позицию последнего вхождения подстроки (sub) в строку (str).
function CountPos(sub,str:String):Integer;
находит число (счетчик) вхождений подстроки (sub) в строку (str).
function NthPos(sub,str:String; n:Integer):Integer;
находит позицию n-го вхождения подстроки (sub) в строку (str).
Заметим, что функция рекомендуется для поиска конкретного вхождения подстроки, а не для организации циклов.
Для цикла итераций по подстрокам лучше использовать posex, т.к. это будет быстрее.
function IsSectionName(str:String):Boolean;
проверяет строку (str), является ли она именем секции типа [SectionName].
Признаком секции является начало [ и завершение ] строки.
Незначащие пробелы впереди и сзади не допускаются.
function IsEnvVarLink(str:String):Boolean;
проверяет строку (str), является ли она ссылкой на переменную окружения типа %VariableName%.
Признаком ссылки является начало % и завершение % строки.
Незначащие пробелы впереди и сзади не допускаются.
function DupeString(s:String; n:Integer):String;
- функция копирует строку s и повторяет её n раз, т.е. возвращает строку, состоящую из n копий строки s.
Возвращает пустую строку, если (n<1) или (s='').
Заметим, что для повторения строки из одного символа лучше использовать функцию StringOfChar(c,n), т.к. она работает быстрее.
function CharStr(Leng:Integer; Ch:Char):String;
function LeftPad(S:String; Leng:Integer; Ch:Char):String;
function RightPad(S:String; Leng:Integer; Ch:Char):String;
function CenterPad(S:String; Leng:Integer; Ch:Char):String;
- функции заполнения строки заданным символом Ch до длины Leng.
CharStr просто заполняет строку символом Ch до заданной длины.
LeftPad заполняет строку S, добавляя символ слева.
RightPad заполняет строку, добавляя символ справа.
CenterPad центрирует строку, добавляя символ справа и слева.
function iValDef(Data:String; aDefault:Integer):Integer;
function rValDef(Data:String; aDefault:Real):Real;
function iEvalDef(Data:String; aDefault:Integer):Integer;
function rEvalDef(Data:String; aDefault:Real):Real;
- функции преобразования строки Data в число (integer или real) с присвоением значения
по умолчанию aDefault в случае пустой строки или строки, не содержащей числового значения.
iValDef,rValDef используют строгое преобразование (val,rval), при этом строка должна явно содержать число.
iEvalDef,rEvalDef использует интерпретатор (eval), при этом строка может содержать сложное выражение.
Функции xEvalDef мощнее, но и потенциально опаснее, т.к. интерпретатор может запустить нежелательные функции.
Если приоритетом является безопасность ввода, используйте xValDef.
Если приоритетом является функциональность и гибкость, используйте xEvalDef.
Эти функции удобны для анализа параметров консольных команд, когда требуется интерпретировать аргументы команды,
либо присваивая им значения по умолчанию, либо сохраняя старое значение в случае пустой или нечисловой строки:
Пример:
i:=iValDef(arg,3); Преобразовать arg в Integer, присвоить i=3, если arg - не число.
r:=rValDef(arg,r); Преобразовать arg в Real, сохранять значение r в случае неудачи.
i:=iEvalDef(arg,i); Вычислить выражение arg, сохранять значение i в случае неудачи.
r:=rEvalDef(arg,0); Вычислить выражение arg, присвоить r=0 если arg - недопустимое выражение.
function WordIndex(s,list:String):Integer;
Пример:
if WordIndex(name,'tom,bob,john')>0 // Проверить, есть ли name в списке имен.
then writeln(name,' is in user list'); // Возвращает 0 или индекс name в списке.
s:=SkipWords(1,'@echo Привет мир!'); // Возвращает s="Привет мир!", после @echo и пробела.
s:=ExtractWordDelims(2,'a\b/c','\/'); // Возвратит s="b", используя \/ как сепараторы слов.
function StrReplace(s,a,b:String; Flags:Integer):String;
- заменяет в строке s подстроку a на строку b. Маска Flags задает режим замены:
1 (бит 0) включает замену всех вхождений a на b (а не только первого),
2 (бит 1) включает режим нечувствительности к регистру, при котором большие и малые буквы считаются идентичными.
Строго это работает для английских букв, а для национальных символов используется кодировка windows-1251.
function StrAddQuotes(s:String; Quote:Char):String;
- добавляет в начало и конец строки s "кавычки", заданные символом Quote.
Если кавычки уже есть, строка не меняется.
function StrRemoveQuotes(s:String; Quote:Char):String;
- убирает из начала и конца строки s "кавычки", заданные символом Quote.
Если кавычек нет, строка не меняется.
function StrAddBrackets(s:String; LBracket,RBracket:Char):String;
- добавляет в начало и конец строки s [скобки], заданные символами LBracket,RBracket.
Если скобки уже есть, строка не меняется.
function StrRemoveBrackets(s:String; LBracket,RBracket:Char):String;
- убирает из начала и конца строки s [скобки], заданные символами LBracket,RBracket.
Если скобок нет, строка не меняется.
function ExtractNameValuePair(arg:String; var Name,Value:String; Sign:Char; Mode:Integer):Integer;
- функция выделяет из строки аргумента arg вида Name Sign Value имя и значение Name,Value,
разделенные символом Sign, и возвращает позицию символа Sign в строке или ноль (если его нет).
Биты (0,1) режима Mode (то есть флаги 1,2) задают режим обрезания (Trim) лишних пробелов Name,Value соответсвенно.
Обычно функция ExtractNameValuePair используется в разборе выражений типа Name=Value.
Например:
s:=' os = unix ';
p:=ExtractNameValuePair(s,n,v,'=',3); // p=5, n='os', v='unix'
s:='Error: not found';
p:=ExtractNameValuePair(s,n,v,':',3); // p=6, n='Error', v='not found'
function ReadIniVar(VarName:String;Mode:Integer):String;
function ReadIniStr(Mode:Integer; FName,SecName,VarName:String):String;
- функции чтения конфигурационных файлов. Отличаются от ReadIni тем, что есть возможность задавать
режим чтения конфигурации - например, отключать подавление пробелов и строк комментария, чтобы было можно
помещать в секцию произвольный текст, см. ReadIniSection.
ReadIniVar читает из секции устройства переменную (VarName = Value), в заданном режиме чтения секции.
При этом можно извлекать строковые переменные, не подвергая их искажению (удаление пробелов, перевод в верхний регистр).
ReadIniStr читает переменную с полным указанием всех координат (файл, секция, имя переменной, режим чтения),
что позволяет считывать данные из произвольных файлов, а не только из файлов текущей конфигурации.
function ReadIniAlter(arg:String;Mode:Integer):String;
- функция чтения конфигурационных файлов с альтернативой (возможностью выбора способа чтения), что значительно расширяет возможности readini.
Параметр arg означает то же самое, что в функции ReadIni, т.е. задает файл, секцию и имя переменной.
Режим Mode задает флаги чтения секции. Часть флагов - такие же как в функции ReadIniSection.
Флаг risAlterSecRef управляет чтением по секционной ссылке (section reference),
т.е. если в качестве значения параметра указано [alterSection] alterName,
то чтение происходит из указанной альтернативной секции и переменной.
Флаг risAlterEnvRef управляет чтением по ссылке на окружение (environment reference),
т.е. если в качестве значения параметра указано %alterName%,
то чтение происходит из указанной альтернативной переменной окружения.
Флаг risModeAlter является суммой флагов risAlterSecRef и risAlterEnvRef.
Если чтение из указанных альтернативных источников дало пустую строку, в качестве результата принимается остаток строки.
Общий формат такой:
s:=ReadIniAlter('[&Sect] Param',risModeDefault+risModeAlter);
При чтении файла CfgFile
[&Sect]
Param = [alterSect] alterName %alterVar% FallbackValue
сначала читается секция [alterSect] alterName
потом, если не удалось: %alterVar%
потом, если не удалось: FallbackValue
Таким образом, будет прочитано одно одно из значений альтернативных источников.
s:=ReadIniAlter('DnsNode',risModeDefault+risModeAlter);
При чтении файла
[&DeviceName]
DnsNode = [&DimSrv] DIM_DNS_NODE %DIM_DNS_NODE% localhost
сначала читается секция [&DimSrv] DIM_DNS_NODE
потом, если не удалось: %DIM_DNS_NODE%
потом, если не удалось: localhost
Таким образом, будет прочитано одно одно из значений альтернативных источников.
При использовании функции ReadIniAlter следует учесть, что она выполняется существенно медленнее, чем readini.
Не следует злоупотреблять её необоснованным использованием.
function Win2Koi(s:String):String;
function Koi2Win(s:String):String;
- функции преобразования из кодировки windows-1251 (которую любим мы) в кодировку KOI (которую любит GnuPlot).
Это просто короткая версия StrConv, удобная для программ, использующих &PlotSrv.
procedure Cryptographer(var Data,Key:String; Encrypt:Boolean; CryptKind:Integer);
- функция шифрования. Она применяется для защиты данных DIM, WEB.
Шифрует (Encrypt=True) или дешифрует (Encrypt=False) строку Data с открытым ключом Key
методом CryptKind (0=CURRENT, 1=Blowfish, 2=Gost, 3=RC2, 4=RC4, 5=RC5, 6=RC6, 7=BASE64, 8=HEX, 9=NONE).
Метод 0 использует текущий метод шифрования, заданный crypt_ctrl.
Методы 1..6 задают один из алгоритмов "сильного" шифрования (для скорости рекомендуется использовать метод 4).
Методы 7 и 8 используют простое кодирование, без шифрования (на вид сразу не поймешь, но взломать легко).
Метод <0 или >8 вообще не меняет Data, то есть шифрования или кодирования при этом не происходит.
Методы без шифрования (7,8) применяются, когда "сильная" защита данных не нужна, при этом достигается высокая скорость
при минимальной защите (неквалифицированный оператор не сможет прочитать пароль, программист легко взломает).
Если нужна "сильная" защита, используются методы 3..6 (для скорости рекомендуется метод 4).
Метод 9 передает данные открытым текстом и может применяться для тестирования и отладки.
function CharReverseStr(s:String):String;
- функция возвращает строку с обратным порядком символов. Например, если s='abcdef', то функция вернет 'fedcba'.
function TextVarScan(var Buff:String; Name:String):String;
- функция ищет в текстовом буфере Buff, состоящем из строк, разделенных EOL, строку типа Name=Value, и возвращает Value.
function TextLineCount(s,delim:String):Integer;
function ExtractTextLine(num:Integer; s,delim:String):String;
- функции для работы с текстом в буфере строки (s), состоящем из строк, разделенных разделителем (delim),
которым обычно является CRLF или EOL.
Функция TextLineCount подсчитывает число строк текста (s) с разделителем (delim).
В отличие от функции WordCount, число разделителей здесь играет существенную роль,
например, WordCount('A'+EOL+EOL+'B')=2, но TextLineCount('A'+EOL+EOL+'B',EOL)=3.
Функция ExtractTextLine извлекает из текста (s) с разделителем (delim) строку номер (num).
Нумерация строк начинается с нуля.
Например:
buff:='Line1'+EOL+'Line2'+EOL;
for i:=0 to TextLineCount(buff,EOL)-1 do begin
writeln('Line ',i,' = ',ExtractTextLine(i,s,EOL));
end;
Из соображений производительности подобный цикл по строкам с помощью функции ExtractTextLine
рекомендуется использовать только для небольших текстов (до нескольких килобайт), так как при работе
с большими текстами трудоемкость такого цикла растет нелинейно (как квадрат размера текста).
Функцию ExtractTextLine можно также использовать для извлечения конкретных строк с известными номерами.
При работе с большими текстами рекомендуется "перегонять" их в объект-текст (StringToText) и работать уже с объектом-текстом.
Несмотря на затраты на перевод строки в объект-текст это будет более эффективно для больших текстов.
function ColorToString(code:Integer):String;
function StringToColor(name:String):Integer;
function StringToColorDef(name:String; def:Integer):Integer;
- функции преобразования цветов: кода цвета (code) в имя цвета (name) и наоборот.
Функции работают на базе вызовов ParamStr('ColorCode/ColorName '..),
но удобнее для использования.
function GuiLanguageSign:String;
function GuiLanguageCode:Integer;
function glc_English:Integer;
function glc_Russian:Integer;
function RusEngStr(rus,eng:String):String;
- функции языковой поддержки для графического интерфейса.
Функция GuiLanguageSign возвращает текстовый 3-значный код языка, rus или eng.
Этот код вычисляется из переменной окружения CRW_DAQ_SYS_LANG.
Функция GuiLanguageCode возвращает целочисленный код языка, Dump2i('rus') или Dump2i('eng').
Этот код вычисляется из значения GuiLanguageSign.
Функция glc_English возвращает код английского языка, равный Dump2i('eng').
Функция glc_Russian возвращает код русского языка, равный Dump2i('rus').
Функция RusEngStr(rus,eng) возвращает строку на русском rus или английском eng,
в зависимости от значения GuiLanguageCode.
Пример:
s:=RusEngStr('Привет мир','Hello world');
if (GuiLanguageCode=glc_Russian) then writeln('Русский язык интерфейса.')
function IfThenStr(Cond:Boolean; S1,S2:String):String;
Возвращает в зависимости от условия Cond значение S1 или S2.
Пример:
s:=IfThenStr(IsWindows,'Must Die!','Long Live!');
function CookieScanAlter(buff,items:String; mode:Integer):String;
- сканирование текстового буфера buff для поиска значения "куки" с альтернативным набором имен параметра,
заданным списком items.
Список items содержит альтернативный список имен читаемого параметра,
разделенных символами запятой (,) или точки с запятой (;).
Для каждого имени (name) из списка items вызывается
функция CookieScan(buff,name,mode),
пока не будет найден непустой результат вызова.
Если такой результат найден, он немедленно возвращается в качестве значения функции.
Таким образом, CookieScanAlter(buff,items,mode) позволяет одним вызовом читать
параметр, который может иметь несколько альтернативных имен.
Чтение параметров с альтернативой выбора используется, например, для анализа строк описания подключения к базам данных.
Различные СУБД используют разные имена для описания параметров подключения.
Например, имя пользователя может быть задано переменными с альтернативным набором имен:
UserName, User ID или UID.
Вызов типа UserName:=CookieScanAlter(Buff,'UserName;User ID;UID',Ord(';')); позволяет
прочитать параметр с альтернативным набором имен одним вызовом, вместо целого блока кода
с тремя вызовами CookieScan для каждого альтернативного варианта.
Пример:
// Буфер для чтения
Buff:='Provider=ADODB;UserName=Master;';
// Чтение переменной с именем UserName
UserName:=CookieScan(Buff,'UserName',Ord(';'));
// Чтение переменной UserName, или User ID, или UID.
UserName:=CookieScanAlter(Buff,'UserName;User ID;UID',Ord(';'));
procedure ClearStdStrings;
procedure InitStdStrings;
procedure FreeStdStrings;
procedure PollStdStrings;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdConsole.inc - файл описания констант модуля StdConsole.
Константы dfTrouble, dfSuccess, dfViewExp, dfViewImp, dfDetails - используются для управления режимом вывода различных групп сообщений: Trouble или Problem при ошибках, Success или Succeed при успешном выполнении, ViewExp при выводе в канал связи, ViewImp при вводе из канала связи, Details при выводе отладочных информационных сообщений. Собственно, режимом вывода управляет переменная DebugFlags, а константы нужны для проверки битов, например,
if iAnd(DebugFlags,dfDetails)>0 then Details(info);
_var_StdConsole.inc - файл описания переменных модуля StdConsole.
Переменная
DebugFlags
управляет режимом вывода:
dfTrouble бит 0 - разрешает сообщения об ошибках Trouble,Problem,Failure
dfSuccess бит 1 - разрешает сообщения об успешных операциях и информации Success
dfViewExp бит 2 - разрешает сообщения при выводе данных в канал или порт ViewExp
dfViewImp бит 3 - разрешает сообщения при вводе данных из канала и порта ViewImp
dfDetails бит 4 - разрешает сообщения при отладке, выводе разных деталей Details
Для задания начального значения флагов используется CFG-переменная DebugFlags = n,
по умолчанию n=3. Для изменения режима вывода в процессе работы служит команда @DebugFlags n,
доступная в рамках шаблона прикладной программы template.pas.
Переменная StdIn_Line - служит временным буфером при анализе консольного ввода. Носит служебный характер.
Переменные StdIn_LineCount, StdIn_CmndCount, StdIn_LineErrors - содержит счетчики соответственно входных строк стандартной консоли, поступающих консольных команд, а также ошибок чтения консоли стандартного ввода StdIn.
Переменная StdIn_StrictErr управляет режимом генерации ошибок при поступлении неизвестных команд. По умолчанию опция включена, то есть при вводе неизвестной команды получим ошибку.
Переменная StdIn_HelpLines является внутренней, использовать ее запрещено.
Переменная StdIn_HelpEchoOn разрешает эхо-вывод справки @Help в консоль, по умолчанию включена.
Переменная StdIn_EnablePoll разрешает цикл опроса консольного процессора StdIn_Processor в основном теле программы. По умолчанию включена. Запретите этот цикл, если вам нужна специальная процедура обработки консольного ввода. Это бывает, например, нужно в драйверах, где консольный ввод должен быть синхронизован с циклом опроса устройства.
Переменные
StdIn_ToStarting,
StdIn_ToStopping,
StdIn_ToPredPoll,
StdIn_ToPostPoll
задают предельное время (Timeout) и порядок вызова консольного процессора StdIn_Processor в цикле опроса
шаблонной программы template.pas.
Реализацию цикла опроса см. в _std_main.inc.
Переменная StdIn_ToStarting задает ограничение по времени (Timeout, ms) при вызове
цикла опроса консольного процессора StdIn_Processor ПОСЛЕ исполнения процедуры
инициализации работы InitApplication в блоке Starting,
то есть при первом вызове программы.
Если Timeout=0, то консольный процессор не вызывается.
Если Timeout=MaxInt, то на время обработки команд ограничений не накладывается.
Остальные значения задают максимальное время на обработку в миллисекундах.
По умолчанию StdIn_ToStarting = 0.
Переменная StdIn_ToStopping задает ограничение по времени (Timeout, ms) при вызове
цикла опроса консольного процессора StdIn_Processor ПЕРЕД исполненем процедуры
завершения работы FreeApplication в блоке Stopping,
то есть при последнем вызове программы.
Если Timeout=0, то консольный процессор не вызывается.
Если Timeout=MaxInt, то на время обработки команд ограничений не накладывается.
Остальные значения задают максимальное время на обработку в миллисекундах.
По умолчанию StdIn_ToStopping = 0.
Переменная StdIn_ToPredPoll задает ограничение по времени (Timeout, ms) при вызове
цикла опроса консольного процессора StdIn_Processor ПЕРЕД исполнением основной процедуры
пользовательской обработки PollApplication в блоке Polling.
Если Timeout=0, то консольный процессор не вызывается.
Если Timeout=MaxInt, то на время обработки команд ограничений не накладывается.
Остальные значения задают максимальное время на обработку в миллисекундах.
По умолчанию StdIn_ToPredPoll = 0.
Переменная StdIn_ToPostPoll задает ограничение по времени (Timeout, ms) при вызове
цикла опроса консольного процессора StdIn_Processor ПОСЛЕ исполнения основной процедуры
пользовательской обработки PollApplication в блоке Polling.
Если Timeout=0, то консольный процессор не вызывается.
Если Timeout=MaxInt, то на время обработки команд ограничений не накладывается.
Остальные значения задают максимальное время на обработку в миллисекундах.
По умолчанию StdIn_ToPostPoll = MaxInt.
_fun_StdConsole.inc - файл описания процедур и функций модуля StdConsole. Это консольные функции общего назначения. Их правильное использование гарантируется в рамках шаблона прикладной программы template.pas.
function DebugFlagEnabled(flag:Integer):Boolean;
- проверяет переменную DebugFlags на наличие в ней заданного флага,
чтобы узнать, включен ли заданный режим отладочного вывода. Например:
if DebugFlagEnabled(dfViewImp) then ViewImp('Приняты данные: '+Data);
if DebugFlagEnabled(dfViewExp) then ViewExp('Посланы данные: '+Data);
if DebugFlagEnabled(dfDetails) then Details('Детали обработки: '+Comment);
Переменная StdIn_CmdHashTab содержит хеш-таблицу команд, зарегистрированных функцией RegisterStdInCmd и используемая функцией GotCommandId. Трогать эту переменную не надо.
procedure Trouble(msg:String);
procedure Problem(msg:String);
procedure Failure(ErrorCode:Integer; msg:String);
procedure Success(msg:String);
procedure Succeed(msg:String);
procedure ViewImp(msg:String);
procedure ViewExp(msg:String);
procedure Details(msg:String);
procedure Disturb(msg:String);
procedure Alerter(msg:String);
procedure CritErr(msg:String);
procedure Fatally(msg:String);
procedure Reports(msg:String);
procedure Assertion(cond:Boolean; msg:String);
- осуществляют вывод сообщений в консоль, с записью в системный журнал (в зависимости от уровня значимости severity).
Режимом вывода в консоль управляет переменная отладочных флагов
DebugFlags, биты которой разрешают вывод сообщений разных типов.
Проверять отладочные флаги можно с помощью функции DebugFlagEnabled.
Trouble, Problem, Failure, Disturb, Alerter, CritErr, Fatally
выводят сообщения об ошибках в различных режимах (консоль, журнал, генерация ошибки).
При этом Trouble, CritErr, Fatally также генерирует
ошибку (FixError(ErrorCode)), связанную с именем данного устройства,
Failure генерирует ошибку, явно указанную при вызове,
а Problem, Disturb, Alerter вообще ничего не генерирует, а только выводит сообщение.
То есть если ошибка заслуживает красной мордочки
, то используется
Trouble, CritErr, Fatally (обычно) или
Failure (в случае специфических ошибок).
А если ошибка не очень важная и не заслуживает
такого внимания, используется Problem, Disturb, Alerter.
ViewExp (экспорт),ViewImp (импорт) выводят сообщения об обмене данными.
Details (детально) используется для отображения отладочных деталей, либо каких-то специфических данных.
Например, если программа что-то пишет в порт, канал или файл, можно дублировать вывод вызовом ViewExp.
Если в консоль приходят сообщения, или приходят данные из порта, канала или файла, их отображает ViewImp.
Можно отображать детали протокола обмена вызовом Details.
С помощью флагов DebugFlags можно включать и отключать нужный поток сообщений, чтобы лишнее не мешало.
При этом процедуры также отличаются уровнем значимости severity сообщений для вывода в журнал событий.
Если расположить сообщения по уровню значимости severity, это будет примерно такой ряд:
Details ≤ ViewImp ≈ ViewExp ≤ Success ≤ Disturb ≤ Succeed ≤ Problem ≤ Alerter ≤ Trouble ≈ Failure ≤ CritErr ≤ Fatally
Assertion(FileExists(f),'FileName='+f);
это эквивалентно вызову
if FileExists(f)
then Success('FileName='+f)
else Trouble('FileName='+f);
но компактнее и исключает дублирование кода
Reports('Success Выполнено успешно.');
Reports('Trouble Возникла ошибка.');
Следует отметитить, что процедуры Trouble, Problem, Failure, Success, Succeed,
ViewImp, ViewExp, Details, Disturb, Alerter, CritErr, Fatally
являются также "обертками" для журнала SysLog.
Они (кроме печати в консоль устройства) заносят записи в журнал в соответствии с настройками уровней значимости
Severity.
Следует также пояснить, что процедуры Success и Succeed работают аналогично,
но имеют разные уровни значимости Severity - значимость Succeed должна быть выше.
Это позволяет разделять потоки событий при записи в журнал.
Вызывайте Succeed для наиболее важных (успешных) событий - таких как старт программы.
Для остальных (не очень важных) событий используйте Success.
Наконец, отметим, что перечисленные процедуры имеют динамический уровень Severity.
Точнее, каждая процедура имеет отдельные уровни для различных фаз выполнения программы
Starting, Stopping и Polling, так как, например, ошибки на старте
имеют гораздо более высокое значение - программа не будет работать при ошибке на старте.
Поэтому необходимо иметь разные уровни Severity для разных фаз выполнения.
Поведение вышеописанных процедур вывода описывается такой таблицей:
| Процедура | Флаг вывода в консоль | SeverityOfStarting SeverityOfStopping SeverityOfPolling | Генерация ошибки FixError | Комментарий |
|---|---|---|---|---|
| Details | dfDetails 16 |
5=DEBUG/Details 5=DEBUG/Details 5=DEBUG/Details |
Нет | Отладочный вывод с высокой детализацией.
Обычно в журнал не пишется. |
| ViewExp | dfViewExp 8 |
6=DEBUG/ViewExp 6=DEBUG/ViewExp 6=DEBUG/ViewExp |
Нет | Отладочный вывод "экспорта" - т.е. вывода данных.
Обычно в журнал не пишется. |
| ViewImp | dfViewImp 4 |
7=DEBUG/ViewImp 7=DEBUG/ViewImp 7=DEBUG/ViewImp |
Нет | Отладочный вывод "импорта" - т.е. ввода данных.
Обычно в журнал не пишется. |
| Success | dfSuccess 2 |
14=INFO/Print 14=INFO/Print 14=INFO/Print |
Нет | Вывод сообщений об успешных действиях.
Обычно в журнал не пишется. |
| Disturb | dfTrouble 1 |
23=WARN/Disturb 23=WARN/Disturb 14=INFO/Print |
Нет | Вывод сообщений об ошибке/проблеме (низкого уровня значимости) без генерации ошибки.
При старте/стопе значимость выше, чем в цикле опроса. Обычно в журнал не пишется. |
| Succeed | dfSuccess 2 |
16=INFO/Success 16=INFO/Success 16=INFO/Success |
Нет | Вывод сообщений об успешном завершении (значимых) операций.
Обычно в журнал пишется. |
| Problem | dfTrouble 1 |
33=ERROR/Fail 33=ERROR/Fail 14=INFO/Print |
Нет | Вывод сообщений об ошибке/проблеме (низкого уровня значимости) без генерации ошибки.
При старте/стопе значимость выше, чем в цикле опроса. Обычно в журнал пишется при старт/стопе, не пишется в цикле опроса. |
| Alerter | dfTrouble 1 |
33=ERROR/Fail 33=ERROR/Fail 29=WARN/Alert |
Нет | Вывод сообщений об ошибке/проблеме (среднего уровня значимости) без генерации ошибки.
При старте/стопе значимость выше, чем в цикле опроса. Обычно в журнал пишется. |
| Trouble | dfTrouble 1 |
41=FATAL/Failure 41=FATAL/Failure 35=ERROR/Trouble |
Да с кодом устройства |
Вывод сообщений об ошибке (высокого уровня значимости) с генерацией ошибки.
При старте/стопе значимость выше, чем в цикле опроса. Обычно в журнал пишется. |
| Failure | dfTrouble 1 |
41=FATAL/Failure 41=FATAL/Failure 35=ERROR/Trouble |
Да с заданным кодом |
Вывод сообщений об ошибке (высокого уровня значимости) с генерацией ошибки.
При старте/стопе значимость выше, чем в цикле опроса. Обычно в журнал пишется. |
| CritErr | dfTrouble 1 |
39=ERROR/Critical 39=ERROR/Critical 39=ERROR/Critical |
Да с кодом устройства |
Вывод сообщений об ошибке (критического уровня значимости) с генерацией ошибки.
Обычно в журнал пишется. |
| Fatally | dfTrouble 1 |
45=FATAL/Emergency 45=FATAL/Emergency 45=FATAL/Emergency |
Да с кодом устройства |
Вывод сообщений об ошибке (фатального уровня значимости) с генерацией ошибки.
Обычно в журнал пишется. |
function IoError:Boolean;
проверяет статус консоли ввода-вывода и при необходимости генерирует ошибку, показывает сообщение и
инкрементирует счетчик ошибок StdIn_LineErrors.
Обычно явно функция не используется, она вызывается автоматически в цикле опроса.
function StdIn_Readln(var Data:String):Boolean;
считывает очередную строку из консоли ввода, а также инкрементирует счетчик StdIn_LineCount
входных строк. Функция обычно используется в теле основной программы в рамках шаблона прикладной программы
template.pas.
procedure StdIn_SetTimeouts(init,stop,pred,post:Integer);
задает значения предельных времен (Timeout)
StdIn_ToStarting, StdIn_ToStopping,
StdIn_ToPredPoll, StdIn_ToPostPoll
цикла обработки консольного ввода при СТАРТЕ, ЗАВЕРШЕНИИ, ПЕРЕД и ПОСЛЕ процедуры обработки данных
(PollApplication) в цикле опроса.
Значения по умолчанию соответствуют вызову StdIn_SetTimeouts(0,0,0,MaxInt).
Функция обычно используется в процедуре InitApplication в рамках шаблона прикладной программы
template.pas.
procedure StdIn_SetScripts(init,stop:String);
задает сообщения (скрипты)
StartingDevMsg, StoppingDevMsg
которые помещаются в консоль после старта и перед завершением программы.
Значения по умолчанию соответствуют вызову StdIn_SetScripts('','').
Функция обычно используется в процедуре InitApplication в рамках шаблона прикладной программы
template.pas.
Пример:
procedure InitApplication;
begin
StdIn_SetScripts('@StartupScript','@FinallyScript'); Задать стартовое,завершающее сообщение в консоль
StdIn_SetTimeouts(0,MaxInt,MaxInt,0); Разрешить обработку завершающих и циклических консольных команд
end;
...
В результате (при условии что в StdIn_Processor вызывается StdIn_DefaultHandler)
будет выполнен StartupScript на старте и FinallyScript при завершении.
function LooksLikeCommand(line:String):Boolean;
- функция (переводится "выглядит как команда") проверяет строку (line) на предмет соответствия формату консольной команды,
у которой первый символ должен быть признаком команды (@), а затем должен следовать хотя бы один символ,
отличный от пробела.
Функция может применяться для проверки формата предполагаемых команд перед их выполнением.
function GotCommand(var Data,cmd,arg:String):Boolean;
- используется для первичной выборки команд в цикле обработки консольных команд (StdIn_Prosessor) в рамках шаблона
прикладной программы template1.pas. При поступлении команд также
инкрементирует счетчик StdIn_CmndCount.
Входными данными является полученная из консоли строка Data.
Возвращается команда cmd с аргументами arg.
Признаком команды служит символ @ в начале строки Data.
Если этого символа нет, функция вернет false.
Функция GotCommand допускается и поддерживается, но считается устаревшей.
Вместо нее рекомендуется использовать GotCommandId.
function GotCommandId(var Data,cmd,arg:String; var cmdid:Integer):Boolean;
- используется для первичной выборки команд в цикле обработки консольных команд (StdIn_Prosessor) в рамках шаблона
прикладной программы template.pas. При поступлении команд также
инкрементирует счетчик StdIn_CmndCount.
Входными данными является полученная из консоли строка Data.
Возвращается команда cmd с аргументами arg и идентификатор команды cmdid, зарегистрированный функцией RegisterStdInCmd.
Признаком команды служит символ @ в начале строки Data.
Если этого символа нет, функция вернет false.
Если команда cmd не совпадает с одной из зарегистрированных функцией RegisterStdInCmd команд,
то идентификатор команды cmdid будет равен -1 для сигнализации незарегистрированной команды.
Если команда cmd совпадает с одной из зарегистрированных функцией RegisterStdInCmd команд,
то идентификатор команды cmdid будет положительным числом.
Функцию GotCommandId рекомендуется использовать вместо GotCommand по причине того,
что это позволяет существенно ускорить обработку консольных команд.
function RegisterStdInCmd(cmd,help:String):Integer;
- регистрирует команду cmd с поясняющим комментарием help в списке консольных команд и возвращает её идентификатор
(положительное целое число) или ноль (признак ошибки).
Зарегистрированные команды используются в цикле обработки консольных команд (StdIn_Prosessor) в рамках шаблона
прикладной программы template.pas.
Признаком команды служит символ @ в начале строки - если его нет, возвращается признак ошибки 0.
Зарегистрированные команды могут использоваться в цикле обработки консольных команд с помощью функции GotCommandId.
Комментарий help отображается в консоли при выполнении команды @help, поэтому надо давать содержательный
комментарий, поясняющий назначение и синтаксис команды. Комментарий может быть текстом, состоящим из строк,
разделенных с помощью EOL. Это позволяет давать развернутые комментарии с подробным описанием команд.
Использование функций RegisterStdInCmd и GotCommandId схематично показано на примере:
program Demo;
...
var
...
cmd_Demo : Integer; { Command @Demo } // Заводим целочисленную переменную под команду
...
procedure InitApplication;
begin
...
cmd_Demo:=RegisterStdInCmd('@Demo','@Demo - Run Demo command'); // Регистрируем команду в списке консольных команд
... // При регистрации задаем комментарий для @Help
end;
...
procedure StdIn_Processor(var Data:String);
var cmd,arg:String; cmdid:Integer;
begin
...
if GotCommandId(Data,cmd,arg,cmdid) then begin // Получаем команду cmd c идентификатором cmdid
{
@Demo ...
}
if (cmdid = cmd_Demo) then begin // Сравниваем команду с этим идентификатором
... // Обрабатываем, если совпало
end else
...
end;
...
end;
...
end.
Для каждой команды заводится целочисленная переменная cmd_XXX.
С помощью функции RegisterStdInCmd эта команда регистрируется при инициализации программы.
В цикле опроса с помощью функции GotCommandId вычисляется идентификатор поступившей команды
и сравнивается с зарегистрированной командой. При совпадении выполняется обработка.
Программы обработки консольного ввода, построенные по такой схеме, выполняются существенно быстрее, чем программы,
построенные на вызове функции GotCommand и последующем сравнении строк.
procedure OpenConsole(Mode:Integer);
открывает консоль в режиме 1 (видна на экране) или 2 (свернута). При режиме 0 консольное окно не создается,
хотя его всегда можно открыть через панель управления DAQ-системой. Для задания режима консоли
используется CFG-переменная OpenConsole = ... в секции описания устройства. При работе в рамках шаблона
прикладной программы template.pas явный вызов процедуры не требуется,
он делается автоматически при старте.
procedure ShowHelp(AllowEcho:Boolean);
- отображает справку @Help. При работе в рамках шаблона прикладной программы
template.pas вызывается автоматически в цикле обработки
консольных команд при вызове StdIn_DefaultHandler.
function GetDateTime(ms:Real):String;
- возвращает строку даты и времени в формате типа 2012.03.18-22:01:47, взяв время ms по часам
msecnow. При форматировании используется последовательность год.месяц.день-час:мин:сек.
Этот формат хорош тем, что при наличии многих меток времени их упорядочевание в алфавитном порядке и в календарном
(по времени) совпадают. Поэтому этот формат служит основой для генерации имен файлов, чтобы файлы были автоматически
упорядочены по дате-времени.
procedure ClearStdConsole;
procedure InitStdConsole;
procedure FreeStdConsole;
procedure PollStdConsole;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdDevices.inc - файл описания констант модуля StdDevices.
Константы DimSrv, WebSrv, DatSrv, FdbSrv, CronSrv, PlotSrv, VkbdSrv, SpeakSrv, EmlSrv, OpcCln, ModbusSrv - задают имена стандартных серверов. Эти имена уже устоялись, менять их нет смысла. Тем более, что многие сервера (как Web,Dim) не допускают запуска двух экземпляров в рамках одной конфигурации.
_var_StdDevices.inc - файл описания переменных модуля StdDevices.
Переменные devMySelf, devDimSrv, devWebSrv, devDatSrv, devFdbSrv, devCronSrv, devPlotSrv, devVkbdSrv, devSpeakSrv, devEmlSrv, devOpcCln, devModbusSrv - содержат ссылки на стандартные сервера и на само устройство (devMySelf). То есть в каждой программе, сделанной по шаблону, эти сервера будут доступными.
_fun_StdDevices.inc - файл описания процедур и функций модуля StdDevices.
procedure InitDevice(var ref:Integer; name:String; typ:Integer);
- инициализирует ссылку ref на устройство name. typ задает режим вывода и генерации ошибок.
При typ=0 просто происходит инициализация, без всяких сообщений или анализа результата.
При typ=1 выводится сообщение (Success) при успешной инициализации, но при неуспешной (Problem) ошибка не генерируется.
При typ=2 выводится сообщение (Success) при успешной инициализации или генерируется ошибка (Trouble) при неуспешной.
Если инициализируемое устройство критически важно, используйте режим 2 - чтобы при отсутствии устройства был сигнал ошибки.
Если инициализируемое устройство опционально, используйте режим 1 или 0 (если не нужны сообщения об инициализации).
В этом случае программа гарантированно стартует без ошибки, но ссылка может оказаться нулевой.
procedure DevSendCmd(devRef:Integer; Cmd:String);
procedure DevPostCmd(devRef:Integer; Cmd:String);
procedure DevSendCmdLocal(Cmd:String);
procedure DevPostCmdLocal(Cmd:String);
- посылает устройству devRef команду (сообщение) Cmd, добавляя к нему маркер конца строки EOL,
используя синхронный метод (devsend) или асинхронный (devpost).
Локальная версия (local) посылает сообщение локально - т.е. устройству devMySelf (самому себе).
Если сообщение не может быть послано, генерируется ошибка (Trouble).
Использовать процедуры удобно - не надо постоянно вспоминать о необходимости добавлять EOL в конец строки (а это важно).
procedure Cron(msg:String);
- посылает сообщение (с добавлением EOL) серверу &CronSrv.
Этот сервер весьма активно используется в последнее время, а процедура облегчает обращение к нему.
procedure Speak(msg:String);
- посылает команду @Speak=msg (с добавлением EOL) серверу речевого синтезатора &SpeakSrv.
Облегчает использование этого сервера для вывода речевых сообщений.
function PlotSend(msg:String):Integer;
function VkbdSend(msg:String):Integer;
- посылает сообщение msg (без добавления EOL) серверу &PlotSrv или &VkbdSrv и возвращает
длину посланного сообщения. Эта форма вызова удобна для использования именно этих серверов, позволяя добиться
выразительной и понятной нотации типа
if PlotSend(PlotJob)>0 then Success('Ok');
procedure ClearStdDevices;
procedure InitStdDevices;
procedure FreeStdDevices;
procedure PollStdDevices;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdCurves.inc - файл описания констант модуля StdCurves.
Пока файл пуст.
_var_StdCurves.inc - файл описания переменных модуля StdCurves.
Пока файл пуст.
_fun_StdCurves.inc - файл описания процедур и функций модуля StdCurves.
procedure UpdateAo(n:Integer; t,y:Real);
procedure UpdateDo(n:Integer; t,y:Real);
- записывают точку (t,y) в AnalogOutput или DigitalOutput номер n.
function crvGetLastY(ref:Integer):Real;
- извлекает из кривой значение Y-координаты последней точки. То есть это текущее значение величины, которая
записывается в кривую.
function GetCrvRange(Crv:Integer; var x1,y1,x2,y2:Real):Integer;
- вычисляет границы диапазона данных по X и Y, внутри которого находятся все точки кривой Crv.
Если точек на кривой нет, диапазон будут пуст (x2<x1,y2<y1).
procedure ClearCurveData(Crv:Integer);
- очищает массив данных кривой.
function FindNaiByName(name:String):Integer;
function FindNdiByName(name:String):Integer;
function FindNaoByName(name:String):Integer;
function FindNdoByName(name:String):Integer;
- находит номер аналогового/цифрового входа/выхода по имени кривой name или возвращает -1, если номер не найден.
Эти функции рекомендуется вызывать при инициализации программы, а не в цикле опроса, т.к. поиск может занимать время.
Лучше найти номер, запомнить его и затем использовать в цикле опроса.
При этом удобно использовать запись TTagRef.
Например:
var DEMO : TTagRef; // Тип TTagRef объявлен в {$ _typ_StdLibrary}
...
DEMO.tag:=FindTag('DEMO'); // Инициализация тега по имени DEMO
DEMO.nai:=FindNaiByName(NameTag(DEMO.tag)); // Номер AnalogInput одноименной кривой
...
bNul(rSetTag(DEMO.tag,GetAi(DEMO.nai))); // В цикле опроса используем этот номер
procedure ClearStdCurves;
procedure InitStdCurves;
procedure FreeStdCurves;
procedure PollStdCurves;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdSounds.inc - файл описания констант модуля StdSounds.
Константы snd_Click, snd_Press, snd_Fails, snd_Error, snd_Break, snd_Fatal, snd_Cancel, snd_Wheel, snd_Deny, snd_Exit, snd_Restart, snd_Alert1, snd_Alert2, snd_Siren, snd_Inc, snd_Dec - содержат имена характерных звуков (достаточно очевидных из названия), которые рекомендуется использовать в программах при вызове voice, вместо не столь очевидных имен файлов.
_var_StdSounds.inc - файл описания переменных модуля StdSounds.
Пока файл пуст.
_fun_StdSounds.inc - файл описания процедур и функций модуля StdSounds.
procedure ClearStdSounds;
procedure InitStdSounds;
procedure FreeStdSounds;
procedure PollStdSounds;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
var iv,rv:TVector; ... procedure ClearApplication; begin iv:=ivec_init(0); rv:=rvec_init(0); // Начальная очистка делается так. ... end; ... procedure FreeApplication; begin ivec_free(iv); rvec_free(rv); // Освобождение памяти делается так. ... end; ... procedure InitApplication; begin iv:=ivec_init(100); rv:=rvec_init(2000); // Инициализация делается так. ... end;
_con_StdVector.inc
- файл описания констант модуля StdVector.
Пока файл пуст.
_var_StdVector.inc
- файл описания переменных модуля StdVector.
Переменная
ShouldPollStdVector
содержит флаг опроса модуля. Менять её нежелательно.
Переменные
iVec_ErrorsCount
rVec_ErrorsCount
содержат счетчики ошибок (при попытке записи в массив с неверным индексом).
_fun_StdVector.inc
- файл описания процедур и функций модуля StdVector.
function iVec_Init(n:Integer):TVector;
function rVec_Init(n:Integer):TVector;
- создает динамический массив целочисленных или вещественных элементов заданной длины n.
Фактически, при вызове функции выделяется (строковый) буфер памяти для хранения двоичных данных
размера n*SizeOfInteger для целочисленных и n*SizeOfReal для вещественных чисел.
Инициализация векторов обязательна перед их использованием - например в InitApplication.
А в ClearApplication рекомендуется сделать начальную очистку типа iv:=ivec_init(0);.
procedure iVec_Free(var vec:TVector);
procedure rVec_Free(var vec:TVector);
- освобождает буфер памяти, выделенный для хранения двоичных данных вектора.
Вызывается в FreeApplication.
function iVec_Get(var vec:TVector; i:Integer):Integer;
function rVec_Get(var vec:TVector; i:Integer):Real;
- возвращает значение элемента i в массиве вектора. Индексация всегда начинается с нуля.
Если индекс i выходит за диапазон вектора, функция вернет 0.
procedure iVec_Set(var vec:TVector; i,data:Integer);
procedure rVec_Set(var vec:TVector; i:Integer; data:Real);
- записывает значение элемента i в массиве вектора. Индексация всегда начинается с нуля.
Если индекс i выходит за диапазон вектора, инкрементируется счетчик ошибок.
function iVec_Length(var vec:TVector):Integer;
function rVec_Length(var vec:TVector):Integer;
- возвращает длину (количество элементов) вектора.
Элементы вектора индексируются в диапазоне [0..Length-1].
procedure iVec_SetLength(var vec:TVector; n:Integer);
procedure rVec_SetLength(var vec:TVector; n:Integer);
- задает новую длину n (количество элементов) вектора.
Если элементов становится больше, массив дополняется нулями.
Содержимое прежних элементов вектора не меняется.
procedure ClearStdVector;
procedure InitStdVector;
procedure FreeStdVector;
procedure PollStdVector;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdTags.inc - файл описания констант модуля StdTags.
Пока файл пуст.
_var_StdTags.inc - файл описания переменных модуля StdTags.
Пока файл пуст.
_fun_StdTags.inc - файл описания процедур и функций модуля StdTags.
procedure InitTag(var tag:Integer; name:String; typ:Integer);
- инициализирует ссылку tag на (существующий) тег, находя его по имени name в базе данных.
При неудаче поиска или неверном типе тега ссылка tag зануляется, и генерируется ошибка Trouble.
Тип тега typ может указываться со знаком минус, в этом случае генерация ошибки подавляется,
то есть вместо Trouble используется Problem. Это удобно для инициализации опциональных тегов,
которые могут отсутствовать.
Обычно инициализация тегов выполняется один раз, в момент старта системы (Starting).
procedure UpdateTag(tag:Integer; newValue:String; min,max:Real);
- обновляет содержимое тега tag, если строка newValue содержит допустимое (десятичное) число,
лежащее в заданном диапазоне (min,max). Если тег имеет строковый тип, данные могут быть любыми,
а диапазон игнорируется. Процедура ориентирована на анализ результатов редактирования или приема данных из сети.
Если диапазон не нужен, указывается min=_MinisInf, max=_PlusInf.
function TagAsDump(tag:Integer):String;
function TagAsText(tag:Integer):String;
- возвращает значение тега в виде десятичной строки (text) или двоичного образа содержимого (dump).
Строковые теги возвращаются "как есть".
procedure ClearStdTags;
procedure InitStdTags;
procedure FreeStdTags;
procedure PollStdTags;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdTimes.inc - файл описания констант модуля StdTimes.
Константы mSecsPerSec, mSecsPerMin, mSecsPerHour, mSecsPerDay, SecsPerMin, MinsPerHour, HoursPerDay задают параметры перевода единиц времени (сколько миллисекунд в минуте и т.д.), вполне понятные из названий. Использование этих констант предпочтительнее, чем использование сбивающих с толку цифр, особенно с большим числом нулей.
Константы JavaTimeBase, JavaTimeUnit, UnixTimeBase, UnixTimeUnit, FileTimeBase, FileTimeUnit, WinsTimeBase, WinsTimeUnit, OleTimeBase, OleTimeUnit служат для перевода времени по часам функции msecnow (миллисекунд от Рождества Христова), принятого пакете за основное, во время по другим часам. Для перевода используются такие формулы
UnixTime = (mSecNow - UnixTimeBase)/UnixTimeUnit; // Перевод времени к единицам Unix time()
JavaTime = (mSecNow - JavaTimeBase)/JavaTimeUnit; // Перевод времени к единицам JavaScript
FileTime = (mSecNow - FileTimeBase)/FileTimeUnit; // Перевод времени к единицам Win32 FileTime
WinsTime = (mSecNow - WinsTimeBase)/WinsTimeUnit; // Перевод времени к единицам Windows System
OleTime = (mSecNow - OleTimeBase)/OleTimeUnit; // Перевод времени к единицам OLE,Delphi/Lazarus
Время UnixTime используется, например, во многих интернет технологиях.
Время JavaTime бывает необходимо, например, для работы Web-серверов,
использующих JavaScript, AJAX и другие Web-технологии.
Время FileTime использует файловая система Win32, а также OPC UA.
Время WinsTime полезно для системного времени Windows.
Время OleTime использует OLE, OLEDB, ADO, OPC DA, а также Delphi/Lazarus (тип TDateTime и Variant).
Так что константы полезные.
Константа SysTimer_MaxNum задает максимальное число таймеров коллективного пользования, см. SysTimer_Pulse.
Константы MSecRangeMin, MSecRangeMax задают диапазон допустимых времен (миллисекунд от начала эры), см. MSecNow. Это диапазон [0..315537897599999], соответствующий датам [0001.01.01-00:00:00.000..9999.12.31-23:59:59.999].
_var_StdTimes.inc - файл описания переменных модуля StdTimes.
Файл содержит переменные, используемые только внутри модуля. Прикладному программисту их использовать не следует. Поэтому не будем их комментировать.
_fun_StdTimes.inc - файл описания процедур и функций модуля StdTimes.
function MsToDaqTime(ms:Real):Real;
function DaqTimeToMs(tm:Real):Real;
function MsToUnixTime(ms:Real):Real;
function UnixTimeToMs(tm:Real):Real;
function MsToJavaTime(ms:Real):Real;
function JavaTimeToMs(tm:Real):Real;
function MsToFileTime(ms:Real):Real;
function FileTimeToMs(tm:Real):Real;
function MsToWinsTime(ms:Real):Real;
function WinsTimeToMs(tm:Real):Real;
function MsToOleTime(ms:Real):Real;
function OleTimeToMs(tm:Real):Real;
- функции преобразования единиц времени (tm) в миллисекунды (ms) и обратно.
Расшифровка: Функции MsToXxxxTime(ms) и XxxxTimeToMs(tm)
ms Число миллисекунд (ms) от Рождества Христова (Xmas), т.е. от 0001.01.01-00:00:00 (UTC).
Можно также назвать эти единицы как "миллисекунды от начала Новой Эры".
Используется как основное время в DaqPascal, см. функцию msecnow.
tm другие единицы времени:
DaqTime Число единиц времени (TimeUnit), прошедших от начала отсчета (TimeBase) часов DAQ.
Используется как основное время (функция time) в системе DAQ.
Шкала времени DaqTime задается в конфигурации DAQ системы.
В секции [DAQ] задается два параметра шкалы времени time:
1. TimeBase - дата и время начала отсчета времени time,
2. TimeUnit - единица времени time в секундах.
UnixTime Число секунд, прошедших от начала эпохи Unix (Epoch), т.е. от 1970.01.01-00:00:00 (UTC).
Используется как основное время - функция time() - в системе Unix, Linux и т.д.
JavaTime Число миллисекунд, прошедших от начала эпохи (Epoch), т.е. от 1970.01.01-00:00:00 (UTC).
Используется в JavaScript, AJAX и других Web технологиях.
FileTime Число 100 наносекундных интервалов от точки начала, т.е. от 1601.01.01-00:00:00 (UTC).
Это единицы времени, в которых Windows хранит время файлов. Также используется в OPC UA.
WinsTime Число миллисекунд от точки начала, т.е. от 1601.01.01-00:00:00 (UTC).
Это единицы времени, в которых Windows хранит системное время.
OleTime Число дней от точки начала (идет от Lotus 1-2-3), т.е. от 1899.12.30-00:00:00 (UTC).
Это единицы времени, которые использует OLE, OLEDB, ADO, OPC DA, Excel, Access, Word.
DbApi (работа с СУБД) использует OleTime, т.к. ADO использует варианты (Variant).
Также эти единицы использует Delphi/Lazarus (тип TDateTime и Variant).
Например: tm:=MsToOleTime(msecnow); // Преобразовали текущее время в формат для записи в СУБД
ms:=OleTimeToMs(tm); // Преобразовали время из СУБД в миллисекунды от начала Новой Эры
Все единицы времени можно конвертировать в основное время пакета (msecnow) или наоборот.
Какие именно единицы времени и функции преобразования использовать, зависит от конкретной задачи.
Например, для СУБД будет полезно использовать OleTime, а для файлов - FileTime.
function msElapsedSinceMarker(t:real):Real;
- возвращает время в миллисекундах, прошедшее с момента "маркера" t, либо ноль, если маркер нулевой.
Функция предназначена для облегчения организации асинхронной обработки событий в цикле опроса.
Работа функции совмещает в себе "флаг" (признак события) и "таймер" (отсчет времени)
и основана на том, что время (mSecNow) всегда 1) монотонно, 2) имеет положительное значение.
Признаком события является положительное (ненулевое) значение маркера времени.
Эту функцию удобно использовать для организации "задержанных одиночных событий",
наступающих через какое-то время после интересующих одиночных событий.
При этом важно, чтобы задержка после события была ненулевой (т.к. ноль используется как признак остутствия события).
Например:
var KickTime:Real; // Маркер события
procedure Init; // Процедура при старте
begin
KickTime:=0; // Инициализация маркера
end;
procedure Poll; // Процедура в цикле опроса
begin
if Event then KickTime:=mSecNow; // По какому-то событию устанавливаем маркер
if msElapsedSinceMarker(KickTime)>5000 then begin // Проверяем, что после события прошло 5 сек
Success('Do something after 5 sec...'); // Что-то делаем в связи с этим
KickTime:=0; // И сбрасываем маркер события
end;
end;
Примечание:
Вызов
if msElapsedSinceMarker(t)>5000 then ...
эквивалентен
if (t>0) and ((mSecNow-t)>5000) then ...
но более понятен ("самодокументирован")
function GetDateAsNumber(ms:real):Integer;
- возвращает для данного времени число, которое в десятичной нотации (Str) содержит год,месяц и день,
например, 20050816. Может пригодиться для генерации имен файлов по времени.
function GetMidnight(ms:Real):Real;
- возвращает для данного времени ms время полуночи (начала суток) того же дня.
То есть ms-GetMidnight(ms) - это время суток в миллисекундах, то есть число миллисекунд от полуночи текущих суток.
Эта функция полезна, например, для генерации отчетов, где требуется знать время суток.
Или для сравнения моментов времени - принадлежат ли они одним и тем же суткам (тогда их время полуночи должно совпадать).
function ms2DayOfWeek(ms:Real):Integer;
function DayOfWeekList(Lang:String):String;
function DayOfWeekStr(DayOfWeek:Integer; Lang:String):String;
function ms2DayOfWeekStr(ms:Real; Lang:String):String;
- функции работы с днями недели.
ms2DayOfWeek возвращает день недели в виде числа (1..7=Mon..Sun).
DayOfWeekList возвращает список дней недели в сответствии с указанным языком Lang:
Lang Result
En Mo,Tu,We,Th,Fr,Sa,Su
Ru Пн,Вт,Ср,Чт,Пт,Сб,Вс
Eng Mon,Tue,Wed,Thu,Fri,Sat,Sun
Rus Пнд,Втр,Срд,Чтв,Птн,Сбт,Вск
English Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
Russian Понедельник,Вторник,Среда,Четверг,Пятница,Суббота,Воскресенье
Default 1,2,3,4,5,6,7
DayOfWeekStr возвращает день недели по номеру (1..7) как строку, в соответствии с Lang.
function MonthList(Lang:String):String;
function MonthStr(Month:Integer; Lang:String):String;
function ms2MonthStr(ms:Real; Lang:String):String;
- функции работы с месяцами.
MonthList возвращает список месяцев в соответствии с указанным языком Lang:
Lang Result
Eng Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
Rus Янв,Фев,Мар,Апр,Май,Июн,Июл,Авг,Сен,Окт,Ноя,Дек
English January,February,March,April,May,June,July,August,September,October,November,December
Russian Январь,Февраль,Март,Апрель,Май,Июнь,Июль,Август,Сентябрь,Октябрь,Ноябрь,Декабрь
Default 01,02,03,04,05,06,07,08,09,10,11,12
MonthStr возвращает месяц по номеру (1..12) как строку, в соответствии с Lang.
function ms2HttpTimeStr(ms:Real):String;
возвращает время в формате HTTP (RFC 1123), это выглядит как Sun, 06 Nov 1994 08:49:37 GMT.
Эта функция полезна, например, при формировании HTTP заголовков на Web-серверах.
procedure SetClockRes(res:Integer);
устанавливает разрешение времени Windows, то есть фактически определяет квант времени операционной системы
res в миллисекундах. Процедуру следует вызывать один раз при инициализации (Starting).
При завершении программы библиотека автоматически восстановит прежнее разрешение.
function FibonacciNumber(arg:Integer):Integer;
вычисляет число Фибоначчи. Причем делает это по трудоемкому (рекурсивному) алгоритму. Так и было задумано.
Потому что эта функция, интересная сама по себе, служит для организации стресс-тестирования компьютера.
procedure CpuProfilerStress(mks:Real);
procedure CpuProfilerStart(Poll,View,Mode,Stress:Integer;ms:Real);
procedure CpuProfilerView;
procedure CpuProfilerPoll;
procedure CpuProfilerProcess(arg:String);
- процедуры реализуют функциональность монитора производительности (профайлера), доступного через командную строку
(см. описание ) в команде @CpuProfiler.
Напрямую функции вызывать не следует, кроме CpuProfilerStress(mks), загружающего процессор замкнутым циклом
на mks микросекунд. Может пригодиться для организации коротких задержек (но это не рекомендуется делать).
Этот профайлер собирает и отображает кучу статистики, вот пример отчета:
&WEBCGI : Profiler report at 2012.03.19-12:40:24: 9 sec.work
&WEBCGI : ---------+----------+----------+----------+--------------+----------+----------+----------+----------+
&WEBCGI : CPU Load | Kernel,% | User,% | Thread,% | VDPM,kOp/sec | Runs/sec | Errs/sec | Line/sec | Cmnd/sec |
&WEBCGI : ---------+----------+----------+----------+--------------+----------+----------+----------+----------+
&WEBCGI : Average | 0.855 | 17.608 | 18.463 | 11664.796 | 53.063 | 0.000 | 1.094 | 1.094 |
&WEBCGI : LastSec | 1.539 | 20.012 | 21.552 | 13164.811 | 51.232 | 0.000 | 0.985 | 0.985 |
&WEBCGI : Peaking | 1.539 | 20.012 | 21.552 | 13532.635 | 66.010 | 0.000 | 1.970 | 1.970 |
&WEBCGI : ---------+----------+----------+----------+--------------+----------+----------+----------+----------+
&WEBCGI : Status & | # Errors | StrTable | Stack | Program Runs | >> Lines | Commands | VDPM,MOp | Time,sec |
&WEBCGI : ---------+----------+----------+----------+--------------+----------+----------+----------+----------+
&WEBCGI : Counters | 0 | 3142 | 16226 | 4046 | 64 | 64 | 226 | 67 |
&WEBCGI : ---------+----------+----------+----------+--------------+----------+----------+----------+----------+
Профайлер показывает % загрузку процессора (CPU Load) (среднюю за период, пиковую за период и за последнюю секунду),
с разбивкой на Kernel(режим ядра), User(режим пользователя), Thread(их сумма). Расчитывается также число операций
виртуальной машины DaqPascal (VDPM,kOp/sec), число опросов в секунду (Runs/sec), число ошибок устройства в секунду
(Errs/sec), число консольных строк и команд в секунду (Line/sec,Cmnd/sec).
Он также показывает счетчики - ошибок (# Errors), свободных строк maxavail (StrTable),
свободного стекового пространства stackavail (Stack),
вызовов программы runcount(Runs), входных строк (Lines) и команд (Commands)
консоли ввода StdIn, операций Virtual Daq Pascal Machine (VDPM,MOp), времени со старта программы (Time,sec).
В целом получается мощный инструмент диагностики работы программы. Причем всегда доступный.
procedure SysTimer_Init;
procedure SysTimer_Poll;
function SysTimer_Pulse(ThePeriod:Integer):Real;
function SysTimer_Count:Integer;
- фунции реализуют еще один тип системного таймера для генерации периодических событий и организации циклов опроса.
Этот таймер работает в режиме "коллективного пользования" и имеет ряд важных отличий от таймеров других типов,
например tm_event.
Процедуры инициализации SysTimer_Init и опроса SysTimer_Poll надо рассмативать как внутренние
- напрямую вызываться не должны, их автоматически вызывает библиотека StdTimes.
Просто используйте шаблон - и забудьте про эти процедуры.
Функция SysTimer_Count возвращает число активных таймеров, от 0 до SysTimer_MaxNum.
Эта функция нужна, чтобы проверить - есть ли еще возможность создать новый коллективный таймер и не переполнена ли таблица
коллективных таймеров.
Функция SysTimer_Pulse(ThePeriod) выполняет основную работу системного коллективного таймера, включая его создание,
освобождение и генерацию событий. Для идентификации коллективных таймеров функция использует период ThePeriod.
При первом вызове функции она, не найдя таймера с данным периодом, создает его вновь в своей внутренней таблице.
При всех последующих вызовах функция находит таймер по периоду и работает с ним, беря данные из таблицы.
Это удобно - для коллективных таймеров не надо создавать индивидуальные переменные, достаточно константы
(периода опроса), которая одновременно служит ссылкой для доступа к коллективному таймеру.
Период таймера, разумеется, должен быть положительным числом.
Таким образом, SysTimer_Pulse реализует таймеры "коллективного пользования", которые ориентированы
на определенный круг задач, связанных с периодическим опросом, но без индивидуальной работы с таймером.
Если таймер предполагается останавливать, запускать, менять его период - используйте другие типы таймеров.
Их у нас уже много.
Вызов SysTimer_Pulse(-ThePeriod) освобождает таймер с периодом ThePeriod, так что одной
и той же функцией можно создать и удалить таймер. Однако удалять таймеры коллективного пользования надо весьма
осторожно - их могут использовать другие подпрограммы. А вообще-то предполагается, что в большинстве случаев
коллективные таймеры создаются автоматически, по факту вызова функции опроса SysTimer_Pulse(ThePeriod)
с постоянным периодом, а потом вовсе не удаляются. Если период является константой, в этом нет необходимости.
При последующих вызовах функция SysTimer_Pulse(ThePeriod) генерирует события каждый цикл таймера с периодом,
заданным аргументом ThePeriod ms. Если очередной цикл таймера еще не наступил, функция возвращает ноль.
Если цикл наступил, функция возвращает число полных циклов с данным периодом, прошедших с момента первого вызова
таймера с этим периодом. Однако точная привязка циклов ко времени не связана с моментом первого вызова таймера.
Моменты смены циклов жестко привязаны к шкале астрономического времени, так что, например, смена цикла секундного
таймера происходит каждую целую секунду, начиная с Рождества Христова. Если первый вызов коллективного таймера
произошел посередине цикла, то началом цикла все равно будет считаться ближайшее "круглое" число, составляющее
целое (без дробной части) число периодов. Эту особенность надо учитывать при использовании коллективных таймеров.
При этом, надо заметить, никакого накопления ошибок привязки к реальному времени из-за задержки исполнения циклов
не происходит - шкала времени в этом смысле совершенно жесткая. В то же время, если программа долго задержалась
с исполнением кода, возможны пропуски просроченных циклов.
В отличие от функции tm_event, которая сбрасывает событие при вызове, функцию
SysTimer_Pulse(ThePeriod) можно вызывать многократно, причем она будет возвращать одно и то же значение
в пределах данного вызова программы (RunCount). Это как раз и позволяет использовать один
и тот же таймер коллективно, для нескольких независимых подпрограмм, которым, возможно, нужен один и тот же период опроса.
Все подпрограммы с одинаковым периодом опроса будут использовать один таймер и вызываться одновременно, в том смысле,
что они будут исполняться в одном и том же цикле опроса программы RunCount.
Фактическое изменение цикла системного таймера SysTimer_Pulse происходит в процедуре SysTimer_Poll,
вызываемой, среди прочих, в цикле опроса программы PollApplication, где-то в начале
исполнения данного прогона программы (RunCount).
После этого, в рамках данного прогона, значение цикла (или ноль если он не наступил) не меняется до следующего RunCount.
Таким образом, функция фиксирует факт наступления очередного цикла и дает возможность всем клиентам, вызывающим
SysTimer_Pulse, исполнить свои периодические задачи.
Этот системный таймер коллективного пользования является хорошим дополнением к уже имеющимся таймерам других типов,
включая индивидуальные таймеры, работающие на переменных и вызовах mSecNow, интервальные таймеры tm_event,
а также календарные таймеры &CronSrv.
Одним из главных достоинств коллективного таймера является его удобство - не надо заводить переменные, инициализировать
и освобождать их - все делается автоматически, по факту вызова SysTimer_Pulse.
Коллективные таймеры имеют жесткую привязку к шкале времени и не накапливают ошибок - это тоже хорошо.
Однако в них есть и свои минусы - например, нельзя менять период таймера, останавливать и запускать его.
Предполагается, что период таймера является константой, а для управления исполнением служат какие-то другие переменные
или условия. Если это не подходит - используйте альтернативные типы таймеров, упомянутые выше.
Создание коллективных таймеров никак не отменяет другие типы таймеров - используйте каждый тип, когда это удобно.
Можно также сказать, что коллективные таймеры ориентированы на задачи опроса состояния внешних по отношению к программе
объектов, когда привязка к моменту старта или к состоянию внутренних переменных программы не важна. Коллективные таймеры
хорошо подходят для опроса состояния сети, файлов, каналов, внешних процессов и т.д.
Пример:
if SysTimer_Pulse(1000)>0 then begin
writeln('Этот вызов будет происходить каждые 1000 ms.');
writeln('Текущий № вызова равен ',SysTimer_Pulse(1000));
end;
if SysTimer_Pulse(5000)>0 then begin
writeln('Этот вызов будет происходить каждые 5000 ms.');
writeln('Текущий № вызова равен ',SysTimer_Pulse(5000));
end;
if SysTimer_Pulse(1000)>0 then begin
writeln('Этот вызов тоже происходит каждые 1000 ms.');
writeln('Текущий № вызова равен ',SysTimer_Pulse(1000));
end;
procedure ClearStdTimes;
procedure InitStdTimes;
procedure FreeStdTimes;
procedure PollStdTimes;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdTexts.inc - файл описания констант модуля StdTexts.
Пока файл пуст.
_var_StdTexts.inc - файл описания переменных модуля StdTexts.
Пока файл пуст.
_fun_StdTexts.inc - файл описания процедур и функций модуля StdTexts.
procedure ClearText(aText:Integer);
- очищает текст, то есть удаляет все строки текста. Сам объект-текст остается.
function TextAppendText(t,s:Integer):Integer;
- добавляет содержимое текста источника s (source) в конец целевого текста t (target) и возвращает ссылку целевого текста t.
Применяется для добавления к тексту содержимого другого текста.
function TextAssignText(t,s:Integer):Integer;
- назначает содержимое текста источника s (source) целевому тексту t (target) и возвращает ссылку целевого текста t.
Применяется для присвоения тексту содержимого другого текста.
function TextAppendString(t:Integer; s:String):Integer;
- добавляет текст из (длинной) строки источника s (source) в конец целевого текста t (target) и возвращает ссылку целевого текста t.
Применяется для добавления к тексту содержимого другого текста, заданного длинной строкой с разделителями строк EOL.
function TextAssignString(t:Integer; s:String):Integer;
- назначает содержимое текста источниука s (source) из (длинной) строки целевому тексту t (target) и возвращает ссылку целевого текста t.
Применяется для присвоения тексту содержимого другого текста, заданного длинной строкой с разделителями строк EOL.
function StringToText(s:String):Integer;
- преобразует (длинную) строку во вновь созданный объект-текст, возвращает ссылку на этот объект.
После использования не забывайте удалять объект текста вызовом text_free.
function TextToString(t:Integer):String;
- преобразует объект-текст в (длинную) строку c разделителями crlf.
function GetTextLength(t:Integer):Integer;
- возвращает полную длину текста, то есть сумму длин всех строк и разделителей строк.
function IsEmptyText(aText:Integer):Boolean;
- проверяет, пустой ли текст, то есть есть ли в нем строки и символы, отличные от пробелов.
function GetStringVar(Text:Integer; Name:String; var Value:String):Boolean;
- ищет переменную с именем (Name) и считывает значение (Value) из текста (Text), содержащего строки вида "Name=Value".
Если строка "Name=Value" не найдена в тексте, возвращается пустое значение Value и результат false.
Если в тексте несколько строк с одинаковыми именами, находится первое вхождение выражения "Name=Value".
При анализе считается, что перед и после знака равно не должно быть пробелов, как принято в INI-файлах
и списках TStringList. Это значит, например, что выражение "Name = Value" с пробелами не подходит.
Функция может применяться, например, для анализа текстов при чтении файлов инициализации (INI)
или в других случаях, когда данные хранятся в списке "Name=Value".
function Text_StrReplace(txt:Integer; a,b:String; Flags:Integer):Integer;
- делает замену строки a на b в каждой строке текста txt, с флагами замены Flags.
Функция всегда возвращает txt. См. также функцию StrReplace.
function Text_IndexOf(txt:Integer; s:String):Integer;
- ищет в тексте (txt) строку (s) и возвращает её индекс (начиная с 0), либо возвращает -1, если строка не найдена.
Поиск выполняется независимо от регистра символов, см. функцию IsSameText.
function Text_Remove(txt:Integer; s:String):Integer;
- ищет в тексте (txt) строку (s) и удаляет её из текста (если строка была найдена).
Возвращает индекс строки (начиная с 0), либо возвращает -1, если строка не найдена.
procedure ClearStdTexts;
procedure InitStdTexts;
procedure FreeStdTexts;
procedure PollStdTexts;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdTools.inc - файл описания констант модуля StdTools.
Пока файл пуст.
_var_StdTools.inc - файл описания переменных модуля StdTools.
FreeAndZero_Verbosity : Integer; - переменная режима печати для процедуры FreeAndZero.
_fun_StdTools.inc - файл описания процедур и функций модуля StdTools.
procedure FreeAndZero(var ref:Integer);
- универсальная процедура деструктора, т.е. освобождения (free) и обнуления (zero) ссылки объекта (ref).
Объект может иметь любой поддерживаемый динамический объектный тип:
(Timer, Text, Task, Pipe, Com, Tcp,
RegExp, HashList, FSM, DB),
который определяется динамически вызовом RefInfo(ref,'Type').
Кроме того, для успешного удаления объект должен принадлежать программе (объяснения см. ниже).
В зависимости от определенного таким образом типа вызывается соответсвующий деструктор
(tm_free, text_free, task_free, pipe_free, pipe_free, pipe_free,
regexp_free, hashlist_free, fsm_free, db_free).
Если ссылка объекта имеет другой (квази статический) тип или не указывает на динамический объект,
принадлежащий программе, то деструктор не вызывается.
После этого ссылка зануляется (т.е. ей присваивается значение 0).
Это маркирует тот факт, что ссылка свободна и не указывает на объект.
По результатам работы процедуры может выдаваться отладочная печать в консоль.
Переменная FreeAndZero_Verbosity задает тип отладочного вывода и задается набором битов:
program demo; // Демонстрационная программа
...
var ref:Integer; // Ссылка объекта
...
procedure ClearApplication; // Процедура начальной очистки
begin
ref:=0; // Начальное обнуление ссылки
...
end;
...
procedure InitApplication; // Инициализация программы
begin
ref:=pipe_init('tcp port 123 ...'); // Вызов какого-то конструктора объекта
...
end;
...
procedure FreeApplication; // Завершение программы
begin
FreeAndZero(ref); // Удаление и обнуление объекта
end;
procedure PollApplication; // Процедура опроса
begin
if (ref<>0) then ... use(ref) ... // Использование объекта, если он существует
...
end;
...
procedure ResetObject; // Если надо "пересоздать" объект
begin
FreeAndNil(ref); // Удаляем старый объект, еcли он был,
ref:=pipe_init(...); // и только затем создаем новый объект.
end;
...
procedure DeleteObject; // Если надо удалить объект
begin
FreeAndZero(ref); // Удаляем объект, зануляем ссылку
end; // Объекта больше нет, ссылки тоже
...
В данном примере программы конструкторы и универсальный деструктор объектов FreeAndZero вызываются так,
чтобы гарантировать корректность ссылки объекта в любой момент времени, незаввисимо от того,
существует объект или нет (еще не создан или уже удален).
Такая дисциплина работы со ссылками повышает надежность работы прикладных программ.
function iGetBitState(Data,BitNum:Integer):Boolean;
- функция чтения в данных Data отдельного бита (номер BitNum, начиная с 0).
Возвращает логическое состояние соответствующего бита.
В принципе, это улучшенный аналог вызова IsBit(Data,BitNum).
function iSetBitState(Data,BitNum:Integer; SetOn:Boolean):Integer;
- функция установки в данных Data отдельного бита (номер BitNum, начиная с 0) в соответствии со значением SetOn.
Функция полезна при работе с целыми числами как с массивами статусных битов, для установки значений отдельных битов.
Для проверки битов можно использовать iGetBitState(Data,BitNum).
Например:
iSetBitState(4,0,true) = 5 (взяли 4 и установили 0-й бит в 1)
iSetBitState(6,1,false) = 4 (взяли 6 и установили 1-й бит в 0)
function iGetTagBitState(tag,BitNum:Integer):Boolean;
- функция чтения в целочисленном теге данных tag отдельного бита (номер BitNum, начиная с 0).
Возвращает логическое состояние соответствующего бита.
В принципе, это улучшенный аналог вызова IsBit(iGetTag(tag),BitNum).
function iSetTagBitState(tag,BitNum:Integer; SetOn:Boolean):Boolean;
- функция установки в целочисленном теге данных tag отдельного бита (номер BitNum, начиная с 0) в соответствии со значением SetOn.
Функция полезна при работе с целыми числами как с массивами статусных битов, для установки значений отдельных битов в целочисленных тегах.
Для проверки битов можно использовать iGetTagBitState(tag,BitNum).
Например:
bNul(iSetTagBitState(tag,0,true)); // установили 0-й бит тега в 1
bNul(iSetTagBitState(tag,1,false)); // сбросили 1-й бит тега в 0
function iGetBit(data,i:Integer):Integer;
- функция чтения в данных data отдельного бита (номер i, начиная с 0).
Возвращает 0 или 1 в соответствии со значением бита.
В принципе, это улучшенный аналог вызова Ord(IsBit(data,i)).
function iSetBit(data,i,v:Integer):Integer;
- функция установки в данных data отдельного бита (номер i, начиная с 0) в соответствии со значением
v (которое должно быть 0 или 1). Функция полезна при работе с целыми числами как с массивами статусных
битов, для установки значений отдельных битов. Для проверки битов можно использовать isbit(data,i).
Например:
iSetBit(4,0,1) = 5 (взяли 4 и установили 0-й бит в 1)
iSetBit(6,1,0) = 4 (взяли 6 и установили 1-й бит в 0)
function iSetTagBit(tag,i,v:Integer):Boolean;
- функция установки в целочисленном теге данных tag отдельного бита (номер i, начиная с 0) в соответствии со значением
v (которое должно быть 0 или 1). Функция полезна при работе с целыми числами как с массивами статусных
битов, для установки значений отдельных битов в целочисленных тегах. Для проверки битов можно использовать isbit(data,i).
Например:
bNul(iSetTagBit(tag,0,1)); // установили 0-й бит тега в 1
bNul(iSetTagBit(tag,1,0)); // установили 1-й бит тега в 0
function iSetTagXor(tag,XorMask:Integer):Boolean;
- функция операции XOR в целочисленном теге данных tag в соответствии со значением XorMask.
Фактически в значении тега tag инвертируются те биты, которые установлены в XorMask, остальные биты не меняются.
Функция полезна при работе с целыми числами как с массивами статусных битов, для инверсии значений битов в целочисленных тегах.
Для проверки битов можно использовать isbit(data,i).
Например:
bNul(iSetTagXor(tag,1)); // инвертировали 0-й бит тега
bNul(iSetTagXor(tag,4)); // инвертировали 2-й бит тега
function invCalibr(n:Integer; y,z,a,b:Real):Real;
- функция обратной калибровки по отношению к calibr(n,x,z).
Параметры a,b задают диапазон для возможных значений x (a<=x<=b).
При удачном расчете функция возвратит такое значение x, что для него calibr(n,x,z)=y.
При неудачном расчете (неверные входные параметры) функция возвратит _NaN.
Функция может оказаться ресурсоемкой (она связана с решением уравнения итерационным методом),
поэтому вызавать ее очень часто не стоит.
procedure RunStartupScript;
procedure RunFinallyScript;
функции служат для выполнения стартового и завершающего консольного скрипта.
Под консольным скриптом понимается набор консольных команд данного устройства, который считывается
из заданной секции файла конфигурации и помещается в консоль StdIn для исполнения.
RunStartupScript следует вызывать в конце InitApplication.
RunFinallyScript следует вызывать в начале FreeApplication.
В конфигурации устройства должно быть примерно следующее:
[DeviceList]
&Demo = device software program
[&Demo]
...
StartupScript=[&Demo.StartupScript]
FinallyScript=[&Demo.FinallyScript]
...
[]
[&Demo.StartupScript]
@StartupCommand 1
@StartupCommand 2
...
[]
[&Demo.FinallyScript]
@FinallyCommand 1
@FinallyCommand 2
...
[]
procedure ClearStdTools;
procedure InitStdTools;
procedure FreeStdTools;
procedure PollStdTools;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
_con_StdTasks.inc - файл описания констант модуля StdTasks.
Пока файл пуст.
_var_StdTasks.inc - файл описания переменных модуля StdTasks.
Переменная CurrentProcessId в начале работы инициализируется в номер (PID, process id) текущего процесса. Это чтобы он всегда был под рукой, и быстро.
_fun_StdTasks.inc - файл описания процедур и функций модуля StdTasks.
function Task_Readln(tid:Integer; var Line,Buff:String):Boolean;
- функция чтения строки (с разделителем EOL) из анонимного канала связи задачи tid.
Возвращается True, если строка успешно прочитана в переменную Line.
Если канал пуст или с задачей что-то не в порядке, возвращается False и пустая Line.
Буфер Buff должен быть глобальной переменной, определенной в основной программе (это очень важно),
поскольку она должна сохранять значение между асихронными вызовами. Кроме того, буфер должен обязательно очищаться
при старте и завершении задачи tid, иначе в буфере в начальный момент будет "мусор".
Надо также иметь в виду, что длина строк не должна превышать 16K (16384), иначе сгенерируется ошибка и буфер будут очищен.
Функция ориентирована на взаимодействие с консольными задачами через каналы стандартного ввода-вывода, переназначенные
анонимный канал (для этого надо при запуске указать ненулевой размер task_ctrl(tid,'StdInPipeSize='..)
и task_ctrl(tid,'StdOutPipeSize='..)).
function ExecCmdWait(ExeFile,CmdLine,Priority,OutFile:String; Display,TimeOut:Integer):Boolean;
procedure CheckBorlndmmDll(ServerExe:String);
function PidCounter(exe:String):Integer;
function RunSysCommand(CmdLine,Params,InText:String; var OutText:String; Timeout:Integer):Integer;
function ExecuteProcessSafe(cmd,arg,opt:String;timeout:Integer):Integer;
procedure ClearStdTasks;
_con_StdPipes.inc
- файл описания констант модуля StdPipes.
Константы
rs_NotAvail,
rs_NoRequest,
rs_WaitQueue,
rs_WaitAnswer,
rs_Answer,
rs_TimeOut
- знакомые до боли состояния цикла опроса COM- порта, часто используемые при написании драйверов устройств
со связью по RS-232/485.
_var_StdPipes.inc
- файл описания переменных модуля StdPipes.
Пока файл пуст.
_fun_StdPipes.inc
- файл описания процедур и функций модуля StdPipes.
function Pipe_Readln(pip:Integer; var Line,Buff:String):Boolean;
function Com_Readln(var Line,Buff:String):Boolean;
procedure Com_Writeln(Data:String);
procedure PurgeComPort;
procedure ClearStdPipes;
_con_StdEdits.inc
- файл описания констант модуля StdEdits.
Константы
Константы
_var_StdEdits.inc
- файл описания переменных модуля StdEdits.
Переменная
StdEditTagFont
_fun_StdEdits.inc
- файл описания процедур и функций модуля StdEdits.
procedure EditReset;
function EditStateReady:Boolean;
function EditGetTypes:String;
function EditGetRequest:String;
function EditGetLastInputLn:String;
function EditGetUID(id:String):String;
function EditGetWellKnownDevices(list:String):String;
function EditGetAnswer(n:Integer):String;
function EditGetResultName:String;
function EditTestResultName(name:String):Boolean;
function EditGetMenuListSelectedIndex:Integer;
function EditAddLine(line:String):Intgeer;
function EditAddOpening(line:String):Intgeer;
procedure EditStartConfirmCommand(Msg,Id,Cmd,Params:String);
procedure EditMenuDefaultHandler(id:String);
procedure Warning(msg:String);
function SetFormUnderSensorLeftBottom(aClickParams:String);
procedure WinSelectUnderClickSensor(win,click,opt:String);
procedure StartEditTagStrEx(tag:Integer; Caption,Data,Params:String);
function CheckEditTag(tag:Integer; var newValue:String):Boolean;
procedure CheckEditTagUpdate(tag:Integer; minValue,maxValue:Real);
function VkbdEditTag(tag,Lang,Shift:Integer; Lab:String):Integer;
procedure WarningEx(Msg,Id,Cmd,Params:String);
procedure MessageBoxDialogEx(DialogType,Msg,Id,Cmd,Params:String);
procedure ClearStdEdits;
_con_StdDims.inc
- файл описания констант модуля StdDims.
Константа
DIM_GuiClickCmnd
- содержит строку @DimGuiClick идентификатора команды графического события (GUI click).
_var_StdDims.inc
- файл описания переменных модуля StdDims.
Переменная
DIM_CryptKind
- указание на используемый метод шифрования: 0..9=DEFAULT,BLOWFISH,GOST,RC2,RC4,RC5,RC6,BASE64,HEX,NONE.
Переменная
DIM_GuiClickTag
- содержит ссылку на тег (типа string), используемый для приемо-передачи событий графического интерфейса (GUI click).
Этот тег должен быть явно инициализирован прикладной программой и связан с командным сервисом DIM
с помощью процедуры DIM_GuiClickInit.
Переменная
DIM_GuiClickFallBackModeTag
- содержит ссылку (изначально нулевую) на тег (типа integer), используемый для включения
резервного режима работы для приемо-передачи событий графического интерфейса (GUI click).
Резервный режим может использоваться, например, при неполадках в сети.
После вызова процедуры DIM_GuiClickInit тег инициализируется
ссылкой на тег с именем &DimSrv.FallBackMode, если он найден в текущей конфигурации.
Тег может также инициализироваться явно, если нужно индивидуальное управление резервным режимом
для данного устройства.
procedure DIM_Send(msg:String);
procedure DIM_UpdateTag(tag:Integer; data:String);
Если в data передается пустая строка, сервер DIM будет обновлять сервис, беря текущие значения тегов
обновляемого сервиса (предполагается, что они уже установлены в нужные значения). Недостатком этого
метода является то, что промежуточные значения тегов или кривых могут пропадать, ведь между посылкой сигнала обновления
и самим обновлением есть зазор по времен, в течение которого тег может измениться, пока сервер занимается поиском и
обновлением сервиса. Так что этот метод не годится для "исторических" данных или очередей событий, где важно каждое
значение. Этот метод рекомендуется для "медленных" данных, где история не важна.
Если же передается непустая строка data, она рассматривается как двоичный дамп (dump)
данных сервиса, преобразуется во внутренний формат (неважно какой) и отсылается серверу &DimSrv.
Значение самого тега при этом не меняется, оно вообще не используется,
т.к. в этом случае тег служит просто маркером для указания нужного сервиса.
Этот метод позволяет отсылать клиентам произвольные данные, вообще не связанные с тегом, а сам тег может быть фиктивным,
заведенным только для передачи данных. Конечно, программист, пишущий сервер, должен хорошо знать что делает.
Однако при этом появляется возможность передачи данных без потерь - все обновления помещаются в очередь и
отсылаются клиентам одно за другим.
Этот метод рекомендуется для передачи "исторических" данных или для организации очередей.
Таким образом, функция мощная, использовать ее надо с умом, выбрав нужный режим обновления исходя из задачи.
Смотрите также пример DEMO_ALIPHOSCOOL.
Смотрите также пример DEMO_RDMS.
function DIM_Encrypt(Data,Key:String):String;
function GrantAccessDim(Guard,User,Host,IP,MAC:String):Boolean;
function DIM_GuardParams(Name:String):String;
function DIM_GuiClickScan(var ClickBuff:String; ParamName:String):String;
function DIM_GuiClickWhat(var ClickBuff:String):Integer;
function DIM_GuiClickPack(What,Button,X,Y:Integer; ms:Real; Device,Window,Sensor,Tag,Curve,Value:String):String;
procedure Dim_GuiConsoleSend(DevName,data:String);
procedure DIM_GuiClickInit(GuiClickParams:String);
procedure ClearStdDims;
_con_StdWebs.inc
- файл описания констант модуля StdWebs.
Константа
HTTP_REQUEST_ACCEPTED_CMD
- определяет имя команды HTTP запроса - @HTTP_REQUEST_ACCEPTED.
Оно обрабатывается консольном процессоре StdIn_Processor примерно так:
_var_StdWebs.inc
- файл описания переменных модуля StdWebs.
Переменная
WEB
содержит запись (record), необходимую для разработки Web-серверов.
Среди полей записи можно указать следующие.
Переменная
JSON_Buffer
служит временным буфером при формировании JSON - ответов на AJAX - запросы.
Напрямую она не используется.
Переменная
JSON_BufferMax
содержит максимальную длину строки при форматировании JSON - ответов на AJAX - запросы.
_fun_StdWebs.inc
- файл описания процедур и функций модуля StdWebs.
function WEB_Encrypt(Data,Key:String):String;
function GrantAccessWeb(User,Host,IP,Password:String):Integer;
function WebAccessGranted(Mode:Integer):Integer;
procedure WEB_Clear;
procedure WEB_Init;
procedure WEB_Reply(s:String);
procedure WEB_ReplyText(t:Integer);
procedure WEB_ReplyEnd;
function WEB_StrEscape(s:String);
function WEB_StrUnescape(s:String);
procedure WEB_ReplyEscape(s:String);
procedure WEB_ReplyTextEscape(t:Integer);
procedure WEB_InitHtmlReply;
function WEB_GetQueryItem(Name:String; var Item:String):Boolean;
function WEB_IsQueryItem(Name,Value:String):Boolean;
function WEB_IsRequestMethod(InList:String):Boolean;
procedure WEB_CopyJsLibrary(Name:String);
procedure WEB_CopyCssLibrary(Name:String);
function WEB_FormDataPosted:Boolean;
procedure WEB_MetaContentType(Data:String);
procedure WEB_MetaRefresh(Period:Integer; Link:String);
procedure WEB_MetaSetCookie(Name,Data:String);
procedure WEB_LinkStyleSheet(NameCss:String);
procedure WEB_AddJavaScript(ScriptName:String);
procedure WEB_NoScriptWarning(msg:String);
function WEB_CheckRequest(Msg:String):Integer;
function WEB_ReadJsFromPas(txt:Integer;Section:String):Integer;
procedure JSON_Flush;
procedure ClearStdWebs;
_con_StdDlls.inc
- файл описания констант модуля StdDlls.
Пока файл пуст.
_var_StdDlls.inc
- файл описания переменных модуля StdDlls.
STD_DLL_REF:Integer;
_fun_StdDlls.inc
- файл описания процедур и функций модуля StdDlls.
function DLL_INIT(DllPath:String):Integer;
procedure DLL_FREE(var hDll:Integer);
procedure DLL_POLL(hDll:Integer);
procedure STD_DLL_INIT(arg:String);
function STD_DLL_FILE_PATH(arg:String):String;
procedure STD_DLL_POLL;
procedure STD_DLL_FREE;
procedure STD_DLL_CLEAR;
procedure ClearStdDlls;
_con_StdUtils.inc
- файл описания констант модуля StdUtils.
Пока файл пуст.
_var_StdUtils.inc
- файл описания переменных модуля StdUtils.
Пока файл пуст.
_fun_StdUtils.inc
- файл описания процедур и функций модуля StdUtils.
procedure WebBrowser(FileName:String);
function RmtShareExe(Share,Local,Options:String; TimeOut:Integer):Boolean;
procedure UnixWBox(Title,Message:String; WW,FS,TM:Integer);
procedure ShowTooltip(args:String);
procedure StdSensorHelpTooltip(msg:String; delay:Integer);
function CustomIniRW(RW:Char; arg:String; mode:Integer):Integer;
Функция возвращает число прочитанных или записанных тегов, или 0 при какой-либо ошибке.
Параметр RW задает требуемую операцию - прочитать (RW='R') или записать (RW='W') группу тегов из/в
INI файл, заданный аргументом arg. Таким образом, одна функция решает обе задачи (чтение и запись параметров).
Параметр arg содержит от 0 до 4 слов (IniFile, CustomSection, TagListSection, BackupDir),
задающих, что и куда сохранять.
Параметр mode содержит битовые флаги, задающие, режимы отображения и резервного копирования.
Значение mode = 0 задает поведение по умолчанию:
Примеры:
function WinListByCurve(crv:Integer; comma:Char):String;
Параметр crv задает ссылку кривой, по которой ищутся окна, где есть эта кривая.
Параметр comma задает разделитель, который используется для списка окон. Обычно можно использовать запятую.
Пример:
function WinSelectByCurve(crv:Integer; selcrv:Integer):Integer;
Функция возвращает число найденных окон, в которых присутствует кривая crv, или ноль, если кривая не отображается в окнах.
Параметр crv задает ссылку кривой, по которой ищутся окна, где есть эта кривая, и которые надо открыть.
Параметр selcrv задает ссылку кривой, которую надо выделить. При указании selcrv=0 выделения кривой не делается.
При указании selcrv=-1 отменяется выделение кривой, то есть показываются все кривые одновременно.
Пример:
function DeviceListEnum(n:Integer):Integer;
Параметр n задает номер устройства, начиная с 0. Возврат нулевой ссылки служит признаком завершения списка.
Пример:
function CalibrOpenByCurve(crv:Integer):Boolean;
Функция возвращает true, если найдена калибровка по кривой crv, или false, если калибровка не найдена.
Параметр crv задает кривую, по которой ищется калибровка.
Пример:
procedure ClearStdUtils;
_con_StdAddons.inc
- файл описания констант модуля StdAddons.
Является специальной пустой заглушкой для инкапсуляции пользовательских констант.
Пользователь может скопировать этот файл в каталог Daqpas, где обычно расположены прикладные программы,
и заполнить его своим содержимым. Это содержимое станет доступным для всех программ в данном каталоге,
использующих стандартную библиотеку. Менять сами программы при этом не надо.
_var_StdAddons.inc
- файл описания переменных модуля StdAddons.
Является специальной пустой заглушкой для инкапсуляции пользовательских переменных.
Пользователь может скопировать этот файл в каталог Daqpas, где обычно расположены прикладные программы,
и заполнить его своим содержимым. Это содержимое станет доступным для всех программ в данном каталоге,
использующих стандартную библиотеку. Менять сами программы при этом не надо.
_fun_StdAddons.inc
- файл описания процедур и функций модуля StdAddons.
Является специальной пустой заглушкой для инкапсуляции пользовательских функций.
Пользователь может скопировать этот файл в каталог Daqpas, где обычно расположены прикладные программы,
и заполнить его своим содержимым. Это содержимое станет доступным для всех программ в данном каталоге,
использующих стандартную библиотеку. Менять сами программы при этом не надо.
procedure ClearStdAddons;
Желаю успешного использования DaqPascal!
- запуск процесса с исполняемым файлом ExeFile, (необязательными) параметрами командной строки CmdLine,
(необязательным) приоритетом процесса (Idle,Low,Normal,Higher,High,RealTime), с (необязательным) переназначением
стандартного вывода в файл OutFile, с режимом отображения Display (0=Hide,1=Show), и с ожиданием
в течение TimeOut ms. Если срок ожидания истек, процесс досрочно "убивается" и возвращается False.
Проверяет наличие и копирует (при необходимости) библиотеку borlndmm.dll в каталог программы ServerExe.
Используется для уверенности, что консольные задачи, созданные во встроенном компиляторе Delphi, найдут нужные
им библиотеки, хранящиеся в папке ресурсов программы.
- возвращает число запущенных процессов с именем exe или 0, если их нет. Служит для проверки того,
запущен ли интересующий процесс, идентифицируемый коротким (без пути, но с расширением) именем исполняемого файла.
Например, if PidCounter('dns.exe')=0 then Problem('DIM DNS server is not running.')
function RunSysCommandAsText(CmdLine,Params,InText:String; var Code:Integer; Timeout:Integer):String;
- функции для вызова команд операционной системы с возвратом кода выхода команды в code и чтением потока вывода stdout команды в OutText.
При вызове задается командная строка для выполнения CmdLine,
параметры Params для task_ctrl в виде списка name=value с разделителем EOL,
входной текст InText для передачи в поток ввода stdin,
а также время ожидания ответа Timeout (в миллисекундах).
При этом, если команда не завершилась в отведенное время, она завершается принудительно,
чтобы не "подвесить" вызывающую программу.
Например:
function GetOsName:String;
var s:String; code:Integer;
begin
s:='';
if IsWindows then s:=RunSysCommandAsText('unix detectwindows','','',code,3000);
if IsUnix then s:=Trim(RunSysCommandAsText('uname -snorm','HomeDir=/tmp'+EOL+'Priority=Normal','',code,3000));
if IsUnix then s:=s+' '+Trim(RunSysCommandAsText('lsb_release -sd','','',code,3000));
GetOsName:=Trim(s);
s:='';
end;
// Возвращает примерно такой вывод:
// Windows: Windows 10 Enterprise LTSC 2019 -- Version 10.0.17763.5458
// Linux: Linux y510p 5.15.0-33-generic x86_64 GNU/Linux Astra Linux CE 2.12.45 (Orel)
Функции RunSysCommand, RunSysCommandsText позволяют использовать команды операционной системы,
а также программные утилиты или сценарии фактически как внешние функции. При этом, однако, следует учитывать,
что вызов является относительно дорогим (минимум несколько миллисекунд) и синхронным (блокирующим), поэтому
он может вызывать задержку выполнения программы вплоть до указанного времени ожидания Timeout.
Поэтому следует ограничивать использование этих функций теми случаями, когда это действительно необходимо
и минимизировать число таких вызовов. Например, допустимо использование функций при инициализации программы
или при обработке событий пользователя (по нажатию кнопок), поскольку это происходит (относительно) редко.
В то же время использовать эти функции в цикле опроса настоятельно НЕ рекомендуется.
Если нужно постоянное взаимодействие с внешней программой, его надо организовывать как асинхронное,
через задачи/каналы, с чтением из каналов в цикле опроса.
- аналогично функции RunSysCommand, функция ExecuteProcessSafe вызывает команду
cmd операционной системы с аргументами arg и опциями (параметрами task_ctrl) opt,
ожидая выполнения в течение timeout миллисекунд и возвращает код выхода команды (программы).
При ошибке (команда не найдена, процесс не запустился или время ожидания превышено) возвращается код -1.
Функция ExecuteProcessSafe помечена как безопасная (safe), потому что ограничение по времени
предотвращает потенциально возможное "зависание" потока, если запускаемый процесс не уложился в заданное время.
Функция ExecuteProcessSafe применяется в тех случаях, когда пользователя не интересует ввод-вывод
запускаемой команды, а важен лишь код возврата (результат выполнения).
Например:
if (ExecuteProcessSafe('chmod','644 demo/test.txt','',3000)=0)
then writeln('Установлены права доступа 644 файла demo/test.txt')
else writeln('Не удалось установить права доступа файла.');
procedure InitStdTasks;
procedure FreeStdTasks;
procedure PollStdTasks;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdPipes
педоставляет набор функций для работы с DAQ-каналами связи,
в рамках шаблона прикладной программы template.pas.
- функция чтения строки (с разделителем EOL) из канала связи pip.
Возвращается True, если строка успешно прочитана в переменную Line.
Если канал пуст или с задачей что-то не в порядке, возвращается False и пустая Line.
Буфер Buff должен быть глобальной переменной, определенной в основной программе (это очень важно),
поскольку она должна сохранять значение между асихронными вызовами. Кроме того, буфер должен обязательно очищаться
при создании и удалении канала pip, иначе в буфере в начальный момент будет "мусор".
Надо также иметь в виду, что длина строк не должна превышать 16K (16384), иначе сгенерируется ошибка и буфер будут очищен.
Функция ориентирована на взаимодействие с каналами связи разного типа (COM порты, сокеты, именованные каналы,
анонимные каналы запущенных задач).
- работает аналогично предыдущей процедуре, только чтение производится из COM-порта, открытого с помощью
comopen.
- записывает данные в COM-порт, открытый вызовом comopen, с добавлением CRLF,
отображением данных (ViewExp,Trouble) и анализом результата записи.
Процедура ориентирована на драйверы RS-232 с ASCII-протоколом.
- полностью очищает буферы ввода-вывода COM-порта, открытого вызовом comopen.
Процедура ориентирована на драйверы RS-232.
procedure InitStdPipes;
procedure FreeStdPipes;
procedure PollStdPipes;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdEdits
предоставляет набор функций для редактирования тегов и параметров с помощью функции edit,
в рамках шаблона прикладной программы template.pas.
ef_Ready (редактор готов к новому заданию на редактирование),
ef_Done (редактирование закончено, можно читать данные),
ef_Busy (идет редакторование, редактор занят),
ef_Preparing (задание на редактирование в процессе приготовления),
ef_ErrorFound (найдена ошибка редактирования)
- содержат статусные флаги функции editstate, полезные в работе.
mr_OK = 1 (нажата кнопка Ok),
mr_CANCEL = 2 (нажата кнопка Cancel),
mr_ABORT = 3 (нажата кнопка Abort),
mr_RETRY = 4 (нажата кнопка Retry),
mr_IGNORE = 5 (нажата кнопка Ignore),
mr_YES = 6 (нажата кнопка Yes),
mr_NO = 7 (нажата кнопка No),
mr_CLOSE = 8 (нажата кнопка Close),
mr_HELP = 9 (нажата кнопка Help)
- возможные значения результата edit(?ans 0'), позволяющие анализировать ввод пользователя.
- содержит описание шрифта, используемого для редактирования тегов (StartEditTag).
Считывается из секции [DAQ]
EditTagFont.
- процедура сброса редактирования в исходное состояние (EditStateReady)
через вызов edit('').
Применяется после завершения редактирования для перехода в режим готовности к новому редактированию.
function EditStateDone:Boolean;
function EditStateBusy:Boolean;
function EditStateError:Boolean;
- функции проверки состояния редактирования (EditState).
Состояние EditStateReady (когда editstate = 0 = ef_Ready) указывает на готовность к редактированию.
Эта проверка применяется перед началом нового редактирования (например, по нажатию сенсора).
Состояние EditStateDone (когда editstate = 1 = ef_Done) указывает на завершение редактирования.
Эта проверка применяется для ожидания завершения редактирования и анализа его результатов.
После анализа результатов надо выполнить сброс EditReset в исходное состояние.
Состояние EditStateBusy (когда editstate = 4 or 8 = ef_Busy or ef_Preparing) указывает на незавершенный процесс редактирования.
В этом состоянии программа должна ожидать завершения редактирования, т.е. перехода в состояние EditStateDone.
Состояние EditStateError (когда editstate = 8 = ef_ErrorFound) указывает на ошибку редактирования.
После обнаружения и обработки ошибки надо выполнить сброс EditReset в исходное состояние.
- функция возвращает edit('?types') - разделенный запятыми список
типов диалогов (Warning,YesNo,..,FileOpenDialog,SelectDirectoryDialog).
Она используется для проверки корректности ввода задания на редактирование.
function EditGetRequestType:String;
function EditGetRequestName:String;
- функция (через вызов edit('?req')) возвращает запрос (request) редактирования,
в который входит тип диалога (type), имя (name) для идентификации диалога, а также другие параметры,
разделенные пробелом.
- функция (через вызов edit('?inp ...')) возвращает последнюю введенную строку
в буфере ввода (input).
Она используется для того, чтобы избежать повторения ввода строк при задании строк подтверждений (confirm) для меню,
см. пример.
- функция возвращает "уникальный идентификатор" UID (unique identifier) для использования в качестве
идентификаторов диалогов в функции edit.
Строка UID создается с помощью идентификатора (id), который должен быть уникальным в рамках данной прикладной программы.
К этому идентификатору добавляется префикс, содержащий имя устройства (DevName), чтобы сделать UID
уникальным в рамках всей системы, см. пример.
- функция возвращает разделенный запятыми список "хорошо известных устройств" (well known devices), т.е. стандартных серверов,
таких как &CronSrv или &DatSrv, который добавляется к заданному списку (list).
Функция применяется при создании меню для работы с устройствами, например, для меню перезапуска устройств.
function EditGetCommand(n:Integer):String;
function EditGetConfirm(n:Integer):String;
function EditGetSetting(n:Integer):String;
function EditGetAnswerCount:Integer;
function EditGetCommandCount:Integer;
function EditGetConfirmCount:Integer;
function EditGetSettingCount:Integer;
function EditGetAnswerText:String;
function EditGetCommandText:String;
function EditGetConfirmText:String;
function EditGetSettingText:String;
- функции извлечения ответа (answer), команды (command), подтверждения (confirm), настроек (setting)
из буфера редактирования edit по номеру строки n
и соответствующих им счетчиков (count) или всего текста (text).
Например:
if EditStateDone then begin
for i:=0 to EditGetAnswerCount-1 do writeln('Answer ',i,' = ',EditGetAnswer(i));
for i:=0 to EditGetCommandCount-1 do writeln('Command ',i,' = ',EditGetCommand(i));
for i:=0 to EditGetConfirmCount-1 do writeln('Confirm ',i,' = ',EditGetConfirm(i));
for i:=0 to EditGetSettingCount-1 do writeln('Setting ',i,' = ',EditGetSetting(i));
writeln('Answer: ',EOL,EditGetAnswerText);
writeln('Command: ',EOL,EditGetCommandText);
writeln('Confirm: ',EOL,EditGetConfirmText);
writeln('Setting: ',EOL,EditGetSettingText);
writeln('Request: '+EditGetRequest);
writeln('Type: '+EditGetRequestType);
writeln('Name: '+EditGetRequestName);
end;
function EditGetResultCode:Integer;
- функции проверяют статус завершения (EditStateDone)
и результат (Edit('?ans 0')) редактирования
и возвращают имя и код (name=code) диалога редактирования,
или пустую строку и 0, если редактирование не завершено.
function EditTestResultCode(code:Integer):Boolean;
- функции проверяют статус завершения (EditStateDone)
и результат (Edit('?ans 0')) редактирования
на совпадение имени и кода (name=code) диалога редактирования,
или возвращают false, если редактирование не завершено.
Например:
procedure PollApplication;
begin
...
{
Edit handling...
}
if EditStateDone then begin
{
Warning dialog completion.
}
if EditTestResultName('Warning') then begin
if EditTestResultCode(mr_OK) then writeln('Warning: OK button pressed.');
if EditTestResultCode(mr_CANCEL) then writeln('Warning: Cancel pressed.');
EditReset;
end;
{
YesNo dialog completion.
}
if EditTestResultName('YesNo') then begin
if EditTestResultCode(mr_YES) then writeln('YesNo: YES button pressed.');
if EditTestResultCode(mr_NO) then writeln('YesNo: NO button pressed.');
EditReset;
end;
... etc ...
end;
if EditStateDone then begin
Problem('Unhandled Edit detected!');
EditReset;
end else
if EditStateError then begin
Problem('Edit error detected!');
EditReset;
end;
...
end;
function EditGetMenuListSelectedCommand:String;
function EditGetMenuListSelectedConfirm:String;
- функции используются для чтения номера (index) выбранного пункта меню,
соответствующей ему команды (command) и строки подтверждения (confirm).
Если пункт меню не выбран или не нажата кнопка
OK,
возвращается индекс -1 и пустые строки команды и подтверждения.
Предполагается, что диалог edit имеет тип списка меню (MenuList,SelectionList,CheckBoxList)
и для каждого пункта меню при создании введена команда и строка подтверждения.
function EditAddText(lines,prefix,filter:String):Integer;
- функции добавляют в задание редактирования (edit)
строку (line) или многострочный текст (lines) с возможным добавлением префикса (prefix)
в начале каждой строки текста и фильтра (filter) для пропуска недопустимых строк.
Через фильтр (если он задан) проходят только те строки текста, первый символ которых входит в строку фильтра.
Функции возвращают 0 при успехе или ненулевое число ошибок, если в тексте обнаружены ошибки.
Например:
function DemoMenuItems:String;
begin
DemoMenuItems:='Казнить'+EOL+'Помиловать'+EOL+'Наградить';
end;
...
if (ClickSensor='DEMO') then
if EditStateReady then begin
if EditAddLine('(Список выбора...')
+EditAddLine(' Выбрать операцию:')
+EditAddText(DemoMenuItems,Dump(' '),'')
+EditAddLine(')MenuList DEMO_MENU 1')>0
then Problem('Edit Error found.');
end;
function EditAddInputLn(line:String):Intgeer;
function EditAddCommand(line:String):Intgeer;
function EditAddConfirm(line:String):Intgeer;
function EditAddSetting(line:String):Intgeer;
function EditAddClosing(typ,id,par:String):Integer;
- функции добавляют в задание редактирования (edit)
открывающую строку (opening), строку во входной буфер (inputln = input line), строку команды (command),
строку подтверждения (confirm), строку настройки (setting), а также завершающую строку (closing)
с указанием типа диалога (typ), идентификатора диалога (id) и других параметров (par).
В отличие от других функций edit, при указании строк (line) не надо указывать первый символ
( '(', ' ', '>', '!', '@', ')' ) для указания типа ввода,
т.к. эти символы добавляются автоматически в каждой функции.
Данная группа функций задумана в первую очередь для облегчения создания и обработки ввода через меню.
При задании списков меню (MenuList, SelectionList, CheckBoxList) надо строго соблюдать равное количество
и порядок вводимых элементов (inputln, command, confirm), чтобы после ввода было соблюдено верное соответствие
по индексу между пунктами меню (inputln), выполняемыми командами (command) и запросами на подтверждение (confirm).
Предполагается, что каждой строке ввода соответствует строка команды и строка запроса подтверждения.
После выбора пункта меню и запроса подтверждения (если он не пустой) выполняется соответствующая команда.
Вот пример задания меню выхода (Close) и его обработки:
{
Menu CLOSE Starter to start editing.
}
procedure MenuCloseStarter;
var n:Integer;
begin
if EditStateReady then begin
//////////////////////////////////////////
n:=0+EditAddOpening('Команда "Закрыть"... ');
n:=n+EditAddInputLn('Что выбираете:');
//////////////////////////////////////////
n:=n+EditAddInputLn('Продолжить работу текущего сеанса АСУ');
n:=n+EditAddConfirm('');
n:=n+EditAddCommand('@syseval @silent @tooltip text "Желаю успешной работы" preset stdNotify delay 15000');
//////////////////////////////////////////
n:=n+EditAddInputLn('Завершить сеанс АСУ и закрыть программу');
n:=n+EditAddConfirm(EditGetLastInputLn);
n:=n+EditAddCommand('@Cron @Shutdown Crw Exit');
//////////////////////////////////////////
n:=n+EditAddInputLn('Завершить сеанс АСУ и продолжить работу');
n:=n+EditAddConfirm(EditGetLastInputLn);
n:=n+EditAddCommand('@Cron @Shutdown Daq Exit');
//////////////////////////////////////////
n:=n+EditAddInputLn('Перезагрузить сеанс АСУ и начать заново');
n:=n+EditAddConfirm(EditGetLastInputLn);
n:=n+EditAddCommand('@Cron @Shutdown Daq Restart');
//////////////////////////////////////////
n:=n+EditAddInputLn('Завершить сеанс Windows');
n:=n+EditAddConfirm(EditGetLastInputLn);
n:=n+EditAddCommand('@Cron @Shutdown Win Logout');
//////////////////////////////////////////
n:=n+EditAddInputLn('Перезагрузить компьютер');
n:=n+EditAddConfirm(EditGetLastInputLn);
n:=n+EditAddCommand('@Cron @Shutdown Win Restart');
//////////////////////////////////////////
n:=n+EditAddInputLn('Выключить компьютер');
n:=n+EditAddConfirm(EditGetLastInputLn);
n:=n+EditAddCommand('@Cron @Shutdown Win Exit');
//////////////////////////////////////////
n:=n+EditAddSetting('@set ListBox.Font Size:16\Style:[Bold]');
n:=n+EditAddSetting('@set Form.Left 530 relative '+Copy(DevName,2)+' PaintBox');
n:=n+EditAddSetting('@set Form.Top 0 relative '+Copy(DevName,2)+' PaintBox');
//////////////////////////////////////////
n:=n+EditAddClosing('MenuList',EditGetUID('MENU_CLOSE'),'');
if (n>0) then Problem('Error initializing MenuList!');
end else Problem('Cannot edit right now!');
end;
{
Menu CLOSE Handler to handle editing.
}
procedure MenuCloseHandler;
begin
EditMenuDefaultHandler(EditGetUID('MENU_CLOSE'));
end;
...
procedure PollApplication;
begin
...
{
Handle clicks...
}
if (ClickButton=VK_LBUTTON) then begin
if IsSameText(ClickSensor,'MAIN.CMD.CLOSE') then begin
bNul(Voice(snd_Click));
MenuCloseStarter;
end;
...
end;
...
{
Edit tags...
}
if EditStateDone then begin
{
Menu CLOSE.
}
MenuCloseHandler;
...
end;
if EditStateDone then begin
Problem('Unhandled edit detected!');
EditReset;
end;
if EditStateError then begin
Problem('Dialog error detected!');
EditReset;
end;
end;
...
- процедура инициирует диалог (YesNo) подтверждения (confirm) с текстом сообщения (Msg), идентификатором (Id),
командой (Cmd) для последующего выполнения и параметрами настройки (Params).
Текст сообщения может быть многострочным с разделителем EOL.
Эта процедура применяется при обработке списков меню (MenuList,SelectionList,CheckBoxList) для того,
чтобы (в случае необходимости подтверждения) подтвердить выполнение выбранной команды,
как показано в примере.
- процедура стандартной (default) обработки (handler) при редактировании (edit) меню (menu) с идентификатором (id).
Предполагается, что меню подготовлено по следующим правилам:
Есть хороший пример использования этой функции.
procedure InfoBox(msg:String);
- процедуры отображают окошко с сообщениями предупреждающего характера (Warning) или информационного характера (InfoBox).
- функция вычисляет параметры для вызова StartEditTagEx, для того, чтобы поместить окно редактирования тега под сенсором.
Аргумент функции берется из вызова ClickParams(''), откуда извлекается имя окна и координаты нажатого сенсора.
Например:
// Редактирование с указанием координат окна под сенсором и шрифта
StartEditTagEx(tag,'Введите значение',SetFormUnderSensorLeftBottom(ClickParams(''))
+'@set StringGrid.Font Name:PT_Mono\Size:14\Color:Black\Style:[Bold]');
- функция рисует (WinDraw) и активизирует (WinSelect) окно win, располагая его под нажатым (click) сенсором, с опциями opt.
Эта функция позволяет, например, вызывать окно мнемосхемы непосредственно под кнопкой вызова этой мнемосхемы.
Параметры нажатого сенсора (click) берутся из ClickParams('').
Например:
if (ClickSensor='DEMO') then WinSelectUnderClickSensor('DEMO.WINDOW',ClickParams(''),'-Left,-Top');
За счет опций можно задавать фиксированное ('-Left,-Top') или свободное ('+Left,+Top') положение вызываемого окна.
procedure StartEditTagStr(tag:Integer; Caption,Data:String);
procedure StartEditTagEx(tag:Integer; Caption,Params:String);
procedure StartEditTag(tag:Integer; Caption:String);
- процедуры начинают редактирование тега tag с заголовком Caption.
Процедура StartEditTagStr берет в качестве начального значения для редактирования данные Data,
а процедура StartEditTag использует текущее значение тега.
Расширенные (Ex = extended) версии этих процедур используют параметры
(Params) для задания дополнительных
параметров редактирования для функции edit (например, для задания шрифта или координат окна).
Например:
// Обычное редактирование
StartEditTag(tag,'Введите значение');
// Редактирование с явным заданием шрифта через параметр
StartEditTagEx(tag,'Введите значение','@set StringGrid.Font Name:PT_Mono\Size:14\Color:Black\Style:[Bold]');
// Редактирование с явным заданием шрифта и координат через параметр
StartEditTagEx(tag,'Введите значение',
'@set StringGrid.Font Name:PT_Mono\Size:14\Color:Black\Style:[Bold]'+EOL+
'@set Form.Left 200 relative DemoEdit PaintBox'+EOL+
'@set Form.Top 10 relative DemoEdit PaintBox');
// Редактирование с заданием шрифта через переменную и восстановлением стандартного значения
StdEditTagFont:='Name:PT_Mono\Size:14\Color:Red\Style:[Bold]';
StartEditTag(tag,'Введите значение');
StdEditTagFont:=ReadIni('[DAQ] EditTagFont');
Смотрите также DEMO_EDIT.
- функция проверяет статус редактирования тега. Если редактирование тега было начато и успешно закончено, функция вернет
True и отредактированные данные в строке newvalue.
Смотрите также DEMO_EDIT.
- процедура вызывает CheckEditTag, и если редактирование успешно завершено, обновляет тег с помощью вызова
UpdateTag. Если тег числовой, присвоение тегу нового значения происходит с проверкой диапазона.
Смотрите также DEMO_EDIT.
- вызывает сервер &VkbdSrv для редактирования тега. Комментировать не буду - смотрите
DEMO_VKBD.
procedure InfoBoxEx(Msg,Id,Cmd,Params:String);
- процедуры отображают окошко с сообщениями (Msg) предупреждающего характера (Warning) или информационного характера (InfoBox),
при этом можно указывать идентификатор редактирования (Id), команду по кнопке Ok или Yes (Cmd), а также параметры редактирования (Params).
Процедуры основаны на вызове MessageBoxDialogEx, при этом обработка выполняется с помощью EditMessageBoxDialogExDefaultHandler(id).
procedure EditMessageBoxDialogExDefaultHandler(id:String);
- процедуры для организации диалогов типа MessageBox с заданным типом диалога (DialogType=YesNo,YesNoCancel,Information,Warning,Error).
MessageBoxDialogEx отображает окошко с сообщением (Msg) заданного типа (DialogType),
при этом можно указывать идентификатор редактирования (Id), команду по кнопке Ok или Yes (Cmd), а также параметры редактирования (Params).
Обработка выполняется с помощью EditMessageBoxDialogExDefaultHandler(Id).
Например:
Вызов окна диалога:
MessageBoxDialogEx('Information','Скажи Привет...','DemoInfoBox','@tooltip text "Привет, мир."',SetFormUnderSensorLeftBottom(ClickParams('')));
Обработка результата:
EditMessageBoxDialogExDefaultHandler('DemoInfoBox');
В результате будет открыт диалог 'Information' с текстом "Скажи Привет...".
Диалог открывается под нажатым сенсором (вызов SetFormUnderSensorLeftBottom).
Если в диалоге нажать кнопку OK, будет выполнена команда @tooltip text "Привет, мир.",
которая покажет окно со всплывающим сообщением "Привет, мир." в нижней части экрана.
Строка 'DemoInfoBox' служит для внутренней идентификации диалога.
procedure InitStdEdits;
procedure FreeStdEdits;
procedure PollStdEdits;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdDims
предоставляет набор функций, облегчающих работу с сервером &DimSrv,
и позволяющих вести разработку распределенных систем управления на основе технологии DIM
в рамках шаблона прикладной программы template.pas.
Переменная
DIM_GuiClickBuff
- содержит строковый буфер для временного хранения данных графического события (GUI click).
Переменная
DIM_ClientServerMode
- содержит флаги декларируемого режима работы DIM: bit[0] = клиент, bit[1] = сервер, т.е. 1=client, 2=server.
_fun_StdDims.inc
- файл описания процедур и функций модуля StdDims.
function ShouldRefresh(var upd:Real; val:Real):Integer;
- функция используется для проверки необходимости обновления DIM сервисов (или других объектов, зависящих от значений параметров).
Для каждого параметра (обычно тега или кривой) заводится копия её величины upd,
хранящая предыдущее значение величины val этого параметра.
В качестве величины val для тегов рекомендуется использовать их "отпечаток" GetStampOfTag,
а в случае кривых используется либо значение (y), либо время (x) последней точки кривой.
ShouldRefresh при вызове проверяет, совпадает ли upd и val. Если совпадает, функция просто вернет 0 (сигнал,
что значение не изменилось). Если они отличаются, upd присваивается значение val и возвращается 1.
В результате получается компактная запись типа:
if ShouldRefresh(upd1,rGetTag(tag1))+
ShouldRefresh(upd2,iGetTag(tag2))>0
then begin
DIM_UpdateTag(tag1,''); // Обновить с текущим значением тегов
Success('Один из тегов изменился, надо обновлять DIM сервис.');
// Считаем что оба тега входят в сервис, поэтому один вызов DIM_Update
end;
Смотрите также пример DEMO_ALIPHOSCOOL.
function GetStampOfTag(tag:Integer; def:Real):Real;
- функция вычисляет "отпечаток" (stamp) для данного тега tag или возвращает значение def, если тег недопустим.
"Отпечаток" тега - это условное числовое значение, указывающее на то, изменился ли тег.
Для численных тегов это просто значение, а для строкового тега - это хэш (контрольная сумма).
Использование "отпечатка" позволяет унифицировать обновление тегов.
Для целочисленного или вещественного тега его отпечаток совпадает со значением тега (iGetTag/rGetTag).
Для строкового тега отпечаток вычисляется с помощью функции HashIndexOf.
Функция используется совместно с ShouldRefresh для проверки необходимости обновления DIM сервисов,
или других объектов, зависящих от значений тегов.
Например:
if ShouldRefresh(upd1,GetStampOfTag(tag1,0))+
ShouldRefresh(upd2,GetStampOfTag(tag2,0))>0
then begin
DIM_UpdateTag(tag1,''); // Обновить с текущим значением тегов
Success('Один из тегов изменился, надо обновлять DIM сервис.');
end;
Достоинством GetStampOfTag является то, что он одинаково работает с тегами всех типов, включая строковый,
и не требует проверки типа тега (эта проверка делается внутри функции).
function ClickIsLocal:Boolean;
- возвращает true, если текущий "клик" (графическое событие) является локальным,
т.е. сгенерировано на локальной системе клавиатурой или мышью.
Удаленные события следует обрабатывать отдельно от локальных, т.к. они зачастую предполагают различную реакцию на событие.
Например, локальное событие может быть послано от клиента к серверу. Сервер, приняв событие (как удаленное), его исполняет.
function ClickIsRemote:Boolean;
- возвращает true, если текущий "клик" (графическое событие) является удаленным,
т.е. сгенерировано на удаленной системе, а затем доставлено по сети и помещено в очередь командой ClickWrite.
Удаленные события следует обрабатывать отдельно от локальных, т.к. они зачастую предполагают различную реакцию на событие.
Например, локальное событие может быть послано от клиента к серверу. Сервер, приняв событие (как удаленное), его исполняет.
function DIM_Avail:Boolean;
- проверяет доступность DIM (фактически - наличие сервера &DimSrv).
function DIM_GuiAvail:Boolean;
- проверяет доступность графического интерфейса DIM (фактически - наличие сервера &DimSrv и тега DIM_GuiClickTag).
function DIM_GuiClickAvail:Boolean;
- проверяет инициализацию графического интерфейса DIM (фактически - наличие тега DIM_GuiClickTag).
см. функцию DIM_GuiClickInit.
function DIM_GuiClickFallBackMode:Integer;
- проверяет режим работы графического интерфейса DIM сервера.
Если функция возвращает 0, это обычный (сетевой) режим работы DIM сервера.
Если функция возвращает -1, DIM сервер отсутствует (тогда используется резервный режим работы).
Если функция возвращает ненулевое значение, DIM сервер использует резервный режим работы
(по тегу DIM_GuiClickFallBackModeTag).
см. описание функции DIM_GuiClickSend.
function DIM_IsClientMode:Boolean;
function DIM_IsServerMode:Boolean;
- возвращает декларируемый режим работы устройства - клиент или сервер DIM.
Фактически проверяет биты внутренней переменной режима DIM_ClientServerMode (но это может измениться в будущем).
Декларируемый режим работы задается через параметры конфигурации DimClientMode=1 или DimServerMode=1.
Декларировать режим работы нужно потому, что зачастую клиент и сервер используют один и тот же программный код
(хотя он физически может находиться на разных машинах), поэтому нужен способ различать, в каком качестве
программа намерена работать в данной конфигурации.
Декларирование режима работы еще не означает доступности DIM-сервера, это лишь "декларация о намерениях"
программы работать в выбранной роли клиента или сервера.
Алгоритмы работы прикладных программ зачастую определяются именно декларируемой ролью, независимо от фактического
состояния связи.
procedure DIM_Post(msg:String);
- посылает сообщение в консоль &DimSrv, без добавления EOL,
используя синхронный метод (devsend) или асинхронный (devpost).
Это важно - вы должны не забывать сами добавлять EOL при вызове, иначе будут сбои связи.
Данные передаются "как есть", без каких-либо преобразований, с необходимыми проверками и фиксацией ошибок.
Смотрите также пример DEMO_ALIPHOSCOOL.
- процедура служит для обновления DIM-сервиса, связанного с данным тегом. Обновлять сервис можно по любому тегу,
входящему в DIM - сервис. Но обычно сервисы идентифицируются и обновляются по первому тегу сервиса,
так быстрее идет поиск.
function DIM_Decrypt(Data,Key:String):String;
function DIM_EncryptCommand(Data:String):String;
function DIM_DecryptCommand(Data:String):String;
function DIM_EncryptAccess(Data:String):String;
function DIM_DecryptAccess(Data:String):String;
- функции шифрования-дешифрации (encrypt-decrypt). Используются для повышения защищенности DIM-серверов.
Для шифрования используется метод DIM_CryptKind: 0=использовать текущий метод (см.crypt_ctrl),
1=Blowfish, 2=Gost, 3=RC2, 4=RC4, 5=RC5, 6=RC6, 7=MIME, 8=HEX, 9=NONE. Три последних метода на самом деле
шифрование не используют (зато работают быстро, см. Cryptographer).
В случае MIME данные передаются в виде mime_encode, в случае HEX - в виде
hex_encode, в случае NONE - открытым текстом, "как есть". Во многих случаях
это вполне допустимый вариант. При необходимости защиты рекомендуется использовать RC2..RC6, так как они работают
довольно быстро (чего не скажешь о Blowfish,Gost).
DIM_Encrypt,DIM_Decrypt - вспомогательные функции, явно они не используются.
DIM_EncryptCommand,DIM_DecryptCommand - используются для шифрования-дешифрации команд и нужны
для скрытия конфиденциальных данных (имени пользователя, пароля и т.д.) при передаче команд DIM-серверу.
DIM_EncryptAccess,DIM_DecryptAccess - используются для шифрования и дешифрации таблицы прав доступа [TrustedUsers].
- служит для определения прав доступа удаленного клиента.
Сама таблица прав доступа находится в секции [TrustedUsers].
При получении запроса клиента данные дешифруются и сравниваются с таблицей прав доступа.
Возвращается True, если клиент (User,Host,IP,MAC) имеет право на запрошенный уровень доступа Guard.
Смотрите также пример DEMO_ALIPHOSCOOL.
Смотрите также пример DEMO_RDMS.
- служит для чтения текущих параметров безопасности (Name):
DIM_GuardParams('Guard') - текущий уровень доступа (Root,User,Guest,Deny),
DIM_GuardParams('User') - имя пользователя, для идентифиукации пользователей,
DIM_GuardParams('Host') - имя сервера, для логической идентификации компьютера,
DIM_GuardParams('IP') - IP адрес компьютера для его идентификации по сетевому адресу,
DIM_GuardParams('MAC') - MAC адрес сетевой карты для аппаратной идентификации компьютера,
DIM_GuardParams('') - список всех перечисленных параметров с именами и разделителями строк.
procedure DIM_GuiClickSendToServer(data:String);
- служит для посылки графического события (удаленному) серверу (путем сообщения серверу &DimSrv).
Для посылки используется командный сервис, связанный со строковым тегом DIM_GuiClickTag,
который должен быть инициализирован процедурой DIM_GuiClickInit и связан с командным сервисом.
Данные data для пересылки обычно генерируются функцией DIM_GuiClickCopy, которая копирует текущее
локальное событие в строковый буфер (обычно это DIM_GuiClickBuff).
Для безопасности данные передаются в зашифрованном виде.
procedure DIM_GuiClickSendLoopBack(data:String);
- служит для посылки графического события локальному серверу в резервном режиме.
Работает аналогично DIM_GuiClickSendToServer, только вместо посылки DIM-серверу используется посылка
в локальную консоль.
Это позволяет работать (локально) при отсутствии DIM-сервера или при недоступной сети.
procedure DIM_GuiClickSend(data:String);
- служит для посылки графического события либо удаленному сетевому серверу
(через вызов DIM_GuiClickSendToServer),
либо в консоль локального сервера
(через вызов DIM_GuiClickSendLoopBack).
Выбор способа посылки осуществляется по результатам вызова функций
DIM_GuiClickFallBackMode и
DIM_IsServerMode.
Использование резервного режима работы (FallBackMode) позволяет работать в условиях,
когда сервер &DimSrv не запущен или когда сеть недоступна.
function DIM_GuiClickCopy:String;
- служит для копирования (copy) графического события (GUI click) в строковый буфер (обычно DIM_GuiClickBuff).
Все поля события считываются из очереди событий и упаковываются в длинную строку текста.
Эта строка текста может быть передана серверу для обработки вызовом DIM_GuiClickSend:
if ClickWhat<>0 then begin // Если было событие
DIM_GuiClickBuff:=DIM_GuiClickCopy; // Прочитать его в буфер
DIM_GuiClickBuff:=DIM_GuiClickBuff+EOL+'NewValue=123'+EOL; // При необходимости можно добавить туда что-то еще
DIM_GuiClickSend(DIM_GuiClickBuff); // Переслать буфер серверу
end;
Собственно, переменная DIM_GuiClickBuff введена для того, чтобы иметь свободу обрабатывать "клики",
полученные в буфере функцией DIM_GuiClickCopy, перед посылкой серверу командой DIM_GuiClickSend.
- служит для сканирования (scan) буфера (ClickBuff) графического события (GUI click) и извлечения параметра с именем (ParamName).
Используется при анализе данных графического события после копирования в строковый буфер.
btn:=DIM_GuiClickScan(DIM_GuiClickBuff,'Button'); // Прочитать код кнопки с которой связано событие
dev:=DIM_GuiClickScan(DIM_GuiClickBuff,'Device'); // Прочитать имя устройства с которым связано событие
win:=DIM_GuiClickScan(DIM_GuiClickBuff,'Window'); // Прочитать имя окна с которым связано событие
sen:=DIM_GuiClickScan(DIM_GuiClickBuff,'Sensor'); // Прочитать имя сенсора с которым связано событие
val:=DIM_GuiClickScan(DIM_GuiClickBuff,'Value'); // Прочитать значение сенсора с которым связано событие
crv:=DIM_GuiClickScan(DIM_GuiClickBuff,'Curve'); // Прочитать имя кривой с которой связано событие
tag:=DIM_GuiClickScan(DIM_GuiClickBuff,'Tag'); // Прочитать имя тега с которым связано событие
- служит для проверки удаленного графического события (GUI click) по типу события при анализе данных на стороне сервера.
Проверяет права доступа вызовом GrantAccessDim, а затем возвращает значение типа события (ClickWhat).
При отказе в доступе или при ошибке возвращает ноль. Функция используется приемником при обработке сообщения @DimGuiClick.
- служит для упаковки данных при генерации искусственных графических событий (например, программно генерируемых нажатий сенсора).
Возвращает длинную строку буфера события, в которой упакованы основные данные события (тип события What, кнопка Button, положение X,Y, время ms, устройство Device, окно Window,
сенсор Sensor, тег Tag, кривая Curve, значение сенсора Value) и параметры безопасности (Guard,User,Host,IP,MAC).
function Dim_GuiConsoleRecv(DevName,ClickBuff:String):String;
- служит для посылки и приема строки данных или команды data в виртуальную консоль устройства с именем DevName.
Функция Dim_GuiConsoleSend используется на стороне передатчика.
Строка передаваемой команды data передается в поле Value искусственно скомпонованного события с параметрами:
What=MOUSEDOWN, Button=VK_LBUTTON, Device=DevName, Window=Console DevName, Sensor=Input.
Передача команд через искусственно сформированное сообщение требуется в том случае, если надо передавать события,
которые не имеют конкретной привязки к окнам и сенсорам - например, консольные команды.
Вызов Dim_GuiConsoleSend дает защищенный способ передачи команд серверу через тот же механизм,
что и события нажатия сенсоров на мнемосхеме.
Функция Dim_GuiConsoleRecv используется на стороне приемника.
Кроме имени устройства DevName ей передается строковый буфер события ClickBuff.
Если передается пустая строка ClickBuff='', то вместо буфера данные считываются из буфера текущего события (ClickParams).
Предполагается, что данные были записаны туда автоматически при обработке сообщения @DimGuiClick после получения данных из сети.
Функция Dim_GuiConsoleRecv проверяет поля события (what,button,device,window,sensor) и возвращает команду только если все
поля заполнены правильно. Учитывая защиту данных при передаче, это хороший способ безопасно передавать команды и небольшие данные серверу.
Однако, учитывая высокие накладные расходы, не следует передавать таким образом большой поток данных.
Предполагается, что трафик команд от клиента к серверу небольшой, а основной поток данных генерирует сервер, передавая
измеренные данные клиентам.
- выполняет инициализацию системы обработки удаленного доступа.
Аргумент GuiClickParams состоит из слов, которые задают список параметров:
Далее, в переменной [Device] TrustedUsers задается секция с описанием прав доступа, по умолчанию [TrustedUsers].
В этой секции задается таблица прав доступа (имеет смысл для сервера), например:
[TrustedUsers]
;guard user host IP MAC
* . . . .
* * * * *
[]
guard = уровень доступа (locked,guest,user,root)
user = имя пользователя
host = имя компьютера
IP = IP адрес
MAC = MAC адрес
. = текущее значение параметра для сервера
* = любое значение параметра
Например
* . * 192.168.0.10 * - разрешить доступ любого уровня для удаленного пользователя, имя которого совпадает
с именем пользователя сервера, с IP адресом 192.168.0.10 и любым MAC
Кроме того, в переменной [Device] EncryptMethod задается метод шифрования, используемый при пересылке команд
из списка (DEFAULT,BLOWFISH,GOST,RC2,RC4,RC5,RC6,BASE64,HEX,NONE), по умолчанию используется BASE64,
рекомендуемое значение RC6.
Шифрование используется для предотвращения возможности несанкционированной посылки команд серверу.
Также при инициализации считываются переменные [Device] DimServerMode, DimClientMode,
которые задают декларируемый режим работы программы в качестве клиента или сервера,
их указание обязательно.
см. DIM_IsClientMode.
procedure InitStdDims;
procedure FreeStdDims;
procedure PollStdDims;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Рекомендации по написанию DIM приложений
1) Для начала надо включить в конфигурацию сервер &DimSrv:
[ConfigFileList] ; Добавляем конфигурацию
ConfigFile = ~~\Resource\DaqSite\Default\DimSrv.cfg ; Стандартная конфигурация
[]
[&DimSrv] ; Надо также добавить:
DIM_TASK = DEMO/SERVER ; Имя задачи для сервера (уникальное в пределах сети)
DIM_DNS_NODE = . ; Имя DIM DNS - сервера имен DIM. Точка означает localhost.
[]
а также надо включить в конфигурацию таблицу прав доступа, например:
[TrustedUsers] ; Секция прав доступа или "доверенных пользователей"
;guard user host IP MAC ; Параметры: * значит "любой", . значит "текущий"
* . . . . ; Разрешить всё текущему пользователю на сервере
* * * * * ; Разрешить всё всем удаленным клиентам
root main daq-host 192.168.0.1 * ; Разрешить уровень root пользователю main компьютера daq-host с данным IP
[]
2) В каждом устройстве, которое будет выступать в качестве DIM сервера или клиента можно (но необязательно)
задать секцию прав доступа (TrustedUsers) и метод шифрования (EncryptMethod=DEFAULT,BLOWFISH,GOST,RC2,RC4,RC5,RC6,BASE64,HEX,NONE).
Если эти параметры не указаны, по умолчанию принимается [TrustedUsers] и BASE64.
Кроме того, надо задать параметры режима работы DIM: сервер (DimServerMode=1) или клиент (DimClientMode=1).
Это нужно для того, чтобы указать программе, в каком качестве он будет работать в данной конфигурации.
Как правило, для сокращения объема и во избежание дублирования кода одна и та же программа работает как сервер или клиент,
в зависимости от декларируемого режима.
[DeviceList]
[&DEMO.CTRL = device software program
[&DEMO.CTRL]
TrustedUsers = [TrustedUsers] ; Задает секцию прав доступа
EncryptMethod = RC6 ; Задает метод шифрования
DimServerMode = 1 ; Задает режим сервера DIM
DimClientMode = 0 ; Задает режим клиента DIM
...
[]
3) Для получения возможности обработки графических событий надо завести строковый тег для обмена данными,
а в стартовой процедуре вызвать DIM_GuiClickInit() с именем этого тега, например:
[TagList]
DEMO.DIMGUICLICK = string * ; The DIM GUI click sensor data
[]
...
procedure InitApplication;
begin
DIM_GuiClickInit('DEMO.DIMGUICLICK');
...
end;
Эта процедура инициализирует тег DIM_GuiClickTag, используемый в процедурах приемо-передачи событий.
4) Надо завести для графического тега командные сервисы DIM:
; Приемник (только на стороне сервера)
[&DimSrv.ServiceList] ; Имя секции описания сервисов (постоянное)
DIS_CMND-DEMO.DIMGUICLICK = dis_cmnd DEMO/DIMGUICLICK ; Декларация приемника, имя сервиса уникально в пределах сети
[DIS_CMND-DEMO.DIMGUICLICK] ; Секция приемника, имя секции уникально в пределах конфигурации
tag DEMO.DIMGUICLICK ; Тег приемо-передачи графических событий
devPostMsg &DEMO.CTRL @DIMGUICLICK=%** ; Присылать сообщение устройству для обработки
[]
; Передатчик (на обеих сторонах)
[&DimSrv.ServiceList] ; Имя секции описания сервисов (постоянное)
DIC_CMND-DEMO.DIMGUICLICK = dic_cmnd DEMO/DIMGUICLICK ; Декларация передатчика, имя сервиса как у приемника
[DIC_CMND-DEMO.DIMGUICLICK] ; Секция передатчика, имя секции уникально в пределах конфигурации
tag DEMO.DIMGUICLICK ; Тег приемо-передачи графических событий
[]
dis_cmnd = dim command server - сервис для приема команд на стороне сервера
dic_cmnd = dim command client - сервис для передачи команд от клиента к серверу
Заметим, что на стороне клиента используется только секция передатчика, а на стороне сервера - обе секции
приемо-передачи, т.к. сервер посылает события в сеть DIM и сам же их получает.
Это сделано для того, чтобы не дублировать локальный и удаленный GUI.
5) Надо завести другие теги и сервисы (информационные и командные) для приема или передачи измеряемых данных.
Например, для передачи данных о времени сервера можно использовать такой сервис:
; Передатчик (только на стороне сервера)
[&DimSrv.ServiceList] ; Имя секции описания сервисов (постоянное)
DIS_INFO-DEMO.TIME = dis_info DEMO/TIME ; Декларация передатчика, имя сервиса уникально в пределах сети
[DIS_INFO-DEMO.TIME] ; Секция передатчика, имя секции уникально в пределах конфигурации
tag DEMO.TIME ; Тег приемо-передачи нужной информации
[]
; Приемник (только на стороне клиента)
[&DimSrv.ServiceList] ; Имя секции описания сервисов (постоянное)
DIC_INFO-DEMO.TIME = dic_info DEMO/TIME ; Декларация приемника, имя сервиса как у передатчика
[DIC_INFO-DEMO.TIME] ; Секция приемника, имя секции уникально в пределах конфигурации
tag DEMO.TIME ; Тег приемо-передачи нужной информации
devPostMsg &DEMO.CTRL @DimTagUpdate=DEMO.TIME ; Присылать сообщение устройству для обработки
[]
dis_info = dim information server - сервис для передачи данных от сервера ко клиенту
dic_info = dim information client - сервис для приема данных на стороне клиента
Сервисы могут включать один или несколько тегов. Если в сервис входит строковый тег, он должен идти последним.
Сообщения (devPostMsg или devSendMsg) необязательны, они используются для уведомления клиентских программ о
поступлении данных, а также могут передавать сами данные (если нужна очередь событий).
6) В цикле опроса надо поместить примерно такой код (показан схематично):
var updTest1,updTest2:Real; // Variables uses to update DIM services
{
GUI polling.
}
procedure GUI_POLL;
var s:String;
begin
DIM_GuiClickBuff:=''; s:='';
{
Handle user mouse/keyboard clicks...
ClickWhat=(cw_Nothing,cw_MouseDown,cw_MouseUp,cw_MouseMove,cw_KeyDown,cw_KeyUp,cw_MouseWheel,...)
ClickButton=(VK_LBUTTON,VK_RBUTTON,VK_CANCEL,VK_MBUTTON,VK_BACK,VK_TAB,VK_CLEAR,VK_RETURN,...)
}
if ClickWhat<>0 then
repeat
{
Copy GUI click to DIM buffer for remote execution.
}
DIM_GuiClickBuff:=DIM_GuiClickCopy;
{
Handle MouseDown/KeyDown
}
if (ClickWhat=cw_MouseDown) or (ClickWhat=cw_KeyDown) then begin
{
Handle Left mouse button click
}
if (ClickButton=VK_LBUTTON) then begin
{
Handle local clicks (works on client side)
}
if ClickIsLocal then begin
{
Local button click. Send it to DIM Server.
}
if ClickTag<>0 then begin
if (ClickTag=Test1ButtonTag) then DIM_GuiClickSend(DIM_GuiClickBuff+'NewValue='+Str(iXor(iGetTag(ClickTag),1)));
if (ClickTag=Test2ButtonTag) then DIM_GuiConsoleSend(DevName,'@Test2Button '+Str(iGetTag(ClickTag)));
end;
{...etc...}
end;
{
Handle remote clicks (works on server side)
Handle remote clicks comes from DIM via @DimGuiClick message.
@DimGuiClick default handler decode and write events to FIFO,
so we can find it as clicks and can handle it in usual way.
}
if ClickIsRemote then begin
{
Handle remote console commands...
}
s:=Dim_GuiConsoleRecv(DevName,'');
if Pos('@',s)=1 then DevSendCmdLocal(s); // @Test2Button received, execute it ...
{
Handle remote sensor clicks...
}
if TypeTag(ClickTag)>0 then begin
s:=ClickParams('NewValue');
if Length(s)>0 then begin
if (ClickTag=Test1ButtonTag) then UpdateTag(ClickTag,s,0,1); // Test1ButtonTag click received with NewValue ...
end;
end;
{...etc...}
end;
end;
end;
until (ClickRead=0);
{
Update services on server side...
}
if DIM_IsServerMode then begin
if ShouldRefresh(updTest1,iGetTag(Test1ButtonTag))>0 // If Test1ButtonTag changed
then DIM_UpdateTag(Test1ButtonTag,''); // then update DIM service
if ShouldRefresh(updTest2,iGetTag(Test2ButtonTag))>0 // If Test1ButtonTag changed
then DIM_UpdateTag(Test2ButtonTag,''); // then update DIM service
end;
{ ...etc... }
DIM_GuiClickBuff:=''; s:='';
end;
{...etc...}
{
Process data coming from standard input...
}
procedure StdIn_Processor(var Data:String);
var cmd,arg:String; tag:Integer;
begin
{
Handle "@cmd=arg" or "@cmd arg" commands:
}
cmd:=''; arg:='';
if GotCommand(Data,cmd,arg) then begin
{
@DimGuiClick - will be handled by StdIn_DefaultHandler(Data,cmd,arg);
}
{
@DimTagUpdate tag
}
if IsSameText('@DimTagUpdate',cmd) then begin
if DIM_IsClientMode and not DIM_IsServerMode then begin
tag:=FindTag(Trim(arg));
if tag=Test1ButtonTag then bNul(iSetTag(tag));
if tag=Test2ButtonTag then bNul(iSetTag(tag));
end;
end else
{
Handle other commands by default handler...
}
StdIn_DefaultHandler(Data,cmd,arg);
end;
Data:=''; cmd:=''; arg:='';
end;
{...etc...}
Здесь видно, что на стороне клиента (в секции ClickIsLocal) локальные нажатия на кнопки
(для примера Test1Button, Test2Button) отсылаются серверу двумя разными методами
- DIM_GuiClickSend и DIM_GuiConsoleSend.
Сервер (в секции ClickIsRemote) принимает события и выполняет какие-то действия.
Кроме того, сервер проводит измерения и публикует данные в секции DIM_IsServerMode.
А клиент получает данные из сети в секции DIM_IsClientMode
с помощью обновления тегов, а также получения сообщений (например, @DimTagUpdate).
Несмотря на условность, приведенный схематический код позволяет понять структуру программ приемо-передачи DIM.
Специально следует оговорить некоторые потенциальные трудности при создании распределенных систем.
Пожалуй, наиболее логически сложным вопросом является разделение кода клиента и сервера с помощью функций
DIM_IsServerMode, DIM_IsClientMode, ClickIsLocal, ClickIsRemote.
Дело в том, что клиент и сервер, которые могут находится на разных компьютерах,
обязательно имеют общий программный код и общие конфигурационные файлы.
Значительная часть кода является общей для клиента и сервера, что не удивительно, учитывая то обстоятельство,
что клиент является удаленной копией ("аватаром") сервера и работает с теми же данными.
Однако часть работы клиент и сервер выполняют по-разному.
Например, сервер выполняет измерения и публикует данные в сети.
А клиент берет даннные из сети, не проводя измерений, но должен обновлять свои теги при получении данных из сети.
Таким образом, часть клиент-серверного кода надо различать.
Для этого различения и служат функции DIM_IsServerMode, DIM_IsClientMode, ClickIsLocal, ClickIsRemote.
Они помогают разделить те "ветки" кода, которые работают по-разному на стороне клиента или сервера.
При этом прикладной программист всегда должен следить за тем, в какой "ветке" кода он находится - серверной,
клиентской или общей.
В процессе разработки всегда нужно держать в уме вопрос "в какой ветке кода я сейчас работаю?".
Таким образом, разработка клиент-серверных программ требует повышенной дисциплины при кодировании.
Модуль StdWebs
предоставляет набор функций, облегчающих работу с сервером &WebSrv,
и позволяющих вести разработку распределенных систем управления на основе Web - технологий
в рамках шаблона прикладной программы template.pas.
{
Example: @HTTP_REQUEST_ACCEPTED=&WEBSRV,63295844422438,1049397,1049374
}
if IsSameText(cmd,HTTP_REQUEST_ACCEPTED_CMD) then begin
WEB_HandleRequest(Data);
Data:='';
end else
... другие сообщения ...
Константы
HTTP_REQUEST_OK (HTTP запрос допустим к обработке),
HTTP_REQUEST_NO (HTTP запрос не найден),
HTTP_REQUEST_BAD (HTTP запрос недопустим)
- определяют статус обработки сообщения @HTTP_REQUEST_ACCEPTED, который возвращает функция
WEB_CheckRequest.
Sender - ссылка устройства Web-сервера, приславшего HTTP запрос для обработки.
ReqTime - время прихода HTTP запроса, ms.
Request - ссылка на текст, содержащий HTTP запрос.
Reply - ссылка на текст, куда надо помещать ответ на HTTP запрос.
Собственно, задачей Web-сервера является корректное заполение этого текста содержимым ответа,
то есть текстом HTML-страницы для отсылки клиенту.
По принятому соглашению, помещение Dump(0) в конце текста маркирует конец формирования Web-страницы.
Это делается вызовом WEB_ReplyEnd.
После этого сервер &WebSrv отсылает сформированную страницу клиенту.
TrustedUsers - ссылка на текст, с таблицей прав доступа к сайту.
CryptKind - указание на используемый метод шифрования: 0..9=Current,Blowfish,Gost,RC2,RC4,RC5,RC6,MIME,HEX,NONE
QueryCount - число параметров GET-запроса (Query), передаваемого через строку адреса (URL).
Например http://akuryaki-xp/cgi-bin/websrv.cgi?Action=View&Mode=1. Запрос Query отделяется от адреса
знаком вопроса, элементы списка запроса разделяются амперсандом & и имеют вид name=value.
QueryItems - ссылка на текст со списком параметров GET-запроса (Query).
CookieCount - число элементов в списке куков (Cookie).
Список куков хранит информацию о сеансе связи с сервером (например, пароли или ключи доступа).
CookieItems - сссылка на текст со списком параметров Cookie.
ContentCount - число элементов в списке POST-запроса (Content).
ContentItems - ссылка на текст со списком параметров POST-запроса (Content).
Этот список используется при передаче данных HTTP запроса методом POST.
GatewayInterface - версия CGI протокола.
RequestMethod - метод передачи данных (поддерживается GET или POST).
QueryString - строка URL-запроса (Query).
Content - строка POST-запроса (Content).
ContentLength - длина строки POST-запроса (Content).
ContentType - тип POST-запроса (например, application/x-www-form-urlencoded для форм).
ServerName - имя разработкика Web-сервера (например, RITLABS S.R.L.).
ServerPort - порт сервера.
ServerProtocol - тип протокола сервера (например, HTTP/1.1).
ServerSoftware название программы Web-сервера (например, TinyWeb/1.93).
RemoteHost - имя компьютера удаленного клиента, приславшего HTTP-запрос.
RemoteAddr - IP адрес удаленного клиента.
ScriptName - имя CGI-скипта с точки зрения клиента (например, /cgi-bin/websrv.cgi).
PathInfo - короткое имя CGI скрипта (например, /websrv.cgi).
PathTranslated - путь CGI скрипта с точки зрения сервера (например, C:\CRW32EXE\DEMO\DEMO_WEB4DAQ\websrv.cgi).
WebSrvName - имя устройства Web-сервера, приславшего HTTP запрос (обычно &WebSrv).
WebSrvSite - имя Web-сайта.
WebSrvRoot - локальный путь корневого каталога Web-сайта с точки зрения сервера. Сточки зрения клиента это всегда "/".
WebSrvPort - номер порта Web-сервера, обычно 80.
WebSrvIndex - локальный путь индексного файла, с которого начинается просмотр Web-сайта, с точки зрения сервера.
UserName - имя пользователя (для аутентификации).
Password - пароль пользователя (для доступа).
DateTime - строка с текущим временем сервера.
CustomStr - строка дополнительных данных (пока не используется).
function WEB_Decrypt(Data,Key:String):String;
function WEB_EncryptCookie(Data:String):String;
function WEB_DecryptCookie(Data:String):String;
function WEB_EncryptAccess(Data:String):String;
function WEB_DecryptAccess(Data:String):String;
- функции шифрования-дешифрации (encrypt-decrypt). Используются для повышения защищенности создаваемых сайтов.
Для шифрования используется метод WEB.CryptKind: 0=использовать текущий метод (см.crypt_ctrl),
1=Blowfish, 2=Gost, 3=RC2, 4=RC4, 5=RC5, 6=RC6, 7=MIME, 8=HEX, 9=NONE. Три последних метода на самом деле
шифрование не используют (зато работают быстро, см. Cryptographer).
В случае MIME данные передаются с в виде mime_encode, в случае HEX - в виде
hex_encode, в случае NONE - открытым текстом, "как есть". Во многих случаях
это вполне допустимый вариант. При необходимости защиты рекомендуется использовать RC2..RC6, так как они работают
довольно быстро (чего не скажешь о Blowfish,Gost).
WEB_Encrypt,WEB_Decrypt - вспомогательные функции, явно они не используются.
WEB_EncryptCookie,WEB_DecryptCookie - используются для шифрования-дешифрации Cookie и нужны
для скрытия конфиденциальных данных (имени пользователя, пароля и т.д.).
WEB_EncryptAccess,WEB_DecryptAccess - используются для шифрования и дешифрации таблицы прав доступа [TrustedUsers].
Смотрите также пример DEMO_WEB4DAQ.
- внутренняя (вспомогательная) функция, проверяет права пользователя (User,Host,IP,Password) с помощью таблицы [TrustedUsers].
Возвращает предоставленный уровень доступа (deny,guest,user,root). Напрямую не используется - вместо нее используется
WebAccesGranted (а она вызывает GrantAccessWeb внутри себя).
- основная функция проверки прав доступа.
При mode=0 использует текущие параметры (WEB.UserName, WEB.RemoteHost, WEB.RemoteAddr, WEB.Password) для вызова GrantAccessWeb.
Этот случай используется один раз, на этапе прохождения процедуры аутентификации (Login), при получении формы с именем
пользователя и паролем.
При mode=1 параметры (UserName,Password) извлекаются из Cookie и дешифруются.
Этот режим используется для всех страниц после прохождения процедуры аутентификации (Login).
Возвращает предоставленный уровень доступа (deny,guest,user,root).
Смотрите также пример DEMO_WEB4DAQ.
- очистка записи web после обработанного HTTP запроса.
Вызывается после обработки запроса для подготовки к следующему запросу.
procedure WEB_Free;
- внутренние (вспомогательные) процедуры инициализации и завершения.
Пользователю не надо их явно вызывать - это делается автоматически, в рамках шаблона прикладной программы
template.pas.
- основная процедура формирования Web-страницы. Записывает строку s в текст WEB.Reply.
Строка s должна содержать очередную строку текста формируемого HTML документа.
- добавляет к формируемой Web-странице текст t. Бывает полезно, если в формируемый документ надо вставить
предварительно подготовленный текст.
- записывает маркер конца текста: WEB_Reply(Dump(0)); Это служит сигналом того, что документ
сформирован и готов к отправке. Поэтому WEB_ReplyEnd должен стоять последним, завершающим вызовом
при формировании Web - страницы.
- функция "маскировки" специальных символов для формирования Web-страницы из "простого текста" (plain text).
Используется для вставки в документ фрагментов простого текста, который преобразуется так, что его можно вставлять
с помощью WEB.Reply в текст формируемого HTML документа.
Заменяет в строке s символы &,<,> на их HTML - представления
(&, <, >).
- функция отмены "маскировки" специальных символов для формирования "простого текста" (plain text) из Web-страницы.
Заменяет в строке s HTML - представления (&, <, >)
на символы &,<,>.
- записывает в текст WEB.Reply "замаскированную" строку s.
Применяется для добавления фрагментов "простого текста" (plain text).
- добавляет к формируемой Web-странице текст t, "маскируя" каждую строку.
Применяется для добавления фрагментов "простого текста" (plain text).
procedure WEB_InitJsonReply;
- формирует HTTP - заголовки для HTML документов и JSON передач, соответственно.
Эти процедуры являются внутренними и явно не используются.
function WEB_GetCookieItem(Name:String; var Item:String):Boolean;
function WEB_GetContentItem(Name:String; var Item:String):Boolean;
- функции извлечения по имени Name параметров Item строки URL-запроса (Query), куков (Cookie),
и POST-данных (Content). HTTP использует два способа передачи параметров запроса - GET и POST.
При методе GET параметры запроса (Query) передаются в строке адреса (URL).
При методе POST параметры запроса передаются в теле запроса (Content).
Куки (Cookie) передаются сервером клиенту (Web-браузеру), который потом включает эти куки в каждый запрос к этому сайту.
Необходимость Cookie связана с тем, что в HTTP нет постоянного соединения клиент-сервер, каждый запрос
независим от других. Так вот в Cookie хранится информация, нужная для поддержания сеанса связи клиент-сервер.
Например, в Cookie может храниться имя и пароль пользователя, чтобы сервер мог определить, кто к нему обращается.
Если поиск по имени прошел удачно, то в Item записывается значение переменной с запрошенным именем и возвращается True.
Если имя не найдено, возвращается False. Пример:
if WEB_GetQueryItem('Action',s) then writeln('В строке запроса есть пункт Action='+s);
if WEB_GetCookieItem('UserName',s) then writeln('В куках есть имя пользователя UserName='+s);
if WEB_GetContentItem('x1',s) then writeln('В теле POST-запроса есть пункт x1='+s);
Смотрите также пример DEMO_WEB4DAQ.
function WEB_IsCookieItem(Name,Value:String):Boolean;
function WEB_IsContentItem(Name,Value:String):Boolean;
- функции проверяют, равен ли элемент с именем Name значению Value для строки запроса (Query),
куков (Cookie) и тела запроса (Content), соответственно. Применяется, если ожидается какое-то определенное
значение параметра при запросе. Например:
if WEB_IsQueryItem('Action','Login') then WebPageLogin; // При запросе ?Action=Login открываем страницу Login.
if WEB_IsQueryItem('Action','Plot') then WebPagePlot; // При запросе ?Action=Plot открываем страницу Plot.
Смотрите также пример DEMO_WEB4DAQ.
- проверяет, находится ли тип HTTP запроса в списке InList. Например:
if WEB_IsRequestMethod('GET') then writeln('Этот HTTP-запрос имеет тип GET.');
if not WEB_IsRequestMethod('GET,POST') then writeln('Поддерживаются только методы GET и POST.');
procedure WEB_KillJsLibrary(Name:String);
- процедуры работы с библиотеками скриптов JavaScript.
В системном каталоге ресурсов Resource\Tools\JavaScript есть несколько библиотек скриптов, в настоящее
время основной библиотекой у нас будет flot.
Для использования библиотеки скриптов в Web сервере эти библиотеки должны быть скопированы в каталог
запущенной конфигурации, так как для Web сервера доступны только внутренние каталоги запущенной
DAQ-системы.
Вызов WEB_CopyJsLibrary('flot') скопирует каталог Crw32exe\Resource\Tools\JavaScript\flot
в локальный каталог ..\Utility\JavaScript\flot. После этого библиотека будет доступна на сайте по пути
/Utility/JavaScript/flot - именно так этот каталог выглядит с точки зрения удаленного клиента.
Этот вызов надо делать при старте системы, чтобы подготовить нужные библиотеки, взяв их копию из ресурсов пакета.
Вызов WEB_KillJsLibrary('flot') напротив удалит локальный каталог ..\Utility\JavaScript\flot,
когда он уже не нужен.
Этот вызов можно делать при завершении работы, чтобы ненужная больше локальная копия библиотеки не занимала место.
Использование этих процедур позволит не заботиться о наличии библиотек скриптов - они всегда доступны
из системных ресурсов пакета.
Прикладным системам также не придется заботиться об обновлении библиотек скриптов - они будут браться из текущей
версии дистрибутива пакета.
Смотрите также пример DEMO_WEB4DAQ.
procedure WEB_KillCssLibrary(Name:String);
- процедуры работы с библиотеками CSS - стилей StyleSheet.
В системном каталоге ресурсов Resource\Tools\StyleSheet есть несколько библиотек стилей, в настоящее время
основной библиотекой у нас будет alex.
Для использования библиотеки стилей в Web сервере эти библиотеки должны быть скопированы в каталог
запущенной конфигурации, так как для Web сервера доступны только внутренние каталоги запущенной
DAQ-системы.
Вызов WEB_CopyCssLibrary('alex') скопирует каталог Crw32exe\Resource\Tools\StyleSheet\alex
в локальный каталог ..\Utility\StyleSheet\alex. После этого библиотека будет доступна на сайте по пути
/Utility/StyleSheet/alex - именно так этот каталог выглядит с точки зрения удаленного клиента.
Этот вызов надо делать при старте системы, чтобы подготовить нужные библиотеки, взяв их копию из ресурсов.
Вызов WEB_KillCssLibrary('alex') напротив удалит локальный каталог ..\Utility\StyleSheet\alex,
когда он уже не нужен.
Этот вызов можно делать при завершении работы, чтобы ненужная больше локальная копия библиотеки не занимала место.
Использование этих процедур позволит не заботиться о наличии библиотек стилей - они всегда доступны
из системных ресурсов пакета.
Прикладным системам также не придется заботиться об обновлении библиотек стилей - они будут браться из текущей
версии дистрибутива пакета.
Смотрите также пример DEMO_WEB4DAQ.
- вернет True, если клиент заполнил форму (поля ввода, кнопки и т.д.) и послал ее серверу.
Признаком этого служит метод запроса (POST) и тип содержимого (application/x-www-form-urlencoded).
В этом случае Web-серверу надо анализировать Content и извлекать оттуда данные, которые ввел в форму
удаленный клиент. Функция применяется при анализе запроса клиента.
Смотрите также пример DEMO_WEB4DAQ.
- устанавливает мета-теги типа содержимого (http-equiv="Content-Type") и кодировки символов (charset).
Мета-теги должны входить в заголовок документа, поэтому процедуру надо вызывать в блоке <HEAD> ... </HEAD>.
Параметр Data, устанавливает MIME-тип (обычно text/html;) содержимого
и кодировку (обычно charset=utf-8).
Если Data -пустая строка, принимается значение Data='text/html; charset=utf-8'.
В результате вставляется мета-тег типа:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Смотрите также пример DEMO_WEB4DAQ.
- устанавливает мета-теги кеширования и автоматического обновления (Refresh).
Мета-теги должны входить в заголовок документа, поэтому процедуру надо вызывать в блоке <HEAD> ... </HEAD>.
Мета-параметры кэширования (отключение кэша) делаются всегда, так как формируются динамические страницы и кэширование не нужно.
Если Period>=0, устанавливает период автоматического обновления (мета-тег Refresh) страницы (в секундах).
Если Period=-1, автоматическое обновление не устанавливается. Можно указать также Link, если надо через
заданное время автоматически перейти на другую страницу.
Смотрите также пример DEMO_WEB4DAQ.
- устанавливает мета-теги куков (Cookie).
Мета-теги должны входить в заголовок документа, поэтому процедуру надо вызывать в блоке <HEAD> ... </HEAD>.
Необходимость Cookie связана с тем, что в HTTP нет постоянного соединения клиент-сервер, каждый запрос
независим от других. Так вот в Cookie хранится информация, нужная для поддержания сеанса связи клиент-сервер.
Например, в Cookie может храниться имя и пароль пользователя, чтобы сервер мог определить, кто к нему обращается.
Сервер устанавливает и посылает клиенту куки, чтобы клиент потом включал эти куки в каждый последующий запрос.
По этим кукам сервер определяет, кто его вызывает. куки могут содержать дополнительную информацию, нужную серверу для
обслуживания запросов клиентов.
Смотрите также пример DEMO_WEB4DAQ.
- устанавливает связь (Link) с файлом стилей CSS. В настоящее время предлагается всегда использовать
/Utility/StyleSheet/alex/default.css. Процедуру надо вызывать в блоке <HEAD> ... </HEAD>.
Смотрите также пример DEMO_WEB4DAQ.
- добавляет ссылку на JavaScript файл.
Например, часто будет использоваться библиотека /Utility/JavaScript/flot/jquery.min.js.
Обычто вызов процедуры делается в блоке <HEAD> ... </HEAD>.
- вставляет в документ красочное сообщение, если отключены скрипты JavaScript.
- анализирует поступивший через консольное сообщение Msg HTTP запрос и заполняет запись WEB.
Возвращает HTTP_REQUEST_OK, если запрос выглядит "правильным", а запись WEB
подготовлена к дальнейшей обработке.
Возвращает HTTP_REQUEST_NO, если сообщение Msg не содержит
ожидаемой команды @HTTP_REQUEST_ACCEPTED.
Возвращает HTTP_REQUEST_BAD, если запрос не может быть обработан (поврежден).
Пример:
{
General procedure to process HTTP request (main web page selector).
All HTTP request data is already located in WEB record when Processing called.
User should add HTML page via WEB_Reply(...) and call WEB_ReplyEnd in the end.
}
procedure WEB_Processing;
begin
if not WEB_IsRequestMethod('Get,Post')
then WEB_ErrorPage('Bad RequestMethod='+WEB.RequestMethod) else
if WEB_IsQueryItem('Action','Login') then WEB_LoginPage else
if WEB_IsQueryItem('Action','Home') then WEB_HomePage else
if WEB_IsQueryItem('Action','View') then WEB_ViewPage else
if WEB_IsQueryItem('Action','Edit') then WEB_EditPage else
if WEB_IsQueryItem('Action','Plot') then WEB_PlotPage else
if WEB_IsQueryItem('Action','Echo') then WEB_EchoPage else
WEB_ErrorPage('Запрошенная функция не существует.');
end;
{
Handle @HTTP_REQUEST_ACCEPTED=ArgList message.
}
procedure WEB_HandleRequest(Msg:String);
var Status:Integer;
begin
Status:=WEB_CheckRequest(Msg);
if Status=HTTP_REQUEST_OK then begin
if DebugEcho then WEB_EchoPage else WEB_Processing;
end else
if Status=HTTP_REQUEST_BAD then WEB_ErrorPage('Invalid HTTP request.');
WEB_Clear;
end;
Смотрите также пример DEMO_WEB4DAQ.
function WEB_ReadJsFromCfg(txt:Integer;Section:String):Integer;
- функции загрузки текста скрипта JavaScript из секции, расположенной соответственно в Pas - файле
исходного текста программы DaqPascal или Cfg - файле конфигурации. Расположение текста JavaScript в секции, помещенной
внутрь комментариев (* *) программы DaqPascal, или в секции конфигурационного файла позволяет держать код сервера
(DaqPascal) и клиента (JavaScript) в одном месте, что бывает удобно.
Смотрите также пример DEMO_WEB4DAQ.
procedure JSON_Write(Data:String);
procedure JSON_BeginPage;
procedure JSON_EndPage;
procedure JSON_AddKey(Key:String);
procedure JSON_AddKeyStr(Key,Value:String;Separator:Char);
procedure JSON_AddKeyNum(Key:String;Value:Real;Separator:Char);
procedure JSON_AddStr(Value:String;Separator:Char);
procedure JSON_AddNum(Value:Real;Separator:Char);
procedure JSON_BeginArray;
procedure JSON_EndArray(Separator:Char);
procedure JSON_BeginObject;
procedure JSON_EndObject(Separator:Char);
- эти процедуры служат для формирования JSON - ответов на AJAX запросы.
AJAX (Asyncrhronous Javascript And XML) - технология, при которой клиентский код на JavaScript
сам периодически посылает запросы серверу, обрабатывает ответ и обновляет отдельные элементы документа
без перезагрузки всей страницы. Это позволяет резко сократить трафик и избежать "моргания" страниц.
Для передачи данных от сервера клиенту в AJAX обычно используется формат JSON (JavaScript Object Notation).
Это гибкий формат, позволяющий описать весьма сложные структуры данных.
В рамках этого формата существуют 1)значения, 2)пары "ключ": значение, 3){объекты}, 4)[массивы].
В качестве значения могут выступать 1)число, 2)"строка" 3){объект} 4)[массив].
Пара "ключ": значение определяет элемент объекта, доступ к которому осуществляется по ключу.
Объекты помечаются фигурными скобками {}. Они содержат список элементов, разделенных запятыми.
Элементами служат пары "ключ": значение, доступ к элементам объекта - по именам ключей.
Массивы помещаются в квадратные скобки []. Они содержат список безымянных значений, разделенных запятыми.
Доступ к элементам массива - по индексу (номеру). Каждый элемент объекта или массива также может содержать значение
в виде объекта или массива, так что структура может иметь любой уровень сложности, за счет рекурсии.
Наконец, на самом верхнем уровне JSON - страница - это объект, то есть все данные помещаются в фигурные скобки.
Пример JSON - страницы:
{
"SinData": [ "On", "477.855", "-1.602", "1.133", 1, 2, 0.1 ],
"HostTime": "2012.03.16-19:21:42", "HostDate": "2012.03.16-19:21:43",
"HostInfo": "AKURYAKI-XP", "AccessId": "Guest"
}
Она создана вызовами:
JSON_BeginPage; // {
JSON_AddKey('SinData'); // "SinData":
JSON_BeginArray; // [
JSON_AddStr('On'),_Comma); // "On",
JSON_AddStr('477.855',_Comma); // "477.855",
JSON_AddStr('-1.602',_Comma); // "-1.602",
JSON_AddStr('1.133',_Comma); // "1.133",
JSON_AddNum(1,_Comma); // 1,
JSON_AddNum(2,_Comma); // 2,
JSON_AddNum(0.1,_Space); // 0.1
JSON_EndArray(_Comma); // ],
JSON_Flush; // New line, for nice view
JSON_AddKeyStr('HostTime','2012.03.16-19:21:42',_Comma);// "HostTime": "2012.03.16-19:21:42",
JSON_AddKeyStr('HostDate','2012.03.16-19:21:43',_Comma);// "HostDate": "2012.03.16-19:21:43",
JSON_AddKeyStr('HostInfo','AKURYAKI-XP',_Comma); // "HostInfo": "AKURYAKI-XP",
JSON_AddKeyStr('AccessId','Guest',_Space); // "AccessId": "Guest"
JSON_EndPage; // }
При формировании JSON - страницы следует придерживаться таких правил:
Смотрите также пример DEMO_WEB4DAQ.
procedure InitStdWebs;
procedure FreeStdWebs;
procedure PollStdWebs;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdDlls
предоставляет набор функций, облегчающих работу с загружаемыми DLL программами (см. DaqDllInit),
в рамках шаблона прикладной программы template.pas.
- переменная содержит ссылку на библиотеку DLL плагина, если она загружена вызовом STD_DLL_INIT,
или содержит ноль. Эта переменная используется для стандартного (упрощенного и наиболее часто используемого)
механизма загрузки и вызова DLL плагинов.
Загружает DLL-библиотеку, вызывает команду инициализации DAQ_DLL_INIT и возвращает ссылку на библиотеку.
Если загрузка и инициализация прошли неуспешно, возвращает ноль. Процедура вызывается пользователем при старте системы.
- вызывает команду завершения библиотеки DAQ_DLL_FREE, освобождает (выгружает) DLL-библиотеку,
и зануляет ссылку hDll на библиотеку. То есть выполняет все, что надо для правильного завершения
работы с загруженной DLL-библиотекой. Процедура вызывается пользователем при завершении работы системы.
- выполняет опрос библиотеки, то есть вызывает команду DAQ_DLL_POLL.
Вызывается пользователем в цикле опроса программы.
- загружает библиотеку плагина DLL и сохраняет её ссылку в стандартную переменную STD_DLL_REF.
Вызывается пользователем при инициализации программы, если требуется стандартная загрузка DLL плагина
и стандартная его обработка (вызов плагина в цикле опроса, выгрузка плагина при завершении работы).
Аргумент arg задает имя параметра INI файла, содержащее путь файла DLL плагина.
Если аргумент пуст, используется параметр DLL_FILE_PATH.
Использование стандартного алгоритма обработку сильно упрощает обслуживание DLL плагина,
т.к. в этом случае от пользователя требуется только инициализировать загрузку DLL библиотеки плагина
вызовом STD_DLL_INIT(). После этого библиотечный код сам будет вызывать плагин в цикле опроса
и освободит (выгрузит) его в конце работы. Главным ограничением стандартного алгоритма DLL плагина
является фиксированный алгоритм работы - вызов плагина в каждом цикле опроса, завершение в конце работы.
Если требуется другая дисциплина (например, вызов плагина только по событию), следует отказаться от стандартного
механизма опроса и использовать явные вызовы DLL_INIT/FREE/POLL.
- вспомогательная функция, которая читает и адаптирует имя файла библиотеки плагина DLL для загрузки.
Аргумент arg задает имя параметра INI файла, содержащее путь файла DLL плагина.
Если аргумент пуст, используется параметр DLL_FILE_PATH.
Функция адаптирует имя файла библиотеки плагина DLL по правилам текущей платформы,
чтобы имя стало пригодным для загрузки библиотеки.
Обычно эта функция вызывается автоматически при вызове STD_DLL_INIT, поэтому её редко требуется вызывать явно.
- выполняет опрос стандартной библиотеки плагина, то есть вызывает для неё команду DAQ_DLL_POLL.
Активизируется после инициализации стандартной библиотеки плагина, т.е. вызова STD_DLL_INIT.
Вызывается автоматически в цикле опроса программы. Пользователю не надо вызывать её самостоятельно.
- освобождает (выгружает) стандартную библиотеку плагина STD_DLL_REF и очищает ссылку.
Обычно пользователю не надо вызывать её самостоятельно, т.к. она вызывается автоматически при завершении программы.
Однако при необходимости досрочной выгрузки библиотеки можно её вызывать.
- очищает ссылку стандартной библиотеки плагина STD_DLL_REF.
Вызывается автоматически при старте программы. Пользователю не надо вызывать её самостоятельно.
procedure InitStdDlls;
procedure FreeStdDlls;
procedure PollStdDlls;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdUtils
предоставляет набор утилит разного назначения,
в рамках шаблона прикладной программы template.pas.
- открывает Web - браузер для отображения файла (или сайта).
- вызывает утилиту rtmshare.exe, которая публикует локальный каталог (делает доступным в сети).
Полный путь локального каталога задается в Local.
Сетевой путь опубликованного каталога задается в Share.
Опции Options задают права доступа, комментарии и т.д.
TimeOut задает максимальное время ожидания результатов работы утилиты.
Утилита полезна, если система носит распределенный характер и ей требуется публиковать каталог данных для
удаленного доступа.
Подробности можно узнать, выполнив команду rtmshare.exe /?
Пример:
RtmShareExe('\\server\test','e:\test','/GRANT Все:READ'
+' /GRANT Администраторы:"FULL CONTROL"'
+' /GRANT "Опытные пользователи":CHANGE'
+' /REMARK:"Test share"');
RtmShareExe('\\server\test','','/DELETE');
- открывает окно с заголовком Title, сообщением Message, шириной окна (window width) WW,
размером фонта (font size) FS, задержкой на экране (timeout) не более TM секунд.
Окно является неблокирующим (немодальным), что вместе с возможностью задания размеров и времени автоматического закрытия
делает эту процедуру удобной альтернативой процедуры Warning(...).
Основная цель процедуры - вывод ненавязчивых уведомляющих сообщений о наступлении тех или иных событий,
без блокировки экрана и обязательного закрытия окна кнопкой.
Если оператор ничего не предпринимает, окно закрывается само, по времени. А если оператору надо что-то быстро предпринять,
неблокирующее окно удобнее, так как не мешает оператору в доступе к интерфейсу управления.
Еще одним достоинством является то, что UnixWBox оставляет сообщение в консоли, то есть журналируется.
Пример:
UnixWBox("Уведомление","Операция успешно завершена.",600,16,15);
procedure UnixTooltipNotifier(args:String);
- открывает всплывающее окно рядом с системной областью уведомлений с заданным в аргументах args сообщением.
Окно является неблокирующим (немодальным), не сбивает текущий фокус ввода, что вместе с возможностью задания
размеров и времени автоматического закрытия делает эту процедуру удобной альтернативой процедуры Warning(...).
Основная цель процедуры - вывод ненавязчивых уведомляющих сообщений о наступлении тех или иных событий,
без блокировки экрана и обязательного закрытия окна кнопкой.
Если оператор ничего не предпринимает, окно закрывается само, по времени. А если оператору надо что-то быстро предпринять,
неблокирующее окно удобнее, так как не мешает оператору в доступе к интерфейсу управления.
Пример:
UnixTooltipNotifier('text "Операция успешно завершена." delay 15000 trans 255 bkColor green textColor 0x000000'
+' font "Arial Black" fontSize 16 ico notify.ico audio notify.wav');
или
ShowTooltip('text "Операция успешно завершена." delay 15000 preset stdSuccess');
где:
text "Операция успешно завершена." - выводимый текст, обязательно в кавычках
delay 15000 - автоматическое закрытие через 15000 ms
trans 255 - прозрачность (transparency), 0..255
bkColor green - цвет фона по имени (red,green,...) или HEX RGB
textColor 0x000000 - цвет текста, HEX RGB, 0xRRGGBB
font "Arial Black" fontSize 16 - имя фонта и размер, pt
ico notify.ico - имя файла картинки (exe,dll,ico)
audio notify.wav - имя звукового файла (wav)
preset stdSuccess - набор параметров stdSuccess (успешное выполнение)
Процедура UnixTooltipNotifier считается устаревшей и сохранена для совместимости.
UnixTooltipNotifier отличается от ShowTooltip тем, что UnixTooltipNotifier вызывает команду
@run -hide unix tooltip-notifier ... , а ShowTooltip вызывает более короткую и новую команду @tooltip ... .
Подробнее см. вывод команды @tooltip.
- открывает стандартное всплывающее справочное окно для отображения сообщения msg
с задержкой (временем экспозиции) delay миллисекунд.
Во всплывающем сообщении также помещается кнопка или ссылка на справку по текущей DAQ системе, взятой по ссылке [DAQ] Help.
Эту процедуру рекомендуется применять для отображения справки (обычно по правой кнопке) на сенсорах (отсюда и название процедуры).
{
Procedure to show sensor help
}
procedure SensorHelp(s:String);
begin
StdSensorHelpTooltip(s,15000);
end;
- функция считывает (при RW='R') или записывает (при RW='W') группу тегов из/в INI файл, с заданным
режимом mode. Эта функция решает задачу загрузки и сохранения настроечных параметров измерительной системы,
что бывает необходимо при создании автоматически загружаемых, автономно работающих конфигураций, которые при старте
сами загружают параметры настройки из INI файла, а также могут сохранять состояние, по команде оператора
или (если надо) автоматически.
Обычно делается так.
Оператор настраивает нужные ему параметры измерительной системы на данной установке и нажимает кнопку "Save INI",
чтобы запомнить это состояние на диске. Это запомненное состояние затем загружается с диска, либо как начальное состояние
при старте, либо по команде оператора "Read INI". Это позволяет облегчить конфигурирование сложных систем, содержащих
большое число настроечных параметров.
Имя файла задается относительно основного конфигурационного файла, например, ..\Config\Custom.ini.
Если имя файла не задано (пустое), то оно берется из ReadIni('CustomIniFileRef').
Теги хранятся в формате "Имя=Значение".
Строковые теги могут содержать пробелы, но не должны содержать управляющих символов (CR,LF и т.д.).
При чтении тегов сначала считываются данные из Custom-секции конфигурационного файла,
а затем из Custom-секции INI-файла. Это сделано для того, чтобы можно было задать значения
тегов "по умолчанию" в конфигурационном файле. Таким образом, теги могут задаваться в трех местах.
В секции [TagList] конфигурационного файла при объявлении тегов всегда задаются их начальные значения.
Затем значения тегов считываются из Custom-секции конфигурационного файла, если эта секция есть.
Затем значения тегов считываются из Custom-секции INI-файла, если он существует.
Таким образом, просто удалив INI-файл, и затем вызвав функцию чтения, можно вернуться к значениям по умолчанию,
которые заданы в конфигурационном файле.
Если имя Custom-секции не задано (пустое), то оно берется из ReadIni('CustomIniSection').
Кроме TagList-секции в группу сохраняемых/загружаемых тегов помещаются теги, которые записаны в
Custom-секции конфигурационного файла, а также в Custom-секции INI-файла.
То есть если тег поместить в Custom-секцию конфигурационного или INI-файла, то он тоже будет
включен в группу, как и теги, описанные в TagList-секции. Это позволяет избежать дублирования тегов
в списках TagList-секции и Custom-секции конфигурационного файла. Одновременное включение
тега в эти списки тоже допустимо, оно не приведет к многократной записи иди чтению тега.
Если имя TagList-секции не задано (пустое), то оно берется из ReadIni('CustomIniTagList').
Имя каталога задается относительно основного конфигурационного файла, например, ..\Config\Custom.ini.
Если имя Backup-каталога не задано (пустое), то оно берется из ReadIni('CustomIniBackups').
1) iNul(CustomIniRW('R','',0)); чтение
iNul(CustomIniRW('W','',0)); запись
Этот пример читает/записывает группу тегов из/в INI файл.
Устройство должно содержать в своей секции описание такого вида:
[&DeviceSection] ; Секция описания устройства
CustomIniFileRef = ..\Config\Custom.ini ; Ссылка на INI файл для записи
CustomIniSection = [CustomParameters] ; Имя секции где храняться параметры
CustomIniTagList = [CustomParameters.TagList] ; Имя секции со списком сохраняемых тегов
CustomIniBackups = ..\Data\Custom ; Имя каталога для резервных копий INI файлов
[CustomParameters.TagList] ; Секция со списком сохраняемых тегов
TagList = tag1,tag2,tag3,tag4 ; TagList = ИмяТега1, ИмяТега2, ...
TagList = tag5,tag6,tag7,... ; Список может быть большим
[CustomParameters] ; Секция со значениями тегов
tag1=1235 ; в формате Имя=Значение
tag2=5.67 ;
tag3=abcd ;
[]
2) iNul(CustomIniRW('R','..\Config\Custom.ini [CustomParameters] [CustomParameters.TagList] ..\Data\Custom',2));
iNul(CustomIniRW('W','..\Config\Custom.ini [CustomParameters] [CustomParameters.TagList] ..\Data\Custom',2));
Этот пример делает то же самое, только тут имена файлов и секций заданы явно.
Кроме того, отключен диалог с сообщением о статусе операции.
- функция возвращает список окон (графиков и таблиц), разделенный символами comma (обычно это запятая), в которых присутствует
кривая, заданная ссылкой crv.
crv:=RefFind('Curve demo'); // Находим ссылку по имени кривой
lst:=WinListByCurve(crv,','); // Получаем список окон, где она присутствует
Writeln('Кривая demo входит в окна:'+lst); // Печатаем этот список в консоли
- функция открывает (рисует и выделяет) окна (кривых и таблиц), в которые входит кривая, заданная ссылкой crv,
а также выделяет в этих окнах кривую, заданную ссылкой selcrv.
if ClickButton=1 then begin // Если нажат сенсор
ClickCurve:=RefFind('Curve '+ClickParams('Curve')); // Находим ассоциированную кривую
if ClickCurve<>0 then begin // Если кривая найдена
iNul(WinSelectByCurve(ClickCurve,ClickCurve)); // Открываем окна графиков, где есть эта кривая и выделяем её
bNul(Voice(snd_Wheel)); // Выдаем звук
end;
end;
- функция возвращает ссылку устройства (Device), по номеру n в списке устройств [DeviceList],
или ноль, если задан несуществующий номер устройства.
n:=0;
repeat
dev:=DeviceListEnum(n);
if dev<>0 then Writeln('Device '+Str(i)+' is '+RefInfo(dev,'Name'));
n:=n+1;
until dev=0;
- функция находит и открывает диалог редактирования калибровки, найденной по кривой crv, к которой эта калибровка относится.
Предполагается, что кривая подключена к аналоговому выходу AnalogOutput номер n программного устройства (device software program),
который по номеру cовпадает с номером калибровки Calibration#n.
Эта функция дает возможность пользователю редактировать калибровку по нажатию поля, ассоциированного с некоторой кривой.
if ClickButton=1 then begin // Если нажат сенсор
ClickCurve:=RefFind('Curve '+ClickParams('Curve')); // Находим ассоциированную кривую
if ClickCurve<>0 then begin // Если кривая найдена
bNul(CalibrOpenByCurve(crv)); // Открываем калибровку
bNul(Voice(snd_Wheel)); // Выдаем звук
end;
end;
procedure InitStdUtils;
procedure FreeStdUtils;
procedure PollStdUtils;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdDiesel
содержит
константы, переменные и функции для организации запуска, завершения и связи с программами DieselPascal
в рамках шаблона прикладной программы template.pas.
_con_StdDiesel.inc
- файл описания констант модуля StdDiesel.
Пока файл пуст.
_var_StdDiesel.inc
- файл описания переменных модуля StdDiesel.
Переменная
ShouldPollStdDiesel
содержит флаг опроса модуля. Менять её нежелательно.
Переменная
LM9_Flag_RedirectRxToStdIn
содержит флаг перенаправления потока данных из каналов связи IPC программ LM9 в консоль StdIn.
По умолчанию флаг включен, т.е. все данные из каналов связи по умолчанию передаются в консоль ввода для обработки.
Предполагается, что по каналам связи передаются команды, обрабатываемые обычным способом.
Если нужна другая обработка, то флаг отключается и обработка делается самостоятельно.
_fun_StdDiesel.inc
- файл описания процедур и функций модуля StdDiesel.
function DieselPascalAssocLm9Exe:String;
- функция возвращает полный путь исполняемого файл для запуска файлов типа *.lm9.
При инсталляции DieselPascal это будет программа CrossMachine.exe, cлужащая для запуска программ *.lm9.
function DieselPascalDManagerExe:String;
- функция возвращает полный путь программы DManager.exe, которая управляет проектами DieselPascal.
function DieselPascalCrossMachineExe:String;
- функция возвращает полный путь программы CrossMachine.exe, которая запускает программы DieselPascal.
function DieselPascalCrossDesignerExe:String;
- функция возвращает полный путь программы CrossDesigner.exe, которая служит для создания и редактированияя проектов DieselPascal.
function HasDieselPascal:Boolean;
- функция проверяет доступность DieselPascal и возможность запуска программ *.lm9.
Функции для организации канала межпроцессной связи EasyIpc с программами DieselPascal:
function EasyIpc_Valid(hIpc:Integer):Boolean; - проверка годности ссылки канала связи
function EasyIpc_Connected(hIpc:Integer):Boolean; - статус подключения
function EasyIpc_IsServer(hIpc:Integer):Boolean; - флаг работы в роли сервера
function EasyIpc_IsClient(hIpc:Integer):Boolean; - флаг работы в роли клиента
function EasyIpc_FileName(hIpc:Integer):String; - UNC имя файла канала связи (\\.\pipe\demo)
function EasyIpc_PipeName(hIpc:Integer):String; - имя канала связи (demo для сервера, .\demo для клиента)
function EasyIpc_HostName(hIpc:Integer):String; - имя сервера (только для клиента)
function EasyIpc_BaseName(hIpc:Integer):String; - базовое имя канала связи (demo)
function EasyIpc_Handle(hIpc:Integer):Integer; - файловая ссылка (handle) канала связи
function EasyIpc_TimeOut(hIpc:Integer):Integer; - время ожидания подключения
function EasyIpc_RxBuffSize(hIpc:Integer):Integer; - размер буфера Rx (приемника)
function EasyIpc_TxBuffSize(hIpc:Integer):Integer; - размер буфера Tx (передатчика)
function EasyIpc_RxLost(hIpc:Integer):Real; - счетчик потерянных байт Rx
function EasyIpc_TxLost(hIpc:Integer):Real; - счетчик потерянных байт Tx
function EasyIpc_RxTotal(hIpc:Integer):Real; - счетчик принятых байт Rx
function EasyIpc_TxTotal(hIpc:Integer):Real; - счетчик переданных байт Tx
function EasyIpc_RxLength(hIpc:Integer):Integer; - длина данных в FIFO буфере Rx, доступных для чтения
function EasyIpc_TxLength(hIpc:Integer):Integer; - длина данных в FIFO буфере Tx, ожидающих передачи
function EasyIpc_RxFifoLimit(hIpc:Integer):Integer; - предел объема данных в FIFO буфере Rx
function EasyIpc_TxFifoLimit(hIpc:Integer):Integer; - предел объема данных в FIFO буфере Tx
function EasyIpc_LogsHistory(hIpc:Integer):Integer; - предел буфера истории событий, строк
function EasyIpc_LogsCount(hIpc:Integer):Integer; - счетчик событий в буфере, строк
function EasyIpc_LogsTextMove(hIpc:Integer):String; - извлечение буфера событий и очистка
function EasyIpc_LogsTextCopy(hIpc:Integer):String; - извлечение буфера событий без очистки
function EasyIpc_Clear(hIpc:Integer; What:String):Boolean; - очистка * - всего
Функции для запуска программ DieselPascal, т.е. файлов *.LM9:
function LM9_TabCount:Integer - счетчик таблицы запущенных программ LM9
function LM9_TidByKey(key:String):Integer - идентификатор задачи (tid) по ключу (key) или 0
function LM9_KeyByTid(tid:Integer):String - ключ (key) по идентификатору задачи (tid) или пустая строка
function LM9_IndexOfKey(key:String):Integer - индекс ключа (key) в таблице или -1
function LM9_KeyByIndex(i:Integer):String - ключ (key) по индексу (i) в таблице или пустая строка
function LM9_IndexOfTid(tid:Integer):Integer - индекс задачи (tid) в таблице или 0
function LM9_TidByIndex(i:Integer):Integer - задача (tid) по индексу (i) или 0
function LM9_IpcByKey(key:String):Integer - канал связи (ipc) по ключу (key) или 0
function LM9_IpcByIndex(i:Integer):Integer - канал связи (ipc) по индексу (i) в таблице или 0
function LM9_IpcByTid(tid:Integer):Integer - канал связи (ipc) по идентификатору задачи (tid) или 0
function LM9_BufByKey(key:String):Integer - буфер текста (buf) по ключу (key) или 0
function LM9_BufByIndex(i:Integer):Integer - буфер текста (buf) по индексу (i) или 0
function LM9_BufByTid(tid:Integer):Integer - буфер текста (buf) по идентификатору задачи (tid) или 0
function LM9_ParamsByKey(key:String):String - параметры вызова (params) по ключу (key) или пустая строка
function LM9_ParamsByIndex(i:Integer):String - параметры вызова (params) по индексу или пустая строка
function LM9_ParamsByTid(tid:Integer):String - параметры вызова (params) по идентификатору задачи (tid) или пустая строка
function LM9_HasKey(key:String):Boolean - проверка наличия ключа (key) в таблице
function LM9_HasTid(tid:Integer):Boolean - проверка наличия идентификатора задачи (tid) в таблице
function LM9_Send(tid:Integer; data:String):Boolean - посылка программе LM9 с идентификатором задачи (tid) текста (data) в канал (ipc)
function LM9_Post(tid:Integer; data:String):Boolean - посылка программе LM9 с идентификатором задачи (tid) текста (data) в канал (ipc) с буферизацией
procedure LM9_KeyFree(key:String) - освободить программу LM9 по ключу (key)
procedure LM9_TidFree(tid:Integer) - освободить программу LM9 по идентификатору задачи (tid)
function LM9_Launch(lm9,arg,opt,inipar,ipcpar:String):Integer - запустить программу LM9 с заданным именем файла(lm9), аргументами (arg), опциями запуска (opt), начальными параметрами (inipar) и параметрами канала связи (ipcpar)
Эта группа функций LM9 для запуска программ DieselPascal довольно сложна и требует пояснений.
Программы DieselPascal хранятся в файлах *.LM9 и выполняются виртуальной машиной CrossMachine.exe.
В DaqPascal эти программы естественным образом представляются в виде запускаемых задач (task).
Однако, поскольку с этой задачей связаны дополнительные объекты (канал связи IPC, временный файл, параметры запуска, буфер текста),
то эти дополнительные объекты хранятся в таблице, элементы которой идентифицируются по номеру задачи (tid, task id).
Таблица позволяет быстро находить по номеру задачи все остальные связанные с ней объекты.
Элемент таблицы, т.е. задачи вместе с связанными с ней объектами, мы и будем называть программами LM9.
Программы LM9 хранятся во внутренней таблице со счетчиком LM9_TabCount.
Каждая программа LM9 после запуска функцией LM9_Launch помещается в таблицу и получает ключ (key),
идентификатор задачи (tid = task id), канал связи (ipc), текстовый буфер (buf) и блок параметров (params),
а также индекс (i) в таблице программ LM9.
Ключ (key) однозначно вычисляется по идентификатору задачи LM9_KeyByTid(tid), поэтому не требует отдельного хранения.
Основным параметром для идентификации программ является идентификатор задачи (tid), который возвращает
функция запуска программы LM9_Launch.
Это идентификатор задачи, созданный вызовом task_init,
который можно использовать в любых функциях работы с задачами.
Другие параметры извлекаются из таблицы по идентификатору задачи (tid), ключу (key) или индексу (i).
Индекс (i) используется только в циклах для перебора программ, он не является постоянным
и может изменяться при запуске новых программ.
Индекс меняется в диапазоне 0..LM9_TabCount-1.
В цикле обработки по индексу вычисляются ссылки (tid,ipc,buf) и дальнейшая работа идет уже с ними.
Канал связи (ipc = inter process communication) прозволяет обмениваться текстовыми данными с программой LM9
в реальном времени. Для создания канала надо указать параметр ipcpar при вызове LM9_Launch.
Предполагается, что данные представлены в виде текста со строками, разделенными EOL.
Передача данных в программу LM9 осуществляется вызовом LM9_Send или LM9_Post.
Вызов LM9_Send передает данные сразу и используется для передачи сообщений с минимальной задержкой.
Однако при большом числе сообщений из соображений производительности лучше использовать буферизацию.
Вызов LM9_Post передает данные не сразу, а использует внутренний буфер (buf) для накопления сообщений,
чтобы передать их одной пачкой в цикле опроса.
Буферизация используется для оптимизации производительности в случае, когда задержка передачи не важна.
Прием данных происходит следующим образом.
Если активен флаг LM9_Flag_RedirectRxToStdIn (по умолчанию он активен),
то прием данных из канала связи идет автоматически и направляется в консоль ввода,
чтобы его можно было обработать обычными средствами.
Этот режим удобен для обработки потока команд, однако при этом теряется информация об источнике данных,
т.е. о канале, из которого пришли данные.
Если это представляет интерес, то надо снять флаг LM9_Flag_RedirectRxToStdIn и обрабатывать ввод самостоятельно.
Для этого по идентификатору задачи (tid) надо получить ссылку на канал связи LM9_IpcByTid(ipc) и использовать функции
EasyIpc.
Параметры вызова (params) - это текст для передачи и хранения дополнительных параметров вызова программы LM9.
Этот текст формируется при вызове LM9_Launch из параметра (inipar) и других аргументов вызова.
Параметры вызова передаются программе LM9 через временный файл, имя которого генерируется автоматически
вызовом CreateTempFile и передается программе в аргументе командной строки
(--params=filename.tmp). Предполагается, что текст содержит строки, разделенные EOL.
В тексте, например, можно передавать параметры в виде выражений Name=Value, либо команды вида @cmd args.
Буфер (buf) используется для промежуточного накопления данных при вызове LM9_Post.
Следует иметь в виду, что буфер использует короткие строки (255 символов),
поэтому буферизацию LM9_Post следует использовать для передачи большого числа коротких сообщений,
а для посылки длинных сообщений надо использовать LM9_Send.
Для запуска программы LM9 надо вызвать LM9_Launch(lm9,arg,opt,inipar,ipcpar).
Здесь:
Для завершении работы с программой LM9 надо вызвать LM9_TidFree(tid)
для завершения задачи (tid), освобождения буфера (buf) и закрытия канала (ipc).
Например:
Запуск программы:
lm9:=LM9_Launch('demo.lm9','--demo','ProcessPriority=4','Volume=100','ipc'+EOL+'LogsHistory=32');
В цикле опроса:
if task_wait(lm9,0) then begin // Если программа работает
data:=StrFmt('RunCount=%g',RunCount); // Получить какие-то данные
bNul(LM9_Post(lm9,data)); // Послать данные в канал связи
end;
Завершение программы:
LM9_TidFree(lm9);
lm9:=0;
procedure ClearStdDiesel;
procedure InitStdDiesel;
procedure FreeStdDiesel;
procedure PollStdDiesel;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль StdAddons
предоставляет собой виртуальный модуль - заглушку,
специально для инкапсуляции
пользовательских констант, переменных и функций в системную библиотеку, то есть во все программы, созданные
в рамках шаблона прикладной программы template.pas.
procedure InitStdAddons;
procedure FreeStdAddons;
procedure PollStdAddons;
- обеспечивают очистку строк, инициализацию, завершение и цикл опроса модуля в рамках шаблона прикладной программы
template.pas.
Модуль PhysLibrary
Модуль PhysLibrary содержит функции
по общей физике и метрологии.
Содержит файлы
_con_PhysLibrary,
_fun_PhysLibrary,
_var_PhysLibrary,
_con_PhysUnits,
_fun_PhysUnits,
_var_PhysUnits,
_con_PhysVapor,
_fun_PhysVapor,
_var_PhysVapor,
_con_PhysRtdTc,
_fun_PhysRtdTc,
_var_PhysRtdTc,
Содержит константы
thermocouple_R,
thermocouple_S,
thermocouple_B,
thermocouple_J,
thermocouple_T,
thermocouple_E,
thermocouple_K,
thermocouple_N,
thermocouple_A1,
thermocouple_A2,
thermocouple_A3,
thermocouple_L,
thermocouple_M,
rtd_id_pt100_385,
rtd_id_pt100_391,
rtd_id_cu100_428,
rtd_id_cu100_426,
rtd_id_ni100_617,
Содержит переменные
thermocouple_cnt,
thermocouple_eps,
thermocouple_tol,
thermocouple_big,
thermocouple_del,
thermocouple_fac,
thermocouple_met,
thermocouple_mit,
restemperdet_cnt,
restemperdet_eps,
restemperdet_tol,
restemperdet_big,
restemperdet_del,
restemperdet_fac,
restemperdet_met,
restemperdet_mit,
Содержит функции
ClearPhysLibrary,
InitPhysLibrary,
FreePhysLibrary,
PollPhysLibrary,
thermocouple_emf,
thermocouple_lot,
thermocouple_hit,
thermocouple_idn,
thermocouple_ids,
thermocouple_ttc,
thermocouple_tst,
restemperdet_ohm,
restemperdet_lot,
restemperdet_hit,
restemperdet_idn,
restemperdet_ids,
restemperdet_ttc,
restemperdet_tst,
atm_to_mbar,
mbar_to_atm,
mmhg_to_mbar,
mbar_to_mmhg,
psi_to_mbar,
mbar_to_psi,
cels_to_kelv,
kelv_to_cels,
cels_to_fahr,
fahr_to_cels,
vapor_efactor,
vapor_pressure,
dewpoint_to_rh,
dewpoint_to_ppmv,
vapor_nFormula,
vapor_mFactor,
TestHumidityCalc.
Модуль NetLibrary
Модуль NetLibrary содержит средства для
поддержки сетевых протоколов, включая MODBUS.
Содержит файлы
_con_NetLibrary,
_fun_NetLibrary,
_var_NetLibrary,
_con_NetModbus,
_fun_NetModbus,
_var_NetModbus,
Содержит константы
modbus_MinUnitId,
modbus_MaxUnitId,
modbus_MaxPduLen,
modbus_MaxTcpLen,
modbus_MaxRtuLen,
modbus_MaxAscLen,
modbus_MaxCSGet,
modbus_MaxISGet,
modbus_MaxIRGet,
modbus_MaxHRGet,
modbus_MaxCSPut,
modbus_MaxHRPut,
modbus_0xFF00,
modbus_sc_off,
modbus_sc_on,
modbus_fn_ReadCS,
modbus_fn_ReadIS,
modbus_fn_ReadHR,
modbus_fn_ReadIR,
modbus_fn_WritSC,
modbus_fn_WritSR,
modbus_fn_ReadES,
modbus_fn_Diagn,
modbus_fn_EvCnt,
modbus_fn_EvLog,
modbus_fn_WritMC,
modbus_fn_WritMR,
modbus_fn_RepSID,
modbus_fn_ReadFR,
modbus_fn_WritFR,
modbus_fn_MaskWR,
modbus_fn_ReWrMR,
modbus_fn_ReadFQ,
modbus_fn_Escape,
modbus_er_OK,
modbus_er_ILLFUN,
modbus_er_ILLADR,
modbus_er_ILLVAL,
modbus_er_EXCEPT,
modbus_er_BADLEN,
modbus_er_BADCRC,
modbus_er_BADUID,
modbus_er_BADCMD,
modbus_er_BADREF,
modbus_MaxWord,
modbus_pr_IP,
modbus_pr_RTU,
modbus_pr_ASCII,
modbus_pr_NAMES,
modbus_pr_ALIAS,
modbus_sw_native,
modbus_sw_normal,
ModbusProxy,
Содержит переменные
modbus_errno,
modbus_MaxCSAddr,
modbus_MaxISAddr,
modbus_MaxIRAddr,
modbus_MaxHRAddr,
devModbusProxy,
hid_NetModbusLrc,
hid_NetModbusСrc,
Содержит функции
ClearNetLibrary,
InitNetLibrary,
FreeNetLibrary,
PollNetLibrary,
modbus_get_tag,
modbus_set_tag,
modbus_inc_tag,
modbus_fixerror,
modbus_swap_int16,
modbus_swap2,
modbus_swap4,
modbus_swap8,
modbus_dump_int16,
modbus_dump_int32,
modbus_dump_float,
modbus_dump_double,
modbus_take_int16,
modbus_take_int32,
modbus_take_float,
modbus_take_double,
modbus_addr_ok,
modbus_prot_ok,
modbus_prot_name,
modbus_prot_alias,
modbus_prot_byname,
modbus_calc_lrc,
modbus_calc_crc,
modbus_join_lrc,
modbus_join_crc,
modbus_encode_ip,
modbus_decode_ip,
modbus_encode_rtu,
modbus_decode_rtu,
modbus_encode_ascii,
modbus_decode_ascii,
modbus_encode_adu,
modbus_decode_adu,
modbus_is_except,
modbus_un_except,
modbus_encode_pdu,
modbus_decode_pdu,
modbus_proxy_poll,
modbus_proxy_nice,
modbus_proxy_reply
modbus_errmsg,
ClearNetModbus,
InitNetModbus,
FreeNetModbus,
PollNetModbus.
Модуль DbLibrary
Модуль DbLibrary содержит средства для
поддержки СУБД (Баз Данных) и является расширением DbApi.
Содержит файлы
_con_DbLibrary,
_fun_DbLibrary,
_var_DbLibrary,
_con_StdAdoDb,
Содержит константы
adOpenUnspecified
adOpenForwardOnly
adOpenKeyset
adOpenDynamic
adOpenStatic
adHoldRecords
adMovePrevious
adAddNew
adDelete
adUpdate
adBookmark
adApproxPosition
adUpdateBatch
adResync
adNotify
adFind
adSeek
adIndex
adLockUnspecified
adLockReadOnly
adLockPessimistic
adLockOptimistic
adLockBatchOptimistic
adOptionUnspecified
adAsyncExecute
adAsyncFetch
adAsyncFetchNonBlocking
adExecuteNoRecords
adConnectUnspecified
adAsyncConnect
adStateClosed
adStateOpen
adStateConnecting
adStateExecuting
adStateFetching
adUseNone
adUseServer
adUseClient
adUseClientBatch
adEmpty
adTinyInt
adSmallInt
adInteger
adBigInt
adUnsignedTinyInt
adUnsignedSmallInt
adUnsignedInt
adUnsignedBigInt
adSingle
adDouble
adCurrency
adDecimal
adNumeric
adBoolean
adError
adUserDefined
adVariant
adIDispatch
adIUnknown
adGUID
adDate
adDBDate
adDBTime
adDBTimeStamp
adBSTR
adChar
adVarChar
adLongVarChar
adWChar
adVarWChar
adLongVarWChar
adBinary
adVarBinary
adLongVarBinary
adChapter
adFileTime
adDBFileTime
adPropVariant
adVarNumeric
adFldUnspecified
adFldMayDefer
adFldUpdatable
adFldUnknownUpdatable
adFldFixed
adFldIsNullable
adFldMayBeNull
adFldLong
adFldRowID
adFldRowVersion
adFldCacheDeferred
adFldNegativeScale
adFldKeyColumn
adEditNone
adEditInProgress
adEditAdd
adEditDelete
adRecOK
adRecNew
adRecModified
adRecDeleted
adRecUnmodified
adRecInvalid
adRecMultipleChanges
adRecPendingChanges
adRecCanceled
adRecCantRelease
adRecConcurrencyViolation
adRecIntegrityViolation
adRecMaxChangesExceeded
adRecObjectOpen
adRecOutOfMemory
adRecPermissionDenied
adRecSchemaViolation
adRecDBDeleted
adGetRowsRest
adPosUnknown
adPosBOF
adPosEOF
adBookmarkCurrent
adBookmarkFirst
adBookmarkLast
adMarshalAll
adMarshalModifiedOnly
adAffectCurrent
adAffectGroup
adAffectAll
adAffectAllChapters
adResyncUnderlyingValues
adResyncAllValues
adCompareLessThan
adCompareEqual
adCompareGreaterThan
adCompareNotEqual
adCompareNotComparable
adFilterNone
adFilterPendingRecords
adFilterAffectedRecords
adFilterFetchedRecords
adFilterPredicate
adFilterConflictingRecords
adSearchForward
adSearchBackward
adPersistADTG
adPersistXML
adClipString
adPromptAlways
adPromptComplete
adPromptCompleteRequired
adPromptNever
adModeUnknown
adModeRead
adModeWrite
adModeReadWrite
adModeShareDenyRead
adModeShareDenyWrite
adModeShareExclusive
adModeShareDenyNone
adXactUnspecified
adXactChaos
adXactReadUncommitted
adXactBrowse
adXactCursorStability
adXactReadCommitted
adXactRepeatableRead
adXactSerializable
adXactIsolated
adXactCommitRetaining
adXactAbortRetaining
adXactAsyncPhaseOne
adXactSyncPhaseOne
adPropNotSupported
adPropRequired
adPropOptional
adPropRead
adPropWrite
adErrInvalidArgument
adErrNoCurrentRecord
adErrIllegalOperation
adErrInTransaction
adErrFeatureNotAvailable
adErrItemNotFound
adErrObjectInCollection
adErrObjectNotSet
adErrDataConversion
adErrObjectClosed
adErrObjectOpen
adErrProviderNotFound
adErrBoundToCommand
adErrInvalidParamInfo
adErrInvalidConnection
adErrNotReentrant
adErrStillExecuting
adErrOperationCancelled
adErrStillConnecting
adErrNotExecuting
adErrUnsafeOperation
adParamSigned
adParamNullable
adParamLong
adParamUnknown
adParamInput
adParamOutput
adParamInputOutput
adParamReturnValue
adCmdUnspecified
adCmdUnknown
adCmdText
adCmdTable
adCmdStoredProc
adCmdFile
adCmdTableDirect
adStatusOK
adStatusErrorsOccurred
adStatusCantDeny
adStatusCancel
adStatusUnwantedEvent
adRsnAddNew
adRsnDelete
adRsnUpdate
adRsnUndoUpdate
adRsnUndoAddNew
adRsnUndoDelete
adRsnRequery
adRsnResynch
adRsnClose
adRsnMove
adRsnFirstChange
adRsnMoveFirst
adRsnMoveNext
adRsnMovePrevious
adRsnMoveLast
adSchemaProviderSpecific
adSchemaAsserts
adSchemaCatalogs
adSchemaCharacterSets
adSchemaCollations
adSchemaColumns
adSchemaCheckConstraints
adSchemaConstraintColumnUsage
adSchemaConstraintTableUsage
adSchemaKeyColumnUsage
adSchemaReferentialConstraints
adSchemaTableConstraints
adSchemaColumnsDomainUsage
adSchemaIndexes
adSchemaColumnPrivileges
adSchemaTablePrivileges
adSchemaUsagePrivileges
adSchemaProcedures
adSchemaSchemata
adSchemaSQLLanguages
adSchemaStatistics
adSchemaTables
adSchemaTranslations
adSchemaProviderTypes
adSchemaViews
adSchemaViewColumnUsage
adSchemaViewTableUsage
adSchemaProcedureParameters
adSchemaForeignKeys
adSchemaPrimaryKeys
adSchemaProcedureColumns
adSchemaDBInfoKeywords
adSchemaDBInfoLiterals
adSchemaCubes
adSchemaDimensions
adSchemaHierarchies
adSchemaLevels
adSchemaMeasures
adSchemaProperties
adSchemaMembers
adSchemaTrustees
adSeekFirstEQ
adSeekLastEQ
adSeekAfterEQ
adSeekAfter
adSeekBeforeEQ
adSeekBefore
adCriteriaKey
adCriteriaAllCols
adCriteriaUpdCols
adCriteriaTimeStamp
adPriorityLowest
adPriorityBelowNormal
adPriorityNormal
adPriorityAboveNormal
adPriorityHighest
adResyncNone
adResyncAutoIncrement
adResyncConflicts
adResyncUpdates
adResyncInserts
adResyncAll
adRecalcUpFront
adRecalcAlways
stNoSchema
stTables
stSysTables
stProcedures
stColumns
stProcedureParams
stIndexes
stPackages
stSchemata
stSequences
stUnknown
stSelect
stInsert
stUpdate
stDelete
stDDL
stGetSegment
stPutSegment
stExecProcedure
stStartTrans
stCommit
stRollback
stSelectForUpd
detCustom
detPrepare
detExecute
detFetch
detCommit
detRollBack
detParamValue
detActualSQL
dsInactive
dsBrowse
dsEdit
dsInsert
dsSetKey
dsCalcFields
dsFilter
dsNewValue
dsOldValue
dsCurValue
dsBlockRead
dsInternalCalc
dsOpening
dsRefreshFields
sqSupportParams
sqSupportEmptyDatabaseName
sqEscapeSlash
sqEscapeRepeat
sqImplicitTransaction
sqLastInsertID
sqSupportReturning
sqSequences
ftUnknown
ftString
ftSmallint
ftInteger
ftWord
ftBoolean
ftFloat
ftCurrency
ftBCD
ftDate
ftTime
ftDateTime
ftBytes
ftVarBytes
ftAutoInc
ftBlob
ftMemo
ftGraphic
ftFmtMemo
ftParadoxOle
ftDBaseOle
ftTypedBinary
ftCursor
ftFixedChar
ftWideString
ftLargeint
ftADT
ftArray
ftReference
ftDataSet
ftOraBlob
ftOraClob
ftVariant
ftInterface
ftIDispatch
ftGuid
ftTimeStamp
ftFMTBcd
ftFixedWideChar
ftWideMemo
dfBinary
dfXML
dfXMLUTF8
dfAny
dfDefault
db_idns_uid,
db_idns_pwd,
db_idns_server,
db_idns_dbname,
db_brm_hide,
db_brm_echo,
db_brm_soft,
db_brm_hard,
db_sym_SQLite3,
db_sym_Firebird,
db_sym_PostgreSQL,
db_sym_MySQL,
db_sym_SYSDBA,
db_sym_masterkey,
db_sym_localhost,
db_sym_IBProvider,
db_sym_punctchars,
db_sym_SysIniFile,
db_sym_DbApiMasterKey,
db_sym_ConnectionString,
db_sym_ConnectionTimeout,
db_sym_CommandTimeout,
db_sym_IsolationLevel,
db_sym_Provider,
db_sym_Driver,
db_sym_DefaultDatabase,
db_sym_CursorLocation,
db_sym_Attributes,
db_sym_AbsolutePage,
db_sym_AbsolutePosition,
db_sym_TimeStampUser1,
db_sym_TimeStampUser2,
db_sym_TimeStampUser3,
db_sym_UserState,
db_sym_UserFlags,
db_sym_UserLink,
db_sym_UserData,
db_sym_Cookies,
db_sym_Bookmark,
db_sym_CacheSize,
db_sym_CursorType,
db_sym_Mode,
db_sym_Filter,
db_sym_Index,
db_sym_LockType,
db_sym_MarshalOptions,
db_sym_MaxRecords,
db_sym_PageCount,
db_sym_PageSize,
db_sym_RecordCount,
db_sym_Sort,
db_sym_Source,
db_sym_Status,
db_sym_StayInSync,
db_sym_GetString,
db_sym_CommandType,
db_sym_CommandText,
db_sym_Properties,
db_sym_Errors,
db_sym_ErrorsCount,
db_sym_ErrorsClear,
db_sym_ConnectionStringInit,
db_sym_TimeStampInit,
db_sym_TimeStampOpen,
db_sym_State,
db_sym_Version,
db_sym_EngineId,
db_sym_EngineName,
db_sym_ProviderNames,
db_sym_RecordsAffected,
db_sym_EditMode,
db_sym_BugsCount,
db_sym_BugsClear,
db_sym_TotalBugsCount,
db_sym_TotalBugsClear,
db_sym_BugReportPrefix,
db_sym_BugReportPrefixAll,
Содержит переменные
ShouldPollDbLibrary,
db_brm_uses,
Содержит функции
db_engine_list,
db_engine_uses,
db_engine_uses_name,
db_engine_uses_assign,
db_AdoTypeToString,
db_SqlDbTypeToString,
db_EngineFieldTypeToString,
db_FieldTypeToString,
db_AdoTypeToTagType,
db_SqlDbTypeToTagType,
db_EngineTypeToTagType,
db_FieldTypeToTagType,
db_AdoTypeIsDateTime,
db_SqlDbTypeIsDateTime,
db_EngineTypeIsDateTime,
db_FieldTypeIsDateTime,
db_EngineTypeIsBlob,
db_FieldTypeIsBlob,
db_DetectBlobImageType,
db_AdoDateTimeToMs,
db_MsToAdoDateTime,
db_SqlDbDateTimeToMs,
db_MsToSqlDbDateTime,
db_EngineDateTimeToMs,
db_MsToEngineDateTime,
db_FieldDateTimeToMs,
db_MsToFieldDateTime,
db_query_connectionstring,
db_subst_connectionstring,
db_sample_connectionstring,
db_read_connectionstring_samples,
db_query_dbapimasterkey,
db_build_connectionstring,
db_validate_adapt_dbname,
db_validate_known_providers,
db_build_selectalltables,
db_supporteddbtypes,
db_bugreport_mode,
db_just_sql_query,
db_easy_sql_query,
db_default_uid,
db_default_pwd,
db_sample_database_file,
db_detect_dbtype,
db_query_selectalltables,
db_query_showtables,
db_read_connectionstring_samples,
db_read_selectalltables_samples,
db_read_database_file_samples,
db_create_local_fdb,
ClearDbLibrary,
InitDbLibrary,
FreeDbLibrary,
PollDbLibrary,
Модуль StdMenuTools
Модуль StdMenuTools содержит средства для
организации меню Инструменты.
Содержит файлы
_man_stdmenutools,
_fun_stdmenutools,
Содержит функции
MenuConsolesStarter,
MenuConsolesHandler,
MenuEditProgramStarter,
MenuEditProgramHandler,
MenuDeviceRestartStarter,
MenuDeviceRestartHandler,
HasUserAccessLevelTip.
Техника программирования Cleanup
В практике прикладного программирования рекомендуется использовать технику Cleanup, описанную ниже.
Мотивация - дисциплинировать разработчиков в плане корректной работы с ресурсами (строками, объектами) в модулях (процедурах и функциях).
Идея состоит в том, чтобы выделить очистку всех локальных ресурсов в отдельную локальную процедуру Cleanup.
Это облегчит проверку корректности очистки и её самосогласованность (т.к. очистка делается в двух местах - при входе и выходе из процедуры/функции).
В шаблонах редактора DaqPascal
есть
процедуры и
функции,
содержащие подпрограммы очистки Cleanup для корректной работы
со строками и другими ресурсами.
Например:
{
Return string with reverse chars order.
}
function ReverseStr(arg:String):String;
var s:String; i:Integer;
procedure Cleanup;
begin
s:=''; // Здесь делаем очистку всех локальных строк.
end;
begin
Cleanup;
for i:=Length(arg) downto 1 do s:=s+arg[i];
ReverseStr:=s;
Cleanup;
end;
Использование процедуры Cleanup с очисткой строк и (возможно) других переменных
гарантирует корректную работу менеджера строк, облегчает программирование
(т.к. очистка переменных теперь делается в одном месте, а не в двух).
Имя процедуры очистки (Cleanup) можно считать стандартным (рекомендуемым).
Поскольку это локальная процедура, она может использоваться без ограничений
в любом числе процедур и функций.
Для корректной работы со строками теперь достаточно двух простых правил:
Процедуру очистки Cleanup рекомендуется размещать сразу после объявления локальных переменных,
чтобы было легко увидеть, какие именно переменные очищаются.
Очистку рекомендуется делать компактно, в порядке объявления переменных, чтобы легче было проверить полноту их списка.
Размещение каждого оператора в отдельной строке в данном случае неуместно, т.к. лишь затрудняет чтение.
Например:
procedure Something(...);
var a,b,c,d,e,f:String;
procedure Cleanup;
begin
a:=''; b:=''; c:=''; d:=''; e:=''; f:='';
end;
...
begin
Cleanup;
... Do Something ...
Cleanup;
end;
В более сложных случаях, например, при наличии локальных объектов
(регулярных выражений, текстов, задач и т.д.),
которые надо инициализировать в начале и освобождать в конце,
можно исспользовать расширенную модификацию техники Cleanup,
включающую параметр (how) для различения способа
очистки - инициализация (how<>0) или завершение (how=0).
Например:
{
Return string with reverse lines order.
}
function ReverseLines(arg:String):String;
var s:String; lines,i:Integer;
procedure Cleanup(how:Integer);
begin
s:=''; // Здесь делаем очистку всех локальных строк.
if (how<>0) then lines:=text_new else FreeAndZero(lines); // Инициализируем/освобождаем локальные объекты.
end;
begin
Cleanup(1);
iNul(StringToText(lines,arg));
for i:=text_numln(arg)-1 downto 0 do s:=s+text_getln(lines,i)+EOL;
ReverseLines:=s;
Cleanup(0);
end;
Другой рекомендуемой техникой является сочетание Cleanup с FreeAndZero,
когда все ссылки используемых объектов обнуляются в Cleanup, а перед финальной
очисткой вызываются деструкторы FreeAndZero. Этот метод гораздо более гибкий,
чем использование Cleanup(how), т.к. он позволяет создавать объекты по мере
необходимости (или не создавать их вовсе). Для корректной работы в Cleanup должны
обнуляться ВСЕ используемые ссылки локальных объектов, чтобы FreeAndZero
мог их потом корректрно обработать. Если объекты не были созданы, FreeAndZero
(вызываемый в конце обработки) увидит нулевую ссылку и проигнорирует вызов деструктора.
По описанным причинам сочетание Cleanup с FreeAndZero является
предпочтительной техникой для организации сложных процедур обработки данных.
Например:
{
Return string with reverse lines order.
}
function ReverseLines(arg:String):String;
var s:String; lines,i:Integer;
procedure Cleanup;
begin
s:=''; // Здесь делаем очистку всех локальных строк.
lines:=0; // Здесь очищаем все ссылки на локальные объекты.
end;
begin
Cleanup; // Делаем стандартную очистку
if (arg<>'') then begin
lines:=text_new; // При необходимости создаем объекты
iNul(StringToText(lines,arg));
for i:=text_numln(arg)-1 downto 0 do s:=s+text_getln(lines,i)+EOL;
end;
ReverseLines:=s;
FreeAndZero(lines); // Освобождаем объекты, если они созданы
Cleanup; // Делаем стандартную очистку
end;
Использование Cleanup является универсальной (применимой всегда) техникой программирования,
которая дисциплинирует и делает программы надежнее, а потому рекомендуется к использованию во всех
прикладных программах.