## Respond to ReadEvent on Serial Port

Please discuss general Delphi programming topics here.

### Respond to ReadEvent on Serial Port

Hi,
I have found many source codes all over the network about how to access the serial port in Delphi. Everybody uses handles with CreateFile (COMx,......) etc, then uses ReadFile, WriteFile. When it comes to receiving characters, almost everybody uses SetCommMask to set for EV_RXCHAR event, then uses WaitCommEvent to respond to this particular event and read the buffer.
But does it mean that I must use some kind of a loop and wait for the event, thus risking to stay in a loop forever and don't do anything else useful? How can I solve this problem more elegantly, like having an event handler for my COMx handle to respond to? I know that one can write an EventHandle for an object, but an event handler for a virtual file handler referring to COMx ??
I would like to stress that I don't want to use some third party components. This solution with having a handle to COMx and making different system calls like GetCommState, WaitCommEvent etc. seems really nice, but how to respond to the incoming characters immediately and empty the input buffer ??? And still not being in a loop and waiting for event, which might not happen at all or occur once in several minutes or so.
ratep2001
Member

Posts: 1
Joined: June 11th, 2003, 8:59 am

You have to add the FILE_FLAG_OVERLAPPED flag to the flags of CreateFile procedure. Then, to read (or write) data you have to defined a TOverlapped record and assign an event handle to its hEvent member. The other members of this record should be zero for communication devices.

The following function returns a handle to the specified COM number and initializes the Overlapped record:

Code: Select all
function OpenComPort(ComNum: Integer; var Overlapped: TOverlapped): Integer;var  ComName: String;begin  ComName := '\\.\COM' + IntToStr(ComNum);  Result := CreateFile(PChar(ComName), GENERIC_READ or GENERIC_WRITE, 0,    nil, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING or FILE_FLAG_OVERLAPPED, 0);  if Result <> HFILE_ERROR then  begin    FillChar(Overlapped, SizeOf(TOverlapped), 0);    Overlapped.hEvent := CreateEvent(nil, True, False, nil);  end;end;

to read the data from the port, you have to call the ReadFile as follow:

Code: Select all
ReadFile(ComHandle, YourBuffer, YourNumberOfBytesToRead, Dummy, Overlapped);

In the above call, the ComHandle and Overlapped arguments are supposed to be initialized by a previous call to OpenComPort function. The Dummy argument is the number of read bytes, which actually is meaningless in an overlapped operation and can be nil. However, because of a mistake in translation of Widnwos header files to Delphi, it is defained as var instead of pointer. The meaning of YourBuffer and YourNumberOfBytesToRead is more clear than others. But just keep in mind that the YourBuffer argument should be available in the scope of the overlapped operation.

Well, now you should wait for the Overlapped.hEvent event to be signalled. You can wait or check this event whatever you want, however it is better to do it in a separate thread. The following sample thread can be used as a pattern for an overlapped read operation:

Code: Select all
procedure TReadThread.Execute;var  NumberOfBytesRead: DWORD;begin  while not Terminated then  begin    // Waits for read completion    WaitForSingleObject(Overlapped.hEvent, INFINITE);    if not Terminated then    begin      // Resets the event for the next read      ResetEvent(Overlapped.hEvent);      // Get the number of bytes read      GetOverlappedResult(ComHandle, Overlapped, NumberOfBytesRead, False);      // Data in the read buffer is ready now      // For example you can post a message to the main form to notify it      // about the new data in the buffer.    end;  end;end;

As you can see in the above code, we should reset the Overlapped.hEvent before any overlapped operation (read or write).

When the COM handle is no longer needed, as well as closing the port we should release the Overlapped.hEvent . For example the finalization code for the above thread can be as follow:

Code: Select all
ReadThread.Terminate;SetEvent(Overlapped.hEvent);ReadThread.WaitFor;CloseHandle(Overlapped.hEvent);CloseHandle(ComHandle);

Depends to your case, you may want two initialize and use two overlapped records, one for the read and the other for the write operation to have these operations simultaneously.

By the way, I have never used overlapped operations, so the code I've written here may have some bugs, as usual.

Hope that it helps.

Greetings,
Kambiz

Kambiz

Posts: 2428
Joined: March 7th, 2003, 7:10 pm

### Use com port

use Synaser

it is a delphi non-visua component. Easy to use to Delphi and Kylix (Win32, Linux)

Here sample code:

Code: Select all
FormCreate......         ser[1]:=TBlockSerial.Create;         ser[1].RaiseExcept:=FALSE;    ser[1].Connect('COM1',9600,8,'N',0,false,false); procedure Send(ser: TBlockSerial; Trama:string); begin    ser.RaiseExcept:=True;    ser.DTR := False;    ser.EnableSoftRTSToggle(true);        if ser.CanWrite(100) = true then    begin       ser.SendString(caracter);                memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time) + ': (send) ' + Trama);                ser.DTR := True;            ser.EnableSoftRTSToggle(false);    end; end; //put into timer to 200 milliseconds Function ReadCOM: string; begin       if ser[i].CanRead(50) = true then       begin                ReadCOM := ser[i].RecvPacket(5); //5 ok       end; end

Pablo Castrillon
Pablo Castrillon
pcastrillon
Member

Posts: 2
Joined: November 29th, 2007, 11:35 am