Рефераты

Программирование на Delphi

в этот диалог. Например, если DDE-сервером является DataBase Desktop, то

нужно загрузить в него какую-нибудь таблицу Paradox, выбрать любое поле и

выбрать пункт меню Edit|Copy. Свойство ServiceApplication заполняется в том

случае, если в поле DDEService содержится имя, отличное от имени программы,

либо если эта программа не находится в текущей директории. В этом поле

указывается полный путь и имя программы без расширения (.EXE). Данная

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

по DDE, если тот еще не был запущен.

Имена сервера DDE и темы содержатся в свойствах:

Property DDEService:string;

Property DDETopic:string;

Способ вхождения в контакт определяется свойством

Property ConnectMode:tDataMode;

Type tDataMode = (DDEAutomatic,DDEManual);

Метод

Function SetLinc(const Service: string; const Topic: string):Boolean;

Присваивает свойствам DDEService и DDETopic соответствующие значения, а в

случае, если задан режим DDEAutomatic, и устанавливает контакт. В режиме

DDEManual для установления контакта необходимо дополнительно вызвать метод

Function OpenLink: Boolean;

Этот метод сначала закрывает предыдущий контакт, затем пытается связаться с

сервером DDEService на тему DDETopic. Если это не удается сразу,

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

свойстве:

Property ServiceApplication: string;

Можно связаться с сервером, поместившим данные в буфер обмена с помощью

метода

Function PasteLink: boolean;

Ниже приведен пример процедуры, осуществляющей связь с сервером.

procedure TMainForm.doNewLink(Sender: TObject);

begin

DdeClient.SetLink(AppName.Text, TopicNameEdit.Text);

DdeClientItem.DdeConv := DdeClient;

DdeClientItem.DdeItem := ItemName.Text;

end;

procedure TMainForm.doPasteLink(Sender: TObject);

var

Service, Topic, Item : String;

begin

if GetPasteLinkInfo (Service, Topic, Item) then begin

AppName.Text := Service;

TopicName.Text := Topic;

ItemName.Text := Item;

DdeClient.SetLink (Service, Topic);

DdeClientItem.DdeConv := DdeClient;

DdeClientItem.DdeItem := ItemName.Text;

end;

end;

После того, как установлена связь, нужно позаботиться о поступающих по DDE

данных, это делается в обработчике события OnChange объекта TDdeClietItem:

procedure TFormD.DdeClientItemChange(Sender: TObject);

begin

DdeDat.Lines := DdeClientItem.Lines;

end;

Это единственная задача объекта TDdeClientItem.

Свойство

Property DDEConv: TddeClientConv

Этого компонента предназначено для связи с соответствующим объектом

DdeClientConv. А свойство

Property DDEItem:string;

Должно содержать имя элемента данных.

Свойства

Property Text: string;

Property Lines: tStrings;

Аналогичны соответствующим свойствам tDDEServerItem и содержат данные.

На объект TDdeClientConv возлагаются еще две задачи: пересылка данных на

сервер и выполнение макросов. Для этого у данного объекта есть

соответствующие методы.

Function ExecuteMacroLines(Cmd:tStrings, WaitFlg:Boolean):Boolean;

Function PokeDataLines(const Item:string,Data:tStrings):Boolean;

Обмен сообщениями

Как уже упоминалось ранее, операционная система Windows® основана на

сообщениях, возникающих в результате действий пользователя, аппаратуры

компьютера или других программ. Поведение каждого окна полностью

определяется тем, какие оно принимает сообщения и как их обрабатывает. В

большинстве случаев, обработка сообщений в Delphi выполняется через

события. Однако, бывают ситуации, когда может потребоваться послать и/или

обработать сообщение самостоятельно. Существуют два типа сообщений, которые

могут потребовать обработки в обход обычной системы сообщений Delphi:

Сообщения Windows®, не обрабатываемые VCL

Сообщения, определяемые пользователем

В принципе, сообщения делятся на две категории:

Командные сообщения

Уведомляющие сообщения

Командные сообщения используются как программистами, тек и Windows®. Они

управляют элементам операционной системы и прикладным программам.

Уведомляющие сообщения содержат информацию об изменении состояния окон

Windows®, их отдельных элементов и устройств системы. Они посылаются только

самой средой окон Windows®.

Каждое сообщение имеет два параметра: WPARAM и LPARAM. В 32-х битной среде

оба эти параметра имеют размер 32 бита (longword). В 16-битной Windows

WPARAM - это 16 битное число (word), а LPARAM - 32-битное (longint).

Для отправки сообщений API Windows® содержит две функции:

function PostMessage( HWND hWnd, // handle of destination window

UINT Msg, // message to post

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

):BOOLEAN;

function SendMessage( HWND hWnd, // handle of destination window

UINT Msg, // message to send

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

): LRESULT;

Первая из них отправляет сообщение в очередь сообщений Windows® и

немедленно возвращает управление. PostMessage возвращает TRUE, если вызов

функции прошел успешно и FALSE в противном случае.

Функция SendMessage отличается от PostMessage тем, что, послав сообщение,

она не возвратит управление до тех пор, пока сообщение не будет доведено до

получателя.

Обе функции имеют одинаковые параметры: HWND - дескриптор окна, которому

предназначается сообщение, UINT - сообщение, которое должно быть послано,

WPARAM и LPARAM - параметры сообщения.

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

приложения:

PostMessage(Handle,WM_QUIT,0,0);

В дополнение к функциям API Windows® VCL содержит метод Perform, который

можно использовать для посылки сообщений любому окну VCL.

function Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

Perform обходит систему передачи сообщений Windows® и направляет сообщение

непосредственно механизму обработки данного окна. С использованием

предыдущий пример будет выглядеть следующим образом:

Perform(WM_QUIT,0,0);

VCL имеет события примерно лишь для 20% наиболее часто используемых

сообщений Windows®. У программиста может возникнуть необходимость

обработать сообщения, для которых в VCL события не предусмотрены. Для

обработки сообщений ОС, не имеющих соответствующих событий в Delphi есть

ключевое слово message. С его помощью конкретный метод в коде программы

может быть связан с любым сообщением Windows®. Когда окно приложения

получает это сообщение вызывается соответствующий метод. Для реализации

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

1. Включить объявление метода для обработки сообщения в объявление класса,

указав ключевое слово message и название сообщения, для обработки которого

данный метод редназначен.

2. Включить определение метода в раздел implementation.

Приведем пример определения метода, обрабатывающего сообщение

WM_ERASEBKGND:

Procedure WmEraseBkgnd(var Msg:tWMEraseBkgnd); message WM_ERASEBKGND;

Ключевое слово message указывает на то, что данный метод используется для

обработки сообщения ОС, имя которого указано после этого ключевого слова:

WM_ERASEBKGND. Следует также отметить, что параметр метода является записью

типа tWMEraseBkgnd.

type TWMEraseBkgnd=record

Msg:Cardinal;

DC:HDC;

Unused:Longint;

Result:Longint;

end;

В VCL имеются записи для большинства сообщений Windows® (они определены в

модуле Messages.pas). Именем записи является имя сообщения с префиксом t и

