Delphi error io pending

Delphi error io pending Именованные каналы (Named Pipes) — это объекты ядра, являющиеся средством межпроцессной коммуникации между сервером канала и одним или несколькими клиентами канала. Сервером канала называется процесс, создающий именованный канал. Клиентом канала называется процесс, подключающийся к созданному именованному каналу. От других аналогичных объектов именованные каналы отличает гарантированная доставка сообщений, возможность асинхронного ввода/вывода, […]

Delphi error io pending

Именованные каналы (Named Pipes) — это объекты ядра, являющиеся средством межпроцессной коммуникации между сервером канала и одним или несколькими клиентами канала.

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

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

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

По своему назначению они похожи на каналы операционной системы UNIX.

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

Базовым объектом для реализации именованных каналов служит объект «файл», поэтому для посылки и приема сообщений по именованным каналам используются те же самые функции Windows API, что и при работы с файлами (ReadFile, WriteFile).

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

Экземпляры одного канала имеют общее имя, указанное при создании, сервер назначает имя канала в соответствии с универсальными правилами именования (Universal Naming Convention, UNC), которые обеспечивают независимый от протоколов способ идентификации каналов в Windows-сетях [1].

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

Создание именованных каналов возможно только в NT-системах, подключение к созданному каналу возможно как в NT-системах, так и в Win9x. Кроме того, API работы с каналами в Win9x не поддерживает асинхронных операций ввода/вывода.

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

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

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

Для работы с именованными каналами Windows API предоставляет следующие функции:

CreateNamedPipe Создание именованного канала или нового экземпляра канала. Функция доступна только серверу.
ConnectNamedPipe или CreateFile Подключение к экземпляру именованного канала со стороны клиента. Функция доступна только клиенту.
WaitNamedPipe Ожидание клиентом появления свободного экземпляра именованного канала для подключения к нему.
ConnectNamedPipe Ожидание сервером подключения клиента к экземпляру именованного канала.
ReadFile, ReadFileEx Чтение данных из именованного канала. Функция доступна как клиенту, так и серверу.
WriteFile, WriteFileEx Запись данных в именованный канал. Функция доступна как клиенту, так и серверу.
PeekNamedPipe Чтение данных из именованного канала без удаления прочитанных данных из буфера канала. Функция доступна как клиенту, так и серверу.
TransactNamedPipe Запись и чтение из именованного канала одной операцией. Функция доступна как клиенту, так и серверу.
DisconnectNamedPipe Отсоединение сервера от экземпляра именованного канала.
GetNamedPipeInfo Получение информации об именованном канале.
GetNamedPipeHandleState Получение текущего режима работы именованного канала и количества созданных экземпляров канала.
SetNamedPipeHandleState Установка текущего режима работы именованного канала.
CloseHandle Закрытие дескриптора экземпляра именованного канала, освобождение связанных с объектом ресурсов.
FlushFileBuffers Сброс данных из кэша в буфер канала.

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

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

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

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

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

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

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

Рассмотрим реализацию потока сервера, обслуживающего экземпляр именованного канала:

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

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

Как уже отмечалось ранее, базовым объектом для реализации именованных каналов является объект «Файл». Это позволяет перечислить созданные в системе именованные каналы программно средствами Native API: открыть корневой каталог файловой системы именованных каналов (DeviceNamedPipe) и перечислить его содержимое. Пример программы перечисления созданных именованных каналов можно найти на сайте http://www.sysinternals.com (PipeList) или в приложении к статье.

Автор выражает признательность Екатерине Субботиной, Дмитрию Заварзину и Александру Жигалину за конструктивную критику в процессе написания статьи.

Источник

Функция ReadFile

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

Эта функция предназначена и для синхронной и асинхронной операции. Функция ReadFileEx предназначена исключительно для асинхронной операции. Это дает возможность приложению выполнять другие действия в ходе операции чтения файла.

[in] Дескриптор файла, который читается. Дескриптор файла должен быть, создан с правом доступа GENERIC_READ . Подробную информацию см. в статье Защита файла и права доступа.

Windows NT/2000/XP: Для асинхронных операций чтения, параметр hFile может быть любым дескриптором открытым с флажком FILE_FLAG_OVERLAPPED функцией CreateFile или дескриптор сокета, возвращенный функцией socket или accept.

Windows 95/98/Me: Для асинхронных операций чтения, параметр hFile может быть коммуникационным ресурсом, открытым с флажком FILE_FLAG_OVERLAPPED функцией CreateFile или дескриптор сокета, возвращенный при помощи функций socket или accept . Вы не можете выполнять асинхронные операции чтения в почтовом ящике в ядре Windows, именованных каналах или файлах на диске.

[out] Указатель на буфер, который принимает прочитанные данные из файла.

[in] Число байтов, которые читаются из файла.

[out] Указатель на переменную, которая получает число прочитанных байтов. Функция ReadFile устанавливает это значение в нуль перед началом любой работы или проверкой ошибок. Если этот параметр равняется нулю, когда ReadFile возвращает значение ИСТИНА (TRUE) для именованного канала, другой конец канала в режиме передачи сообщений вызывает функцию WriteFile с параметром nNumberOfBytesToWrite установленным в нуль.

Windows NT/2000/XP: Если параметр lpOverlapped имеет значение ПУСТО (NULL), параметр lpNumberOfBytesRead не может быть значением ПУСТО (NULL). Если lpOverlapped имеет — значение не ПУСТО (NULL), lpNumberOfBytesRead может быть значением ПУСТО (NULL). Если это — асинхронная операция чтения, Вы можете получить число прочитанных байтов при помощи вызова функции GetOverlappedResult . Если параметр hFile связан с портом завершения ввода-вывода, Вы можете получить число прочитанных байтов при помощи вызова функции GetQueuedCompletionStatus .

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

Windows 95/98/Me: Этот параметр не может быть ПУСТО (NULL).

[in] Указатель на структуру OVERLAPPED. Эта структура требуется тогда, если параметр hFile создавался с флажком FILE_FLAG_OVERLAPPED .

Если hFile был открыт с флажком FILE_FLAG_OVERLAPPED , у параметра lpOverlapped не должно быть значения ПУСТО (NULL). Он должен указать на правильную структуру OVERLAPPED. Если hFile создавался с флажком FILE_FLAG_OVERLAPPED , а lpOverlapped имеет значение ПУСТО (NULL), функция может неправильно сообщить о завершении операций чтения.

Если hFile был открыт с флажком FILE_FLAG_OVERLAPPED , а lpOverlapped имеет значение не ПУСТО (NULL), операция чтения начинается при смещении, заданном в структуре OVERLAPPED, и ReadFile может возвратить значение прежде, чем операция чтения будет закончена. В этом случае, ReadFile возвращает значение ЛОЖЬ (FALSE), а функция GetLastError возвращает значение ERROR_IO_PENDING . Это дает возможность вызывающему процессу продолжиться, в то время как операция чтения заканчивается. Событие, определяемое в структуре OVERLAPPED устанавливается в сигнальное состояние после завершения операции чтения. Вызывающая программа должна корректировать местоположение указателя позиции в файле после завершения работы.

Функция ReadFile сбрасывает событие, указанное членом hEvent структуры OVERLAPPED в несигнальное состояние, когда она начинает операцию ввода-вывода (I/O). Поэтому, нет необходимости для вызывающей программы, чтобы делать так.

Если hFile не открывался с флажком FILE_FLAG_OVERLAPPED , а lpOverlapped — значение ПУСТО (NULL), операции чтения начинается в текущей позиции файла и ReadFile не возвращает значения до тех пор, пока операция не будет закончена. Система модернизирует указатель позиции в файле после завершения работы.

Если hFile не открывался с FILE_FLAG_OVERLAPPED , а lpOverlapped — не ПУСТО (NULL), операция чтения стартует при смещении, указанном в структуре OVERLAPPED. ReadFile не возвращает значение до тех пор, пока операция чтения не завершилась. Система модернизирует позицию указателя в файле после завершения работы.

Windows 95/98/Me: Для операций с файлами, дисками, каналами или почтовыми ящиками в ядре Windows, этим параметром должно быть значение ПУСТО (NULL); указатель на структуру OVERLAPPED порождает вызов, который завершиться ошибкой. Тем не менее, Windows 95/98/Me поддерживает асинхронные операции на последовательных и параллельных портах.

Функция ReadFile возвращает значение тогда, когда выполнено одно из ниже перечисленных условий:

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

Если функция завершается успешно, величина возвращаемого значения — не ноль.

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

Если величина возвращаемого значения — не ноль, а число прочитанных байтов равняется нулю, указатель файла был за пределами текущего конца файла на момент операции чтения. Однако, если файл был открыт с флажком FILE_FLAG_OVERLAPPED , и lpOverlapped имеет значение не ПУСТО (NULL), величина возвращаемого значения — ноль, а GetLastError возвращает ошибку ERROR_HANDLE_EOF , когда указатель файла проходит вне текущего конца файла.

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

Когда работа с файлами открывается с флажком FILE_FLAG_NO_BUFFERING , приложение должно соответствовать некоторым требованиям:

  • Доступ к файлу должен начаться при смещениях байтов в пределах файла, которые должны быть целыми числами кратными размеру сектора тома. Чтобы выяснить размер сектора тома, вызовите функцию GetDiskFreeSpace .
  • Доступ к файлу должен быть числом байтов, которое является целым числом кратным размеру сектора тома. Например, если размер сектора — 512 байтов, приложение может запросить для чтения и записи 512, 1024 или 2048 байтов, но не 335, 981 или 7171 байт.
  • Адреса буферов для операций чтения и записи должны выравниваться кратно сектору (выравниваются адреса в памяти, которые являются целыми числами, кратными размеру сектора тома). Один из способов выравнивания буфера кратно сектору, является использование функции VirtualAlloc , которая назначает буфера. Эта функция выделяет память, выравнивая их по адресам, которые являются целыми числами, кратными размеру страницы памяти системы. Поскольку размеры и страницы памяти, и сектора тома являются степенью 2, память, выровненная кратно размеру страницы памяти системы также выравнивается кратно и размеру сектора тома.

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

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

Если именованный канал читается в режиме передачи сообщений, а длина следующего сообщения больше, чем это устанавливается параметром nNumberOfBytesToRead, ReadFile возвращает значение ЛОЖЬ (FALSE), а GetLastError возвращает ошибку ERROR_MORE_DATA . Остаток сообщения может быть прочитан последующим вызовом функции ReadFile или PeekNamedPipe .

При чтении в устройстве обмена информацией (коммуникационном), поведение ReadFile управляется текущими коммуникационными задержками как установлено и возвращено обратно использованием функций SetCommTimeouts и GetCommTimeouts. Непредсказуемые результаты могут произойти, если Вы будете не в состоянии установить значения времени простоя. Для получения дополнительной информации о коммуникационных блокировках по времени, см. статью о структуре COMMTIMEOUTS.

Если ReadFile пытается читать из почтового слота, буфер которого является слишком маленьким, функция возвращает значение ЛОЖЬ (FALSE), а GetLastError возвращает ошибку ERROR_INSUFFICIENT_BUFFER .

Если дескриптор анонимного канала записи был закрыт, а функция ReadFile пытается читать используя дескриптор соответствующего анонимного канала чтения, функция возвращает значение ЛОЖЬ (FALSE), а GetLastError возвращает ошибку ERROR_BROKEN_PIPE .

Функция ReadFile может завершиться ошибкой и возвратить значение ERROR_INVALID_USER_BUFFER или ERROR_NOT_ENOUGH_MEMORY всякий раз, когда имеется слишком много ожидающих обработки асинхронных запросов ввода — вывода.

Код проверки ReadFile положения метки конца файла (eof) различается для синхронных и асинхронных операций чтения.

Когда синхронная операция чтения достигает конца файла, ReadFile возвращает значение ИСТИНА (TRUE) и устанавливает в параметре *lpNumberOfBytesRead нуль. Нижеследующий типовой код проверяет метку конца файла для синхронной операции чтения:

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

Если EOF обнаруживается функцией ReadFile во время асинхронной операции чтения, ReadFile возвращает значение ЛОЖЬ (FALSE), а GetLastError возвращает ошибку ERROR_HANDLE_EOF .

Если EOF обнаруживается в ходе последующей асинхронной операции, происходит вызов функции GetOverlappedResult , чтобы получить в результате этой операции возвращенное значение ЛОЖЬ (FALSE), а GetLastError возвратит ошибку ERROR_HANDLE_EOF .

Чтобы отменить все ждущие обработки асинхронные операции ввода-вывода (I/O), используйте функцию CancelIo . Эта функция отменяет только операции, порождаемые вызывающим потоком для заданного дескриптора файла. Отмененные операции ввода-вывода (I/O) завершаются ошибкой ERROR_OPERATION_ABORTED .

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

Нижеследующий типовой код иллюстрирует проверку на метку конца файла для асинхронной операции чтения:

Размещение и совместимость ReadFile

Источник

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
procedure writetofile(const str: string);
var
  FileHandle: THandle;
  OverLap: OVERLAPPED;
  StringToWrite : string;
  Size: Integer;
  LastError, Written: DWORD;
begin
   ZeroMemory(@OverLap,sizeof(OverLap));
   StringToWrite := str;
   Size := Length(StringToWrite) * SizeOf(Char); // Юникодные версии Дельфи
 
   FileHandle := CreateFile('F:Protocol.txt', GENERIC_WRITE, 0, nil,
      OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
   if FileHandle <> INVALID_HANDLE_VALUE then // Файл создан?
   begin
      // Да, все нормально, продолжаем. Создаем Event и сбрасываем его
      Overlap.hEvent := CreateEvent(nil, True, False, nil);
      ResetEvent(OverLap.hEvent);
 
      // Тут у нас запись началась в любом случае, ведь WriteFile вызывается.
      // Ошибки будем обрабатывать позже
      Form1.Memo3.Lines.Add('Запись началась');
 
      // Теперь о вызове WriteFile. Внимательно смотрим на второй параметр. Так передаются строки
      if not WriteFile(FileHandle, StringToWrite[1], Size, Written, @Overlap) then
      begin
 
         // Обработка ошибок. Если WriteFile вернула False, но при этом GetLastError = ERROR_IO_PENDING
         // Значит, это была не ошибка, и надо дождаться окончания записи
         LastError := GetLastError;
         if LastError = ERROR_IO_PENDING then
            while WaitForSingleObject(Overlap.hEvent, 1000) = WAIT_TIMEOUT do
               Application.ProcessMessages
         else
            // А вот если не ERROR_IO_PENDING - то была ошибка, рассказываем пользователю об этом
            Form1.Memo3.Lines.Add(Format('WriteFile. Ошибка: Error = %s',
               [SysErrorMessage(GetLastError)]))
            (*
            ShowMessage(Format('WriteFile. Ошибка: Error = %s',
               [SysErrorMessage(GetLastError)]));
            *)
      end;
      // Эта часть вообще не нужна. Если WriteFile вернула True - то всё в порядке
      (*
      else
         ShowMessage(Format('Запись потерпела неудачу : Error = %s',
            [SysErrorMessage(GetLastError)]));
      *)
 
      // А вот теперь смотрим, что даёт GetOverlappedResult
      if not GetOverlappedResult(FileHandle, Overlap, Written, True) then
      begin
         Form1.Memo3.Lines.Add(Format('GetOverlappedResult. Запись потерпела неудачу : Error = %s',
            [SysErrorMessage(GetLastError)]));
         (*
         ShowMessage(Format('GetOverlappedResult. Запись потерпела неудачу : Error = %s',
            [SysErrorMessage(GetLastError)]));
         *)
      end
      else
         Form1.Memo3.Lines.Add(Format('Запись закончена! Записано байт %d', [Written]));
         // ShowMessage(Format('Запись закончена! Записано байт %d', [Written]));
 
      CloseHandle(Overlap.hEvent);
      CloseHandle(FileHandle);
   end
   else
      Form1.Memo3.Lines.Add('Не удалось открыть файл');
      // ShowMessage('Не удалось открыть файл');
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   writetofile('Just a test');
end;

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

Для обмена данными между приложениями Windows существует много механизмов: буфер обмена (Clipboard), технология COM, передача данных между окнами процессов с помощью сообщения WM_COPYDATA, удаленный вызов процедур (RPC), сокеты (Windows Sockets), динамический обмен данными (DDE), именованные каналы (Named Pipes), почтовый ящик (Mailslot) и проецируемые в память файлы (Mapped file). У каждого способа есть свои плюсы и минусы. Здесь я не буду их рассматривать.

В данной статье, я хотел бы рассмотреть только один из перечисленных механизмов – именованные каналы (Named Pipes). Вообще существует два вида каналов: именованные и анонимные. Анонимные каналы обычно создаются родительским процессом и передаются дочернему процессу при его создании. У именованных каналов есть имя и любой процесс, знающий это имя, может подключиться к каналу. В любом случае, при работе с каналами у вас есть сервер (приложение создающее канал) и клиенты (приложения, подключающиеся к каналу).

Для работы с каналами в Windows есть системные функции, такие как CreateNamedPipe, TransactNamedPipe и другие. Но реализация Pipe-сервера и Pipe-клиента не лёгкая задача, поэтому я отправился на поиски готовых реализаций в Интернет.

Демонстрационные классы FWIOCompletionPipe

Первое, что я нашел решение FWIOCompletionPipes. Собственно это демонстрационная реализация сервера и клиента. В архиве вы найдёте юнит FWIOCompletionPipes.pas и примеры. В юните FWIOCompletionPipes.pas есть класс для создания сервера TFWPipeServer и класс клиента TFWPipeClient. Сразу скажу, что работу сервера я не проверял, а клиент пригодился, но об этом ниже.

Юнит Pipes

Отличная реализация Pipe-клиента и Pipe-сервера попалась мне на блоге Mick’s Mix. Чтобы код заработал под Delphi XE3 без ошибок и предупреждений, пришлось кое-что поправить. Мой исправленный вариант вы можете скачать здесь:

Файлы:

Есть вариант юнита c поддержкой всех версий Delphi и платформы Win64:

В юните есть класс-сервер TPipeServer, класс-клиент TPipeClient и даже есть приятный бонус — класс для запуска и управления консольным приложением с перехватом потока вывода TPipeConsole. Юнит лучше всего подключить к новому bpl-проекту и инсталлировать в среде Delphi. Тогда компоненты будут доступны в окне инструментов Tool Palette.

Пользоваться компонентами одно удовольствие. Чтобы создать сервер, создайте экземпляр класса TPipeServer (или положите его на форму или модуль данных), задайте имя канала, установите свойство Active в true и обрабатывайте событие OnPipeMessage. Пример обработки полученного сообщения (здесь и ниже будем считать, что в качестве сообщения мы передаём и получаем XML-документ):

procedure TForm1.PipeServer1PipeMessage(Sender: TObject; Pipe: NativeUInt;
   Stream: TStream);
var
   msg: IXMLDocument;
begin
   msg := TXMLDocument.Create(nil);
   //Загружаем XML-документ из потока.
   msg.LoadFromStream(Stream);
   //Отображаем полученный XML-документ.
   ShowMessage(msg.XML.Text);
end;

Теперь, чтобы отправить сообщение серверу нужно воспользоваться функцией SendStream:

procedure TForm2.Button1Click(Sender: TObject);
var
   xml: IXMLDocument;
   memoryStream: TMemoryStream;
begin
   memoryStream := TMemoryStream.Create;
   try
      //Создаём XML-документ для отправки.
      xml := TXMLDocument.Create(nil);
      xml.LoadFromXML('<MyMessage />');
      //Сохраняем XML-документ в поток.
      xml.SaveToStream(memoryStream);
      //Подключаемся к Pipe-серверу.
      PipeClient1.Connect;
      //Отправляем данные серверу.
      PipeClient1.SendStream(memoryStream);
   finally
      memoryStream.Free;
   end;
end;

Теперь разберёмся, как ответить клиенту на сообщение. Это можно сделать так:

procedure TForm1.PipeServer1PipeMessage(Sender: TObject; Pipe: NativeUInt;
   Stream: TStream);
var
   xml: IXMLDocument;
begin
   xml := TXMLDocument.Create(nil);
   //Загружаем XML-документ из потока.
   xml.LoadFromStream(Stream);
   //Отображаем полученный XML-документ.
   ShowMessage(xml.XML.Text);
   //Отправляем клиенту полученное сообщение обратно.
   TPipeServer(Sender).SendStream(Pipe, Stream);
end;

Если нам нужно отправить сообщение всем клиентам (сделать рассылку), это можно сделать так:

procedure TForm1.Button1Click(Sender: TObject);
var
   i: Integer;
   xml: IXMLDocument;
   memoryStream: TMemoryStream;
begin
   memoryStream := TMemoryStream.Create;
   try
      //Создаём XML-документ для отправки.
      xml := TXMLDocument.Create(nil);
      xml.LoadFromXML('<MyMessage />');
      //Сохраняем XML-документ в поток.
      xml.SaveToStream(memoryStream);
      //Рассылаем сообщение всем клиентам.
      for i := 0 to PipeServer1.ClientCount - 1 do
         PipeServer1.SendStream(PipeServer1.Clients[i], memoryStream);
   finally
      memoryStream.Free;
   end;
end;

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

program CmdClient;
 
 {$APPTYPE CONSOLE}
 
 uses
   Windows, Messages, SysUtils, Pipes;
 
 type
   TPipeEventHandler = class(TObject)
   public
      procedure OnPipeSent(Sender: TObject; Pipe: HPIPE; Size: DWORD);
   end;
 
   procedure TPipeEventHandler.OnPipeSent(Sender: TObject; Pipe: HPIPE; Size: DWORD);
   begin
      WriteLn('On Pipe Sent has executed!');
   end;
 
 var
   lpMsg: TMsg;
   WideChars: Array [0..255] of WideChar;
   myString: String;
   iLength: Integer;
   pcHandler: TPipeClient;
   peHandler: TPipeEventHandler;
 
 begin
 
// Create message queue for application
PeekMessage(lpMsg, 0, WM_USER, WM_USER, PM_NOREMOVE);
 
// Create client pipe handler
pcHandler:=TPipeClient.CreateUnowned;
// Resource protection
try
   // Create event handler
   peHandler:=TPipeEventHandler.Create;
   // Resource protection
   try
      // Setup clien pipe
      pcHandler.PipeName:='myNamedPipe';
      pcHandler.ServerName:='.';
      pcHandler.OnPipeSent:=peHandler.OnPipeSent;
      // Resource protection
      try
         // Connect
         if pcHandler.Connect(5000) then
         begin
            // Dispatch messages for pipe client
            while PeekMessage(lpMsg, 0, 0, 0, PM_REMOVE) do DispatchMessage(lpMsg);
            // Setup for send
            myString:='the message I am sending';
            iLength:=Length(myString) + 1;
            StringToWideChar(myString, wideChars, iLength);
            // Send pipe message
            if pcHandler.Write(wideChars, iLength * 2) then
            begin
               // Flush the pipe buffers
               pcHandler.FlushPipeBuffers;
               // Get the message
               if GetMessage(lpMsg, pcHandler.WindowHandle, 0, 0) then DispatchMessage(lpMsg);
            end;
         end
         else
            // Failed to connect
            WriteLn('Failed to connect to ', pcHandler.PipeName);
      finally
         // Show complete
         Write('Complete...');
         // Delay
         ReadLn;
      end;
   finally
      // Disconnect event handler
      pcHandler.OnPipeSent:=nil;
      // Free event handler
      peHandler.Free;
   end;
finally
   // Free pipe client
   pcHandler.Free;
end;
 
end.

Но этот случай не подходит для оконного приложения. Здесь мне пришлось изобретать велосипед, чтобы получить ответ от сервера синхронно. Я использовал компонент TFWPipeClient, что описан выше, чтобы отправить сообщение и получить ответ. Но выяснилось, что компонент TPipeServer отправляет клиенту не просто данные, а так называемый пакет данных. Т.е. сначала он отправляет клиенту маркер начала пакета, затем сами данные (т.к. если объём данных большой, то данные разбиваются на куски) и, в конце, маркер конца пакета. Также мне понадобился хэндл канала открытого компонентом TFWPipeClient поэтому пришлось добавить свойство Pipe. Мой дополненный вариант юнита FWIOCompletionPipes.pas можете скачать здесь:

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

//Функция отправляет сообщение серверу и ждёт ответа, если параметр waitForAnswer выставлен в true.
function TForm2.SendMessageToServer(Message: IXMLDocument; waitForAnswer: boolean): IXMLDocument;
var
   inputStream, outputStream: TMemoryStream;
   fwPipeClient: TFWPipeClient;
   buff: array [0..MAXWORD - 1] of Char;
   lpBytesRead: DWORD;
 
    //Функция проверки, являются ли полученные данные маркерами начала и конца пакета.
   function IsPacketBound(buffer: Pointer; size: Int64; controlCode: Cardinal): boolean;
   var
      lpControlMsg : PPipeMsgBlock;
   begin
      result := false;
      if size = SizeOf(TPipeMsgBlock) then
      begin
         lpControlMsg := PPipeMsgBlock(buffer);
         if (lpControlMsg^.Size = SizeOf(TPipeMsgBlock))
               and (lpControlMsg^.MagicStart = MB_MAGIC)
               and (lpControlMsg^.MagicEnd = MB_MAGIC)
               and (lpControlMsg^.ControlCode = controlCode) then
            result := true;
      end;
   end;
 
 begin
   result := nil;
   inputStream := TMemoryStream.Create;
   try
      Message.SaveToStream(inputStream);
      if not waitForAnswer then
         //Если не нужно ожидать ответа, то отправляем сообщение как обычно.
         PipeClient1.SendStream(inputStream)
      else
      begin
         //Создаём альтернативный Pipe-клиент.
         fwPipeClient := TFWPipeClient.Create('.', PipeClient1.PipeName);
         try
            //Подключаем Pipe-клиент к серверу.
            fwPipeClient.Active := true;
            outputStream := TMemoryStream.Create;
            try
               //Отправляем данные серверу.
               fwPipeClient.SendData(inputStream, outputStream);
               //Если сервер вернул маркер начала пакета, то будем считывать весь пакет.
               if IsPacketBound(outputStream.Memory, outputStream.Size, MB_START) then
               begin
                  outputStream.Size := 0;
                  while True do
                  begin
                     if ReadFile(fwPipeClient.Pipe, buff[0], MAXWORD, lpBytesRead, nil) then
                     begin
                        if IsPacketBound(@buff[0], lpBytesRead, MB_END) then
                           break
                        else
                           outputStream.Write(buff, lpBytesRead);
                     end;
                  end;
               end;
               result := TXMLDocument.Create(nil);
               result.LoadFromStream(outputStream);
            finally
               outputStream.Free;
            end;
         finally
            fwPipeClient.Free;
         end;
      end;
   finally
      inputStream.Free;
   end;
end;

А вот пример использования функции SendMessageToServer:

procedure TForm2.Button2Click(Sender: TObject);
var
   msg, answer: IXMLDocument;
begin
   //Создаём XML-документ для отправки.
   msg := TXMLDocument.Create(nil);
   msg.LoadFromXML('<MyMessage />');
   //Отправляем сообщение и получаем ответ.
   answer := SendMessageToServer(msg, true);
   //Отображаем полученное от сервера сообщение.
   ShowMessage(answer.XML.Text);
end;

Позже, при тестировании, выяснилось, что в классе TFWPipeClient неправильно реализовано подключение к каналу, поэтому при активной работе с каналом часто возникает ошибка System Error. Code: 231. Все копии канала заняты. Поэтому мне пришлось переписать функцию SendMessageToServer отказавшись от использования класса TFWPipeClient:

//Функция отправляет сообщение серверу и ждёт ответа, если параметр waitForAnswer выставлен в true.
function TForm2.SendMessageToServer(Message: IXMLDocument; waitForAnswer: boolean): IXMLDocument;
var
   inputStream, outputStream: TMemoryStream;
   buff: array [0..MAXWORD - 1] of Char;
   lpBytesRead: DWORD;
   lpNumberOfBytesWritten: DWORD;
   lpMode: DWORD;
   pipe: THandle;
 
   //Функция проверки, являются ли полученные данные маркерами начала и конца пакета.
   function IsPacketBound(buffer: Pointer; size: Int64; controlCode: Cardinal): boolean;
   var
      lpControlMsg : PPipeMsgBlock;
   begin
      result := false;
      if size = SizeOf(TPipeMsgBlock) then
      begin
         lpControlMsg := PPipeMsgBlock(buffer);
         if (lpControlMsg^.Size = SizeOf(TPipeMsgBlock))
               and (lpControlMsg^.MagicStart = MB_MAGIC)
               and (lpControlMsg^.MagicEnd = MB_MAGIC)
               and (lpControlMsg^.ControlCode = controlCode) then
            result := true;
      end;
   end;
 
 begin
   result := nil;
   inputStream := TMemoryStream.Create;
   try
      Message.SaveToStream(inputStream);
      if not waitForAnswer then
         //Если не нужно ожидать ответа, то отправляем сообщение как обычно.
         PipeClient1.SendStream(inputStream)
      else
      begin
         pipe := INVALID_HANDLE_VALUE;
         try
            //Подключаемся к каналу.
            while True do
            begin
               pipe := CreateFile(PChar('\.pipe' + PipeClient1.PipeName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0);
               if pipe <> INVALID_HANDLE_VALUE then
                  break;
               if GetLastError <> ERROR_PIPE_BUSY then
                  RaiseLastOSError;
               Win32Check(WaitNamedPipe(PChar('\.pipe' + PipeClient1.PipeName), NMPWAIT_WAIT_FOREVER));
            end;
            lpMode := PIPE_READMODE_MESSAGE;
            Win32Check(SetNamedPipeHandleState(pipe, lpMode, nil, nil));
            //Отправляем данные серверу.
            Win32Check(WriteFile(pipe, inputStream.Memory^, inputStream.Size, lpNumberOfBytesWritten, nil));
            outputStream := TMemoryStream.Create;
            try
               Win32Check(ReadFile(pipe, buff[0], MAXWORD, lpBytesRead, nil));
               outputStream.Write(buff, lpBytesRead);
               //Если сервер вернул маркер начала пакета, то будем считывать весь пакет.
               if IsPacketBound(outputStream.Memory, outputStream.Size, MB_START) then
               begin
                  outputStream.Size := 0;
                  while True do
                  begin
                     if ReadFile(pipe, buff[0], MAXWORD, lpBytesRead, nil) then
                     begin
                        if IsPacketBound(@buff[0], lpBytesRead, MB_END) then
                           break
                        else
                           outputStream.Write(buff, lpBytesRead);
                     end;
                  end;
               end;
               result := TXMLDocument.Create(nil);
               result.LoadFromStream(outputStream);
            finally
               outputStream.Free;
            end;
         finally
            if pipe <> INVALID_HANDLE_VALUE then
               CloseHandle(pipe);
         end;
      end;
   finally
      inputStream.Free;
   end;
end;

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

//Функция отправляет сообщение серверу и ждёт ответа указанное в параметре timeout время (в мс), если параметр waitForAnswer выставлен в true.
function TForm2.SendMessageToServer(Message: IXMLDocument;
    waitForAnswer: boolean; timeout: DWORD): IXMLDocument;
var
  inputStream, outputStream: TMemoryStream;
  buff: array [0..MAXWORD - 1] of Char;
  lpBytesRead: DWORD;
  lpNumberOfBytesWritten: DWORD;
  lpMode: DWORD;
  pipe: THandle;
  isPacket: boolean;
  pendingRead: boolean;
  lastError: integer;
  olapRead: TOverlapped;
  olapWrite: TOverlapped;
  events: array [0 .. 0] of THandle;
 
  function IsPacketBound(buffer: Pointer; size: Int64; controlCode: Cardinal): boolean;
  var
    lpControlMsg : PPipeMsgBlock;
  begin
    result := false;
    if size = SizeOf(TPipeMsgBlock) then
    begin
      lpControlMsg := PPipeMsgBlock(buffer);
      if (lpControlMsg^.Size = SizeOf(TPipeMsgBlock))
        and (lpControlMsg^.MagicStart = MB_MAGIC)
        and (lpControlMsg^.MagicEnd = MB_MAGIC)
        and (lpControlMsg^.ControlCode = controlCode) then
        result := true;
    end;
  end;
 
  function AddPortion: boolean;
  begin
    result := false;
    if (not isPacket) and IsPacketBound(@buff[0], lpBytesRead, MB_START) then
      isPacket := true
    else if (not isPacket) then
    begin
      outputStream.Write(buff, lpBytesRead);
      result := true;
    end
    else if isPacket then
    begin
      if IsPacketBound(@buff[0], lpBytesRead, MB_END) then
      begin
        isPacket := false;
        result := true;
      end
      else
        outputStream.Write(buff, lpBytesRead);
    end;
  end;
 
  procedure ReadMessage(timeout: integer; generateTimeoutError: boolean);
  begin
    while true do
    begin
      pendingRead := false;
      if ReadFile(pipe, buff[0], MAXWORD, lpBytesRead, @olapRead) then
      begin
        if AddPortion then
          exit;
        ResetEvent(olapRead.hEvent);
      end
      else
      begin
        lastError := GetLastError;
        if lastError = ERROR_IO_PENDING then
          pendingRead := true
        else if (lastError = ERROR_MORE_DATA) then
        begin
          if AddPortion then
            exit;
        end
        else
          RaiseLastOSError(lastError);
      end;
      if pendingRead then
      begin
        events[0] := olapRead.hEvent;
        case WaitForMultipleObjects(1, @events, false, timeout) of
          WAIT_OBJECT_0:
            begin
              if GetOverlappedResult(pipe, olapRead, lpBytesRead, true) then
              begin
                if AddPortion then
                  exit;
              end
              else
              begin
                lastError := GetLastError;
                if lastError = ERROR_MORE_DATA then
                begin
                  if AddPortion then
                    exit;
                end
              end;
            end;
          WAIT_TIMEOUT:
            if generateTimeoutError then
              raise Exception.Create('За отведённое время не получен ответ.')
            else
              break;
        else
          RaiseLastOSError;
        end;
      end;
    end;
  end;
 
begin
  isPacket := false;
  pipe := INVALID_HANDLE_VALUE;
  pendingRead := false;
  ClearOverlapped(olapRead, True);
  ClearOverlapped(olapWrite, True);
  result := nil;
  inputStream := TMemoryStream.Create;
  try
    Message.SaveToStream(inputStream);
    if not waitForAnswer then
      PipeClient1.SendStream(inputStream)
    else
    begin
      try
        olapRead.hEvent := CreateEvent(nil, True, False, nil);
        olapWrite.hEvent := CreateEvent(nil, True, False, nil);
        try
          outputStream := TMemoryStream.Create;
          try
            while True do
            begin
              pipe := CreateFile(PChar('\.pipe' + PipeClient1.PipeName), GENERIC_READ or GENERIC_WRITE,
                0, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
              if pipe <> INVALID_HANDLE_VALUE then
                break;
              if GetLastError <> ERROR_PIPE_BUSY then
                RaiseLastOSError;
              Win32Check(WaitNamedPipe(PChar('\.pipe' + PipeClient1.PipeName), timeout));
            end;
            lpMode := PIPE_READMODE_MESSAGE;
            Win32Check(SetNamedPipeHandleState(pipe, lpMode, nil, nil));
 
            while PeekNamedPipe(pipe, nil, 0, nil, @lpBytesRead, nil) and (lpBytesRead > 0) do
            begin
              ReadMessage(timeout, false);
              outputStream.Size := 0;
            end;
 
            if WriteFile(pipe, inputStream.Memory^, inputStream.Size,
              lpNumberOfBytesWritten, @olapWrite) then
            begin
              FlushFileBuffers(pipe);
              ResetEvent(olapWrite.hEvent);
            end
            else
            begin
              lastError := GetLastError;
              if lastError = ERROR_IO_PENDING then
              begin
                events[0] := olapWrite.hEvent;
                case WaitForMultipleObjects(1, @events, false, timeout) of
                  WAIT_OBJECT_0:
                  begin
                    if GetOverlappedResult(pipe, olapWrite, lpNumberOfBytesWritten, True) then
                      FlushFileBuffers(pipe)
                    else
                      RaiseLastOSError;
                  end;
                  WAIT_TIMEOUT:
                    raise Exception.Create('За отведённое время не удалось отправить сообщение.');
                else
                  RaiseLastOSError;
                end;
              end
              else
                RaiseLastOSError(lastError);
            end;
 
            ReadMessage(timeout, true);
 
            result := TXMLDocument.Create(nil);
            result.LoadFromStream(outputStream);
 
          finally
            outputStream.Free;
          end;
        finally
          CloseHandle(olapRead.hEvent);
          CloseHandle(olapWrite.hEvent);
        end;
      finally
        if pipe <> INVALID_HANDLE_VALUE then
          CloseHandle(pipe);
      end;
    end;
  finally
    inputStream.Free;
  end;
end;

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

>
Вызов FileRead в асинхронном режиме
, чтение файла в асинхронном режиме

  • Подписаться на тему
  • Сообщить другу
  • Скачать/распечатать тему

  


Сообщ.
#1

,
16.12.07, 10:21

    Full Member

    ***

    Рейтинг (т): 14

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

    ExpandedWrap disabled

      procedure TForm1.DoAsyncLoad();

      var

        lReaded: Cardinal;

        lTransferred: Cardinal;

        lBuff: Pointer;

        lRes: Boolean;

        lErr, lErr2: Cardinal;

        lFileHandle: Cardinal;

        lOverlapped: TOverlapped;

      begin

        lFileHandle := CreateFile( PChar( edtFileName.Text ),

                                   GENERIC_READ, 0,

                                   nil, OPEN_EXISTING,

                                   FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0 );

        if lFileHandle = INVALID_HANDLE_VALUE then

          RaiseLastOSError;

        try

          lReaded := 0;

          ZeroMemory( @lOverlapped, SizeOf( TOverlapped ));

          GetMem( lBuff, C_BLOCK_SIZE ); //используем большой буфер

          try

            repeat

              lReaded := 0;

              //обнуляем внутренние данные для нового вызова «перекрываемой» функции

              lOverlapped.Internal := 0;

              lOverlapped.InternalHigh := 0;

              //hEvent не задаю, так как нигде ожидать ничего не будем

              lRes := ReadFile( lFileHandle,

                                lBuff^,

                                C_BLOCK_SIZE,

                                lReaded,

                                @lOverlapped );

              if not lRes then

                lErr := GetLastError

              else

                lErr := ERROR_SUCCESS;

              if not lRes then

              begin //обрабатываем случаи, когда ReadFile = False

                case lErr of //проверка ощибки ReadFile

                  ERROR_IO_PENDING: //асинхронная операция

                  begin

                    lTransferred := 0;

                    repeat

                      lRes := GetOverlappedResult( lFileHandle,

                                                   lOverlapped,

                                                   lTransferred,

                                                   False );

                      if not lRes then

                        lErr2 := GetLastError;

                      if not lRes then

                        case lErr2 of //проверка ошибки  GetOverlappedResult

                          ERROR_IO_INCOMPLETE: Application.HandleMessage; //операция не завершилась? обработаем очередное сообщение

                          ERROR_HANDLE_EOF:

                          begin //файл кончился?

                            lReaded := 0; //ничего не прочитали

                            break; //выходим из внутреннего цикла

                          end;

                          else

                            RaiseLastOSError;

                        end;

                    until lRes; //пока не завершиться операция…

                    if lRes then

                      lReaded := lTransferred; //надо ли?

                  end;

                  ERROR_HANDLE_EOF: lReaded := 0; //если файл кончилсо, то выходим из цикла

                  else

                     RaiseLastOSError;

                end;

              end;//конец ветки, когда ReadFile = false

              if lRes then //здесь мы оказываемся в 2х случаях:1.ReadFile=true; 2.GetOverlappedResult в конце концов тоже вернул True

              begin

                //

                //что-то делаем с lBuff

                //

                lOverlapped.Offset := lOverlapped.Offset + lReaded; //сдвигаем

              end;

            until lReaded = 0;

          finally

            FreeMem( lBuff );

          end;

        finally

          CloseHandle( lFileHandle );

        end;

      end;

    все бы ничего… да только не всегда получается асинхронный вызов.
    в случае попытки чтения большого файла с винчестера функция FileRead ждет окончания чтения и возвращает true (
    и только в случае первой попытки обращения к съемному носителю срабатывает часть кода по работе с GetOverlappedResult…
    объясните знающие люди, в чем соль?!

    Сообщение отредактировано: MetalFan — 19.12.07, 09:34


    Ahilles



    Сообщ.
    #2

    ,
    16.12.07, 13:10

      Full Member

      ***

      Рейтинг (т): 10

      неплохая статья про асинхронный ввод-вывод.


      MetalFan



      Сообщ.
      #3

      ,
      16.12.07, 13:32

        Full Member

        ***

        Рейтинг (т): 14

        Ahilles Спасибо конечно, но ничего нового статья мне не рассказала. вся информация есть в MSDN.
        да и примеры там все с флопом.


        medved_68



        Сообщ.
        #4

        ,
        17.12.07, 05:38

          Цитата MetalFan @ 16.12.07, 10:21

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

          Вернее — работает не пойми как. :D

          Цитата MetalFan @ 16.12.07, 10:21

          ExpandedWrap disabled

            //hEvent не задаю, так как нигде ожидать ничего не будем

                    lRes := ReadFile( lFileHandle,

                                      lBuff^,

                                      C_BLOCK_SIZE,

                                      lReaded,

                                      @lOverlapped);

          И?? Как же так — пытаешься использовать асинхронное чтение, а главный «семафор окончания отложенной операции» не создаешь? Далее :

          Цитата MetalFan @ 16.12.07, 10:21

          ExpandedWrap disabled

            if not lRes then

                      case lErr of //проверка ощибки ReadFile

                        ERROR_IO_PENDING: //асинхронная операция

          Это не ошибка — это признак того, что отложенная операция нормально стартовала. Здесь бы лучше запустить таймер и по его срабатыванию проверять — закончилась ли операция чтения (считано ли требуемое в ReadFile количество байт (WaitForSingleObject на эвент Overlapp’a должен вернуть WAIT_OBJECT_0 — если нет — то по GetOverlappedResult смотрим, сколько считано и сравниваем с размером файла, или по флагу ERROR_HANDLE_EOF принимаем решение сбросить дальнейшее чтение). Но лучше в любом случае перед считыванием получить размер файла, чтобы знать наверняка, сколько читать.)


          MetalFan



          Сообщ.
          #5

          ,
          17.12.07, 06:29

            Full Member

            ***

            Рейтинг (т): 14

            Цитата medved_68 @ 17.12.07, 05:38

            И?? Как же так — пытаешься использовать асинхронное чтение, а главный «семафор окончания отложенной операции» не создаешь?

            создавать и использовать его не обязательно. но даже его создание и использование положения не меняет.

            Цитата medved_68 @ 17.12.07, 05:38

            Это не ошибка — это признак того, что отложенная операция нормально стартовала.

            отложенная операция удачно «стартовала» только если GetLastError вернул ERROR_IO_PENDING.
            да, это не ошибка по сути.

            Цитата medved_68 @ 17.12.07, 05:38

            сколько считано и сравниваем с размером файла

            как ты заметил, файл читается не целиком, а кускакми

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

            Сообщение отредактировано: MetalFan — 17.12.07, 06:31


            —Ins—



            Сообщ.
            #6

            ,
            19.12.07, 08:53

              M

              MetalFan, я ваш вопрос отредактировал, код не подсвечивался. Там в теге CODE было CODE=delphi, а нужно — CODE=pas, ну это так, на будущее.

              Что касается вопроса — думаю, не нужно вызывать Application.HandleMessage; так как эта процедура в конце вызывает засыпание потока на неопределенное время — пока в очередь не поступит сообщение (Idle из которого вызывается WaitMessage). Как обрабатывать сообщения и одновременно дожидаться окончания ввода/вывода гляньте здесь:
              DeviceIOControl

              Там вся соль в функции MsgWaitForMultipleObjects


              MetalFan



              Сообщ.
              #7

              ,
              19.12.07, 08:55

                Full Member

                ***

                Рейтинг (т): 14

                Rose Сорри, с винграда скопировал и не заметил ньюанса с pas.
                по ссылке счас почитаю, спасибо

                Добавлено 19.12.07, 09:00

                Цитата Rose @ 19.12.07, 08:53

                так как эта процедура в конце вызывает засыпание потока

                возможно… но не в этом проблема…
                если отрабатывает часть кода при FileRead = 0(False), то все ок.
                видимо я не совсем ясно расписал ситуацию и ты меня не понял :wacko:


                —Ins—



                Сообщ.
                #8

                ,
                19.12.07, 09:04

                  Цитата

                  возможно… но не в этом проблема…

                  Все же попробуй переделать свой код на менер того, как в приведенной ссылке — Event заведи, MsgWaitForMultipleObjects используй, ProcessMessages вместо HandleMessage, все должно работать.


                  MetalFan



                  Сообщ.
                  #9

                  ,
                  19.12.07, 09:17

                    Full Member

                    ***

                    Рейтинг (т): 14

                    Rose Да нет! ты не понял… ветка кода с GetOverlappedResult и так работает на «ура»! но просто при чтении с ЖД FileRead всегда возвращает true.
                    при чем тут переделка кода?


                    —Ins—



                    Сообщ.
                    #10

                    ,
                    19.12.07, 09:27

                      Цитата

                      Rose Да нет! ты не понял…

                      Видимо действительно не понял. Вторая попытка :) Рискну предположить что дело вот в чем: ReadFile не обязана вернуть код ошибки ERROR_IO_PENDING в асинхронном режиме, и в справке об этом написано. Операция чтения может быть завершена достаточно быстро и в этом случае даже в асинхрооном режиме будет прочитано сразу все. Может у Вас именно этот случай? Или на ReadFile код надолго останавливается?


                      MetalFan



                      Сообщ.
                      #11

                      ,
                      19.12.07, 09:37

                        Full Member

                        ***

                        Рейтинг (т): 14

                        Rose можно на «ты»? ) а то «ты мне тут не выкайте»)

                        Цитата Rose @ 19.12.07, 09:27

                        ReadFile не обязана вернуть код ошибки ERROR_IO_PENDING

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

                        Цитата Rose @ 19.12.07, 09:27

                        Может у Вас именно этот случай? Или на ReadFile код надолго останавливается?

                        у меня именно этот случай при работе с HDD!!!!
                        как я уже ранее предположил, система скорее всего с жесткого считывает быстро, а вот мой 50Мб-йтный буфер заполняется как раз эти 0,5-1 секунды задержки…

                        Сообщение отредактировано: MetalFan — 19.12.07, 09:38


                        —Ins—



                        Сообщ.
                        #12

                        ,
                        19.12.07, 09:44

                          Цитата

                          Rose можно на «ты»? ) а то «ты мне тут не выкайте»)

                          Привычка от других форумов, где принято на «Вы» :ph34r: Перехожу на «ты».

                          Попробую твой код в ближайшее время, пока сам не увижу — наверное не пойму в чем проблема.


                          MetalFan



                          Сообщ.
                          #13

                          ,
                          19.12.07, 10:48

                            Full Member

                            ***

                            Рейтинг (т): 14

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

                            Цитата MetalFan @ 19.12.07, 09:37

                            как я уже ранее предположил, система скорее всего с жесткого считывает быстро, а вот мой 50Мб-йтный буфер заполняется как раз эти 0,5-1 секунды задержки…


                            —Ins—



                            Сообщ.
                            #14

                            ,
                            19.12.07, 11:07

                              Цитата

                              как я уже ранее предположил, система скорее всего с жесткого считывает быстро, а вот мой 50Мб-йтный буфер заполняется как раз эти 0,5-1 секунды задержки…

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


                              MetalFan



                              Сообщ.
                              #15

                              ,
                              19.12.07, 11:12

                                Full Member

                                ***

                                Рейтинг (т): 14

                                Цитата Rose @ 19.12.07, 11:07

                                Если флаг FILE_FLAG_NO_BUFFERING не установлен

                                проверял и с ним и без него.
                                но еще один ньюанс у меня возник (с FILE_FLAG_NO_BUFFERING) — получаю Invalid Parameter вместо ERROR_HANDLE_EOF по достижению конца файла…

                                Добавлено 19.12.07, 11:16
                                брр… возможно я не учел замечания в msdn по поводу выделения памяти под буфер

                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)

                                0 пользователей:

                                • Предыдущая тема
                                • Delphi: Система, Windows API
                                • Следующая тема

                                [ Script execution time: 0,0783 ]   [ 16 queries used ]   [ Generated: 9.02.23, 09:51 GMT ]  

                                Программирование порта delphi

                                Delphi: работа с COM-портом

                                Оригинальное название статьи — «Вид на порт из окна» — прим. ред.

                                Вопрос «как работать с COM-портами?» стал классическим на многих конференциях по языкам программирования. Рано или поздно чуть не каждому программисту приходится работать с портами ввода/вывода. Сегодня я хочу рассказать про работу с последовательным портом из-под самой распространенной на сегодняшний день 32-разрядной операционной системы — Windows. К статье прилагается пример программы, работающей с COM-портом, написанной на Borland Delphi 7.

                                Статья построена по принципу «от простого к сложному». Сначала будут изложены основы работы с портами из-под Win32 с описанием необходимых функций. Затем рассмотрим применение этих функций на примере Delphi-программы. Конечным результатом будет класс, предназначенный для работы с COM-портом, и пример использующей его программы.

                                Очень часто программисту приходится управлять с помощью компьютера каким-либо внешним устройством, или просто анализировать состояние этого устройства. Порты ввода/вывода — самый распространенный способ сопряжения компьютера и внешнего устройства. Давным-давно уже написано множество классов, библиотек и компонент для работы с портами, поэтому можно, конечно, воспользоваться уже готовым и к тому же бесплатным решением. Именно так я и поступил лет семь назад, при этом потеряв самое главное — своевременное понимание того, как все-таки работать с портами из-под Win32. Незнание внутренних механизмов — это, во-первых, пробел в стройном ряду знаний, а во-вторых, актуальная возможность ошибок в работе программы.

                                С портами из-под Win32 работают так же, как и с обычными файлами, используя при этом всего несколько специфичных функций WinAPI. Однако коммуникационный порт — это не совсем обычный файл. Для него, например, нельзя выполнить позиционирование файлового указателя, или же создать порт, если таковой отсутствует. Любая работа с портом начинается с его открытия. Для этого используется файловая функция WinAPI (описания WinAPI-функций взяты из MSDN (Microsoft Developer Network), следовательно, приводятся в синтаксисе C):

                                HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );

                                lpFileName — указатель на строку с нулевым завершающим символом. Обычно это имя открываемого файла, но в нашем случае это должно быть название порта (COM1, COM2, …).

                                dwDesiredAccess — тип доступа. В нашем случае должен быть равен GENERIC_READ|GENERIC_WRITE.

                                dwShareMode — параметр совместного доступа. Для коммуникационных портов всегда равен 0.

                                lpSecurityAttributes — атрибут защиты. Для коммуникационных портов всегда равен NULL.

                                dwCreationDistribution — режим автосоздания. Для коммуникационных портов всегда равен OPEN_EXESTING.

                                dwFlagsAndAttributes — атрибут режима обработки. Для коммуникационных портов должен быть равен 0 или FILE_FLAG_OVERLAPPED.

                                hTemplateFile — описатель файла-шаблона. Для коммуникационных портов должен быть равен NULL.

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

                                Сразу оговорюсь: все недостающие описания можно найти на http://msdn.microsoft.comи еще по ряду адресов, которые вам подскажет поисковый сервер.

                                Из всех параметров функции CreateFile() особого пояснения требует dwFlagsAndAttributes. Работа с портом может быть организована в синхронном (nonoverlapped) или асинхронном (overlapped) режимах обработки, что и задается этим флагом. При синхронном режиме (когда параметр dwFlagsAndAttributes = 0) только один поток приложения может либо читать, либо писать в порт. Помните переговорное устройство в лифте? Нажали кнопку — можем только говорить, отпустили кнопку — можем только слушать.

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

                                Асинхронный режим (когда параметр dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED) позволяет производить операции чтения и записи в порт параллельно из разных потоков. В то время, пока один поток приложения принимает данные, другой поток может параллельно с первым передавать данные — как при разговоре по телефону, когда вы можете слушать и говорить одновременно. Данный режим обработки больше импонирует идее многозадачности Windows. Но за все надо платить: для реализации этого режима обработки требуется в два раза больше написанного кода, вдобавок, умения писать многопоточные программы. Какой режим выбрать — решайте сами. Но если уж разбираться в работе порта, то разбираться «по-взрослому», до конца, а потому и рассмотрим более сложный вариант — асинхронную обработку.

                                На практике открытие порта для асинхронного режима обработки из программы на Delphi выглядит примерно так:

                                hPort := CreateFile(‘COM1’, GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if hPort = INVALID_HANDLE_VALUE then raise Exception.Create(‘Error opening port’);

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

                                BOOL CloseHandle( HANDLE hObject );

                                В качестве единственного параметра надо передать полученный ранее описатель порта (hPort).

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

                                BOOL SetCommState( HANDLE hFile, LPDCB lpDCB );

                                hFile — описатель открытого порта.

                                lpDCB — указатель на структуру DCB.

                                Основные параметры последовательного порта описываются структурой DCB. Она содержит массу полей, каждое из которых соответствует определенному параметру настройки порта. Мы рассмотрим несколько полей, которые нам нужны:

                                BaudRate — скорость передачи данных. Возможно указание констант —CBR_100, CBR_300, CBR_600, CBR_1200, …, CBR_256000.

                                Parity — схема контроля четности. Может содержать одно из следующих значений: EVENPARITY, MARKPARITY, NOPARITY, ODDPARITY, SPACEPARITY.

                                ByteSize — число информационных бит в передаваемых и принимаемых байтах.

                                StopBits — количество стоповых бит. Может быть ONESTOPBIT, ONE5STOPBIT, TWOSTOPBIT.

                                Чтобы не заполнять структуру DCB вручную, ее можно заполнить информацией о текущем состоянии порта вызовом функции GetCommState(), затем изменить необходимые поля и установить настройки вызовом функции SetCommState(). Настройку порта желательно производить сразу после его открытия. На Delphi это выглядит так:

                                var Dcb: TDcb; … if not GetCommState(hPort, Dcb) then raise Exception.Create(‘Error setting port state’); Dcb.BaudRate := CBR_9600; Dcb.Parity := NOPARITY; Dcb.ByteSize := 8; Dcb.StopBits := ONESTOPBIT; if not SetCommState(hPort, Dcb) then raise Exception.Create(‘Error setting port state’);

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

                                BOOL PurgeComm( HANDLE hFile, DWORD dwFlags );

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

                                hFile — описатель открытого порта.

                                dwFlags — производимые действия в виде набора флагов PURGE_TXABORT, PURGE_RXABORT, PURGE_TXCLEAR, PURGE_RXCLEAR.

                                Пример на Delphi:

                                if not PurgeComm(hPort, PURGE_TXCLEAR or PURGE_RXCLEAR) then raise Exception.Create(‘Error purging port’);

                                На этом подготовительная фаза заканчивается, и можно приступать непосредственно к приему/передаче данных. Прием данных у нас будет происходить по событийной схеме; программа будет ожидать прием одного или нескольких символов (байт). Для перевода порта в этот режим необходимо вызвать функцию SetCommMask() с флагом EV_RXCHAR:

                                if not SetCommMask(hPort, EV_RXCHAR) then raise Exception.Create(‘Error setting port mask’);

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

                                BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ); BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped );

                                hFile — описатель открытого порта.

                                lpBuffer — адрес буфера.

                                nNumberOfBytesToRead/nNumberOfBytesToWrite — число ожидаемых к приему или предназначенных для передачи байт.

                                lpNumberOfBytesRead/lpNumberOfBytesWritten — число фактически принятых или переданных байт.

                                lpOverlapped — адрес структуры OVERLAPPED, используемой для асинхронных операций.

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

                                var dwWrite: DWORD; OverWrite: TOverlapped; WriteBytes: array of Byte; … begin OverWrite.hEvent := CreateEvent(nil, True, False, nil); if OverWrite.hEvent = Null then raise Exception.Create(‘Error creating write event’); … if (not WriteFile(hPort, WriteBytes, SizeOf(WriteBytes), dwWrite, @OverWrite)) and (GetLastError <> ERROR_IO_PENDING) then raise Exception.Create(‘Error writing port’); end;

                                В данном примере функция WriteFile() выполняет асинхронную запись массива байтов WriteBytes в порт. Она сразу возвращает управление, и запись в порт происходит параллельно с выполнением основного кода потока. Если результат WriteFile() равен False, то это значит, что на момент возврата управления передача массива байтов еще не закончилась. Поэтому код ошибки выполнения WriteFile() в данном случае должен быть равен ERROR_IO_PENDING. Переменная OverWrite — overlapped-структура, необходимая для асинхронных операций.

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

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

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

                                procedure TReadThread.Execute; var ComStat: TComStat; dwMask, dwError: DWORD; OverRead: TOverlapped; Buf: array[0..$FF] of Byte; dwRead: DWORD; begin OverRead.hEvent := CreateEvent(nil, True, False, nil); if OverRead.hEvent = Null then raise Exception.Create(‘Error creating read event’); FreeOnTerminate := True; while not Terminated do begin if not WaitCommEvent(hPort, dwMask, @OverRead) then begin if GetLastError = ERROR_IO_PENDING then WaitForSingleObject(OverRead.hEvent, INFINITE) else raise Exception.Create(‘Error waiting port event’); end; if not ClearCommError(hPort, dwError, @ComStat) then raise Exception.Create(‘Error clearing port’); dwRead := ComStat.cbInQue; if dwRead > 0 then begin if not ReadFile(hPort, Buf, dwRead, dwRead, @OverRead) then raise Exception.Create(‘Error reading port’); // В Buf находятся прочитанные байты // Далее идет обработка принятых байтов end; end; {while} end;

                                В приведенном примере в потоке крутится цикл, тем самым инициируется ожидание события порта вызовом функции WaitCommEvent(), ожидание же самого этого события задается функцией WaitForSingleObject(). Для определения количества принятых символов используется функция ClearCommError(). Когда количество принятых символов (dwRead) известно, непосредственное чтение символов выполняется функцией ReadFile().

                                Используя вышеописанные выкладки, я написал на Borland Delphi 7 класс TComPort для работы с COM-портами. К классу прилагается пример приложения, использующий его. Для проверки работоспособности программы я просто соединил нуль-модемным кабелем два COM-порта на своем компьютере и запустил два экземпляра программы для каждого порта. Данные передаются через один порт и одновременно принимаются через другой. Скриншот программы прилагается.

                                Для передачи и приема данных предусмотрены отдельные окна. Формат передаваемых данных — строка. Принимаемые данные представляются в виде массива байт.

                                Работа с COM-портами в Delphi.

                                Вопрос «как работать с COM-портами?» стал классическим на многих конференциях
                                по языкам программирования. Рано или поздно чуть не каждому программисту
                                приходится работать с портами ввода/вывода. Сегодня я хочу рассказать
                                про работу с последовательным портом из-под самой распространенной на
                                сегодняшний день 32-разрядной операционной системы — Windows. Статья построена
                                по принципу «от простого к сложному». Сначала будут изложены основы работы
                                с портами из-под Win32 с описанием необходимых функций. Затем рассмотрим
                                применение этих функций на примере Delphi-программы. Конечным результатом
                                будет класс, предназначенный для работы с COM-портом, и пример использующей
                                его программы.
                                По ссылке Serial Net Tools — находится написанная мной программа для работы с COM-портом.

                                Очень часто программисту приходится управлять с помощью компьютера
                                каким-либо внешним устройством, или просто анализировать состояние этого
                                устройства. Порты ввода/вывода — самый распространенный способ сопряжения
                                компьютера и внешнего устройства. Давным-давно уже написано множество
                                классов, библиотек и компонент для работы с портами, поэтому можно,
                                конечно, воспользоваться уже готовым и к тому же бесплатным решением.
                                Именно так я и поступил лет семь назад, при этом потеряв самое главное
                                — своевременное понимание того, как все-таки работать с портами из-под
                                Win32. Незнание внутренних механизмов — это, во-первых, пробел в стройном
                                ряду знаний, а во-вторых, актуальная возможность ошибок в работе программы.

                                С портами из-под Win32 работают так же, как и с обычными файлами, используя
                                при этом всего несколько специфичных функций WinAPI. Однако коммуникационный
                                порт — это не совсем обычный файл. Для него, например, нельзя выполнить
                                позиционирование файлового указателя, или же создать порт, если таковой
                                отсутствует. Любая работа с портом начинается с его открытия. Для этого
                                используется файловая функция WinAPI (описания WinAPI-функций взяты
                                из MSDN (Microsoft Developer Network), следовательно, приводятся в синтаксисе
                                C):

                                HANDLE CreateFile(

                                LPCTSTR lpFileName,
                                DWORD dwDesiredAccess,
                                DWORD dwShareMode,
                                LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                                DWORD dwCreationDistribution,
                                DWORD dwFlagsAndAttributes,

                                HANDLE hTemplateFile
                                );lpFileName — указатель на строку с нулевым завершающим символом. Обычно
                                это имя открываемого файла, но в нашем случае это должно быть название
                                порта (COM1, COM2, …).

                                dwDesiredAccess — тип доступа. В нашем случае должен быть равен GENERIC_READ|GENERIC_WRITE.

                                dwShareMode — параметр совместного доступа. Для коммуникационных портов всегда равен 0.

                                lpSecurityAttributes — атрибут защиты. Для коммуникационных портов всегда равен NULL.

                                dwCreationDistribution — режим автосоздания. Для коммуникационных портов
                                всегда равен OPEN_EXESTING.

                                dwFlagsAndAttributes — атрибут режима обработки. Для коммуникационных
                                портов должен быть равен 0 или FILE_FLAG_OVERLAPPED.

                                hTemplateFile — описатель файла-шаблона. Для коммуникационных портов
                                должен быть равен NULL.

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

                                Сразу оговорюсь: все недостающие описания можно найти на http://msdn.microsoft.comи
                                еще по ряду адресов, которые вам подскажет поисковый сервер.

                                Из всех параметров функции CreateFile() особого пояснения требует dwFlagsAndAttributes.
                                Работа с портом может быть организована в синхронном (nonoverlapped)
                                или асинхронном (overlapped) режимах обработки, что и задается этим
                                флагом. При синхронном режиме (когда параметр dwFlagsAndAttributes =
                                0) только один поток приложения может либо читать, либо писать в порт.
                                Помните переговорное устройство в лифте? Нажали кнопку — можем только
                                говорить, отпустили кнопку — можем только слушать.

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

                                Асинхронный режим (когда параметр dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED)
                                позволяет производить операции чтения и записи в порт параллельно из
                                разных потоков. В то время, пока один поток приложения принимает данные,
                                другой поток может параллельно с первым передавать данные — как при
                                разговоре по телефону, когда вы можете слушать и говорить одновременно.
                                Данный режим обработки больше импонирует идее многозадачности Windows.
                                Но за все надо платить: для реализации этого режима обработки требуется
                                в два раза больше написанного кода, вдобавок, умения писать многопоточные
                                программы. Какой режим выбрать — решайте сами. Но если уж разбираться
                                в работе порта, то разбираться «по-взрослому», до конца, а потому и
                                рассмотрим более сложный вариант — асинхронную обработку.

                                На практике открытие порта для асинхронного режима обработки из программы
                                на Delphi выглядит примерно так:

                                hPort := CreateFile(‘COM1’, GENERIC_READ or GENERIC_WRITE, 0, nil,
                                OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
                                if hPort = INVALID_HANDLE_VALUE then
                                raise Exception.Create(‘Error opening port’);Функция возвращает описатель
                                порта (hPort), который нам потом пригодится для вызова других функций
                                работы с портом. Если в результате открытия порта описатель не получен,
                                то возбуждается исключение с соответствующим текстом ошибки. Открыв
                                порт, мы получаем его в свое распоряжение. Теперь с этим портом может
                                работать только наша программа (точнее, только наш процесс). По окончании
                                работы с портом его следует закрыть, вызвав функцию:

                                BOOL CloseHandle(
                                HANDLE hObject
                                );В качестве единственного параметра надо передать полученный ранее
                                описатель порта (hPort).

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

                                BOOL SetCommState(
                                HANDLE hFile,

                                LPDCB lpDCB
                                );hFile — описатель открытого порта.
                                lpDCB — указатель на структуру DCB.

                                Основные параметры последовательного порта описываются структурой DCB.
                                Она содержит массу полей, каждое из которых соответствует определенному
                                параметру настройки порта. Мы рассмотрим несколько полей, которые нам
                                нужны:

                                BaudRate — скорость передачи данных. Возможно указание констант —CBR_100,
                                CBR_300, CBR_600, CBR_1200, …, CBR_256000.
                                Parity — схема контроля четности. Может содержать одно из следующих
                                значений: EVENPARITY, MARKPARITY, NOPARITY, ODDPARITY, SPACEPARITY.

                                ByteSize — число информационных бит в передаваемых и принимаемых байтах.

                                StopBits — количество стоповых бит. Может быть ONESTOPBIT, ONE5STOPBIT,
                                TWOSTOPBIT.
                                Чтобы не заполнять структуру DCB вручную, ее можно заполнить информацией
                                о текущем состоянии порта вызовом функции GetCommState(), затем изменить
                                необходимые поля и установить настройки вызовом функции SetCommState().
                                Настройку порта желательно производить сразу после его открытия. На
                                Delphi это выглядит так:

                                var
                                Dcb: TDcb;

                                if not GetCommState(hPort, Dcb) then
                                raise Exception.Create(‘Error setting port state’);

                                Dcb.BaudRate := CBR_9600;
                                Dcb.Parity := NOPARITY;
                                Dcb.ByteSize := 8;
                                Dcb.StopBits := ONESTOPBIT;

                                if not SetCommState(hPort, Dcb) then
                                raise Exception.Create(‘Error setting port state’);
                                Еще одна операция, которая нам понадобится сразу после открытия порта
                                — его сброс.

                                BOOL PurgeComm(
                                HANDLE hFile,
                                DWORD dwFlags

                                );Вызов этой функции очищает очередь приема/передачи и завершает все
                                находящиеся в ожидании запросы ввода/вывода.

                                hFile — описатель открытого порта.
                                dwFlags — производимые действия в виде набора флагов PURGE_TXABORT,
                                PURGE_RXABORT, PURGE_TXCLEAR, PURGE_RXCLEAR.
                                Пример на Delphi:

                                if not PurgeComm(hPort, PURGE_TXCLEAR or PURGE_RXCLEAR) then
                                raise Exception.Create(‘Error purging port’);На этом подготовительная
                                фаза заканчивается, и можно приступать непосредственно к приему/передаче
                                данных. Прием данных у нас будет происходить по событийной схеме; программа
                                будет ожидать прием одного или нескольких символов (байт). Для перевода
                                порта в этот режим необходимо вызвать функцию SetCommMask() с флагом
                                EV_RXCHAR:

                                if not SetCommMask(hPort, EV_RXCHAR) then
                                raise Exception.Create(‘Error setting port mask’);Прием и передача данных
                                выполняется функциями ReadFile() и WriteFile(), то есть теми же самыми
                                функциями, которые используются для работы с дисковыми файлами. Вот
                                их описание:

                                BOOL ReadFile(
                                HANDLE hFile,
                                LPVOID lpBuffer,
                                DWORD nNumberOfBytesToRead,

                                LPDWORD lpNumberOfBytesRead,
                                LPOVERLAPPED lpOverlapped
                                );

                                BOOL WriteFile(
                                HANDLE hFile,
                                LPCVOID lpBuffer,

                                DWORD nNumberOfBytesToWrite,
                                LPDWORD lpNumberOfBytesWritten,
                                LPOVERLAPPED lpOverlapped
                                );
                                hFile — описатель открытого порта.
                                lpBuffer — адрес буфера.

                                nNumberOfBytesToRead/nNumberOfBytesToWrite — число ожидаемых к приему
                                или предназначенных для передачи байт.
                                lpNumberOfBytesRead/lpNumberOfBytesWritten — число фактически принятых
                                или переданных байт.
                                lpOverlapped — адрес структуры OVERLAPPED, используемой для асинхронных
                                операций.
                                Передача данных является довольно быстрой операцией, поэтому как правило
                                ее выполняют из главного потока приложения. На Delphi это выглядит так:

                                var
                                dwWrite: DWORD;

                                OverWrite: TOverlapped;
                                WriteBytes: array of Byte;

                                begin
                                OverWrite.hEvent := CreateEvent(nil, True, False, nil);
                                if OverWrite.hEvent = Null then

                                raise Exception.Create(‘Error creating write event’);

                                if (not WriteFile(hPort, WriteBytes, SizeOf(WriteBytes),
                                dwWrite, @OverWrite))
                                and (GetLastError <> ERROR_IO_PENDING) then

                                raise Exception.Create(‘Error writing port’);
                                end;В данном примере функция WriteFile() выполняет асинхронную запись
                                массива байтов WriteBytes в порт. Она сразу возвращает управление, и
                                запись в порт происходит параллельно с выполнением основного кода потока.
                                Если результат WriteFile() равен False, то это значит, что на момент
                                возврата управления передача массива байтов еще не закончилась. Поэтому
                                код ошибки выполнения WriteFile() в данном случае должен быть равен
                                ERROR_IO_PENDING. Переменная OverWrite — overlapped-структура, необходимая
                                для асинхронных операций.

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

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

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

                                procedure TReadThread.Execute;

                                var
                                ComStat: TComStat;
                                dwMask, dwError: DWORD;
                                OverRead: TOverlapped;
                                Buf: array[0..$FF] of Byte;
                                dwRead: DWORD;

                                begin
                                OverRead.hEvent := CreateEvent(nil, True, False, nil);
                                if OverRead.hEvent = Null then
                                raise Exception.Create(‘Error creating read event’);

                                FreeOnTerminate := True;

                                while not Terminated do

                                begin
                                if not WaitCommEvent(hPort, dwMask, @OverRead) then
                                begin
                                if GetLastError = ERROR_IO_PENDING then
                                WaitForSingleObject(OverRead.hEvent, INFINITE)
                                else

                                raise Exception.Create(‘Error waiting port event’);
                                end;

                                if not ClearCommError(hPort, dwError, @ComStat) then
                                raise Exception.Create(‘Error clearing port’);

                                dwRead := ComStat.cbInQue;

                                if dwRead > 0 then
                                begin
                                if not ReadFile(hPort, Buf, dwRead, dwRead, @OverRead) then
                                raise Exception.Create(‘Error reading port’);
                                // В Buf находятся прочитанные байты

                                // Далее идет обработка принятых байтов
                                end;
                                end; {while}
                                end;
                                В приведенном примере в потоке крутится цикл, тем самым инициируется
                                ожидание события порта вызовом функции WaitCommEvent(), ожидание же
                                самого этого события задается функцией WaitForSingleObject(). Для определения
                                количества принятых символов используется функция ClearCommError().
                                Когда количество принятых символов (dwRead) известно, непосредственное
                                чтение символов выполняется функцией ReadFile().

                                Используя вышеописанные выкладки, я написал на Borland Delphi 7 класс
                                TComPort для работы с COM-портами. Для проверки работоспособности программы
                                я просто соединил нуль-модемным кабелем два COM-порта на своем компьютере
                                и запустил два экземпляра программы для каждого порта. Данные передаются
                                через один порт и одновременно принимаются через другой.

                                Для передачи и приема данных предусмотрены отдельные окна. Формат передаваемых
                                данных — строка. Принимаемые данные представляются в виде массива байт.

                                Статьи на смежную тематику:

                                Allen Denver, Serial Communications in Win32 ( http://msdn.microsoft.com/library/en-us/dnfiles/html/msdn_serial.asp).
                                Дмитрий Кузан, Работа с портами ввода-вывода в Delphi ( http://www.delphikingdom.ru/mastering/ports1.htm).
                                Олег Титов, Работа с коммуникационными портами (COM и LPT) в программах для Win32 ( http://www.happytown.ru/prog/practika/com_win32.html).

                                Оригинал статьи http://www.mycomp.com.ua/. Автор статьи Игорь ПАВЛОВ.

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

                                Читайте также:

                              • Deep sea electronics ошибки
                              • Deep rock galactic сессия закончена ошибка
                              • Deep rock galactic ошибка при запуске
                              • Deep rock galactic как изменить имя на пиратке
                              • Deep rock galactic the error cube

                              • 0 0 голоса
                                Рейтинг статьи
                                Подписаться
                                Уведомить о
                                guest

                                0 комментариев
                                Старые
                                Новые Популярные
                                Межтекстовые Отзывы
                                Посмотреть все комментарии