IsInfinite — Функция Delphi


Математические функции в Дельфи

Математические функции описаны в модуле Math. Этот модуль должен быть подключен к приложению оператором uses.

Таблица математических функций в Delphi:

Функция

Описание

Аргумент

Abs (X)

абсолютное значение

целое и действительное

выражение

Ceil (X)

округление донаименьшего целого

Compare

Value (A, B) сравнение двух значений

целые и действительные

выражения

DivMod (Divided,

Divisor, Result,

Remainer) целочисленное деление:Result – результат,

Remainder – остаток

EnsureRange

(AValue,

Amin,Amax) возвращает ближайшеек Avalue в диапазоне

Amin — Amax

целые и действительные

выражения

Exp(X)

экспонента

выражение

Floor (X)

округление до наиб целого,меньшего или равного

аргумента

Frac (X)

дробная часть X-Unt(X)

Frexp(X, Mantissa,

Exponent) выделяет мантиссуи показатель степени 2

Int(X)

целая часть аргумента

выражение

IntPower(X,E)

возведение Xв целую степень E: X в степени Е

Integer

IsInfinite(X)

определяет, не равенли аргумент бесконеч

выражение

IsNan (X)

определяет, не равен лиаргумент Nan – нечисловой

величине

выражение

IsZero(X, Epsilon)

определяет, не явлли аргумент от нуля

менее чем на Epsilon

целые или действ

числа

Ldepx(X,P)

умножение X на 2 в степени Р

Integer

Ln(X)

натуральный логарифм (X)

выражение

LnXP1(X)

натуральный логарифм(X+1)

Log10(X)

десятичный логарифм от X

Log2(X)

логарифм от Xпо основанию 2

LogN (N,X)

логарифм от Xпо основанию N

Max(A,B)

максимум двух чисел

int64, Single, Double

Extended

Min(A,B)

минимум двух чисел

Pi

Poly(X,C)

вычисляет полином Xс массивом коэфф С

массив Double

Power (X, E)

Delphi 2006. Справочное пособие: Язык Delphi, классы, функции Win32 и .NET. — Архангельский А.Я.

Определяет, лежит ли указанное число в заданном диапазоне

Библиотеки VCL Win32, VCL .NET, NFCL

Модуль в VCL Win32 Math

Пространство имен в .NET Borland.Vcl.Math

function InRange (const AValue, AMin, AMax: Integer): Boolean; function InRange(const AValue, AMin, AMax: Int64): Boolean; function InRange(const AValue, AMin, AMax: Double): Boolean;

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

L := InRange(5, 1, 10); L := InRange(1, 1, 10) ; L := InRange(10, 1, 10) ;

зададут булевой переменной L значение true, а операторы

L := InRange(11, 1, 10) ; L := InRange(0, 1, 10);

зададут L = false. Insert

Всталяет в строку другую заданную строку Библиотеки VCL Win32, VCL .NET Модуль в VCL Win32 System

Пространство имен в .NET Borland.Delphi.System Объявление

procedure Insert (Source: string; var S: string; Index: Integer);

Процедура Insert вставляет строку Source в строку S, начиная с индекса Index. Символы, имеющие в строке S индексы больше Index, сдвигаются к концу строки. Если индекс Index превышает длину S, то строка Source вставляется в конец S. Int

Округляет действительное число

См. описание функции в разд. «Ceil, Ceiling, Floor, Int, Round, Trunc». IntPower, Power

Возводят число в заданную степень Библиотеки VCL Win32, VCL .NET, NFCL Модуль в VCL Win32 Math Пространство имен в .NET Borland.Vcl.Math Объявления

function IntPower(Base: Extended; Exponent: Integer): Extended; function Power(Base, Exponent: Extended): Extended;

Функции IntPower и Power возводят Base в степень Exponent: IntPower — в целую степень, Power — в произвольную. При отрицательном значении Base степень Exponent в функции Power должна быть целой. В противном случае Power возвращает значение NaN. При переполнении обе функции возвращают значение Infinity.

Преобразует целое значение в строку Библиотеки VCL Win32, VCL .NET Модуль в VCL Win32 SysUtils Пространство имен в .NET Borland.Vcl.SysUtils Объявления

function IntToStr(Value: Integer) Г string; function IntToStr(Value: Int64): string;

Функция IntToStr преобразует целое значение Value в строку. Параметр Value — целая константа или выражение. Если преобразовываемое значение превышает величину, допустимую для целого типа данных, результат преобразования может быть неверным.

Определяет, не равен ли аргумент бесконечности Библиотеки VCL Win32, VCL .NET, NFCL Модуль в VCL Win32 Math Пространство имен в .NET Borland.Vcl.Math Объявление

function IsInfinite(const AValue: Double): Boolean; 1032

Глава 12 ¦ Описания функций

Функция IsInfinite определяет, не равен ли аргумент значениям Infinity или Neglnfinity — константам, определяющим положительную и отрицательную бесконечности (см. подробнее в разд. 2.8.3). В случае бесконечного значения аргумента возвращается true. В этом случае знак бесконечности можно определить функцией Sign. Например:

IsInfinite — Функция Delphi

Изучив основные «кирпичики», из которых составляются программные инструкции, а именно — переменные и операторы, мы можем приступить к исследованию вопросов их эффективного расположения в теле программы. Для этих целей рассмотрим вопрос использования подпрограмм.

О подпрограммах в Object Pascal

Важной составной частью программирования в Object Pascal является использование подпрограмм — специальным образом оформленных и логически законченных блоков инструкций. Подпрограмму можно вызывать любое число раз из других мест программы, или из других подпрограмм. Таким образом, использование подпрограмм позволяет сделать исходный код более стройным и наглядным.

Структура подпрограммы похожа на программу в миниатюре: она содержит заголовок, блок объявления переменных и блок инструкций. Из отличий можно выделить лишь невозможность подключать модули (блок uses), а так же ограничения на объявления типов данных: если локальные простые и даже составные типы в подпрограммах вполне допустимы, то более сложные типы — объекты, классы и интерфейсы, локальными быть не могут, а потому в подпрограммах их объявлять нельзя.

Использование подпрограммы состоит из 2 этапов: сначала подпрограмму описывают, а затем, уже в блоке инструкций программы, вызывают. Отметим, что в библиотеке Delphi имеется описание тысяч готовых подпрограмм, описывать которые, разумеется, уже не надо. А их вызовом мы уже неоднократно занимались — достаточно взглянуть на любой пример, где мы встречали инструкции, подобные таким:

write(‘Hello, world!’); readln;

Здесь и write, и readln — стандартные подпрограммы Object Pascal. Таким образом, с вызовом подпрограмм мы уже знакомы. Осталось узнать, как создавать собственные, или пользовательские, подпрограммы. Но прежде отметим, что все подпрограммы делятся на 2 лагеря: процедуры и функции. Мы уже использовали эти термины, и даже давали им описание, однако повторимся: процедуры — это такие подпрограммы, которые выполняют предназначенное действие и возвращают выполнение в точку вызова. Функции в целом аналогичны процедурам, за тем исключением, что они еще и возвращают результат своего выполнения. Результатом работы функции могут быть данные любого типа, включая объекты.

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

Как процедурам, так и функциям могут передаваться данные для обработки. Делается это при помощи списка параметров. Список параметров в описании подпрограммы и список аргументов, указываемых при ее вызове должен совпадать. Иначе говоря, если в описании определено 2 параметра типа Integer, то, вызывая такую подпрограмму, в качестве аргументов так же следует указать именно 2 аргумента и именно типа Integer или совместимого (скажем, Word или Int64).

ПРИМЕЧАНИЕ
На самом деле, Object Pascal позволяет довольно гибко обращаться с аргументами, для чего имеются различные методы, включая «перегружаемые» функции, значения параметров по умолчанию и т.д. Тем не менее, в типичном случае, количество, тип, и порядок перечисления аргументов при объявлении и при вызове процедуры или функции, должны совпадать.

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

Процедуры

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

Заголовок состоит из ключевого слова procedure, за которым следует имя процедуры и, при необходимости, список параметров, заключенных в круглые скобки:

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

ПРИМЕЧАНИЕ
Вопросы локальных и глобальных переменных, и вообще видимости в программах, будет рассмотрен позже в этой главе.

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