без подчеркивания. Сам метод можно назвать как угодно, но приведенная выше

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

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

необходимо вызвать виртуальный метод класса tObject DefaultHandler:

procedure tObject.DefaultHandler(var Message); virtual;

Помимо нормальных сообщений Windows®, можно создать свое собственное

сообщение. еализация и перехват определяемого пользователем сообщения

идентичны обработке сообщений Windows®. Единственное отличие состоит в том,

что это сообщение необходимо сначала определить:

Const My_Message = WM_USER + 1;

Эта строка объявляет пользовательское сообщение с именем My_Message.

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

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

tMessage:

type tMessage = record

Msg:Cardinal;

WParam:Longint;

LParam:Longint;

Result:Longint;

end;

Здесь, как и во всех других определенных в VCL для сообщений записях, поле

Msg определяет передаваемое сообщение, поле Result - результат действия

обработчика. Уникальные для данной записи поля WParam и LParam содержат

соответствующие параметры сообщения.

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

SendMessage, PostMessage, однако, предпочтительнее в данном случае

использовать Perform.

Сокеты

Наиболее современным и даже "модным" является общение процессов на уровне

сокетов. Популярность их обусловлена взрывным ростом интереса как

пользователей, так и специалистов к Internet и всему, что связано с этой

сетью.

Общепринятой и стандартизованной на международном уровне является

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

открытых систем (Open Systems Interface, OSI). На каждом из уровней — от

первого, физического, до высших уровней представления и приложений —

решается свой объем задач и используется свой инструментарий.

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

уровне семиуровневой структуры. "Под ним", на сетевом уровне, находится

протокол IP (основа TCP/IP — Transmission Control Protocol/Internet

Protocol). Над ним находятся протоколы сеансового уровня (сервисы),

ориентированные на конкретные задачи — например, FTP (для передачи файлов),

SMTP (почтовый), всем известный гипертекстовый протокол HTTP и другие.

Использование сокетов, с одной стороны, позволяет абстрагироваться от

частностей работы на нижних уровнях, с другой — решать широкий круг задач,

недоступный специализированным протоколам.

С точки зрения своей сущности сокет — это модель одного конца сетевого

соединения, со своими свойствами и возможностью читать и записывать данные.

С точки зрения содержания — это прикладной программный интерфейс, входящий

в состав разных операционных систем, в том числе Windows — начиная с версии

3.11. Последняя его реализация носит название WinSock 2.0. Прототипы

функций содержатся в файле WINSOCK.PAS, поставляемом с Delphi.

API сокетов впервые появился в среде Unix и стал популярен вместе с (и

благодаря) протоколом TCP/IP. Именно они являются строительным материалом,

из которого построена сеть Internet. Но сокеты не обязательно базируются на

TCP/IP, они могут базироваться на IPX/SPX и других протоколах.

Механизм соединения при помощи сокетов таков. На одной стороне создается

клиентский сокет. Для инициализации связи ему нужно задать путь к

серверному сокету, с которым предстоит установить соединение.

Путь в сети задается двумя параметрами: адресом или равноценным ему именем

хоста, или просто хостом и номером порта. Хост — это система, в которой

запущено приложение, содержащее сокет. Неверно приравнивать понятие "хост"

или "адрес" к понятию "компьютер" — у компьютера может быть несколько

сетевых устройств и несколько адресов. Адрес в сетях TCP/IP задается

четверкой чисел в диапазоне 0..255, например, так: 192.168.99.1.

Естественно, каждый адрес даже в рамках Internet уникален — за этим следит

специальная организация. Имя хоста, как правило, — символьная строка,

поставленная в соответствие адресу и записанная по правилам UNC, например

http://www.microsoft.com. Взаимное соответствие между именами и адресами

может осуществляться по-разному, в зависимости от масштаба сети и

применяемой ОС. В Internet существует система имен доменов (DNS) —

специальные серверы, хранящие и поддерживающие таблицы соответствия между

символьным именем и адресом. Но в любом случае соединение по адресу

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

В случае, если ваши клиент/серверные приложения отлаживаются на одной и той

же машине, можно связать сокеты четырьмя способами:

. Указанием сетевого имени вашего компьютера (узнать его можно через

апплет "Сеть" Панели управления)

. Указанием IP — адреса вашего компьютера (узнать его можно в свойствах

протокола ТСР/IP: на машине должен стоять этот протокол и иметься

постоянный IP-адрес)

. Указанием имени localhost, указывающего, что сервер находится на том

же компьютере

. Указанием IP-адреса 127.0.0.1, указывающего на тоже самое

Номер порта — простое средство для поддержания одновременно нескольких

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

протоколов более высокого уровня. Так, для протокола FTP выделен порт 21,

SMTP — 25, популярная игра Quake II использует порт 27910 и т. п.

Программист должен ознакомиться со списком уже закрепленных портов, прежде

чем установит и использует свое значение.

С одной из двух вступающих в связь сторон запускается серверный сокет.

Первоначально он находится в состоянии просушивания (listening), то есть

ожидания связи. После получения запроса от другой стороны — клиента —

устанавливается связь. Но в то же время создается новый сокет для

продолжения прослушивания.

Естественно, в составе Delphi имеется полноценная поддержка сокетов. Еще в

версии 2 появился заголовочный файл WINSOCK.PAS. Есть он и сейчас — для

желающих использовать API WinSock напрямую. Мы же рассмотрим здесь

компоненты TServerSocket и TClientSocket, имеющиеся в Delphi 4 на странице

Internet Палитры компонентов.

Очень важным моментом в использовании сокетов является задание их типа —

блокирующего (синхронного) и неблокирующего (асинхронного). По существу,

работа с сокетами — не что иное, как операции ввода/вывода, которые, как мы

знаем, также могут быть синхронными и асинхронными (отложенными). В первом

случае при вызове функции ввода/вывода приложение блокируется до его

окончания. Во втором — инициируется ввод/вывод и выполнение приложения

сразу же продолжается; окончание ввода/вывода будет "ознаменовано" в

системе возникновением некоторого события. В библиотеке WinSock 2.0

поддерживаются оба типа операций с сокетами; соответственно, в компонентах

Delphi также можно установить нужный тип. Отвечают за него свойства

serverType и clientType, о которых рассказано ниже.

Специфика компонентов TServerSocket и TClientSocket в том. что они являются

"двухэтажными" надстройками над API сокетов. И у того, и у другого имеется

свойство:

property Socket: TClientWinSocket;

у компонента TClientSocket и

property Socket: TServerWinSocket;

у компонента TServerSocket

Это свойство представляет собой объект — собственно оболочку сокета, со

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

азделение труда между ними такое—на уровне TServerSocket (TClientSocket)

сосредоточены основные опубликованные свойства и события, обработку которых

можно запрограммировать; на уровне TServerWinSocket (TClientWinSocket)

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

Объект TServerWinSocket

На уровне этого объекта ведется список соединений с клиентскими сокетами,

содержащийся в свойстве:

property Connections [Index: Integer]: TCustomWinSocket;

Общее число соединений (и число элементов в свойстве connections) равно

значению свойства:

property ActiveConnections: Integer;

Этим списком и счетчиком удобно пользоваться для рассылки всем клиентам

какой-нибудь широковещательной информации, например:

for i:=0 to ServerSocket.Socket.ActiveConnections-1 do

ServerSocket.Socket.Connections[i].SendText('Hi! ');

Тип сервера (блокирующий/неблокирующий) задается свойством

type TServerType = (stNonBiocking, stThreadBiocking);

property ServerType: TServerType;

Поскольку сервер, который блокируется каждым чтением/записью, представить

себе трудно, разработчики фирмы Inprise пошли таким путем. Блокирующий

режим заменен режимом stThreadBlocking. В этом случае при установлении

каждого нового соединения запускается отдельный программный поток3 (объект

класса TServerclientThread). Он отвечает за связь с отдельным клиентом, и

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

Если вы не хотите порождать TServerclientThread, а хотите описать свой

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

обработчик события:

property OnGetThread: TGetThreadEvent;

type TGetThreadEvent = procedure (Sender: TObject;

ClientSocket: TServerClientWinSocket; var SocketThread:

TServerCiientThread) of object;

В отличие от stThreadBlocking, тип stNonBlocking своим поведением ничем не

отличается от описанного выше — операции происходят асинхронно, и

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

окончания.

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

собой определенные системные накладные расходы. Чтобы избежать этого, в

рассматриваемом объекте ведется кэш потоков. По завершении соединения

потоки не уничтожаются, а переводятся в состояние ожидания нового

соединения.

Свойство: property ThreadCacheSize: Integer;

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

для установления соединения с клиентом. Это количество должно

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

с клиентами. Лишние потоки поглощают системные ресурсы, в первую очередь

память и процессорное время. Чтобы оптимизировать использование кэша

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

property ActiveThreads: Integer;

property IdieThreads: Integer;

показывающих число активных (занятых обслуживанием клиентов) и

простаивающих (ожидающих) потоков соответственно.

Старт и завершение потока, работающего с сокетом, обрамлены событиями:

property OnThreadStart: TThreadNotifyEvent;

property OnThreadEnd: TThreadNotifyEvent;

type TTnreadNotifyEvent=procedure(Sender: TObject;

Thread: TServerClientThread) of object;

Чтобы избежать ситуаций тупиков или гонок при работе с сокетами, имеются

два метода:

procedure Lock;

procedure Unlock;

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

среде, заключите его между вызовами методов Lock и unlock — на это время

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

Методы чтения и записи для блокирующего и неблокирующего режима существенно

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

(асинхронного) режима.

Средства для организации чтения представлены группой из трех методов:

function ReceiveLength: Integer;

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

клиента о передаче

function ReceiveText: string;

возвращает прочитанную из сокета текстовую строку

function ReceiveBuf(var Buf; Count:Integer): Integer;

возвращает данные, прочитанные из сокета в буфер Buf, в количестве count

байт

Аналогично, методы:

function SendBuf(var Buf; Count: Integer): Integer;

procedure SendText(const S: string);

function SendStream (AStream: TStream) : Boolean;

посылают клиенту буфер, текстовую строку и поток данных. В дополнение к

этому метод:

function SendStrearoThenDrop (AStream: TStream): Boolean;

посылает клиенту поток данных и завершает соединение.

Передаваемый в качестве параметра последних двух функций поток данных

AStream переходит "под надзор" объекта TServerWinsocket и удаляется им по

мере пересылки. Программист не должен предпринимать попыток удалить AStream

после вызова методов SendSrteam или SendSrteamThenDrop.

При помощи метода:

function GetClientThread(ClientSocket: TServerClientWinSocket):

TServerClientThread;

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

сокета.

События:

property OnClientConnect;

property OnClientDisconnect;

property OnClientRead;

property OnClientWrite;

имеют одинаковый тип:

TSocketNotifyEvent=procedure(Sender: TObject;

Socket: TCustomWinSocket) of object;

Они происходят при соединении/отключении от клиента, а также при чтении и

записи. Если произошла ошибка, возникает событие:

property OnClientError; TSocketErrorEvent;

type

TErrorEvent = (eeGeneral, eeSend, eeReceive, eeConnect, eeDisconnect,

eeAccept);

TSocketErrorEvent = procedure (Sender: TObject;

Socket: TCustomWinSocket;

ErrorEvent: TErrorEvent;

var ErrorCode: Integer) of object;

Параметры его имеют следующее назначение. ErrorEvent указывает на тип

операции, во время которой произошла ошибка. При этом ErrorCode содержит

код ошибки Windows. Если вы сами обработали ошибку и не хотите дальнейших

действий по ее обработке со стороны системы, нужно установить параметр

ErrorCode в 0.

Компонент TServerSocket

Самое главное свойство этого компонента — уже упоминавшаяся ссылка на

объект:

property Socket: TServerWinSocket;

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

же создан только для того, чтобы опубликовать необходимые свойства и

события. В нем имеются свои события OnclientConnect, OnClientDisconnect,

OnClientRead, OnClientWrite, OnClientError, но они не самостоятельны, а

только отсылают к соответствующим событиям объекта TServerWinSocket. Также

обстоит дело и со свойствами serverType и ThreadCacheSize.

Дополнительно в компоненте предусмотрены события:

property OnListen: TSocketNotifyEvent;

происходит после того, как заданы адрес и порт сокета и перед тем, как он

включается в режим прослушивания (готовности к соединению)

property OnAccept: TSocketNotifyEvent;

происходит непосредственно после установки соединения

Свойство property Active: Boolean;

отвечает за состояние сокета. Для клиентского сокета изменение его значения

соответствует подключению/отключению от сервера. Для серверного —

включение/выключение состояния прослушивания. Использование этого свойства

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

procedure Open;

procedure Close;

Свойство property Service: string;

служит для идентификации предназначения сокета. Здесь должно храниться

символьное имя сервиса, для которого используется сокет (ftp, http, telnet

и др.)

Объект TClientWinSocket

Многие из событий и методов этого объекта уже описаны выше (см. объект

TServerwinSocket), так как они имеют общего предка. Но есть и различия,

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

типов:

type TCiientType = (ctNonBlocking, ctBlocking);

property ClientType: TCiientType;

В отличие от сервера, в блокировке клиента большой беды нет. Если

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

завершения операции. В режиме ctNonBiocking операции выполняются

асинхронно.

Компонент TClientSocket

Основное внимание при рассмотрении этого компонента обратим на логику

событий, происходящих при подключении клиентского сокета к серверу. Она

такова:

1. Вызывается метод open (или свойство Active устанавливается в True).

2. Перед началом инициализации происходит событие

property onLookup: TSocketNotifyEvent;.

В этот момент еще можно поменять свойства объекта TClieniwinSocket: адрес,

номер порта и т. п.

3. Сокет полностью инициализируется и начинает поиск. Когда серверный сокет

обнаружен, происходит событие

property onConneciing: TSocketNotifyEvent;.

4. Когда клиентский запрос удовлетворен сервером и установлено соединение,

происходит событие

property OnConnect: TSocketNotifyEvent;

Проиллюстрируем сказанное на примере пересылки информации о дате и времени.

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

procedure TClientForm.FileConnectltemClick(Sender: TObject);

Begin

if ClientSocket.Active then ClientSocket.Active := False;

if InputQuery('Сервер', 'Адрес (имя)', Server) then

if Length(Server)>0 then

with ClientSocket do begin

Host := Server;

Active := True;

end;

End;

После установления соединения клиент реагирует на событие onClientRead:

procedure TCiientFom.ClientSocketRead(Sender: TObject; Socket:

TCustomWinSocket);

var s: string;

Begin

s:= Socket.ReceiveText;

if ((s[l]='T') and (TimeSpeedButton.Down)) then

TimeSpeedButton.Caption:=Copy(s, 2, Length(s))

else if ((s[l]='M') and (MemSpeedButton. Down)) then

KemSpeedButton.Caption:=Copy(s, 2, Length (s));

End;

В серверном приложении сокет устанавливается в активное состояние

(прослушивание) при запуске программы. Все подключившиеся клиенты

автоматически заносятся как элемент списка (свойство connections). Саму

информацию о дате и времени сервер рассылает по таймеру в виде

отформатированных текстовых строк:

procedure TServerForm.TimerITimerlSender: TObject);

var i: Integer;

s: string;

ms : TMemoryStatus;

Begin

with ServerSocket.Socket do

for i:=0 to ActiveCcnnections-I do

Connections[i].SendText('T'+TimeToStr(Now));

GlobaiMemoryStatus(ms);

s:=Format('%1OdK',[(ms.dwAvaiiPageFile + ms.dwAvaiiPhys) div 1024]); with

ServerSocket.Socket do

for i:=0 to ActiveConnections-I do

Connections [ i ] . SendText ( ' M' +s ) ;

End;

Сервер может отреагировать на сообщение от клиента. Ответ следует

отправлять через параметр socket произошедшего события onClientRead:

procedure TServerForm.ServerSocketClientRead (Sender: TObject;

Socket: TCustomWinSocket);

Begin

Memo1.Lines.Add(Socket. ReceiveText );

Socket.SendText( ' I am understand' );

End;

К сокетам проявляют интерес многие разработчики, что можно объяснить их

универсальностью и широким распространением. Если вы не нашли чего-то для

вас необходимого в компонентах TClientSocket и TServerSocket, или наоборот

— сочли их слишком сложными в применении, вы можете использовать компонент

TPowersock, разработанный компанией NetMasters. Он находится также на

странице Internet Панели инструментов.

Совместное использование общей памяти

Традиционным является метод межзадачного взаимодействия при помощи

совместно используемой памяти. В DOS и 16-разрядной Windows он был простым

и не требующим пояснений — у всех задач, в том числе у операционной

системы, было общее адресное пространство. Но именно из этого проистекали

все беды и проблемы данных ОС. В 32x разрядных версиях Windows у каждого

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

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

Разделение данных осуществляется посредством отображения некоторого объема

совместно используемой памяти в адресное пространство каждого приложения,

участвующего в обмене. Память может быть разделяемой потому, что она

фактически располагается вне адресного пространства приложения. Вообще

говоря, в 32-разрядной Windows под "памятью" подразумевается не только

оперативная память (ОЗУ), но также и память, резервируемая операционной

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

Код и данные отображаются на жесткий диск посредством страничной системы

(paging system) подкачки. Страничная система использует для отображения

страничный файл (win386.swp в Windows 95 и pagefile.sys в Windows NT).

Необходимый фрагмент виртуальной памяти переносится из страничного файла в

ОЗУ и, таким образом, становится доступным.

Для выделения фрагмента совместно используемой памяти должен быть создан

специальный системный объект Win 32, называемый отображаемым файлом. Этот

объект "знает", как соотнести файл, находящийся на жестком диске, с

памятью, адресуемой процессами. В нашем случае файл, который мы посредством

объекта файлового отображения хотим сделать доступным нашему приложению —

это не файл с расширением ТХТ, DLL. или ЕХЕ. Это страничный файл подкачки,

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

адресуемую нашим приложением, мы, по сути дела, получили фрагмент памяти,

на который в дальнейшем будем ссылаться, как на данные объекта файлового

отображения.

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

отображения в память любого файла, а не только страничного файла

операционной системы.

Одно или более приложений могут открыть отображаемый файл и получить тем

самым доступ к данным этого объекта. Таким образом, данные, помещенные в

страничный файл приложением, использующим отображаемый файл, будут доступны

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

файл.

Разделение данных между приложениями осуществляется посредством

использования функций API, предоставляемых Win 32 для создания и

использования объектов файлового отображения. Вот наиболее важные из этих

функций:

CreateFileMapping

MapViewOfFile

UnMapViewOfFiie

Создание объекта файлового отображения

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

CreateFileMapping. Этот объект поддерживает соответствие между содержимым

файла и адресным пространством процесса, использующего этот файл. В нашем

случае целью будет совместное использование данных. Страничный файл

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

создав отображаемый файл, связанный со страничным файлом операционной

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

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

объекта файлового отображения, открытый программой, запущенной в качестве

другого процесса, может иметь к ней доступ.

Функция CreateFileMapping имеет шесть параметров:

function CreateFileMapping (hFile: THandle;

ipFileMappingAttributes: PSecurityAttributes;

flProtect,

dwMaximumSizeHign,

dwMaximurnSizeLow: DWORD;

lpName: PChar): THandle; stdcall;

Первый параметр имеет тип THandle. Для наших целей значение этого параметра

всегда должно быть $FFFFFFFF (значок "$" указывает на то, что значение

шестнадцатеричное). В принципе на этом месте должен стоять дескриптор уже

открытого при помощи функции CreateFile файла; но, поскольку мы имеем дело

с необычным файлом, указание значения $FFFFFFFF приводит к связыванию

объекта файлового отображения именно со страничным файлом операционной

системы.

Второй параметр — указатель на запись типа TSecurityAttributes. В нашем

случае значение этого параметра всегда равно nil. Третий параметр имеет тип

dword. Он определяет атрибут защиты. Чтобы при помощи отображаемого файла

организовать совместное использование данных, третьему параметру следует

присвоить значение PAGE_READWRITE.

Четвертый и пятый параметры также имеют тип dword. В 32-разрядной

операционной системе значение dword имеет дайну 32 бита. Когда выполняется

функция CreateFileMapping, значение типа dword четвертого параметра

сдвигается влево на четыре байта и затем объединяется со значением пятого

параметра посредством операции and. Проще говоря, значения объединяются в

одно 64-разрядное число, равное объему памяти, выделяемой объекту файлового

отображения из страничного файла операционной системы.

Поскольку вы вряд ли попытаетесь осуществить выделение более чем четырех

гигабайт данных, то значение четвертого параметра всегда должно быть равно

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

байтах необходимо зарезервировать в качестве совместной.

Шестой параметр имеет тип pСhar. Это значение представляет собой имя

объекта файлового отображения, которое должно быть уникальным.

Функция СreateFileMapping возвращает значение типа THandle. В случае

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

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

какой-либо ошибки возвращаемое значение будет равно 0.

Следующий фрагмент кода демонстрирует использование функции

СreateFileMapping:

var

hMappedFiie: THandie;

Begin

hMappedFile:=CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,

25, 'SharedBiock');

if (hMappedFiie = 0) then ShowMessage('Mapping error ! ');

End;

В этом примере функция выделяет 25 байт. езультирующий отображаемый файл

называется SharedBlock. В случае успеха функция вернет описатель текущего

объекта файлового отображения в переменную hFileMapObj. Переменная

hFileMapObj имеет тип THandie. Если переменная hFileMapObj равна нулю, то

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

соответствующее сообщение.

Подключение объекта файлового отображения к адресному пространству

При помощи функции CreateFileMapping мы только создали объект типа

"отображаемый файл"; следующая задача — спроецировать данные файла в

адресное пространство нашего процесса. Этой цели служит функция

MapViewOfFile. Функция MapViewOfFile имеет пять параметров:

function MapViewOfFile(hFileMappingObject: THandie;

dwDesiredAccess: DWORD;

dwFileOffsetHigh,

dwFiieOffsetLow,

dwIMumberOfBytesToMap: DWORD): Pointer; stdcall;

Первый параметр имеет тип THandle. Его значением должен быть дескриптор

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

CreateFileMapping. Второй параметр имеет тип dword. Его значение должно

быть установлено в FILE_MAP_WRITE (или. что то же самое, в

FILE_MAP_ALL_ACCESS); это означает, что данные объекта файлового

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

Третий и четвертый параметры также имеют тип dword. Это — смещение

отображаемого участка относительно начала файла в байтах. В нашем случае

эти параметры должны быть установлены в нуль, поскольку значение, которое

мы даем пятому (последнему) параметру функции MapViewOfFile, также равно

нулю.

Пятый и последний параметр функции MapViewOfFile, как и предыдущие

параметры, имеет тип dword. Он используется для определения (в байтах)

количества данных объекта файлового отображения, которые надо отобразить в

процесс (сделать доступными для вас). Для достижения наших целей это

значение должно быть установлено в нуль, что означает автоматическое

отображение в процесс всех данных, выделенных перед этим функцией

CreateFileMapping.

Значение, возвращаемое функцией MapViewOfFile, имеет тип "указатель". Если

функция отработала успешно, то она вернет начальный адрес данных объекта

файлового отображения.

Следующий фрагмент кода демонстрирует вызов функции MapViewOfFile:

var

hMappedFile: THandie;

pSharedBuf: PChar;

Begin

hMappedFile:=CreateFileMapping ($FFFFFFFF, nil, PAGE_READWRITE, 0,

25, 'SharedBlock');

if (hMappedFile = 0) then ShowMessage ('Mapping error !') else begin

pSharedBuf:=MapViewOfFile(hMappedFile,FILE_MAP_ALL_ACCESS,0,0,0);

if (pSharedBuf = nil) then ShowMessage ('MapView error');

end;

End;

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

CreateFileMapping. Имя результирующего объекта файлового отображения будет

SharedBlock. Если функция завершится успешно, то ссылка на новый

отображаемый файл будет возвращена в переменную hMappedFile. Переменная

hMappedFile имеет тип Thandle, если значение переменной hMappedFile равно

нулю, то это означает, что функция отработала с ошибкой, о чем будет

выведено соответствующее сообщение. Если значение не нулевое, то будет

вызвана функция MapViewOfFile. Дескриптор hMappedFile является первым

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

процессом. Если функция завершится успешно, то в переменную pSharedBuf

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

объекта файлового отображения с именем SharedBlock. Если функция отработает

с ошибкой, то значение, возвращаемое функцией MapViewOfFile будет равно

nil, в этом случае будет показано сообщение, констатирующее этот факт.

Совместное использование отображаемых данных

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

операционной системы, каждый процесс, участвующий в разделении, должен

отобразить один и тот же фрагмент одного и того же файла. Такой результат

может быть достигнут передачей во всех указанных процессах одинаковых

параметров для функций CreateFileMapping и MapViewOfFile. Указатель,

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

записывать согласно своим потребностям. Если данные, связанные с этим

указателем, доступны одному из процессов, то они также доступны и другим

процессам.

Займемся очередным "клонированием" нашего примера с передачей данных о

времени и свободной памяти. Общим для клиента и сервера теперь является

описанный выше код по открытию и отображению файла. Он описан как реакция

на событие формы onCreate. Когда запускается программа и создается форма,

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

так:

procedure TSrvForm.FormCreate (Sender: TObjecr);

begin

hFileMapObj:=CreateFiieMapping(MAXDWORD, nil, PAGE_READWRITE, 0, 25,

'SharedMem');

if (hFiieMapObj:=0) then ShowMessage('Error: cannot map this file')

else begin

pSharedBuf:=MapViewOfFile(hFiieMapObj,FILE_MAP_WRITE,0,0,0);

if (pSharedBuf = nil) then

ShowMessage ('Error: pSharedBuf is nil');

end;

end;

Приведенный выше код работает точно так, как описано выше в разделе

"Подключение объекта файлового отображения к адресному пространству".

Единственным отличием является лишь то, что объявления переменных

hFileMapObj и pSharedBuf перенесены в раздел var секции interface так, как

это показано ниже:

interface

var Form1: TForm1;

hFileMapObj: THandle;

pSharedBuf; Pointer;

implementation

Компоненты типа TTimer есть и у клиента и у сервера. При каждом очередном

событии от этого компонента сервер помещает нужные данные в совместно

используемую область памяти:

type

pDataRecord = ^TDataRecord;

TDataRecord = record

DateTime : TDateTime;

Mem : Cardinal ;

end;

procedure TServerForm.Timer1Timer(Sender: TObject);

var ms : TMemoryStatus;

begin

GlobalMemoryStatus (ms);

pDataRecord(pSharedBuf)^.DateTime:= Now;

pDataRecord(pSharedBuf)^.Mem := ms.dwAvailPageFile + ms.dwAvaiiPhys;

end;

а клиент — читает эти данные, если нажата соответствующая кнопка:

procedure TClientForm.TimerITimerfSender: TObject);

begin

if MemSpeedButton.Down then MemSpeedButton.Caption:=

Format('%8dK',[pDataRecord(pSharedBuf)^.Mem div 1024]);

if TimeSpeedButton.Down then TimeSpeedButton.Caption:=

TimeToStr(pDataRecord(pSharedBuf)^.DateTime);

end;

Последним штрихом станет восстановление надписи на кнопке после того, как

она снова нажата пользователем:

procedure TClientForm.TimeSpeedButtonClick (Sender: TObject);

begin

if not TimeSpeedButton.Down then TimeSpeedButton.Caption:='Time';

end;

procedure TClientForm.Mem.SpeedButtonClick(Sender: TObject);

begin

if not MemSpeedButton. Down then MemSpeedButton.Caption:='Memory';

end;

В рассматриваемой программе есть несколько операторов, необходимых для

прекращения отображения данных и освобождения объекта файлового

отображения. Эти операторы обсуждаются в следующем разделе.

Прекращение отображения данных и закрытие объекта файлового отображения

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

называются UnMapViewOfFile И CloseHandle. ФУНКЦИЯ UnMapViewOfFile делает

то, что подразумевает ее название. Она прекращает отображение в адресное

пространство процесса того файла, который перед этим был отображен при

помощи функции MapViewOfFile. Функция CloseHandle также делает то, что

подразумевает ее название; она закрывает дескриптор, в нашем случае это

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

CreateFileMapping.

Функция UnMapViewOfFile должна вызываться перед функцией CloseHandle

Функции UnMapViewOfFile передается единственный параметр типа указатель:

procedure TCIientForm.FormDestroy(Sender: TObject);

begin

UnMapViewOfFiie (pSharedBuf) ;

CloseHandle (hFileMapObj);

end;

Помимо возможности совместного доступа отображаемые файлы позволяют заметно

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

инструментом доступа к данным.

Каналы

Протокол DDE пережил полосу своего расцвета, и хотя впоследствии появилась

его сетевая версия (Network DDE, или NDDE), стандартом де-факто она не

стала. Одним из кандидатов на замену DDE стал механизм каналов (pipes). О

его роли говорит хотя бы то, что это один из основных протоколов работы

такого продукта, как Microsoft SQL Server.

Сразу оговоримся, что каналы бывают двух видов — именованные (named pipes)

и безымянные (anonymous pipes). Вторые не предназначены для связи между

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

Win32 API. В этой книге рассматриваются только именованные каналы.

И еще одна очень важная оговорка. Каналы рассматривались Microsoft как

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

часть реализована только в среде Windows NT и не поддерживается в Windows

95/98; клиенты могут быть созданы во всех этих ОС.

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

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

двухсторонним. Тем не менее, одно из приложений играет роль сервера (оно

создает канал), другое (или другие) лишь подключается к нему. Противоречия

с вышесказанной фразой о двух приложениях здесь нет: на сервере канал виден

как ресурс с единственным уникальным именем. Когда к серверу подключается

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

дескриптором, буфером и т, п. Но имя канала для всех клиентов одинаково.

Имя канала записывается в соответствии с так называемым соглашением UNC

(Universal Naming Convention). Выглядеть оно должно так:

\servername\pipe\pipename

де servernanie — сетевое имя компьютера — сервера,

ipename — имя канала.

В случае, если клиенту нужно подключиться к серверу на том же компьютере,

его сетевое имя заменяется точкой:

\.\pipe\pipename

Максимальная длина имени канала ограничена 256 символами; строчные и

прописные буквы не отличаются.

Для программиста алгоритмы создания серверного и клиентского "концов"

канала отличаются. Для создания сервера применяются специальные функции Win

32 API; клиентский же конец открывается как обычный файл и работа с ним

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

"упрячем" эти функции в класс Delphi — потомок HandleStream, ближайшего

родственника файлового потока TFileStream.

Пока же рассмотрим искомые функции API. Главная из них для нас следующая:

function CreateNainedPipe(lpName: PChar;

dwOperiMode,

dwPipeMode,

nMaxInstances,

nOutBufferSize,

nInBufferSize,

nDefauitTimeOut: DWORD;

lpSecurityAttributes:PSecurityAttributes):THandle;

Она создает серверный конец канала с именем lpName. Остальные параметры

перечислены в табл. 8.1.

Параметры функции createNamedPipe

Параметр Назначение

dwOpenMode

Режим открытия. Флаги:

PIPE_ACCESS_DUPLEX — двунаправленный обмен данными

PIPE_ACCESS_INBOUND — только от клиента к серверу

PIPE_ACCESS_OUTBOUND — только от сервера к клиенту

FILE_FLAG_WRITE_THROUGH — запись данных, минуя кэш

FILE_FLAG_OVERLAPPED — режим отложенной операции ввода/ вывода

dwPipeMode

Режим работы канала. Флаги:

PIPE_TYPE_BYTE — запись в режиме потока байт

PIPE TYPE_MESSAGE — запись в режиме потока сообщений

PIPE_READMODE_BYTE — чтение в режиме потока байт

PIPE_READMODE_MESSAGE — чтение в режиме потока сообщений

PIPE_WAIT — функции ввода/вывода не будут возвращать управление до

завершения операции

PIPE_NOWAIT — функции ввода/вывода возвращают управление немедленно

nMaxinstances

Максимальное количество открываемых экземпляров канала, от 1 до

PIPE_UNLIMITED_INSTANCES

nOutBufferSize

Размер буфера для записи

nInBufferSize

Размер буфера для чтения

nDefauitTimeOut

Задает время ожидания конца операции ввода/вывода в канале (в мс)

IpSecurityAttributes

Указатель на структуру Windows NT, содержащую информацию о правах доступа к

каналу Pежимы потока байт и потока сообщений не слишком отличаются друг от

друга — в первом случае система смешивает данные от различных операций

чтения/записи в единый поток, во втором — разделяет их на отдельные порции.

Канал в режиме PIPE_TYPE_BYTE может работать на чтение только в режиме

чтения PIPE_READMODE_BYTE, канал в режиме PIPE_TYPE_MESSAGE — в обоих

режимах чтения.

Функция

function ConnectNainedPipe(hNamedPipe: THandle;

lpOverlapped: POverlapped): BOOL;

позволяет подключиться к уже созданному каналу hNamedPipe, а функция

function DisconnectNaitledPipe (hNamedPipe: THandie): BOOL;

позволяет отключить клиента — она разрывает связь клиентского и серверного

концов канала hNamedPipe.

Чтобы собрать информацию о состоянии канала hNamedPipe, нужно вызвать

функцию

function GetNamedPipeln.fo(hMamedPipe: THandle;

var ipFlags: DWORD;

lpOutBufferSize,

lpInBufferSize,

lpMaxInstances: Pointer): BOOL;

Указатели lpOutBufferSize, lpInBufferSize, ipMaxInstances должны указывать

на переменные, куда будут записаны размеры буферов и число открытых

экземпляров канала. Параметр lpFlags указывает на переменную, в которую

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

PIPE_TYPE_MESSAGE, а также флаг PIPE_SERVER_END. Он установлен, если

hNamedPipe — серверный конец канала.

function PeekNamedPipe (hNamedPipe: THandle;

lpBuffer: Pointer;

nBufferSize: DWORD;

lpBytesRead,

lpTotalBytesAvail,

lpBytesLeftThisMessage: Pointer): BOOL;

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

(доступными для последующего чтения). Кроме того, она возвращает

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

адресу, указанному в параметре lpTotalBytesAvail, будет записано число еще

не прочитанных байт в канале. Функция

Function WaitNamedPipe(lpNamedPipeName: PChar; nTimeOut: DWORD): BOOL;

позволяет организовать ожидание окончания операции в канале. Параметр

nTimeOut задает время ожидания в миллисекундах; возможны еще два особых

значения — NMPWAIT_USE_DEFAULT_WAIT (ожидать в течение времени, указаного

при создании канала) и NMPWAIT_WAIT_FOREVER (ждать бесконечно).

Осталось добавить, что чтение и запись в канал осуществляется так же, как и

в обычный файл — функциями ReadFile и WriteFile.

Каналы хорошо приспособлены для обмена данными в сети. Но все же их

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

Поэтому в глобальных сетях применяется другой вариант механизма

межзадачного взаимодействия — сокеты.

4. Многопоточные приложения

Потоки - это объекты, получающие время процессора. Потоки позволяют в

рамках одной программы решать несколько задач одновременно. Операционная

система предоставляет приложению некоторый интервал времени центрального

процессора (называемый квантом) и в момент, когда приложение переходит к

ожиданию сообщений или освобождает процессор, ОС передает управление другой

задаче. Планируя время центрального процессора, Windows распределяет его

межу потоками, а не между приложениями. Время процессора выделяется

квантами (около 19 мс). Чтобы использовать все возможности операционной

системы, программист должен знать, как создавать потоки.

Существует два типа потоков: асимметричные и симметричные.

Асимметричные потоки (asymmetric threads) решают различные задачи, и, как

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

Симметричные потоки (symmetric threads) выполняют одну и ту же работу,

разделяет одни и те же ресурсы и исполняют один код. Пример приложения с

симметричными потоками - электронные доски объявлений (Bulletin Board

Systems, BBS). Для обслуживания каждого дозвонившегося туда пользователя

BBS запускает новый поток.

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

Процесс - это совокупность виртуальной памяти, исполняемого кода, потоков и

данных. Процесс должен содержать по крайней мере один поток.

Если задачи приложения можно органично разделить на различные подмножества:

обработка событий, ввод-вывод, связь и т.д., то потоки могут быть органично

встроены в программное решение. Сделав приложение многопоточным,

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

через управление приоритетами потоков. Другое важное преимущество

использования потоков - при возрастании "нагрузки" на приложение можно

увеличить их количество и можно увеличить их количество, и, тем самым,

снять проблему.

Две типичные проблемы, с которыми может столкнуться программист при работе

с потоками - это ситуации тупиков и гонок.

Гонки

Ситуация гонок возникает, когда два или более потоков пытаются получить

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

пример. Пусть поток 1 получил доступ к ресурсу и изменил его в своих

интересах; затем активизировался поток 2 и модифицировал этот же ресурс до

завершения потока 1. Поток 1 полагает, что ресурс остался в том же

состоянии, в котором он был до переключения (строго говоря, потоку 1 вообще

ничего не известно о переключении). В зависимости от того, когда был

изменен ресурс, результаты будут варьироваться - иногда код будет

выполняться правильно, иногда - нет. Программист не должен строить никаких

иллюзий по поводу выполнения потоков, т.к. планировщик ОС может запускать

их и останавливать в любое время.

Тупики

Тупики имеют место, когда поток ожидает ресурс, который в данный момент

принадлежит другому потоку. ассмотрим пример: поток 1 захватывает объект А

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

В то же время, поток 2 захватывает объект Б и ждет возможности захватить

объект А. В результате оба потока оказываются заблокированными - ни один из

них не будет выполняться. Для исключения ситуаций тупиков и гонок потоки

следует синхронизировать.

Приоритеты потоков

Операционная система планирует время в соответствии с приоритетами потоков.

Когда поток создается, ему назначается приоритет, соответствующий

приоритету породившего его процесса. Процессы, в свою очередь, имеют

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

1. Pеального времени (Real Time). Этот класс определяет приоритет даже

больший, чем у многих процессов операционной системы. Такой процесс

необходим для процессов, обрабатывающих высокоскоростные потоки данных.

2. Высокий (High). Этот класс определяет приоритет выше нормального. Он

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

3. Нормальный (Normal). Большинство процессов запускается в рамках этого

класса.

4. Фоновый (Idle). Процессы с таким приоритетом запускаются только в

случае, если в очереди диспетчера задач нет других процессов. Программисты

могут использовать этот класс приоритетов для организации фоновых процедур

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

сохранение файла в MS Word.

Приоритеты имеют значения от 0 до 31. Приоритет потока может отличаться от

приоритета породившего его процесса на плюс-минус две единицы.

Соответствующие величины показаны ниже.

Низший

Пониженный

Нормальный

Повышенный

Высший

Фоновый

2

3

4

5

6

Нормальный заднего плана

5

6

7

8

9

Нормальный переднего плана

7

8

9

10

11

Высокий

11

12

13

14

15

Реального времени

22

23

24

25

26

Класс tThread

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

это ее объект. При создании он получает дескриптор и отслеживается ОС.

Объект класса tThread - это конструкция Delphi, соответствующая потоку ОС.

Для создания потока служит конструктор

Constructor Create(CreateSuspended:Boolean);

Если параметр CreateSuspended равен TRUE, вновь созданный поток не начинает

выполняться до тех пор, пока не будет вызван метод Resume. В противном

случае сразу после создания поток начинает исполнение.

Метод Procedure Resume;

класса tThread вызывается, когда поток возобновляется после остановки, или

если он был создан конструктором с параметром CreateSuspended, равным TRUE.

Вызов метода

Procedure Suspend;

Приостанавливает поток с возможностью повторного запуска в последствии.

Выполнение продолжается с точки останова.

Свойство Property Suspended:boolean;

Позволяет программисту определить, не приостановлен ли поток. С помощью

этого свойства можно также запускать и останавливать поток.

Для окончательного завершения потока (без последующего запуска) существует

метод

procedure Terminate;

он останавливает поток и возвращает управление вызвавшему его процессу

только после того, как это произошло. Свойство

property Terminated:boolean;

позволяет узнать, вызывался ли уже метод Terminate.

Метод Function WaitFor: integer;

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

когда завершится другой. Если внутри потока FirstThread написать код:

Code:=SecondThread.WaitFor;

то это означает, что поток FirstThread останавливается до завершения потока

SecondThread . Метод WaitFor возвращает значение свойства ReturnValue.

Свойство Property Priority: tThreadPriority;

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

tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest, tpTimeCritrcal.

Метод Procedure Synchronyze(Method: tTHreadMethod);

предназначен для безопасного вызова методов VCL внутри потоков. Этот метод

гарантирует, что к каждому объекту VCL одновременно имеет доступ только

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

метод Synchronyze - это имя метода, который производит обращение к VCL.

Вызов Synchronyze с таким аргументом, это то же самое, что и вызов самого

метода. Synchronyze находится в области видимости protected, и,

следовательно, доступен только в методах потомков класса tThread.

Главным методом класса tThread является абстрактный метод

Procedure Execute; virtual; abstract;

Этот метод должен быть переопределен в классе-потомке tThread. В его теле

должен содержаться код, который и представляет собой собственно поток. Если

поток был создан с аргументом CreateSuspended, равным FALSE, то метод

Execute вызывается сразу после создания потока; в противном случае - после

вызова метода Resume.

Если поток рассчитан на однократное выполнение каких либо действий, то

никакого специального кода завершения для него писать не нужно. После

выполнения метода Execute будет вызван деструктор, который сделает все

необходимое.

Если же в потоке будет выполняться бесконечный цикл, и поток должен

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

примерно такими:

rocedure tMyThread.Execute;

Begin

Repeat

// Код потока

Until CancelCondition or Terminated;

End;

Здесь CancelCondition - ваше личное условие завершения потока, а свойство

Terminated говорит о завершении потока извне (скорее всего, завершился

породивший его процесс).

С завершением потока нужно быть очень внимательным: если он зациклился,

зависнет все приложение.

Свойство Property ReturnValue: Integer;

Позволяет узнать код завершения потока. Эта величина полностью определяется

пользователем.

Для создания своего потока следует найти в репозитории значок Thread Object

(он находится на странице New). Двойной щелчок на этом значке вызовет

диалоговое окно New Thread Object, в которое следует ввести имя будущего

класса-потомка tThread. Нажатие кнопки ОК вызовет генерацию шаблона нового

потока:

unit ThreadUnit;

interface

uses Classes;

type

tMyThread = class(TThread)

private

{ Private declarations }

protected

procedure Execute; override;

end;

implementation

{ Important: Methods and properties of objects in VCL can only be used in a

method called using Synchronize, for example,

Synchronize(UpdateCaption);

and UpdateCaption could look like,

procedure tMyThread.UpdateCaption;

begin

Form1.Caption := 'Updated in a thread';

end; }

{ tMyThread }

procedure tMyThread.Execute;

begin

{ Place thread code here }

end;

end.

Средства синхронизации потоков

Главные понятия механизма синхронизации - функции ожидания и объекты

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

выполнение вызвавшего эту функцию потока вплоть до того момента, как будет

изменено состояние какого-нибудь объекта, называемого объектом ожидания.

(Под этим термином понимается не объект Delphi, а объект операционной

системы.) К возможным вариантам относятся четыре объекта, разработанных

специально для синхронизации:

1. Событие (event);

2. Взаимное исключение (mutex);

3. Семафор (semaphore);

4. Таймер (timer).

Кроме них, ждать можно и других объектов, дескриптор которых используется в

основном для других целей, но может применяться и для ожидания. К ним

относятся: процесс, поток, оповещение об изменении в файловой системе

(change notification) и консольный ввод. Соответствующие классы находятся в

модуле SYNCOBJS.PAS.

Событие

(класс tEvent) имеет два метода: SetEvent и ResetEvent, переводящие объект

в активное и пассивное состояние соответственно. Конструктор класса имеет

вид:

Constructor Create(EventAttributes: pSecurityAttributes; ManualReset,

InitialState:Boolean; const Name:string);

Здесь параметр EventAttributes определяет права доступа к событию и, в

большинстве случаев должен быть равен nil. Параметр InitialState определяет

начальное состояние события. Параметр ManualReset - способ его сброса. Если

этот параметр равен TRUE, то событие должно быть сброшено вручную. В

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

стартует хотя бы один дожидавшийся его поток.

Метод

type tWaitResult = (wrSignaled, wrTimeOut, wrAbandoned, wrError);

Function WaitFor(TimeOut:DWORD): tWaitResult;

Дает возможности ожидать активизации события в течение TimeOut миллисекунд.

Метод возвращает wrSignaled в случае, если произошла активизация события и

wrTimeOut, если за время тайм-аута ничего не произошло. Если нужно ждать

бесконечно долго, следует установить параметр TimeOut в значение INFINITE.

Для синхронизации потоков следует вставить вызов WaitFor в необходимом

месте кода потока (метода Execute), и тогда выполнение потока будет

приостановлено до момента активизации события (то есть когда какой-либо

другой поток вызовет его метод SetEvent) или окончания тайм-аута.

Взаимные исключения

Объект типа взаимное исключение (класс tMutex, файл IPCTHRD.PAS) позволяет

только одному потоку в данный момент владеть им. Его можно сравнить с

эстафетной палочкой. Первоначально, данный объект не принадлежит никому.

Метод

function Get(TimeOut:Integer):Boolean;

Производит попытку в течение TimeOut миллисекунд завладеть объектом (в этом

случае метод возвращает TRUE). Если объект больше не нужен, следует вызвать

метод

function Release: boolean;

Критическая секция

Критические секции (класс tCriticalSection) подобны взаимным исключениям по

сути, но между ними существуют два главных отличия:

1. Взаимные исключения могут быть совместно использованы потоками в

различных процессах.

2. Если критическая секция принадлежит другому потоку, ожидающий поток

блокируется вплоть до освобождения критической секции. В отличие от этого,

взаимное исключение разрешает продолжение по истечении тайм-аута.

Критические секции эффективнее взаимных исключений, так как они используют

меньше системных ресурсов.

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

многопотоковом приложении создается и инициализируется общая для всех

потоков критическая секция. Когда один из потоков достигает критически

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

. . .

CriticalSection.Enter;

try

// критический участок кода

finally

CriticalSection.Leave;

end;

. . .

Когда другие потоки доходят до оператора захвата секции и обнаруживают, что

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

первым потоком путем вызова метода Leave. Критические секции являются

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

все другие рассматриваемые здесь объекты).

Процесс. Порождение дочернего процесса

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

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

завершении процесса. С практической точки зрения такая ситуация возникает,

когда в рамках вашего приложения возникает необходимость исполнить другое

приложение.

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

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

function CreateProcess(lpApplicationName: pChar; lpCommandLine: pChar;

lpProcessAttributes, lpThreadAttributes: pSecurityAttribytes;

bInheritHandles:BOOL;

dwCreationFlags: DWORD;

lpEnviroment: Pointer;

lpCurrentDirectory:pChar;

const lpStartupInfo: tStartupInfo;

var lpProcessInformation: tProcessInformation):BOOL;

Первые два параметра - это имя запускаемого приложения и передаваемые ему в

командной строке параметры. Параметры dwCreationFlags содержит флаги,

определяющие способ создания нового процесса и его приоритет. Структура

lpStartupInfo содержит сведения о размере, цвете, положении окна

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

lpProcessInformation. В ней программисту возвращаются дескрипторы и

идентификаторы созданного процесса и его первичного потока.

Локальные данные потока

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

потоков. Как избежать совместного использования одних и тех же переменных

различными потоками? В первую очередь следует использовать поля объекта –

потомка tThread. Каждый поток соответствует отдельному экземпляру объекта,

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

использовании функций API, которые знать не знают об объектах Delphi и их

полях и свойствах. Для поддержки разделения данных на нижнем уровне в

Object Pascal введена специальная директива - threadvar, которая отличается

от обычной var тем, что применяется только к локальным данным потока.

Следующее описание:

var data1:integer;

threadvar data2:integer;

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

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

Ермаков P.В. |Основные разделы

Железо:

> раздел hardware

Обзоры программ:

> раздел software

Программирование:

> раздел programming

Операционные системы:

> раздел ОС

Графика и дизайн:

> раздел graphics

Сетевые технологии:

> раздел network

| |

Страницы: 1, 2, 3


© 2010 Современные рефераты