{ 10-05-1999 10:36:51 PM > [martin on MARTIN] checked out /Reformatting according to Delphi guidelines. } { 14-04-1999 11:59:10 PM > [martin on MARTIN] update: Changing dynamic methods to virtual. (0.2) / } { 14-04-1999 11:53:03 PM > [martin on MARTIN] checked out /Changing dynamic methods to virtual. } { 06-04-1999 7:49:29 PM > [martin on MARTIN] checked out /Modifying Class Names } unit MCHPipeSocket; {$OVERFLOWCHECKS OFF} {Martin Harvey 7/11/1998 This unit does the required interfacing from the socket paradigm to the pipe paradigm. It does the required interfacing between the pipe threads, which may block, and the pipe transaction manager, which treats all events as aynchronous. Main points to consider are: DLL Loading will be handled by the session. We will regularly have to check the state of the reader and writer threads. If either of them terminates, then we need to find out why, and signal that as a disconnection event or error event. We also need to check for success during connection. Note that writer thread will not terminate unless it fails to write data. Reader thread may terminate at any time after it has stopped waiting for the peer to connect. The connection status variable tells us whether we are disconnected, fully connected, or waiting for the peer. If we are waiting for the peer, ONCP code should not attempt to send anything, but signal an error. Although we signal a connection, this will probably not be used by the ONCP Session. It will just check whether we are connected every time something has to be sent, and signal an error if we aren't. Design modification: 15/12/98 An additional "issue" has cropped up. It is entirely possible for an immediate reconnection attempt after a disconnection to mean that unhandled messages from the previous connection get applied to the current connection. *NOT* what we want! We get around this by having a "Session Number" integer, which starts at 0, and always increments. We only allow messages given by the current connection any notice. It is not protected, because it's only read when the reader/writer threads do call our callbacks, and written when the threads do not exist. } interface uses Classes,MCHPipeThreads,MCHPipeTypes,MCHTransactions, Messages,Windows,Controls; const WM_ASYNC_LAZY_READ = WM_USER + 2878; WM_ASYNC_TERMINATE = WM_USER + 2879; WM_ASYNC_CONNECT = WM_USER + 2880; type psServerType = (psServer,psClient,psPeer); TMCHPipeConnectionStatus = (pcsNotConnected,pcsConnecting,pcsConnected); TMCHPipeSocket = class(TComponent) private FSockHandle:TMCHHandle; FHWnd:THandle; FReaderThread:TMCHPipeReaderThread; FWriterThread:TMCHPipeWriterThread; FOnDisconnect,FOnConnect:TNotifyEvent; FOnSockError:TNotifyEvent; FOnRead:TNotifyEvent; FConnected:TMCHPipeConnectionStatus; FManager:TMCHCustomTransactionManager; FServer:psServerType; FSessionNumber:Word; protected procedure HandleDataRecieved(Sender:TObject); {Handle data, Called in separate thread} procedure HandleTerminate(Sender:TObject); {Handle termination, Called in Separate Thread} procedure HandleConnect(Sender:TObject); {Handle peer connection, Called in separate thread} procedure MessageHandler(var Msg:TMessage); {Message handling loop for bridging thread gap} procedure DoAsyncHandleTerminate(var Msg:TMessage); {asynchronous handler} procedure DoAsyncHandleDataRecieved(var Msg:TMessage); {asynchronous handler} procedure DoAsyncHandleConnect(var Msg:TMessage); {asynchronous handler} procedure DoDisconnect;virtual; {Event trigger} procedure DoSockError;virtual; {Event trigger} procedure DoRead;virtual; {Event trigger} procedure DoConnect;virtual; {Event trigger} procedure StartThreads; {Sets up handles and resumes threads} public constructor Create(AOwner:TComponent);override; destructor Destroy;override; function Connect:boolean; {Signals whether connection successful pending remote connection} procedure Disconnect; {Closes handles, and Frees threads} function ReadData(Stream:TStream):integer; {Appends new data to stream. Returns how many bytes read} procedure WriteData(Stream:TStream); {Writes stream data.} published {On Disconnect is to be treated by the higher layers like a dWinsock disconnection was in the original DOP/ONCP stuff.} property OnDisconnect:TNotifyEvent read FOnDisconnect write FOnDisconnect; {OnPipeError will normally just be handled by the transaction manager, which will signal OnFatalError, However, you can assign a handler if you want.} property OnSockError:TNotifyEvent read FOnSockError write FOnSockError; property OnRead:TNotifyEvent read FOnRead write FOnRead; property OnConnect:TNotifyEvent read FOnConnect write FOnConnect; property Connected:TMCHPipeConnectionStatus read FConnected; property Manager:TMCHCustomTransactionManager read FManager write FManager; property Server:psServerType read FServer write FServer; end; implementation uses MCHPipeInterface2,Forms,MCHPipeTransactions; constructor TMCHPipeSocket.Create(AOwner:TComponent); begin inherited Create(AOwner); FHWnd := AllocateHWnd(MessageHandler); FManager := TMCHPipeTransactionManager.Create; (FManager as TMCHPipeTransactionManager).Socket := Self; FSessionNumber := 0; end; destructor TMCHPipeSocket.Destroy; begin if Assigned(FManager) then begin FManager.Free; FManager := nil; end; Disconnect; DeallocateHWnd(FHWnd); inherited Destroy; end; procedure TMCHPipeSocket.HandleDataRecieved(Sender:TObject); begin PostMessage(FHwnd,WM_ASYNC_LAZY_READ,FSessionNumber,0); end; procedure TMCHPipeSocket.HandleTerminate(Sender:TObject); begin PostMessage(FHwnd,WM_ASYNC_TERMINATE,FSessionNumber,Longint(Sender)); end; procedure TMCHPipeSocket.HandleConnect(Sender:TObject); begin PostMessage(FHwnd,WM_ASYNC_CONNECT,FSessionNumber,0); end; procedure TMCHPipeSocket.MessageHandler(var Msg:TMessage); begin {The session number check gets rid of a multitude of problems.} {In particular, it means that we only handle the first termination message from the two threads... all later messages are discarded} if Msg.WParam = FSessionNumber then begin case Msg.Msg of WM_ASYNC_LAZY_READ:DoAsyncHandleDataRecieved(Msg); WM_ASYNC_TERMINATE:DoAsyncHandleTerminate(Msg); WM_ASYNC_CONNECT:DoAsyncHandleConnect(Msg); end; end; end; procedure TMCHPipeSocket.DoAsyncHandleTerminate(var Msg:TMessage); var Sender:TObject; Error:TMCHError; OrigConnected:TMCHPipeConnectionStatus; begin Sender := TObject(Msg.LParam); {Find out termination reason} Error := (Sender as TMCHPipeThread).TermReason; {Call disconnect, to disconnect everything & free both threads} OrigConnected := FConnected; Disconnect; if not ((Error = meClientNotConnected) or (Error = meServerNotConnected)) then begin {Serious algorithm failure} DoSockError; end else begin {Normal disconnection by peer} {Don't signal this if it's as a result of us disconnecting} if OrigConnected <> pcsNotConnected then DoDisconnect; end; end; procedure TMCHPipeSocket.DoAsyncHandleDataRecieved(var Msg:TMessage); begin {Check that we are connected and that we have a thread to read from!} if (FConnected = pcsConnected) and Assigned(FReaderThread) then DoRead; end; procedure TMCHPipeSocket.DoAsyncHandleConnect(var Msg:TMessage); begin if FConnected = pcsConnecting then begin FConnected := pcsConnected; DoConnect; end else {Serious algorithm failure} DoSockError; end; procedure TMCHPipeSocket.DoDisconnect; begin (Manager as TMCHPipeTransactionManager).HandleDisconnect; if Assigned(FOnDisconnect) then FOnDisconnect(Self); end; procedure TMCHPipeSocket.DoConnect; begin (Manager as TMCHPipeTransactionManager).HandleConnect; if Assigned(FOnConnect) then FOnConnect(Self); end; procedure TMCHPipeSocket.DoSockError; begin (Manager as TMCHPipeTransactionManager).HandleSockError; if Assigned(FOnSockError) then FOnSockError(Self); end; procedure TMCHPipeSocket.DoRead; begin (Manager as TMCHPipeTransactionManager).HandleSockRead; if Assigned(FOnRead) then FOnRead(Self); end; function TMCHPipeSocket.Connect:boolean; {Assumes DLL already loaded.} begin if (FConnected = pcsNotConnected) then begin if Server = psServer then result := MCHPipeInterface2.ConnectServer(FSockHandle) = meOK else if Server = psClient then result := MCHPipeInterface2.ConnectClient(FSockHandle) = meOK else {Server=psPeer} begin result := MCHPipeInterface2.ConnectServer(FSockHandle) = meOK; if (not result) then result := MCHPipeInterface2.ConnectClient(FSockHandle) = meOK; end; if result then begin FConnected := pcsConnecting; StartThreads; end; end else result := FConnected <> pcsNotConnected; end; procedure TMCHPipeSocket.StartThreads; begin {Sets both reader and writer threads into a known state. This state is where all info is flushed from buffers, and they have just made their first read/write request or are waiting for the peer. } {OVERFLOWCHECKS ARE OFF} Inc(FSessionNumber); {Set session number to make socket ignore queued messages from all previous connections} FReaderThread := TMCHPipeReaderThread.Create(true); FWriterThread := TMCHPipeWriterThread.Create(true); FReaderThread.PipeReadHandle := FSockHandle; FWriterThread.PipeWriteHandle := FSockHandle; FReaderThread.OnDataRecieved := HandleDataRecieved; FReaderThread.OnConnect := HandleConnect; FReaderThread.Resume; FReaderThread.OnTerminate := HandleTerminate; FWriterThread.OnTerminate := HandleTerminate; FWriterThread.Resume; end; procedure TMCHPipeSocket.Disconnect; begin {Close handles} if MCHPipeInterface2.DisconnectClient(FSockHandle) <> meOK then MCHPipeInterface2.DisconnectServer(FSockHandle); {Free threads} {Reader thread is already unblocked} {Writer thread may only unblock when it's destructor is called} if Assigned(FReaderThread) then begin with FReaderThread do begin Terminate; {Don't need to wait. Destructor calls WaitFor} Free; end; FReaderThread := nil; end; if Assigned(FWriterThread) then begin with FWriterThread do begin Terminate; {Don't need to wait. Destructor calls WaitFor} Free; end; FWriterThread := nil; end; FConnected := pcsNotConnected; {OVERFLOWCHECKS ARE OFF} Inc(FSessionNumber); end; function TMCHPipeSocket.ReadData(Stream:TStream):integer; begin if FConnected = pcsConnected then {FReader should be assigned} result := FReaderThread.ReadData(Stream) else result := 0; end; procedure TMCHPipeSocket.WriteData(Stream:TStream); begin if FConnected = pcsConnected then FWriterThread.WriteData(Stream); end; end.