procedure TriplePrint(str: string); var i: integer; begin for i := 1 to 3 do begin writeln(‘»‘+str+'»‘); end; // конец цикла for end; // конец процедуры TriplePrint

Здесь мы определили процедуру TriplePrint, которая будет трижды выводить переданную ей в качестве аргумента строку, заключенную в двойные кавычки. Как видно, данная процедура имеет все составные части: ключевое слово procedure, имя, список параметров (в данном случае он всего один — строковая переменная str), блок объявления собственных переменных (целочисленная переменная i), и собственное тело, состоящее из оператора цикла for.

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

Отметим так же, что рассмотренная нами процедура сама содержит вызов другой процедуры — writeln. Процедуры могут быть встроенными. Иначе говоря, объявление одной процедуры можно помещать в заголовочную часть другой. Например, наша процедура TriplePrint может иметь вспомогательную процедуру, которая будет «подготавливать» строку к выводу. Для этого перед объявлением переменной i, разместим объявление еще одной процедуры. Назовем ее PrepareStr:

procedure PrepareStr; begin str := ‘»‘+str+'»‘; end;

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

Таким образом, мы получаем две процедуры, одна из которых (TriplePrint) может использоваться во всей программе, а другая (PrepareStr) — только внутри процедуры TriplePrint. Чтобы преимущество использования процедур было очевидно, рассмотрим их на примере программы, которая будет использовать ее неоднократно, для чего обратимся к листингу 6.1 (см. так же пример в Demo\Part1\Procs).

Листинг 6.1. Использование процедур

program procs; <$APPTYPE CONSOLE>procedure TriplePrint(str: string); procedure PrepareStr; begin str := ‘»‘+str+'»‘; end; var i: integer; begin PrepareStr; for i := 1 to 3 do begin writeln(str); end; end; // конец процедуры TriplePrint begin // начало тела основной программы TriplePrint(‘Hello. ‘); // первый вызов TriplePrint TriplePrint(‘How are you. ‘); // 2-й вызов TriplePrint(‘Bye. ‘); // 3-й readln; end.

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

Функции

Подобно процедурам, описание функции состоит из заголовка и тела. Однако описание заголовка имеет 2 отличия: прежде всего, для функций используется ключевое слово function. Кроме того, поскольку функции всегда возвращают результат, завершается строка заголовка типом возвращаемого значения. Таким образом, для объявления функции мы получаем следующий синтаксис:

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

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

function cube(value: integer) : integer; result := value * value * value; >

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

В результате выполнения этого выражения переменной x будет присвоено значение 27. Данный пример иллюстрирует использование функций в классическом случае — для явного вычисления значения переменной. Однако функции могут использоваться в выражениях и напрямую. Например, можно поставить вызов функции cube в каком-либо месте арифметического выражения подобно обычной переменной:

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

procedure TriplePrint(str: string); function PrepareStr(s: string) : string; begin result := ‘»‘+s+'»‘; end; var i: integer; begin for i := 1 to 3 do begin writeln(PrepareStr(str)); // функция использована как переменная end; end;

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

function cube(value: integer) : integer; cube := value * value * value; >

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

Рекурсия

Таким образом мы подошли к теме рекурсии — вызову подпрограммы из самой себя. Это не является ошибкой, более того, целый ряд алгоритмов решить без рекурсии вообще было бы затруднительно.

Рассмотрим вопрос рекурсии на следующем примере:

function recfunc(x: integer) : integer begin dec(x); // функция декремента, уменьшает целое на 1 if x > 5 then x := recfunc(x); result := 0; // возвращаемое значение тут не используется end;

Здесь мы объявили функцию recfunc, принимающую один аргумент, и вызывающую саму себя до тех пор, пока значение этого аргумента больше 5. Хотя на первый взгляд может показаться, что такое поведение функции похоже на обычный цикл, на самом деле все работает несколько по-иному: если вы вызовите ее со значением 8, то она выдаст вам 3 сообщения в следующей последовательности: 5, 6, 7. Иначе говоря, функция вызывала саму себя до тех пор, пока значение x было больше 5, и собственно вывод сообщений начала 3-я по уровню получившейся вложенности функция, которая и вывела первое сообщение (в данном случае им стало 5, т.е. уменьшенное на единицу 6).

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

Листинг 6.2. Рекурсия с комментариями

program recurse; <$APPTYPE CONSOLE>function recfunc(x, depth: integer) : integer; begin dec(x); if x > 5 then begin write(‘Current recursion depth is: ‘); write(depth); write(‘, current x value is: ‘); writeln(x); inc(depth); depth:=recfunc(x, depth); end else writeln(‘End of recursive calls. ‘); write(‘Current recursion depth is: ‘); write(depth); write(‘, current x value is: ‘); writeln(x); dec(depth); result := depth; end; begin recfunc(8,0); readln; end.

Исходный код находится в Demo\Part1\Recurse, там же находится и исполняемый файл recurse.exe, результат работы которого вы можете увидеть на своем экране.

Использование параметров

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

procedure Circle (square: real; var radius, length: real);

Данная процедура принимает «на обработку» одно значение — площадь (square), а возвращает через свои параметры два — радиус (radius) и длину окружности (length). Практическая ее реализация может выглядеть таким образом:

procedure Circle (square: real; var radius, length: real); begin radius := sqrt(square / pi); // функция pi возвращает значение числа ? length := pi * radius * 2; end;

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

var r,l: real; . Circle(100,r,l);

После вызова функции Circle, переменные r и l получат значения радиуса и длины окружности. Остается их вывести при помощи writeln. Исходный код программы приведен в листинге 6.3.

Листинг 6.3. Процедура с параметрами

program params; <$APPTYPE CONSOLE>procedure Circle (square: real; var radius, length: real); begin //функция sqrt извлекает корень, а функция pi возвращает значение числа ? radius := sqrt(square / pi); length := pi * radius * 2; end; var r,l: real; begin Circle(100,r,l); writeln(r); writeln(l); readln; end.

Запустив такую программу, можно убедиться, что она работает и выводит верные результаты, однако вид у них получается довольно-таки неудобочитаемый, например, длина окружности будет представлена как «3,54490770181103E+0001». Чтобы сделать вывод более удобным для восприятия, нам понадобится функция FloatToStrF. С ее помощью мы можем определить вывод числа на свое усмотрение, например:

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

writeln(‘Radius is: ‘+FloatToStrF(r,ffFixed,12,8)); writeln(‘Length is: ‘+FloatToStrF(l,ffFixed,12,8));

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

. var s,r,l: real; begin write(‘Input square: ‘); readln(s); Circle(s,r,l); writeln(‘Radius is: ‘+FloatToStrF(r,ffFixed,12,8)); writeln(‘Length is: ‘+FloatToStrF(l,ffFixed,12,8)); readln; end.

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

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

function Circle(square: real; var radius, length: real) : boolean; begin result := false; if (square

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

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

if Circle(s,r,l) then begin // вывод end else // сообщить об ошибке

Результатом проделанной работы будет программа, приведенная в листинге 6.4. Она же находится в Demo\Part1\Params.

Листинг 6.4. Функция с параметрами

program params; <$APPTYPE CONSOLE>uses sysutils; //этот модуль соджержит функцию FloatToStrF function Circle(square: real; var radius, length: real) : boolean; begin result := false; if (square

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

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

function MyBetterFunc(val1: integer; const val2: integer = 2); begin result := val1*val2; end;

Обращение же к такой функции может иметь 2 варианта: с указанием только одного аргумента (для параметра val1), или же с указанием обоих:

x := MyBetterFunc(5); // получим 10 x := MyBetterFunc(5,4); // получим 20

Оба вызова будут верными, просто в первом случае для второго параметра будет использовано значение, заданное по умолчанию.

Области видимости

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

program Project1; procedure Proc1; var a: integer; begin a := 5; //верно. Локальная переменная a здесь видна end; begin a := 10; //Ошибка! Объявленная в процедуре Proc1 переменнаая здесь не видна end.

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

program Project2; var a: integer; // глобальная переменная a procedure Proc1; begin a := 5; // верно b := 10; // Ошибка! Переменая b на этот момент еще не объявлена end; var b: integer; // глобальная переменная b begin a := 10; // верно b := 5; // тоже верно. Здесь видны все г var a: integer; // глобальная переменная end.

Теперь рассмотрим такой вариант, когда у нас имеются 2 переменных с одним и тем же именем. Разумеется, компилятор еще на стадии проверки синтаксиса не допустит, чтобы в программе были объявлены одноименные переменные в рамках одного диапазона видимости (скажем, 2 глобальных переменных X, или 2 локальных переменных X в одной и той же подпрограмме). Речь в данном случае идет о том, что произойдет, если в одной и той же программе будет 2 переменных X, одна — глобальная, а другая — локальная (в какой-либо подпрограмме). Если с основным блоком программы все ясно — в нем будет присутствовать только глобальная X, то как быть с подпрограммой? В таком случае в действие вступает правило близости, т.е. какая переменная ближе (по структуре) к данному модулю, та и есть верная. Применительно к подпрограмме ближней оказывается локальная переменная X, и именно она будет задействована внутри подпрограммы.

Илон Маск рекомендует:  Что такое код asp mimemap

program Project3; var X: integer; procedure Proc1; var X: integer; begin X := 5; // Здесь значение будет присвоено локальной переменной X end; begin X := 10; // Здесь же значение будет присвоено голобальной переменной X end.

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

program Project1; procedure Proc1; procedure SubProc; begin end; begin SubProc; // Верно. Вложенная процедура здесь видна. end; begin Proc1; // Верно. Процедура Proc1 объявлена в зоне глобальной видимости SubProc; // Ошибка! Процедура SubProc недоступна за пределами Proc1. end.

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

Видимость в модулях

Все то, что мы уже рассмотрели, касалось программ, умещающихся в одном единственном файле. На практике же, особенно к тому моменту, когда мы перейдем к визуальному программированию, программы будут включать в себя множество файлов. В любом случае, программа на Object Pascal будет иметь уже изученный нами файл проекта — dpr, или основной модуль программы. Все прочие файлы будут располагаться в других файлах, или модулях (units), с типичным для Pascal расширением pas. При объединении модулей в единую программу возникает вопрос видимости переменных, а так же процедур и функций в различных модулях.

Для начала вернемся к рассмотрению структуры модуля, которая имеет ряд отличий от структуры программы. Итак, в простейшем случае, модуль состоит из названия, определяемого при помощи ключевого слова unit, и 2 секций — interface и implementation. Так вот как раз первая секция, interface, и служит для определения (декларации) типов данных, переменных, функций и процедур данного модуля, которые должны быть доступны за пределами данного модуля.

Чтобы лучше в этом разобраться, создадим программу, состоящую из 2 модулей — основного (dpr) и дополнительного (pas). Для этого сначала создайте новый проект типа Console Application, а затем добавьте к нему модуль, для чего из подменю File ‘ New выберите пункт Unit. После этого сохраните проект, щелкнув по кнопке Save All (или File ‘ Save All). Обратите внимание, что первым будет предложено сохранить не файл проекта, а как раз файл дополнительного модуля. Назовем его extunit.pas, а сам проект — miltiunits (см. Demo\Part1\Visibility). При этом вы увидите, что в части uses файла проекта произошло изменение: кроме постоянно добавляемого модуля SysUtils, появился еще один модуль — extunit, т.е. код стал таким:

uses SysUtils, extunit in ‘extunit.pas’;

Мы видим, что Delphi автоматически добавила пояснение, в каком файле находится подключаемый модуль. Это вызвано тем, что если о расположении собственных модулей Delphi все известно, то пользовательские модули могут находиться где угодно на жестком диске ПК. Но в данном случае мы сохранили и файл программы, и подключаемый модуль в одном каталоге, следовательно, их пути совпадают, и данное указание можно было бы опустить:

uses SysUtils, extunit;

Тем не менее, оставим код как есть, и приступим к разработке модуля extunit. В нем, в части implementation, напишем 2 процедуры — ExtProc1 и ExtProc2. Обе они будут делать одно и то же — выводить строку со своим названием. Например, для первой:

Теперь вернемся к главному модулю программы и попробуем обратиться к процедуре ExtProc1:

. begin ExtProc1; end.

Попытка компиляции или запуска такой программы приведет к ошибке компилятора «Undeclared identifier», что означает «неизвестный идентификатор». И действительно, одного лишь описания процедуры недостаточно, чтобы она была доступна вне своего модуля. Так что перейдем к редактированию extunit и в секции interface напишем строку:

Такая строка, помещенная в секцию interface, является объявлением процедуры ExtProc1, и делает ее видимой вне данного модуля. Отметим, что в секции interface допускается лишь объявлять процедуры, но не определять их (т.е. тело процедуры здесь будет неуместно). Еще одним полезным эффектом от объявления процедур является то, что таким образом можно обойти такое ограничение, как необходимость определения подпрограммы до ее вызова. Иначе говоря, поскольку в нашем файле уже есть 2 процедуры, ExtProc1и ExtProc2, причем они описаны именно в таком порядке — сначала ExtProc, а потом ExtProc2, то выведя объявление ExtProc2 в interface, мы сможем обращаться к ExtProc2 из ExtProc1, как это показано в листинге 6.5:

Листинг 6.5. Объявление процедур в модуле

unit extunit; interface procedure ExtProc1; procedure ExtProc2; implementation procedure ExtProc1; begin writeln(‘ExtProc1’); ExtProc2; // Если объявления не будет, то компилятор выдаст ошибку end; procedure ExtProc2; begin writeln(‘ExtProc2’); end; end.

Отметим, что теперь процедуры ExtProc2, так же, как и ExtProc1, будет видна не только по всему модулю extunit, но и во всех использующей этот модуль программе multiunits.

Разумеется, все, что было сказано о процедурах, верно и для функций. Кроме того, константы и переменные, объявленные в секции interface, так же будут видны как во всем теле модуля, так и вне него. Остается лишь рассмотреть вопрос пересечения имен, т.е. когда имя переменной (константы, процедуры, функции) в текущем модуле совпадает с таковым в подключенном модуле. В этом случае вновь вступает в силу правило «кто ближе, тот и прав», т.е. будет использоваться переменная из данного модуля. Например, если в extunit мы объявим типизированную константу Z, равную 100, а в multiunits — одноименную константу, равную 200, то обратившись к Z из модуля extunit, мы получим значение 100, а из multiunits — 200.

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

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

Некоторые стандартные функции

В Object Pascal, как уже отмечалось, имеются огромное количество стандартных процедур и функций, являющихся составной частью языка, и с некоторыми мы уже знакомы (например, приведенные в табл. 5.1 и 5.2 функции преобразования). Детальное описание всех имеющихся в Object Pascal процедур и функций можно получить в справочной системе Delphi, однако мы все-таки рассмотрим здесь некоторые из них, чтобы составить общее представление — см. таблицу 6.1.

Таблица 6.1. Некоторые стандартные процедуры и функции Delphi

Синтаксис Группа Модуль Описание
function Abs(X); арифметические System Возвращает абсолютное значение числа
procedure ChDir(const S: string); управления файлами System Изменяет текущий каталог
function Concat(s1 [, s2. sn]: string): string; строковые System Объединяет 2 и более строк в 1
function Copy(S; Index, Count: Integer): string; строковые System Возвращает часть строки
function Cos(X: Extended): Extended; тригонометрические System Вычисляет косинус угла
procedure Delete(var S: string; Index, Count: Integer); строковые System Удаляет часть строки
function Eof(var F): Boolean; ввод-вывод System Проверяет, достигнут ли конец файла
procedure Halt [ ( Exitcode: Integer) ]; управления System Инициирует досрочное прекращение программы
function High(X); диапазона System Возвращает максимальное значение из диапазона
procedure Insert(Source: string; var S: string; Index: Integer); строковые System Вставляет одну строку в другую
function Length(S): Integer; строковые System Возвращает длину строки или количество элементов массива
function Ln(X: Real): Real; арифметические System Возвращает натуральный логарифм числа (Ln(e) = 1)
function Low(X); диапазона System Возвращает минимальное значение из диапазона
procedure New(var P: Pointer); размещения памяти System Создает новую динамическую переменную и назначает указатель для нее
function ParamCount: Integer; командной строки System Возвращает количество параметров командной строки
function ParamStr(Index: Integer): string; командной строки System Возвращает указанный параметр из командной строки
function Pos(Substr: string; S: string): Integer; строковые System Ищет вхождение указанной подстроки в строку и возвращает порядковый номер первого совпавшего символа
procedure RmDir(const S: string); ввод-вывод System Удаляет указанный подкаталог (должен быть пустым)
function Slice(var A: array; Count: Integer): array; разные System Возвращает часть массива
function UpCase(Ch: Char): Char; символьные System Преобразует символ в верхний регистр
function LowerCase(const S: string): string; строковые SysUtils Преобразует ASCII-строку в нижний регистр
procedure Beep; разные SysUtils Инициирует системный сигнал
function CreateDir(const Dir: string): Boolean; управления файлами SysUtils Создает новый подкаталог
function CurrentYear: Word; даты и времени SysUtils Возвращает текущий год
function DeleteFile(const FileName: string): Boolean; управления файлами SysUtils Удаляет файл с диска
function ExtractFileExt(const FileName: string): string; имен файлов SysUtils Возвращает расширение файла
function FileExists(const FileName: string): Boolean; управления файлами SysUtils Проверяет файл на наличие
function IntToHex(Value: Integer; Digits: Integer): string; форматирования чисел SysUtils Возвращает целое в шестнадцатеричном представлении
function StrPCopy(Dest: PChar; const Source: string): PChar; строковые SysUtils Копирует Pascal-строку в C-строку (PChar)
function Trim(const S: string): string; строковые SysUtils Удаляет начальные и конечные пробелы в строке
function TryStrToInt(const S: string; out Value: Integer): Boolean; преобразования типов SysUtils Преобразует строку в целое
function ArcCos(const X: Extended): Extended; тригонометрические Math Вычисляет арккосинус угла
function Log2(const X: Extended): Extended; арифметические Math Возвращает логарифм по основанию 2
function Max(A,B: Integer): Integer; арифметические Math Возвращает большее из 2 чисел
function Min(A,B: Integer): Integer; арифметические Math Возвращает меньшее из 2 чисел

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

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

Функции в действии

В целом мы уже ознакомились с несколькими десятками предопределенных процедур и функций, а так же умеем создавать собственные. Пора применить полученные знания на практике, для чего вновь вернемся к программе, рассмотренной в главе, посвященной операторам — игре «Угадай-ка». В ней, по сути, был реализован только один из рассмотренных в самом начале книги алгоритмов — угадывания числа. Что касается алгоритма управления, то на тот момент мы оставили его без внимания.

Но прежде, чем вносить в программу изменения, определимся с тем, что мы все-таки хотим получить в итоге. Допустим, что мы хотим сделать следующие вещи:

  1. Реализовать-таки возможность повторного прохождения игры без перезапуска программы;
  2. Добавить немного «геймплея». Иначе говоря, введем уровни сложности и подсчет очков. Новые уровни можно реализовать как повторное прохождение игры с увеличением сложности (скажем, за счет расширения диапазона загадываемых значений);
  3. В продолжение п. 2 добавить еще и таблицу рекордов, которая будет сохраняться на диске.

Поскольку часть работы уже выполнена, то для того, чтобы приступить к разработке новой версии игры (назовем ее «Угадай-ка 2.0»), мы не будем как обычно создавать новый консольный проект в Delphi, а откроем уже существующий (Ugadaika) и сохраним его под новым именем, скажем, Ugadaika2, и в новом каталоге. Таким образом, мы уже имеем часть исходного кода, отвечающую за угадывание, в частности, цикл while (см. листинг 4.5). Этот фрагмент логичнее всего выделить в отдельную процедуру, вернее даже функцию, которая будет возвращать число попыток, сделанное пользователем. Для этого создадим функцию, которая будет принимать в качестве аргумента число, которое следует угадать, а возвращаемым значением будет целое, соответствующее числу попыток. Ее объявление будет таким:

function GetAttempts(a: integer):integer;


Данная функция так же должна иметь в своем распоряжении переменную, необходимую для ввода пользователем своего варианта ответа. Еще одна переменная нужна для подсчета результата, т.е. количества попыток. В качестве первой можно было бы использовать глобальную переменную (b), однако во избежание накладок, для локального использования в функции следует использовать локальную же переменную. Что касается переменной-счетчика, то для нее как нельзя лучше подходит автоматическая переменная result. Еще одним изменением будет использование цикла repeat вместо while. Это вызвано тем, что с одной стороны, тем, что хотя бы 1 раз пользователь должен ввести число, т.е. условие можно проверять в конце цикла, а с другой мы можем избавиться от присвоения лишнего действия, а именно — присвоения заведомо ложного значения переменной b. Ну и еще одно дополнение — это второе условие выхода, а именно — ограничение на число попыток, которое мы установим при помощи константы MAXATTEMPTS:

const MAXATTEMPTS = 10;

В результате код функции получится таким, как представлено в листинге 6.6.

Листинг 6.6. Функция GetAttempts

function GetAttempts(a: integer):integer; var b: integer; begin Result:=0; repeat inc(Result); // увеличиваем счетчик числа попыток write(#13+#10+’?:’); read(b); if (b>a) then begin write(‘Too much!’); continue; end; if (b

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

var level, score, attempt: integer; f: TextFile; s: string;

Теперь инициализируем счетчик псевдослучайных чисел (т.е. оставим randomize на месте) и инициализируем нулем значения счета и уровня:

Наконец, напишем цикл для основного блока программы. Этот цикл должен быть выполнен хотя бы один раз и будет продолжать выполняться до тех пор, пока число попыток в последнем уровне было меньше максимально допустимого. В результате получаем цикл repeat со следующим условием:

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

repeat writeln(‘Level ‘+IntToStr(level)+’:’); writeln(‘From 0 to ‘+IntToStr(level*100)); attempt:=GetAttempts(random(level*100+1)); score:=score+(MAXATTEMPTS-attempt)*level; writeln(#10+’You current score is: ‘+IntToStr(score)); inc(level); until attempt>MAXATTEMPTS;

После завершения работы цикла, т.е. когда пользователь хоть раз истратит на отгадывание все 10 попыток, следует сообщить итоговый результат и сравнит его с предыдущим значением, которое следует считать из файла. Файл мы назовем records.txt, и сопоставим с переменной f:

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

if not FileExists(‘record.txt’) then begin Rewrite(f); writeln(f,’0′); // первая строка содержит число-рекорд writeln(f,’None’); // а вторая — имя последнего победителя CloseFile(f); end;

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

Reset(f); readln(f, attempt); readln(f,s); writeln(#10+’BEST SCORE: ‘+IntToStr(attempt)+’ by ‘+s); CloseFile(f);

Ну и последнее, чего нам остается — это проверить, является ли новое значение выше рекорда, и если да — то записать новый рекорд в файл, не забыв спросить имя игрока:

Вот, собственно, и все. Полный код получившейся программы можно увидеть на листинге 6.7, или же в файле проекта в каталоге Demo\Part1\Ugadaika2.

Листинг 6.7. Программа угадай-ка, окончательный вариант

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

Функция: как делать код при бесконечности?

05.01.2020, 20:42

Как доказать, что тот же интеграл от минус бесконечности до бесконечности равен sqrt(pi/2)
Интеграл от нуля до бесконечность sin(x^2) dx = 1/2 sqrt (pi/2) Как доказать , что тот же интеграл.

Как посчитать интеграл от — бесконечности до x0 и от x0 до +бесконечности
Проблема: Warning: Infinite or Not-a-Number function value encountered. > In quad at 100 .

Как обозначить знак бесконечности при решении определенного интеграла?
Здравствуйте форумчани. Столкнулся с интересной проблемой. Решаю определенный интеграл. Пределы.

Как представить знак бесконечности при решении определенного интеграла?
Здравствуйте форумчани. Проблема следующая. Решаю интеграл определенный. Пределы заданны от 8 до.

Как при интегрировании от -бесконечности до определенного числа задать -бесконечность?
Здравствуйте! Пожалуйста, подскажите, как в С# при интегрировании от -бесконечности до.

Функции Delphi модуля Math

Модуль Math

Предлагаем список функций модуля Math, используемого в среде разработки Delphi.

ArcCos Арккосинус числа, возвращается в радианах
ArcSin Арксинус числа, возвращается в радианах
DegToRad Преобразование значения градусов в радианы
IsInfinite Проверяет, является ли число с плавающей запятой бесконечным
IsNaN Выясняет, содержит ли число с плавающей запятой настоящее число
Log10 Вычисляет логарифм числа с основанием 10
Max Выдает максимальное число из двух целых значений
Mean Выдает среднее число из набора чисел
Min Выдает минимальное из двух целых значений
RadToDeg Преобразовывает значение радиана в градусы
RandomRange Генерирует произвольное число в пределах введённого диапазона
Sum Находит сумму элементов массива, состоящего из чисел с плавающей точкой
Tan Тангенс числа

Функции Delphi

Стандартные функции Delphi:

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

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

откуда ln — функция, вычисляющая натуральный логарифм числа exp(x), exp — функция, вычисляющая экспоненту в степени x, x — число, n-ую степень которого надо найти, а n — степень числа x. Каждая функция обладает следующими характеристиками: тип значений, тип параметров.

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

Математические функции Delphi:

Библиотеки языка Delphi включаются в себя и множество математических функций:

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

где a выражает угол в градусах; 3.1415926 означает число pi. На месте константы 3.1415926 с дробной частью для достижения большей точности чаще всего пользуются стандартной именованной константой pi. Тогда выражения для угла в пересчете в радианы будет выглядеть следующим образом:

Функции преобразования Delphi:

Наиболее частое использование функций преобразования связано с инструкциями, которые обеспечивают ввод/вывод какой-либо информации. Например, для вывода значения переменной c типом real в поле вывода диалогового окна (компонент Label), нужно провести преобразование числа в строку символов, которая собственно изображает данное число. Это можно достичь, применяя функцию FloatToStr, которая заменяет значение выражения (оно указано как параметр функции) его строковым представлением.

Пример.

В приведенном примере значение переменной m будете выведено в поле Label. В таблице ниже Вам будут представлены основные функции преобразования Delphi:

Применение функций Delphi:

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

Примеры.

Структура функции Delphi

Как организована инструкция функции в языке Delphi? В любом языке программирования на первом этапе описания функции указывается ее заголовок. Далее за заголовком программист описывает раздел объявления констант const (если таковы имеются), затем занимается описанием раздела объявления типов type, далее следует раздел объявления переменных var и, наконец, раздел инструкций.

В приведенном примере в заголовке функции вначале указывается зарезервированное слово function, а следом идет имя функции. Далее в скобках программист перечисляет список параметров, и вслед за ним, используя символ «:», указывает тип значения функции. В конце каждого заголовка стоит символ «;». После заголовка следуют раздел констант, раздел типов, раздел переменных. Внутри раздела инструкций кроме констант и переменных, описанных соответственно в разделах const и var, может находится переменная result.

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

DelphiComponent.ru — бесплатно видеоуроки по Delphi, статьи, исходники

Процедуры и функции в Delphi

Посмотрите видеоурок по процедурам и функциям (подпрограммы):

Скачайте бесплатно видеокурс Мастер Delphi Lite прямо сейчас — в нем больше видеоуроков — СКАЧАТЬ БЕСПЛАТНО!

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

Использование процедур Write и WriteLn действительно не представляет особой сложности, поскольку они встроены в компилятор Delphi. Компилятор содержит лишь небольшое количество встроенных процедур. Большинство процедур и функций можно найти в отдельных исходных файлах, называемых модулями. Все модули Delphi имеют расширение . pas.

Прежде чем процедуру можно будет использовать в приложении, следует знать имя процедуры, модуль, в котором она объявлена, и принимаемые процедурой параметры. Имя процедуры и список параметров — составные части заголовка процедуры. Заголовок простой процедуры выглядит следующим образом:

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

Илон Маск рекомендует:  Собираем серьезный игровой компьютер за 35000 рублей

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

Показать скрытое содержание
unit Unit1;
interface
implementation
end.

Мониторинг изменений в директориях и файлах средствами Delphi. Часть #2.

В предыдущем посте мы остановились на том. что разработали небольшое приложение, которое проводило мониторинг изменений в определенной директории и, в случае обнаружения какого-либо изменения, «сигналило» нам. Для организации мониторинга мы использовали поток (TThread) в котором использовалось три взаимосвязанные функции Windows: FindFirstChangeNotification, FindNextChangeNotification и FindCloseChangeNotification.

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

Итак, сначала вспомним как выглядел TURLClient.Execute

Здесь мы получили всего один хэндл события с помощью которого отслеживали сразу несколько возможных изменений в директории — изменение имени файла, изменение имени директории и изменение размера. Так как хэндл был всего один, то нам вполне достаточно было использовать функцию WaitForSingleObject для ожидания события. Но нам ведь никто не запрещает получить не один, а, например, три хэндла событий каждое из которых будет срабатывать на конкретно заданное изменение? Давайте сделаем это, а заодно и разберемся с работой ещё одной функции для обработки событий. Итак, пишем новый Execute потока:

В приведенном выше Execute мы определили массив на 3 элемента, где каждый элемент — это хэндл события рассчитанного на определенный вид изменений в директории. Таким образом мы получили возможность пусть и не полностью, но конкретизировать изменения. Так как мы планируем ожидать несколько событий, то вместо метода WaitForSingleObject мы воспользовались методом WaitForMultipleObjects, который в MSDN имеет следующее описание:

WaitForMultipleObjects

nCount: DWORD — количество хэндлов событий. Этот параметр не может быть равен нулю. Максимальное количество ожидаемых событий ограничивается значением константы MAXIMUM_WAIT_OBJECTS = 64 ;
lpHandles: PWOHandleArray — указатель на массив дескрипторов. Массив не должен содержать повторяющихся THandle.
bWaitAll: boolean — если значение этого параметра равно True , то функция вернет значение только в том случае, если будет получен сигнал от всех событий хэндлы которых определены в массиве. В случае, если значение флага равно False , то функция будет возвращать значения при срабатывание любого из событий, при этом возвращаемое значение будет указывать на объект, который изменил состояние.
dwMilliseconds:DWORD — время в мс ожидания события. Если значение этого параметра равно INFINITE , то функция будет возвращать значение только при срабатывании одного из событий.

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

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

Использование функций CreateFile и ReadDirectoryChangesW

Прежде, чем перейдем к примеру на Delphi, посмотрим на описание этих двух функций.
Функция CreateFile создает или открывает файл или другое устройство ввода/вывода (файл, файловый поток, каталог и т.д.

CreateFile

lpFileName: PChar — имя файла или устройства ввода/вывода, которое будет создано или открыто.
dwDesiredAccess: cardinal — флаг, определяющий режим доступа к файлу или устройству (чтение, запись, чтение и запись). Наиболее часто используемые значения GENERIC_READ, GENERIC_WRITE, или сразу оба (GENERIC_READ or GENERIC_WRITE).
dwShareMode:cardinal — один или несколько флагов, определяющих режим совместного доступа к файлу или устройству. Для установки значений могут использоваться следующие константы:
FILE_SHARE_DELETE (0x00000004) — разрешает другим процессам получать доступ к файлу или устройству в том числе и выполнять операции удаления.
FILE_SHARE_READ (0x00000001) — разрешает другим процессам выполнять операции чтения.
FILE_SHARE_WRITE (0x00000002) — разрешает другим процессам выполнять операции записи.
lpSecurityAttributes:PSecurityAttributes — указатель на структуру SECURITY_ATTRIBUTES, содержащую дополнительные параметры безопасности работы с файлом или устройством. Этому параметру можно присваивать значение nil.
dwCreationDisposition:cardinal — флаг, который определяет какие действия следует применить к файлу или устройству. Для всех устройств, кроме файлов, этот параметр обычно устанавливается в значение OPEN_EXISTING. Также может принимать следующие значения:
CREATE_ALWAYS (2) — всегда создавать новый файл. Если файл уже существует и доступен для записи, то код последней ошибки устанавливается в значение ERROR_ALREADY_EXISTS (183) . Если файл не существует и указан правильный к нему путь, то создается новый файл. а код последней ошибки устанавливается в 0.
CREATE_NEW (1) — создать новый файл. Если файл с таким именем уже существует, то вернется код ошибки ERROR_FILE_EXISTS (80) .
OPEN_ALWAYS (4) — всегда открывать файл. Если файл существует, то функция завершится успешно, а код последней ошибки будет установлен в значение ERROR_ALREADY_EXISTS (183) . Если же файл не существует и указан верный путь, то создастся новый файл, а значение код последней ошибки будет установлено в 0.
OPEN_EXISTING (3) — открыть файл или устройство, только если файл или устройство существуют. Если объект не найден, то вернется код ошибки ERROR_FILE_NOT_FOUND (2) .
TRUNCATE_EXISTING (5) — открыть файл и урезать его размер до 0 байт. Если файл не существует, то вернется код ошибки ERROR_FILE_NOT_FOUND (2).
dwFlagsAndAttributes:cardinal — сочетание флагов и атрибутов файла или устройства. Значения обычно начинаются с FILE_ATTRIBUTE_* или FILE_FLAG_*. Наиболее часто используется значение FILE_ATTRIBUTE_NORMAL. При использовании функции CreateFile совместно с ReadDirectoryChangesW необходимо использовать также флаг FILE_FLAG_BACKUP_SEMANTICS.
hTemplateFile: THandle — дескриптор временного файла с режимом доступа GENERIC_READ. Значение этого параметра может быть равно nil.

ReadDirectoryChangesW

hDirectory:THandle — дескриптор директории за которой будет проводится наблюдение. Это значение мы получим, выполнив CreateFile.
lpBuffer:pointer — указатель на буфер в который буду записываться обнаруженные изменения. Структура записей в буфере соответствует структуре FILE_NOTIFY_INFORMATION (см. описание ниже). Буфер может записываться как синхронно, так и асинхронно в зависимости от заданных параметров.
nBufferLength:cardinal — размер буфера lpBuffer в байтах.
bWatchSubtree:booleanTrue указывает на то, что в результаты мониторинга будут попадать также изменения в подкаталогах.
dwNotifyFilter:cardinal — фильтр событий. Может состоять из одного или нескольких флагов:
FILE_NOTIFY_CHANGE_FILE_NAME (0x00000001) — любое изменение имени файла. Сюда же относятся и операции удаления или добавления файла в каталог или подкаталог.
FILE_NOTIFY_CHANGE_DIR_NAME (0x00000002) — любое изменение имени подкаталога, включая добавление и удаление.
FILE_NOTIFY_CHANGE_ATTRIBUTES (0x00000004) — изменение атрибутов файла или каталога.
FILE_NOTIFY_CHANGE_SIZE (0x00000008) — изменение размера файла. Событие будет вызвано только после того как размер файла изменится и файл будет записан.
FILE_NOTIFY_CHANGE_LAST_WRITE (0x00000010) — изменение времени последней записи в файл или каталог.
FILE_NOTIFY_CHANGE_LAST_ACCESS (0x00000020) — изменение времени последнего доступа к файлу или каталогу.
FILE_NOTIFY_CHANGE_CREATION (0x00000040) — изменение времени создания файла или каталога.
FILE_NOTIFY_CHANGE_SECURITY (0x00000100) — изменение параметров безопасности файла или каталога.
lpBytesReturned:cardinal — в случае синхронного вызова функции этот параметр будет содержать количество байт информации, записанной в буфер. Для асинхронных вызовов значение этого параметра остается неопределенным.
lpOverlapped:POVERLAPPED — указатель на структуру OVERLAPPED, которая поставляет данные, которые будут использоваться во время асинхронной операции. Это значение может быть равным nil .
lpCompletionRoutine:POVERLAPPED_COMPLETION_ROUTINE — указатель на callback-функцию. Этот параметр может устанавливаться в значение nil .

Описания функций есть. Теперь напишем с помощью этих двух функций новую заготовку для Execute нашего потока для мониторинга изменений в директориях и файлах Windows:

Бесконечная петля в процедуре Delphi

У меня странная проблема с использованием Delphi TMemoryStream (или TFileStream, если на то пошло). Читая часть потока в массив байтов. Вот пример кода.

процедура называется как,

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

ms.ReadBuffer(buffer, recordSize); процедуру выйти преждевременно, я считаю, что проблема связана с ms.ReadBuffer(buffer, recordSize); но я не понимаю, почему это вызовет проблему.

Эта процедура вызывается только один раз. В моих тестовых данных имеется только одна запись/данные. Любая помощь будет принята с благодарностью.

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

В любом случае, вызов FillChar бесполезен. Так или иначе, вы все равно будете читать весь массив. Удалите вызов FillChar .

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

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

делает ту же самую ошибку, записывая переменную массива, а не массив, и, таким образом, повреждает память.

Которые должны быть

Первые 4 строки внутри цикла неуклюжи. Прочтите непосредственно в переменной:

Я рекомендую вам выполнить некоторые проверки здравомыслия о значении recordSize которое вы читаете. Например, любое значение меньше 4 , очевидно, является ошибкой.

Там не так много, чтобы переместить указатель потока назад и снова прочитать. Вы можете скопировать recordSize в первые 4 байта и массив, а затем прочитать остальные.

Поток памяти также кажется расточительным. Зачем читать весь файл в памяти? Это будет подчеркивать ваше адресное пространство для больших файлов. Используйте буферный поток.

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

Ваш блок try/finally ошибочен. Вы должны приобрести ресурс непосредственно перед try . Как и у вас, исключение в конструкторе приводит к тому, что вы вызываете Free на неинициализированную переменную.

IsInfinite — Функция Delphi

Синхронизация процессов при работе с Windows
Задача синхронизации встает при одновременном доступе нескольких процессов (или нескольких потоков одного процесса) к какому-либо ресурсу. Поскольку поток в Win32 может быть остановлен в любой, заранее ему неизвестный момент времени возможна ситуация, когда один из потоков не успел завершить модификацию ресурса (например, отображенной на файл области памяти), но был остановлен, а другой поток попытался обратиться к этому же ресурсу. В этот момент ресурс находится в несогласованном состоянии, и последствия обращения к нему могут быть самыми неожиданными — от порчи данных, до нарушения защиты памяти.
Главной идеей, положенной в основу синхронизации потоков в Win32 является использование объектов синхронизации и функций ожидания. Объекты могут находиться в одном из двух состояний — Signaled или Not Signaled. Функции ожидания блокируют выполнение потока до тех пор, пока заданный объект находится в состоянии Not Signaled. Таким образом, поток, которому необходим эксклюзивный доступ к ресурсу, должен выставить какой-либо объект синхронизации в несигнальное состояние, а по окончании — сбросить его в сигнальное. Остальные потоки должны перед доступом к этому ресурсу вызвать функцию ожидания, которая позволит им дождаться освобождения ресурса.
Рассмотрим, какие объекты и функции синхронизации предоставляет нам Win32 API.

Функции синхронизации
Функции синхронизации делятся на две основные категории — это функции, ожидающие единственного объекта и функции, ожидающие одного из нескольких объектов

Функции, ожидающие единственного объекта
Простейшей функцией ожидания является

function WaitForSingleObject(
hHandle: THandle; // идентификатор объекта
dwMilliseconds: DWORD // период ожидания
): DWORD; stdcall;

Функция ожидает перехода объекта hHandle в сигнальное состояние в течении dwMilliseconds миллисекунд. Если в качестве параметра dwMilliseconds передать значение INFINITE, функция будет ждать в течение неограниченного времени. Если dwMilliseconds равен 0, то функция проверяет состояние объекта и немедленно возвращает управление.
Функция возвращает одно из следующих значений:
WAIT_ABANDONED Поток, владевший объектом, завершился, не переведя объект в сигнальное состояние.
WAIT_OBJECT_0 Объект перешел в сигнальное состояние
WAIT_TIMEOUT Истек срок ожидания. Обычно в этом случае генерируется ошибка, либо функция вызывается в цикле до получения другого результата
WAIT_FAILED Произошла ошибка, например неверное значение hHandle. Более подробную информацию можно получить, вызвав GetLastError
Следующий фрагмент кода запрещает Action1 до перехода объекта ObjectHandle в сигнальное состояние. Например, таким образом можно дожидаться завершения процесса, предав в качестве ObjectHandle его идентификатор, полученный функцией CreateProcess.

delphi
var
Reason: DWORD ;
ErrorCode: DWORD ;

Action1.Enabled := FALSE ;
try
repeat
Application.ProcessMessages;
Reason := WailForSingleObject(ObjectHandle, 10 );
if Reason = WAIT_FAILED then begin
ErrorCode := GetLastError ;
raise Exception.CreateFmt(
‘Wait for object failed with error: %d’ , [ErrorCode]);
end ;
until Reason <> WAIT_TIMEOUT;
finally
Actionl.Enabled := TRUE ;
end ;

В случае, когда требуется одновременно с ожиданием объекта, перевести в сигнальное состояние другой объект может использоваться функция:

function SignalObjectAndWait(
hObjectToSignal: THandle; // объект, который будет переведен в
// сигнальное состояние
hObjectToWaitOn: THandle; // объект, которого ожидает функция
dwMilliseconds: DWORD; // период ожидания
bAlertable: BOOL // задает, должна ли функция возвращать
// управление в случае запроса на
// завершение операции ввода-вывода
): DWORD; stdcall;

Возвращаемые значения аналогичны функции WaitForSingleObject.

delphi
var
Handles: array [ 0 .. 1 ] of THandle ;
Reason: DWORD ;
RestIndex: Integer ;

Handles[ 0 ] := OpenMutex(SYNCHRONIZE, FALSE , ‘FirstResource’ );
Handles[ 1 ] := OpenMutex(SYNCHRONIZE, FALSE , ‘SecondResource’ );
// Ждем первого из объектов
Reason := WaitForMultipleObjects( 2 , @Handles, FALSE , INFINITE);
case Reason of
WAIT_FAILED: RaiseLastWin32Error ;
WAIT_OBJECT_0, WAIT_ABANDONED_0:
begin
ModifyFirstResource;
RestIndex := 1 ;
end ;
WAIT_OBJECT_0 + 1 , WAIT_ABANDONED_0 + 1 :
begin
ModifySecondResource;
RestIndex := 0 ;
end ;
// WAIT_TIMEOUT возникнуть не может
end ;
// Теперь ожидаем освобождения следующего объекта
if WailForSingleObject(Handles[RestIndex],
INFINITE) = WAIT_FAILED then
RaiseLastWin32Error ;
// Дождались, модифицируем оставшийся ресурс.
if RestIndex = 0 then
ModifyFirstResource
else
ModifySecondResource;

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

function MsgWaitForMultipleObjects(
nCount: DWORD; // количество объектов синхронизации
var pHandles; // адрес массива объектов
fWaitAll: BOOL; // Задает, требуется ожидание всех
// объектов или любого
dwMilliseconds, // Период ожидания
dwWakeMask: DWORD // Тип события, прерывающего ожидание
): DWORD; stdcall;

Главное отличие этой функции от предыдущей — параметр dwWakeMask, который является комбинацией битовых флагов QS_XXX и задает типы сообщений, которые прерывают ожидание функции, независимо от состояния ожидаемых объектов. Например, маска QS_KEY позволяет прервать ожидание при появлении в очереди сообщений WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP или WM_SYSKEYDOWN, а маска QS_PAINT — сообщения WM_PAINT. Полный список значений, допустимых для dwWakeMask имеется в документации по Windows SDK. При появлении в очереди потока, вызвавшего функцию, сообщений, соответствующих заданной маске функция возвращает значение WAIT_OBJECT_0 + nCount. Получив это значение, Ваша программа может обработать его и снова вызвать функцию ожидания. Рассмотрим пример с запуском внешнего приложения. Необходимо, чтобы на время его работы вызывающая программа не реагировала на ввод пользователя, однако её окно должно продолжать перерисовываться.

delphi
procedure TForm1.Button1Click(Sender: TObject );
var
PI : TProcessInformation;
SI: TStartupInfo;
Reason: DWORD ;
Msg: TMsg;
begin
// Инициализируем структуру TStartupInfo
FillChar (SI, SizeOf (SI), 0 );
SI.cb := SizeOf (SI);
// Запускаем внешнюю программу
Win32Check (CreateProcess( NIL , ‘COMMAND.COM’ , NIL ,
NIL , FALSE , 0 , NIL , NIL , SI, PI ));
//**************************************************
// Попробуйте заменить нижеприведенный код на строку
// WaitForSingleObject(PI.hProcess, INFINITE);
// и посмотреть, как будет реагировать программа на
// перемещение других окон над её окном
//**************************************************
repeat
// Ожидаем завершения дочернего процесса или сообщения
// перерисовки WM_PAINT
Reason := MsgWaitForMultipleObjects( 1 , PI .hProcess, FALSE ,
INFINITE, QS_PAINT);
if Reason = WAIT_OBJECT_0 + 1 then begin
// В очереди сообщений появился WM_PAINT – Windows
// требует обновить окно программы.
// Удаляем сообщение из очереди
PeekMessage(Msg, 0 , WM_PAINT , WM_PAINT , PM_REMOVE );
// И перерисовываем наше окно
Update;
end ;
// Повторяем цикл, пока не завершится дочерний процесс
until Reason = WAIT_OBJECT_0;
// Удаляем из очереди накопившиеся там сообщения
while PeekMessage(Msg, 0 , 0 , 0 , PM_REMOVE ) do ;
CloseHandle( PI .hProcess);
CloseHandle( PI .hThread)
end ;

Если в потоке, вызывающем функции ожидания явно (функцией CreateWindow) или неявно (используя TForm, DDE, COM) создаются окна Windows — поток должен обрабатывать сообщения. Поскольку широковещательные сообщения посылаются всем окнам в системе — поток, не обрабатывающий сообщения может вызвать взаимоблокировку, (система ждет, когда поток обработает сообщение, поток — когда система или другие потоки освободят объект) и привести к зависанию Windows. Если в Вашей программе имеются подобные фрагменты — необходимо использовать MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx и позволять прервать ожидание для обработки сообщений. Алгоритм аналогичен вышеприведенному примеру.

Прерывание ожидания по запросу на завершение операции ввода-вывода или APC
Windows поддерживает асинхронные вызовы процедур. При создании каждого потока (thread) с ним ассоциируется очередь асинхронных вызовов процедур (APC queue). Операционная система (или приложение пользователя, при помощи функции QueueUserAPC) может помещать в неё запросы на выполнение функций в контексте этого потока. Эти функции не могут быть выполнены немедленно, поскольку поток может быть занят. Поэтому, операционная система вызывает их, когда поток вызывает одну из следующих функций ожидания:

function SleepEx(
dwMilliseconds: DWORD; // Период ожидания
bAlertable: BOOL // задает, длжна ли функция возвращать
// управление в случае запроса на
// асинхронный вызов процедуры
): DWORD; stdcall;

function WaitForSingleObjectEx(
hHandle: THandle; // Идентификатор объекта
dwMilliseconds: DWORD; // Период ожидания
bAlertable: BOOL // задает, длжна ли функция возвращать
// управление в случае запроса на
// асинхронный вызов процедуры
): DWORD; stdcall;

function WaitForMultipleObjectsEx(
nCount: DWORD; // количество объектов
lpHandles: PWOHandleArray;// адрес массива идентификаторов объектов
bWaitAll: BOOL; // Задает, требуется ожидание всех
// объектов или любого
dwMilliseconds: DWORD; // Период ожидания
bAlertable: BOOL // задает, должна ли функция возвращать
// управление в случае запроса на
// асинхронный вызов процедуры
): DWORD; stdcall;

function SignalObjectAndWait(
hObjectToSignal: THandle; // объект, который будет переведен в
// сигнальное состояние
hObjectToWaitOn: THandle; // объект, которого ожидает функция
dwMilliseconds: DWORD; // период ожидания
bAlertable: BOOL // задает, должна ли функция возвращать
// управление в случае запроса на
// асинхронный вызов процедуры
): DWORD; stdcall;

function MsgWaitForMultipleObjectsEx(
nCount: DWORD; // количество объектов синхронизации
var pHandles; // адрес массива объектов
fWaitAll: BOOL; // Задает, требуется ожидание всех
// объектов или любого
dwMilliseconds, // Период ожидания
dwWakeMask: DWORD // Тип события, прерывающего ожидание
dwFlags: DWORD // Дополнительные флаги
): DWORD; stdcall;

Если параметр bAlertable равен TRUE (либо dwFlags в функции MsgWaitForMultipleObjectsEx содержит MWMO_ALERTABLE) , то при появлении в очереди APC запроса на асинхронный вызов процедуры операционная система выполняет вызовы всех имеющихся в очереди процедур, после чего функция возвращает значение WAIT_IO_COMPLETION.
Такой механизм позволяет реализовать, например, асинхронный ввод-вывод. Поток может инициировать фоновое выполнение одной или нескольких операций ввода-вывода функциями ReadFileEx или WriteFileEx, передав им адреса функций-обработчиков завершения операции. По завершении вызовы этих функций будут поставлены в очередь асинхронного вызова процедур. В свою очередь, инициировавший операции поток, когда он будет готов обработать результаты, может, используя одну из вышеприведенных функций ожидания, позволить операционной системе вызвать функции-обработчики. Поскольку очередь APC реализована на уровне ядра ОС, она более эффективна, чем очередь сообщений и позволяет реализовать гораздо более эффективный ввод-вывод.

Объекты синхронизации
Объектами синхронизации называются объекты Windows, идентификаторы которых могут использоваться в функциях синхронизации. Они делятся на две группы — объекты, использующиеся только для синхронизации и объекты, которые используются в других целях, но могут вызывать срабатывание функций ожидания. К первой группе относятся:
Event (событие)
Позволяет известить один или несколько ожидающих потоков о наступлении события. Event бывает
Отключаемый вручную Будучи установленным в сигнальное состояние, остается в нем до тех пор, пока не будет переключен явным вызовом функции ResetEvent
Автоматически отключаемый Автоматически переключается в несигнальное состояние операционной системой, когда один из ожидающих его потоков завершается.
Для создания объекта используется функция:

function CreateEvent(
lpEventAttributes: PSecurityAttributes; // Адрес структуры
// TSecurityAttributes
bManualReset, // Задает, будет Event переключаемым
// вручную (TRUE) или автоматически (FALSE)
bInitialState: BOOL; // Задает начальное состояние. Если TRUE —
// объект в сигнальном состоянии
lpName: PChar // Имя или NIL, если имя не требуется
): THandle; stdcall; // Возвращает идентификатор созданного
// объекта

Структура TSecurityAttributes описана, как:

TSecurityAttributes = record
nLength: DWORD; // Размер структуры, должен
// инициализироваться как
// SizeOf(TSecurityAttributes)
lpSecurityDescriptor: Pointer; // Адрес дескриптора защиты. В
// Windows 95 и 98 игнорируется
// Обычно можно указывать NIL
bInheritHandle: BOOL; // Задает, могут ли дочерние
// процессы наследовать объект
end;

Если не требуется задание особых прав доступа под Windows NT или возможности наследования объекта дочерними процессами, в качестве параметра lpEventAttributes можно передавать NIL. В этом случае объект не может наследоваться дочерними процессами и ему задается дескриптор защиты «по умолчанию».
Параметр lpName позволяет разделять объекты между процессами. Если lpName совпадает с именем уже существующего объекта типа Event, созданного текущим или любым другим процессом, функция не создает нового объекта, а возвращает идентификатор уже существующего. При этом игнорируются параметры bManualReset, bInitialState и lpSecurityDescriptor. Проверить, был объект создан, или используется уже существующий можно следующим образом:

hEvent := CreateEvent(NIL, TRUE, FALSE, ‘EventName’);
if hEvent = 0 then
RaiseLastWin32Error;
if GetLastError = ERROR_ALREADY_EXISTS then begin
// Используем ранее созданный объект
end;

Если объект используется для синхронизации внутри одного процесса, его можно объявить как глобальную переменную и создавать без имени.
Имя объекта не должно совпадать с именем любого из существующих объектов типов Semaphore, Mutex, Job, Waitable Timer или FileMapping. В случае совпадения имен, функция возвращает ошибку.
Если известно, что Event уже создан, для получения доступа к нему можно вместо CreateEvent воспользоваться функцией:

function OpenEvent(
dwDesiredAccess: DWORD; // Задает права доступа к объекту
bInheritHandle: BOOL; // Задает, может ли объект наследоваться
// дочерними процессами
lpName: PChar // Имя объекта
): THandle; stdcall;

Функция возвращает идентификатор объекта, либо 0, в случае ошибки. Параметр dwDesiredAccess может принимать одно из следующих значений:
EVENT_ALL_ACCESS Приложение получает полный доступ к объекту
EVENT_MODIFY_STATE Приложение может изменять состояние объекта функциями SetEvent и ResetEvent
SYNCHRONIZE Только для Windows NT — приложение может использовать объект только в функциях ожидания
После получения идентификатора можно приступать к его использованию. Для этого имеются следующие функции:

function SetEvent(hEvent: THandle): BOOL; stdcall;

Устанавливает объект в сигнальное состояние

function ResetEvent(hEvent: THandle): BOOL; stdcall;

Сбрасывает объект, устанавливая его в несигнальное состояние

function PulseEvent(hEvent: THandle): BOOL; stdcall

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

delphi
var
Events: array [ 0 .. 1 ] of THandle ; // Массив объектов синхронизации
Overlapped: array [ 0 .. 1 ] of TOverlapped;

// Создаем объекты синхронизации
Events[ 0 ] := CreateEvent( NIL , TRUE , FALSE , NIL );
Events[ 1 ] := CreateEvent( NIL , TRUE , FALSE , NIL );

// Инициализируем структуры TOverlapped
FillChar (Overlapped, SizeOf (Overlapped), 0 );
Overlapped[ 0 ].hEvent := Events[ 0 ];
Overlapped[ 1 ].hEvent := Events[ 1 ];

// Начинаем асинхронную запись в файлы
WriteFile(hFirstFile, FirstBuffer, SizeOf (FirstBuffer),
FirstFileWritten, @Overlapped[ 0 ]);
WriteFile(hSecondFile, SecondBuffer, SizeOf (SecondBuffer),
SecondFileWritten, @Overlapped[ 1 ]);

// Ожидаем завершения записи в оба файла
WaitForMultipleObjects( 2 , @Events, TRUE , INFINITE);

// Уничтожаем объекты синхронизации
CloseHandle(Events[ 0 ]);
CloseHandle(Events[ 1 ]);

По завершении работы с объектом, он должен быть уничтожен функцией CloseHandle.
Delphi предоставляет класс TEvent, инкапсулирующий функциональность объекта Event. Класс расположен в модуле SyncObjs.pas и объявлен следующим образом:

delphi
type
TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError);

TEvent = class (THandleObject)
public
constructor Create(EventAttributes: PSecurityAttributes;
ManualReset, InitialState: Boolean ; const Name: string );
function WaitFor(Timeout: DWORD ): TWaitResult;
procedure SetEvent;
procedure ResetEvent;
end ;

Назначение методов очевидно из их названий. Использование этого класса позволяет не вдаваться в тонкости реализации вызываемых функций Windows API. Для простейших случаев объявлен еще один класс с упрощенным конструктором.

delphi
type
TSimpleEvent = class (TEvent)
public
constructor Create;
end ;

constructor TSimpleEvent.Create;
begin
FHandle := CreateEvent( nil , True , False , nil );
end ;

Mutex (Mutually Exclusive)
Мутекс — это объект синхронизации, который находится в сигнальном состоянии только тогда, когда он не принадлежит ни одному из процессов. Как только хотя бы один процесс запрашивает владение мутексом, он переходит в несигнальное состояние и остается в нем до тех пор, пока не будет освобожден владельцем. Такое поведение позволяет использовать мутексы для синхронизации совместного доступа нескольких процессов к разделяемому ресурсу. Для создания мутекса используется функция:

function CreateMutex(
lpMutexAttributes: PSecurityAttributes; // Адрес структуры
// TSecurityAttributes
bInitialOwner: BOOL; // Задает, будет ли процесс владеть
// мутексом сразу после создания
lpName: PChar // Имя мутекса
): THandle; stdcall;

Функция возвращает идентификатор созданного объекта, либо 0. Если мутекс с заданным именем уже был создан, возвращается его идентификатор. В этом случае функция GetLastError вернет код ошибки ERROR_ALREDY_EXISTS. Имя не должно совпадать с именем уже существующего объекта типов Semaphore, Event, Job, Waitable Timer или FileMapping
Если неизвестно, существует ли уже мутекс с таким именем, программа не должна запрашивать владение объектом при создании (т.е. должна передать в качестве bInitialOwner значение FALSE).
Если мутекс уже существует, приложение может получить его идентификатор функцией

function OpenMutex(
dwDesiredAccess: DWORD; // Задает права доступа к объекту
bInheritHandle: BOOL; // Задает, может ли объект наследоваться
// дочерними процессами
lpName: PChar // Имя объекта
): THandle; stdcall;

Параметр dwDesiredAccess может принимать одно из следующих значений
MUTEX_ALL_ACCESS Приложение получает полный доступ к объекту
SYNCHRONIZE Только для Windows NT — приложение может использовать объект только в функциях ожидания и функции ReleaseMutex
Функция возвращает идентификатор открытого мутекса, либо 0, в случае ошибки. Мутекс переходит в сигнальное состояние после срабатывания функции ожидания, в которую был передан его идентификатор. Для возврата в несигнальное состояние служит функция
function ReleaseMutex(hMutex: THandle): BOOL; stdcall;
Если несколько процессов обмениваются данными, например, через файл, отображенный на память, каждый из них должен содержать следующий код для обеспечения корректного доступа к общему ресурсу:

delphi
var
Mutex: THandle ;

// При инициализации программы
Mutex := CreateMutex( NIL , FALSE , ‘UniqueMutexName’ );
if Mutex = 0 then
RaiseLastWin32Error ;

.
// Доступ к ресурсу
WaitForSingleObject(Mutex, INFINITE);
try
// Доступ к ресурсу, захват мутекса гарантирует,
// что остальные процессы пытающиеся получить доступ
// будут остановлены на функции WaitForSingleObject
.
finally
// Работа с ресурсом окончена, освобождаем его
// для остальных процессов
ReleaseMutex(Mutex);
end ;

.
// При завершении программы
CloseHandle(Mutex);

Подобный код удобно инкапсулировать в класс, который создает защищенный ресурс, мутекс, имеет свойства и методы для оперирования ресурсом, защищая их при помощи функций синхронизации.
Разумеется, если работа с ресурсом может потребовать значительного времени, то необходимо либо использовать функцию MsgWaitForSingleObject, либо вызывать WaitForSingleObject в цикле с нулевым периодом ожидания, проверяя код возврата. В противном случае Ваше приложение окажется замороженным. Всегда защищайте захват-освобождение объекта синхронизации при помощи блока try . finally, иначе ошибка во время работы с ресурсом приведет к блокированию работы всех процессов, ожидающих его освобождения.
Semaphore (семафор)
Семафор представляет собой счетчик, содержащий целое число в диапазоне от 0 до заданной при его создании максимальной величины. Счетчик уменьшается каждый раз, когда поток успешно завершает функцию ожидания, использующую семафор и увеличивается вызовом функции ReleaseSemaphore. При достижении семафором значения 0 он переходит в несигнальное состояние, при любых других значениях счетчика — его состояние сигнальное. Такое поведение позволяет использовать семафор в качестве ограничителя доступа к ресурсу, поддерживающему заранее заданное количество подключений.
Для создания семафора служит функция:

function CreateSemaphore(
lpSemaphoreAttributes: PSecurityAttributes; // Адрес структуры
// TSecurityAttributes
lInitialCount, // Начальное значение счетчика
lMaximumCount: Longint; // Максимальное значение счетчика
lpName: PChar // Имя объекта
): THandle; stdcall;

Функция возвращает идентификатор созданного семафора, либо 0, если создать объект не удалось.
Параметр lMaximumCount задает максимальное значение счетчика семафора, lInitialCount задает начальное значение счетчика и должен быть в диапазоне от 0 до lMaximumCount. lpName задает имя семафора. Если в системе уже есть семафор с таким именем, то новый не создается, а возвращается идентификатор существующего семафора. В случае если семафор используется внутри одного процесса, можно создать его без имени, передав в качестве lpName значение NIL. Имя семафора не должно совпадать с именем уже существующего объекта типов event, mutex, waitable timer, job, или file-mapping.
Идентификатор ранее созданного семафора может быть, также, получен функцией:

function OpenSemaphore(
dwDesiredAccess: DWORD; // Задает права доступа к объекту
bInheritHandle: BOOL; // Задает, может ли объект наследоваться
// дочерними процессами
lpName: PChar // Имя объекта
): THandle; stdcall;

Параметр dwDesiredAccess может принимать одно из следующих значений:
SEMAPHORE_ALL_ACCESS Поток получает все права на семафор
SEMAPHORE_MODIFY_STATE Поток может увеличивать счетчик семафора функцией ReleaseSemaphore
SYNCHRONIZE Только Windows NT — поток может использовать семафор в функциях ожидания
Для увеличения счетчика семафора используется функция:

function ReleaseSemaphore(
hSemaphore: THandle; // Идентификатор семафора
lReleaseCount: Longint; // Счетчик будет увеличен на эту величину
lpPreviousCount: Pointer // Адрес 32-битной переменной,
// принимающей предыдущее значение
// счетчика
): BOOL; stdcall;

Если значение счетчика после выполнения функции превысит заданный для него функцией CreateSemaphore максимум, то ReleaseSemaphore возвращает FALSE и значение семафора не изменяется. В качестве параметра lpPreviousCount можно передать NIL, если это значение нам не нужно.
Рассмотрим пример приложения, запускающего на выполнение несколько заданий в отдельных потоках (например, программа для фоновой загрузки файлов из Internet). Если количество одновременно выполняющихся заданий будет слишком велико, то это приведет к неоправданной загрузке канала. Поэтому реализуем потоки, в которых будет выполняться задание таким образом, чтобы при превышении их количества заранее заданной величины поток останавливался и ожидал завершения работы ранее запущенных заданий.

delphi
unit LimitedThread;

type
TLimitedThread = class (TThread)
procedure Execute; override ;
end ;

const
MAX_THREAD_COUNT = 10 ;

var
Semaphore: THandle ;

procedure TLimitedThread.Execute;
begin
// Уменьшаем счетчик семафора. Если к этому моменту уже запущено
// MAX_THREAD_COUNT потоков – счетчик равен 0 и семафор в
// несигнальном состоянии. Поток будет заморожен до завершения
// одного из запущенных ранее.
WaitForSingleObject(Semaphore, INFINITE);

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

// Поток завершил работу, увеличиваем счетчик семафора и позволяем
// начать обработку другим потокам.
ReleaseSemaphore(Semaphore, 1 , NIL );
end ;

initialization
// Создаем семафор при старте программы
Semaphore := CreateSemaphore( NIL , MAX_THREAD_COUNT,
MAX_THREAD_COUNT, NIL );

finalization
// Уничтожаем семафор по завершении программы
CloseHandle(Semaphore);
end ;

Waitable timer (таймер ожидания)
Таймер ожидания отсутствует в Windows 95 и для его использования необходима Windows 98 или Windows NT 4.0 и выше.
Таймер ожидания переходит в сигнальное состояние по завершении заданного интервала времени. Для его создания используется функция:
function CreateWaitableTimer(
lpTimerAttributes: PSecurityAttributes; // Адрес структуры
// TSecurityAttributes
bManualReset: BOOL; // Задает, будет ли таймер переходить в
// сигнальное состояние по завершении функции
// ожидания
lpTimerName: PChar // Имя объекта
): THandle; stdcall;

Если параметр bManualReset равен TRUE, то таймер после срабатывания функции ожидания остается в сигнальном состоянии до явного вызова SetWaitableTimer, если FALSE — таймер автоматически переходит в несигнальное состояние.
Если lpTimerName совпадает с именем уже существующего в системе таймера — функция возвращает его идентификатор, позволяя использовать объект для синхронизации между процессами. Имя таймера не должно совпадать с именем уже существующих объектов типов event, semaphore, mutex, job или file-mapping.
Идентификатор уже существующего таймера можно получить функцией:

function OpenWaitableTimer(
dwDesiredAccess: DWORD; // Задает права доступа к объекту
bInheritHandle: BOOL; // Задает, может ли объект наследоваться
// дочерними процессами
lpTimerName: PChar // Имя объекта
): THandle; stdcall;

Параметр dwDesiredAccess может принимать следующие значения:
TIMER_ALL_ACCESS Разрешает полный доступ к объекту
TIMER_MODIFY_STATE Разрешает изменять состояние таймера функциями SetWaitableTimer и CancelWaitableTimer
SYNCHRONIZE Только Windows NT — разрешает использовать таймер в функциях ожидания
После получения идентификатора таймера, поток может задать время его срабатывания функцией

function SetWaitableTimer(
hTimer: THandle; // Идентификатор таймера
const lpDueTime: TLargeInteger; // Время срабатывания
lPeriod: Longint; // Период повторения срабатывания
pfnCompletionRoutine: TFNTimerAPCRoutine; // Процедура-обработчик
lpArgToCompletionRoutine: Pointer;// Параметр процедуры-обработчика
fResume: BOOL // Задает, будет ли операционная
// система «пробуждаться»
): BOOL; stdcall;

Рассмотрим параметры подробнее.
lpDueTime
Задает время срабатывания таймера. Время задается в формате TFileTime и базируется на coordinated universal time (UTC), т.е. должно указываться по Гринвичу. Для преобразования системного времени в TFileTime используется функция SystemTimeToFileTime. Если время имеет положительный знак, оно трактуется как абсолютное, если отрицательный — как относительное от момента запуска таймера.
lPeriod
Задает срок между повторными срабатываниями таймера. Если lPeriod равен 0 — таймер сработает один раз.
pfnCompletionRoutine
Адрес функции, объявленной как:

procedure TimerAPCProc(
lpArgToCompletionRoutine: Pointer; // данные
dwTimerLowValue: DWORD; // младшие 32 разряда значения таймера
dwTimerHighValue: DWORD; // старшие 32 разряда значения таймера
); stdcall;

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

delphi
unit WaitThread;

uses Classes, Windows;

type
TWaitThread = class (TThread)
WaitUntil: TDateTime ;
procedure Execute; override ;
end ;

procedure TWaitThread.Execute;
var
Timer: THandle ;
SystemTime: TSystemTime;
FileTime, LocalFileTime: TFileTime;
begin
Timer := CreateWaitableTimer( NIL , FALSE , NIL );
try
DateTimeToSystemTime (WaitUntil, SystemTime);
SystemTimeToFileTime(SystemTime, LocalFileTime);
LocalFileTimeToFileTime(LocalFileTime, FileTime);
SetWaitableTimer(Timer, TLargeInteger(FileTime), 0 ,
NIL , NIL , FALSE );
WaitForSingleObject(Timer, INFINITE);
finally
CloseHandle(Timer);
end ;
end ;

end .
Использовать этот класс можно, например, следующим образом:
type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject );
private
procedure TimerFired(Sender: TObject );
end ;

procedure TForm1.Button1Click(Sender: TObject );
var
T: TDateTime ;
begin
with TWaitThread.Create( TRUE ) do
begin
OnTerminate := TimerFired;
FreeOnTerminate := TRUE ;
// Срок ожидания закончится через 5 секунд
WaitUntil := Now + 1 / 24 / 60 / 60 * 5 ;
Resume;
end ;
end ;

procedure TForm1.TimerFired(Sender: TObject );
begin
ShowMessage( ‘Timer fired !’ );
end ;

Дополнительные объекты синхронизации
Некоторые объекты Win32 API не предназначены исключительно для целей синхронизации, однако могут использоваться с функциями синхронизации. Такими объектами являются:
Сообщение об изменении папки (change notification)
Windows позволяет организовать слежение за изменениями объектов файловой системы. Для этого служит функция

function FindFirstChangeNotification(
lpPathName: PChar; // Путь к папке, изменения в которой нас
// интересуют
bWatchSubtree: BOOL; // Задает необходимость слежения за
// изменениями во вложенных папках
dwNotifyFilter: DWORD // Фильтр событий
): THandle; stdcall;

Параметр dwNotifyFilter — это битовая маска из одного или нескольких следующих значений:
FILE_NOTIFY_CHANGE_FILE_NAME
Слежение ведется за любым изменением имени файла, в т.ч. созданием и удалением файлов
FILE_NOTIFY_CHANGE_DIR_NAME
Слежение ведется за любым изменением имени папки, в т.ч. созданием и удалением папок
FILE_NOTIFY_CHANGE_ATTRIBUTES
Слежение ведется за любым изменением аттрибутов
FILE_NOTIFY_CHANGE_SIZE
Слежение ведется за изменением размера файлов. Изменение размера происходит при записи в файл. Функция ожидания срабатывает только после успешного сброса дискового кэша
FILE_NOTIFY_CHANGE_LAST_WRITE
Слежение за изменением времени последней записи в файл, т.е., фактически, за любой записью в файл. Функция ожидания срабатывает только после успешного сброса дискового кэша.
FILE_NOTIFY_CHANGE_SECURITY
Слежение за любыми изменениями дескрипторов защиты
Идентификатор, возвращенный этой функцией, может использоваться в любой функции ожидания. Он переходит в сигнальное состояние, когда в папке происходят запрошенные для слежения изменения. Продолжить слежение можно, используя функцию:
function FindChangeNotification(
hChangeHandle: THandle
): BOOL; stdcall;
По завершении работы, идентификатор должен быть закрыт при помощи функции:
function FindCloseChangeNotification(
hChangeHandle: THandle
): BOOL; stdcall;
Чтобы не блокировать исполнение основного потока программы функцией ожидания, удобно реализовать ожидание изменений в отдельном потоке. Реализуем поток на базе класса TThread. Для того чтобы можно было прервать исполнение потока методом Terminate необходимо, чтобы функция ожидания, реализованная в методе Execute, также прерывалась при вызове Terminate. Для этого будем использовать вместо WaitForSingleObject функцию WaitForMultipleObjects, и прерывать ожидание по событию (event), устанавливаемому в Terminate.

delphi
type
TCheckFolder = class (TThread)
private
FOnChange: TNotifyEvent;
Handles: array [ 0 .. 1 ] of THandle ; // Идентификаторы объектов
// синхронизации
procedure DoOnChange;
protected
procedure Execute; override ;
public
constructor Create(CreateSuspended: Boolean ;
PathToMonitor: String ; WaitSubTree: Boolean ;
OnChange: TNotifyEvent; NotifyFilter: DWORD );
destructor Destroy; override ;
procedure Terminate;
end ;

procedure TCheckFolder.DoOnChange;
// Эта процедура вызывается в контексте главного потока приложения
// В ней можно использовать вызовы VCL, изменять состояние формы,
// например перечитать содержимое TListBox, отображающего файлы
begin
if Assigned (FOnChange) then
FOnChange( Self );
end ;

procedure TCheckFolder.Terminate;
begin
inherited ; // Вызываем TThread.Terminate, устанавливаем
// Terminated = TRUE
SetEvent(Handles[ 1 ]); // Сигнализируем о необходимости
// прервать ожидание
end ;

constructor TCheckFolder.Create(CreateSuspended: Boolean ;
PathToMonitor: String ; WaitSubTree: Boolean ;
OnChange: TNotifyEvent; NotifyFilter: DWORD );
var
BoolForWin95: Integer ;
begin
// Создаем поток остановленным
inherited Create( TRUE );
// Windows 95 содержит не очень корректную реализацию функции
// FindFirstChangeNotification. Для корректной работы, необходимо,
// чтобы:
// — lpPathName — не содержал завершающего слэша «\» для
// некорневого каталога
// — bWatchSubtree — TRUE должен передаваться как BOOL(1)
if WaitSubTree then
BoolForWin95 := 1
else
BoolForWin95 := 0 ;
if ( Length (PathToMonitor) > 1 ) and
(PathToMonitor[ Length (PathToMonitor)] = ‘\’ ) and
(PathToMonitor[ Length (PathToMonitor)- 1 ] <> ‘:’ ) then
Delete (PathToMonitor, Length (PathToMonitor), 1 );
Handles[ 0 ] := FindFirstChangeNotification(
PChar (PathToMonitor), BOOL (BoolForWin95), NotifyFilter);
Handles[ 1 ] := CreateEvent( NIL , TRUE , FALSE , NIL );
FOnChange := OnChange;
// И, при необходимости, запускаем
if not CreateSuspended then
Resume;
end ;

destructor TCheckFolder.Destroy;
begin
FindCloseChangeNotification(Handles[ 0 ]);
CloseHandle(Handles[ 1 ]);
inherited ;
end ;

procedure TCheckFolder.Execute;
var
Reason: Integer ;
Dummy: Integer ;
begin
repeat
// Ожидаем изменения в папке, либо сигнала о завершении
// потока
Reason := WaitForMultipleObjects( 2 , @Handles, FALSE , INFINITE);
if Reason = WAIT_OBJECT_0 then begin
// Изменилась папка, вызываем обработчик в контексте
// главного потока приложения
Synchronize(DoOnChange);
// И продолжаем поиск
FindChangeNotification(Handles[ 0 ]);
end ;
until Terminated;
end ;

Поскольку метод TThread.Terminate не виртуальный, этот класс нельзя использовать с переменной типа TThread, т.к. в этом случае будет вызываться Terminate от TThread, который не может прервать ожидания, и поток будет выполняться до изменения в папке, за которой ведется слежение.
Устройство стандартного ввода с консоли (console input)
Идентификатор, стандартного устройства ввода с консоли, полученный при помощи вызова функции GetStdHandle(STD_INPUT_HANDLE), можно использовать в функциях ожидания. Он находится в сигнальном состоянии, если очередь ввода консоли непустая и в несигнальном, если пустая. Это позволяет организовать ожидание ввода символов, либо, при помощи функции WaitForMultipleObjects совместить его с ожиданием каких-то других событий.
Задание (Job)
Job — это новый механизм Windows 2000, позволяющий объединить группу процессов в одно задание и манипулировать ими одновременно. Идентификатор задания находится в сигнальном состоянии, если все процессы, ассоциированные с ним завершились по причине истечения лимита времени на выполнение задания.
Процесс (Process)
Идентификатор процесса, полученный при помощи функции CreateProcess, переходит в сигнальное состояние по завершении процесса. Это позволяет организовать ожидание завершения процесса, например, при запуске из приложения внешней программы.

var
PI: TProcessInformation;
SI: TStartupInfo;

FillChar(SI, SizeOf(SI), 0);
SI.cb := SizeOf(SI);
Win32Check(CreateProcess(NIL, ‘COMMAND.COM’, NIL,
NIL, FALSE, 0, NIL, NIL, SI, PI));
// Задерживаем исполнение программы до завершения процесса
WaitForSingleObject(PI.hProcess, INFINITE);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);

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

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

procedure InitializeCriticalSection(
var lpCriticalSection: TRTLCriticalSection
); stdcall;

После создания объекта поток, перед доступом к защищаемому ресурсу должен вызвать функцию:

procedure EnterCriticalSection(
var lpCriticalSection: TRTLCriticalSection
); stdcall;

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

procedure LeaveCriticalSection(
var lpCriticalSection: TRTLCriticalSection
); stdcall;

Эта функция освобождает объект независимо от количества предыдущих вызовов потоком функции EnterCriticalSection. Если имеются другие потоки, ожидающие освобождения секции, один из них становится её владельцем и продолжает исполнение. Если поток завершился, не освободив критическую секцию, её состояние становится неопределенным, что может вызвать блокировку работы программы.
Имеется возможность попытаться захватить объект без замораживания потока. Для этого служит функция:

function TryEnterCriticalSection(
var lpCriticalSection: TRTLCriticalSection
): BOOL; stdcall;

Она проверяет, захвачена секция ли в момент её вызова. Если да — функция возвращает FALSE, в противном случае — захватывает секцию и возвращает TRUE.
По завершении работы с критической секцией, она должна быть уничтожена вызовом функции:

procedure DeleteCriticalSection(
var lpCriticalSection: TRTLCriticalSection
); stdcall;

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

delphi
var
// Глобальные переменные
CriticalSection: TRTLCriticalSection;
BytesSummary: Cardinal ;
TimeSummary: TDateTime ;
AverageSpeed: Float;

// При инициализации приложения
InitializeCriticalSection(CriticalSection);
BytesSummary := 0 ;
TimeSummary := 0 ;
AverageSpeed := 0 ;

//В методе Execute потока, загружающего данные.
repeat
BytesRead := ReadDataBlockFromNetwork;
EnterCriticalSection(CriticalSection);
try
BytesSummary := BytesSummary + BytesRead;
TimeSummary := TimeSummary + ( Now — ThreadStartTime);
if TimeSummary > 0 then
AverageSpeed := BytesSummary / (TimeSummary/ 24 / 60 / 60 );
finally
LeaveCriticalSection(CriticalSection)
end ;
until LoadComplete;

// При завершении приложения
DeleteCriticalSection(CriticalSection);

Delphi предоставляет класс, инкапсулирующий функциональность критической секции. Класс объявлен в модуле SyncObjs.pas

type
TCriticalSection = class(TSynchroObject)
public
constructor Create;
destructor Destroy; override;
procedure Acquire; override;
procedure Release; override;
procedure Enter;
procedure Leave;
end;

Методы Enter и Leave являются синонимами методов Acquire и Release соответственно и добавлены для лучшей читаемости исходного кода.

procedure TCriticalSection.Enter;
begin
Acquire;
end;

procedure TCriticalSection.Leave;
begin
Release;
end;

Защищенный доступ к переменным (Interlocked Variable Access)
Часто возникает необходимость совершения операций над разделяемыми между потоками 32-разрядными переменными. Для упрощения решения этой задачи WinAPI предоставляет функции для защищенного доступа к ним, не требующие использования дополнительных (и более сложных) механизмов синхронизации. Переменные, используемые в этих функциях, должны быть выровнены на границу 32-разрядного слова. Применительно к Delphi это означает, что если переменная объявлена внутри записи (record), то эта запись не должна быть упакованной (packed) и при её объявлении должна быть активна директива компилятора <$A+>. Несоблюдение этого требования может привести к возникновению ошибок на многопроцессорных конфигурациях.

delphi
type
TPackedRecord = packed record
A: Byte ;
B: Integer ;
end ;
// TPackedRecord.B нельзя использовать в функциях InterlockedXXX

TNotPackedRecord = record
A: Byte ;
B: Integer ;
end ;

<$A->
var
A1: TNotPackedRecord;
// A1.B нельзя использовать в функциях InterlockedXXX
I: Integer
// I можно использовать в функциях InterlockedXXX, т.к. переменные в
// Delphi всегда выравниваются на границу слова безотносительно
// к состоянию директивы компилятора $A

<$A+>
var
A2: TNotPackedRecord;
// A2.B можно использовать в функциях InterlockedXXX

function InterlockedIncrement (
var Addend: Integer
): Integer ; stdcall ;

Функция увеличивает переменную Addend на 1. Возвращаемое значение зависит от операционной системы:
Windows 98, Windows NT 4.0 и старше
Возвращается новое значение переменной Addend
Windows 95, Windows NT 3.51
Если после изменения Addend 0 возвращается положительное число, не обязательно равное Addend.

function InterlockedDecrement(
var Addend: Integer
): Integer; stdcall;

Функция уменьшает переменную Addend на 1. Возвращаемое значение аналогично функции InterlockedIncrement.

function InterlockedExchange(
var Target: Integer;
Value: Integer
): Integer; stdcall;

Функция записывает в переменную Target значение Value и возвращает предыдущее значение Target
Следующие функции для выполнения требуют Windows 98 или Windows NT 4.0 и старше.

function InterlockedCompareExchange(
var Destination: Pointer;
Exchange: Pointer;
Comperand: Pointer
): Pointer; stdcall;

Функция сравнивает значения Destination и Comperand. Если они совпадают, значение Exchange записывается в Destination. Функция возвращает начальное значение Destination.

function InterlockedExchangeAdd(
Addend: PLongint;
Value: Longint
): Longint; stdcall;

Илон Маск рекомендует:  Как сделать глобальную переменную, доступной всем моим файлам cpp
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL