home *** CD-ROM | disk | FTP | other *** search
- unit MSComm;
-
- { TMSComm VCL component version history
- -----------------------------------
-
- 7/24/95 Version 1.00, FREEWARE by Jeff Atwood
-
- General information
- -------------------
-
- This is a drop-in replacement for the MSCOMM control available in VB 3.0
- professional! I modified it with the goal of making the control work like that
- one, since I used it all the time.. but that was pre-Delphi. :)
-
- There are no known bugs. This control is freely distributable. Any comments,
- rants, raves, or other horticultural delights can be E-Mailed to me at
- JAtwood159@AOL.COM. Especially let me know if you find a bug or add a new
- nifty feature!
-
- How to Use
- ----------
-
- See the demo code for a good example. Otherwise, check the code below for
- comments. IMPORTANT: when opening the port, make sure that the TxBuf
- is larger than the largest chunk of data you will send through the port.
-
- }
-
- interface
-
- uses Messages, WinTypes, WinProcs, Classes, Forms, SysUtils;
-
- { These are the enumerated types supported by the TMSComm control }
-
- type
- TBaudRate = (br110, br300, br600, br1200, br2400, br4800, br9600, br14400,
- br19200, br38400, br56000, br128000, br256000);
- TParityBits = (pbNone, pbOdd, pbEven, pbMark, pbSpace);
- TDataBits = (dbFour, dbFive, dbSix, dbSeven, dbEight);
- TStopBits = (sbOne, sbOnePointFive, sbTwo);
- TCommEvent = (ceBreak, ceCts, ceCtss, ceDsr, ceErr, cePErr, ceRing, ceRlsd,
- ceRlsds, ceRxChar, ceRxFlag, ceTxEmpty);
- TFlowControl = (fcNone, fcRTSCTS, fcXONXOFF);
- TCommEvents = set of TCommEvent;
-
- type
-
- { These are the events for the TComm object }
-
- TNotifyCommEventEvent = procedure(Sender: TObject; CommEvent: TCommEvents) of object;
- TNotifyReceiveEvent = procedure(Sender: TObject; Count: Word) of object;
- TNotifyTransmitLowEvent = procedure(Sender: TObject; Count: Word) of object;
-
- { This is the TMSComm object }
-
- TMSComm = class(TComponent)
- private
- FVersion: Single;
- FPort: Byte;
- FBaudRate: TBaudRate;
- FParityBits: TParityBits;
- FDataBits: TDataBits;
- FStopBits: TStopBits;
- FFlowControl: TFlowControl;
- FRxBufSize: Word;
- FTxBufSize: Word;
- FRxFull: Word;
- FTxLow: Word;
- FEvents: TCommEvents;
- FOnCommEvent: TNotifyCommEventEvent;
- FOnReceive: TNotifyReceiveEvent;
- FOnTransmitLow: TNotifyTransmitLowEvent;
- FhWnd: hWnd;
- cId: Integer; { handle to comm port }
- Error: String;
- procedure SetPort(Value: Byte);
- procedure SetBaudRate(Value: TBaudRate);
- procedure SetParityBits(Value: TParityBits);
- procedure SetDataBits(Value: TDataBits);
- procedure SetStopBits(Value: TStopBits);
- procedure SetFlowControl(Value: TFlowControl);
- procedure SetRxBufSize(Value: Word);
- procedure SetTxBufSize(Value: Word);
- procedure SetRxFull(Value: Word);
- procedure SetTxLow(Value: Word);
- procedure SetEvents(Value: TCommEvents);
- procedure WndProc(var Msg: TMessage);
- procedure DoEvent;
- procedure DoReceive;
- procedure DoTransmit;
- function parseOpenErr(Errcode: Integer): String;
- function parseGenErr: String;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Write(Data: PChar; Len: Word);
- procedure Read(Data: PChar; Len: Word);
- function Open: Boolean;
- procedure Close;
- function GetError: String;
- published
- property Version: Single read FVersion;
- property Port: Byte read FPort write SetPort;
- property BaudRate: TBaudRate read FBaudRate write SetBaudRate;
- property ParityBits: TParityBits read FParityBits write SetParityBits;
- property DataBits: TDataBits read FDataBits write SetDataBits;
- property StopBits: TStopBits read FStopBits write SetStopBits;
- property FlowControl: TFlowControl read FFlowControl write SetFlowControl;
- property TxBufSize: Word read FTxBufSize write SetTxBufSize;
- property RxBufSize: Word read FRxBufSize write SetRxBufSize;
- property RxFullCount: Word read FRxFull write SetRxFull;
- property TxLowCount: Word read FTxLow write SetTxLow;
- property Events: TCommEvents read FEvents write SetEvents;
- property OnCommEvent: TNotifyCommEventEvent read FOnCommEvent write FOnCommEvent;
- property OnReceive: TNotifyReceiveEvent read FOnReceive write FOnReceive;
- property OnTransmitLow: TNotifyTransmitLowEvent read FOnTransmitLow write FOnTransmitLow;
- end;
-
- procedure Register;
-
- implementation
-
- { Set com port value. Used when you open the port. NOTE: This only takes effect when
- opening the port-- obviously! Only works for ports 1 thru 9 currently, though I
- think newer versions of Windows support up to 254 comm ports. Set this to port
- zero (0) if you want to disable the comm control.}
- procedure TMSComm.SetPort(Value: Byte);
- begin
- FPort := Value;
- end;
-
- { Set baud rate: 110-256,000. Notice that this will change the baud rate of the port
- immediately-- if it is currently open! This goes for most of the other com port
- settings below as well.}
- procedure TMSComm.SetBaudRate(Value: TBaudRate);
- var
- DCB: TDCB;
- begin
- FBaudRate := Value;
- if cId >= 0 then begin
- GetCommState(cId, DCB);
- case Value of
- br110: DCB.BaudRate := CBR_110;
- br300: DCB.BaudRate := CBR_300;
- br600: DCB.BaudRate := CBR_600;
- br1200: DCB.BaudRate := CBR_1200;
- br2400: DCB.BaudRate := CBR_2400;
- br4800: DCB.BaudRate := CBR_4800;
- br9600: DCB.BaudRate := CBR_9600;
- br14400: DCB.BaudRate := CBR_14400;
- br19200: DCB.BaudRate := CBR_19200;
- br38400: DCB.BaudRate := CBR_38400;
- br56000: DCB.BaudRate := CBR_56000;
- br128000: DCB.BaudRate := CBR_128000;
- br256000: DCB.BaudRate := CBR_256000;
- end;
- SetCommState(DCB);
- end;
- end;
-
- { set parity: none, odd, even, mark, space }
- procedure TMSComm.SetParityBits(Value: TParityBits);
- var
- DCB: TDCB;
- begin
- FParityBits := Value;
- if cId < 0 then
- exit;
- GetCommState(cId, DCB);
- case Value of
- pbNone: DCB.Parity := 0;
- pbOdd: DCB.Parity := 1;
- pbEven: DCB.Parity := 2;
- pbMark: DCB.Parity := 3;
- pbSpace: DCB.Parity := 4;
- end;
- SetCommState(DCB);
- end;
-
- { set # of data bits 4-8 }
- procedure TMSComm.SetDataBits(Value: TDataBits);
- var
- DCB: TDCB;
- begin
- FDataBits := Value;
- if cId < 0 then
- exit;
- GetCommState(cId, DCB);
- case Value of
- dbFour: DCB.ByteSize := 4;
- dbFive: DCB.ByteSize := 5;
- dbSix: DCB.ByteSize := 6;
- dbSeven: DCB.ByteSize := 7;
- dbEight: DCB.ByteSize := 8;
- end;
- SetCommState(DCB);
- end;
-
- { set number of stop bits 1, 1.5 or 2 }
- procedure TMSComm.SetStopBits(Value: TStopBits);
- var
- DCB: TDCB;
- begin
- FStopBits := Value;
- if cId < 0 then
- exit;
- GetCommState(cId, DCB);
- case Value of
- sbOne: DCB.StopBits := 0;
- sbOnePointFive: DCB.StopBits := 1;
- sbTwo: DCB.StopBits := 2;
- end;
- SetCommState(DCB);
- end;
-
- { Set flow control: None, RTS/CTS, or Xon/Xoff. Flow control works in conjunction
- with the read and write buffers to ensure that the flow of data *will* stop if
- the buffers get critically full. If there is no flow control, it's possible
- to lose data.. with flow control on, technically, it's impossible since if the
- buffers get full, flow control will kick in and stop the data flow until the
- buffers have time to get clear. }
- procedure TMSComm.SetFlowControl(Value: TFlowControl);
- var
- DCB: TDCB;
- begin
- FFlowControl := Value;
- if cId < 0 then
- exit;
- GetCommState(cId, DCB);
- DCB.Flags := DCB.Flags xor (dcb_OutxCtsFlow or dcb_Rtsflow or dcb_OutX or dcb_InX);
- case Value of
- fcNone: ;
- fcRTSCTS: DCB.Flags := DCB.Flags or dcb_OutxCtsFlow or dcb_Rtsflow;
- fcXONXOFF: DCB.Flags := DCB.Flags or dcb_OutX or dcb_InX;
- end;
- SetCommState(DCB);
- end;
-
- { RxBuf is the amount of memory set aside to buffer reads (incoming data)
- to the serial port. It is possible to overflow the read buffer depending on how
- frequently you are servicing (reading) the incoming data and how fast data is
- coming in the serial port. NOTE: This setting takes effect only when opening
- the port. }
- procedure TMSComm.SetRxBufSize(Value: Word);
- begin
- FRxBufSize := Value;
- end;
-
- { TxBuf is the amount of memory set aside to buffer writes (outgoing data)
- to the serial port. Must be larger than any chunk of data you plan to write at
- once. It is possible to overflow the tx buffer depending on how fast data
- is going out of the modem, and how fast you're writing to the serial port. NOTE: this
- setting takes effect only when opening the port. }
- procedure TMSComm.SetTxBufSize(Value: Word);
- begin
- FTxBufSize := Value;
- end;
-
- { RxFull indicates the number of bytes the COM driver must write to the
- application's input queue before sending a notification message. The message
- signals the application to read information from the input queue. This "forces"
- the driver to send notification during periods of data "streaming." It will
- stop what it's doing and notify you when it gets at least this many chars.
- This will only affect data streaming; normally data is sent during lulls in
- the "stream." If there are no lulls, this setting comes into effect. The
- event OnReceive fires when ANY amount of data is received. The maximum
- chunk of data you will receive is set by the RxFull amount. }
- procedure TMSComm.SetRxFull(Value: Word);
- begin
- FRxFull := Value;
- if cId < 0 then
- exit;
- EnableCommNotification(cId, FhWnd, FRxFull, FTxLow);
- end;
-
- { TxLow Indicates the minimum number of bytes in the output queue. When the
- number of bytes in the output queue falls below this number, the COM driver
- sends the application a notification message, signaling it to write information
- to the output queue. This can be handy to avoid overflowing the (outgoing)
- read buffer. The event OnTransmitLow fires when this happens.}
- procedure TMSComm.SetTxLow(Value: Word);
- begin
- FTxLow := Value;
- if cId < 0 then
- exit;
- EnableCommNotification(cId, FhWnd, FRxFull, FTxLow);
- end;
-
- { Build the event mask. Indicates which misc events we want the comm control to
- tell us about. }
- procedure TMSComm.SetEvents(Value: TCommEvents);
- var
- Events: Word;
- begin
- FEvents := Value;
- if cId < 0 then
- exit;
- Events := 0;
- if ceBreak in FEvents then Events := Events or EV_BREAK;
- if ceCts in FEvents then Events := Events or EV_CTS;
- if ceCtss in FEvents then Events := Events or EV_CTSS;
- if ceDsr in FEvents then Events := Events or EV_DSR;
- if ceErr in FEvents then Events := Events or EV_ERR;
- if cePErr in FEvents then Events := Events or EV_PERR;
- if ceRing in FEvents then Events := Events or EV_RING;
- if ceRlsd in FEvents then Events := Events or EV_RLSD;
- if ceRlsds in FEvents then Events := Events or EV_RLSDS;
- if ceRxChar in FEvents then Events := Events or EV_RXCHAR;
- if ceRxFlag in FEvents then Events := Events or EV_RXFLAG;
- if ceTxEmpty in FEvents then Events := Events or EV_TXEMPTY;
- SetCommEventMask(cId, Events);
- end;
-
- { This is the message handler for the invisible window; it handles comm msgs
- that are handed to the invisible window. We hook into these messages using
- EnableCommNotification and our invisible window handle. This routine hands
- off to the "do(x)" routines below. }
- procedure TMSComm.WndProc(var Msg: TMessage);
- begin
- with Msg do begin
- if Msg = WM_COMMNOTIFY then begin
- case lParamLo of
- CN_EVENT: DoEvent;
- CN_RECEIVE: DoReceive;
- CN_TRANSMIT: DoTransmit;
- end;
- end
- else
- Result := DefWindowProc(FhWnd, Msg, wParam, lParam);
- end;
- end;
-
- { some comm event occured. see if we need to report it as an event based
- on the FOnEvent flags set in the control. }
- procedure TMSComm.DoEvent;
- var
- CommEvent: TCommEvents;
- Events: Word;
- begin
- if (cId < 0) or not Assigned(FOnCommEvent) then
- exit;
- Events := GetCommEventMask(cId, Integer($FFFF));
- CommEvent := [];
- if (ceBreak in FEvents) and (events and EV_BREAK <> 0) then
- CommEvent := CommEvent + [ceBreak];
- if (ceCts in FEvents) and (events and EV_CTS <> 0) then
- CommEvent := CommEvent + [ceCts];
- if (ceCtss in FEvents) and (events and EV_CTSS <> 0) then
- CommEvent := CommEvent + [ceCtss];
- if (ceDsr in FEvents) and (events and EV_DSR <> 0) then
- CommEvent := CommEvent + [ceDsr];
- if (ceErr in FEvents) and (events and EV_ERR <> 0) then
- CommEvent := CommEvent + [ceErr];
- if (cePErr in FEvents) and (events and EV_PERR <> 0) then
- CommEvent := CommEvent + [cePErr];
- if (ceRing in FEvents) and (events and EV_RING <> 0) then
- CommEvent := CommEvent + [ceRing];
- if (ceRlsd in FEvents) and (events and EV_RLSD <> 0) then
- CommEvent := CommEvent + [ceRlsd];
- if (ceRlsds in FEvents) and (events and EV_RLSDS <> 0) then
- CommEvent := CommEvent + [ceRlsds];
- if (ceRxChar in FEvents) and (events and EV_RXCHAR <> 0) then
- CommEvent := CommEvent + [ceRxChar];
- if (ceRxFlag in FEvents) and (events and EV_RXFLAG <> 0) then
- CommEvent := CommEvent + [ceRxFlag];
- if (ceTxEmpty in FEvents) and (events and EV_TXEMPTY <> 0) then
- CommEvent := CommEvent + [ceTxEmpty];
- FOnCommEvent(Self, CommEvent);
- end;
-
- { we rec'd some data, see if receive event is on and fire }
- procedure TMSComm.DoReceive;
- var
- Stat: TComStat;
- begin
- if (cId < 0) or not Assigned(FOnReceive) then
- exit;
- GetCommError(cId, Stat);
- FOnReceive(Self, Stat.cbInQue);
- GetCommError(cId, Stat);
- end;
-
- { This event will fire when the transmit buffer goes BELOW the point set
- in txLowCount. It will NOT fire when a transmission takes place. }
- procedure TMSComm.DoTransmit;
- var
- Stat: TComStat;
- begin
- if (cId < 0) or not Assigned(FOnTransmitLow) then
- exit;
- GetCommError(cId, Stat);
- FOnTransmitLow(Self, Stat.cbOutQue);
- end;
-
- { construct: create invisible message window, set default values }
- constructor TMSComm.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- FhWnd := AllocateHWnd(WndProc);
- Error := '';
- FVersion := 1.00;
- FPort := 2;
- FBaudRate := br9600;
- FParityBits := pbNone;
- FDataBits := dbEight;
- FStopBits := sbOne;
- FTxBufSize := 2048;
- FRxBufSize := 2048;
- FRxFull := 512;
- FTxLow := 512;
- FEvents := [];
- cId := -1;
- end;
-
- { destructor: close invisible message window, close comm port }
- destructor TMSComm.Destroy;
- begin
- DeallocatehWnd(FhWnd);
- if cId >= 0 then
- CloseComm(cId);
- inherited Destroy;
- end;
-
- { Write data to comm port. This routine will reject an attempt
- to write a chunk of data larger than the write buffer size. WARNING: This
- routine could *potentially* wait forever for the buffer to clear. But at least
- your machine won't lock up since we're processing messages in the wait loop.
- NOTE: theoretically, you should check the Error property for errors
- after every write. Any error during read or write can stop flow of data. }
- procedure TMSComm.Write(Data: PChar; Len: Word);
- var
- Stat: TComStat;
- bufroom: Integer;
- begin
- if cId < 0 then
- exit;
- if Len > FTxBufSize then begin
- Error := 'write larger than transmit buffer size';
- exit;
- end;
-
- repeat
- GetCommError(cId, Stat);
- bufroom := FTxBufSize - stat.cbOutQue;
- Application.ProcessMessages;
- until bufroom >= len;
-
- if WriteComm(cId, Data, Len) < 0 then
- Error := ParseGenErr;
- GetCommEventMask(cId, Integer($FFFF));
- end;
-
- { Read data from comm port. Should only do read when you've been notified you
- have data. Attempting to read when nothing is in read buffer results
- in spurious error. You can never read a larger chunk than the read buffer
- size. NOTE: theoretically, you should check the Error property for errors
- after every read. Any error during read or write can stop flow of data. }
- procedure TMSComm.Read(Data: PChar; Len: Word);
- begin
- if cId < 0 then
- exit;
- if ReadComm(cId, Data, Len) < 0 then
- Error := ParseGenErr;
- GetCommEventMask(cId, Integer($FFFF));
- end;
-
- { failure to open results in a negative cId, this will translate the
- negative cId value into an explanation. }
- function TMSComm.parseOpenErr(Errcode: Integer): String;
- begin
- case errcode of
- IE_BADID: result := 'Device identifier is invalid or unsupported';
- IE_OPEN: result := 'Device is already open.';
- IE_NOPEN: result := 'Device is not open.';
- IE_MEMORY: result := 'Cannot allocate queues.';
- IE_DEFAULT: result := 'Default parameters are in error.';
- IE_HARDWARE: result := 'Hardware not available (locked by another device).';
- IE_BYTESIZE: result := 'Specified byte size is invalid.';
- IE_BAUDRATE: result := 'Device baud rate is unsupported.';
- else
- result := 'Open error ' + IntToStr(Errcode);
- end;
- end;
-
- { failure to read or write to comm port results in a negative returned
- value. This will translate the value into an explanation. }
- function TMSComm.ParseGenErr: String;
- var
- stat: TComStat;
- errCode: Word;
- begin
- errCode := GetCommError(cId, stat);
- case errcode of
- CE_BREAK: result := 'Hardware detected a break condition.';
- CE_CTSTO: result := 'CTS (clear-to-send) timeout.';
- CE_DNS: result := 'Parallel device was not selected.';
- CE_DSRTO: result := 'DSR (data-set-ready) timeout.';
- CE_FRAME: result := 'Hardware detected a framing error.';
- CE_IOE: result := 'I/O error during communication with parallel device.';
- CE_MODE: result := 'Requested mode is not supported';
- CE_OOP: result := 'Parallel device is out of paper.';
- CE_OVERRUN: result := 'Character was overwritten before it could be retrieved.';
- CE_PTO: result := 'Timeout during communication with parallel device.';
- CE_RLSDTO: result := 'RLSD (receive-line-signal-detect) timeout.';
- CE_RXOVER: result := 'Receive buffer overflow.';
- CE_RXPARITY: result := 'Hardware detected a parity error.';
- CE_TXFULL: result := 'Transmit buffer overflow.';
- else
- result := 'General error ' + IntToStr(errcode);
- end;
- end;
-
- { returns error text (if any) and clears it }
- function TMSComm.GetError: String;
- begin
- Result := Error;
- Error := '';
- end;
-
- { Explicitly open port. Returns success/failure, check error property for details.
- This routine also begins hooking the comm messages to our invisible window we
- created upon instantiation. Will close port (if open) before re-opening. }
- function TMSComm.Open: Boolean;
- var
- commName: PChar;
- tempStr: String;
- begin
- if Fport = 0 then
- exit;
- close;
- tempStr := 'COM' + IntToStr(Fport) + ':';
- commName := StrAlloc(10);
- StrPCopy(commName, tempStr);
- cId := OpenComm(commName, RxBufSize, TxBufSize);
- StrDispose(commName);
- if cId < 0 then begin
- Error := parseOpenErr(cId);
- result := False;
- exit;
- end;
- SetBaudRate(FBaudRate);
- SetParityBits(FParityBits);
- SetDataBits(FDataBits);
- SetStopBits(FStopBits);
- SetFlowControl(FFlowControl);
- SetEvents(FEvents);
- EnableCommNotification(cId, FhWnd, FRxFull, FTxLow);
- result := True;
- end;
-
- { closes the comm port, if it is open. }
- procedure TMSComm.Close;
- begin
- if cId >= 0 then
- CloseComm(cId);
- end;
-
- { registers this VCL component and adds the icon to the palette }
- procedure Register;
- begin
- RegisterComponents('System', [TMSComm]);
- end;
-
- end.
-