Mega Code Archive

 
Categories / Delphi / Examples
 

Using PIPES for messages

Title: Using PIPES for messages Question: A pipe is a section of shared memory that processes use for communication. The process that creates a pipe is the pipe server. A process that connects to a pipe is a pipe client. One process writes information to the pipe, then the other process reads the information from the pipe. (MSDN) Answer: WHAT PIPES ARE ============== Pipes are used by independent processes to communicate with each other. For every pipe there must be a server that creates and manages the pipe and one or more clients that use the pipe to interchange messages between each other. Pipes can be used for communication of processes residing on the same computer as well as processes residing on different machines within a network. WHEN CAN YOU USE PIPES ====================== Basically all Windows NT 3.51 and up, as well as Win95 and up support named pipes. You will use named pipes only to transfer information between applications within a network, or similar. I would not use them for use within a single application or when the SendMessage/PostMessage routines will suffice. Named Pipes will ensure the data transport between to processes - therefore you will use them when data transport is essential. Mailslots, similar to named pipes, will nmot ensure data transport between processes, are, however much more efficient. BLOCKING AND NON-BLOCKING MODES =============================== Pipes can be created supporting blocking and non-blocking modes. This is essential for three routines: ReadFile, WriteFile, and ConnectNamedPipe. These routines will not return during blocking-mode until data are read/sent. MS recommends the use of the blocking-mode. THEORIE OF THIS SAMPLE ====================== Your Pipe-Server will create a named pipe and wait for clients to access the pipe in order to send data. Once a Pipe-Client sends data, the Pipe-Server will open the Pipe to the Client, process the data, send the "answer", and closes the Pipe to the Client. The server will close the pipe after every message processed. NOTE ==== This is a simple sample for the use of Pipes only, as samples are hard to find anyway. I am working on a more complex one, this may, however take quite some time - depending on my spare time. :) THE UNIT UPIPES.PAS =================== In this sample, the Pipe-Server will reverse the data send by the Pipe-Client as Response. No Range Checking is done! unit uPipes; interface uses Classes, Windows; const cShutDownMsg = 'shutdown pipe '; cPipeFormat = '\\%s\pipe\%s'; type RPIPEMessage = record Size: DWORD; Kind: Byte; Count: DWORD; Data: array[0..8095] of Char; end; TPipeServer = class(TThread) private FHandle: THandle; FPipeName: String; protected public constructor CreatePipeServer(aServer, aPipe: String; StartServer: Boolean); destructor Destroy; override; procedure StartUpServer; procedure ShutDownServer; procedure Execute; override; end; TPipeClient = class private FPipeName: String; function ProcessMsg(aMsg: RPIPEMessage): RPIPEMessage; protected public constructor Create(aServer, aPipe: String); function SendString(aStr: String): String; end; implementation uses SysUtils; procedure CalcMsgSize(var Msg: RPIPEMessage); begin Msg.Size := SizeOf(Msg.Size) + SizeOf(Msg.Kind) + SizeOf(Msg.Count) + Msg.Count + 3; end; { TPipeServer } constructor TPipeServer.CreatePipeServer( aServer, aPipe: String; StartServer: Boolean ); begin if aServer = '' then FPipeName := Format(cPipeFormat, ['.', aPipe]) else FPipeName := Format(cPipeFormat, [aServer, aPipe]); // clear server handle FHandle := INVALID_HANDLE_VALUE; if StartServer then StartUpServer; // create the class Create(not StartServer); end; destructor TPipeServer.Destroy; begin if FHandle INVALID_HANDLE_VALUE then // must shut down the server first ShutDownServer; inherited Destroy; end; procedure TPipeServer.Execute; var I, Written: Cardinal; InMsg, OutMsg: RPIPEMessage; begin while not Terminated do begin if FHandle = INVALID_HANDLE_VALUE then begin // suspend thread for 250 milliseconds and try again Sleep(250); end else begin if ConnectNamedPipe(FHandle, nil) then try // read data from pipe InMsg.Size := SizeOf(InMsg); ReadFile(FHandle, InMsg, InMsg.Size, InMsg.Size, nil); if (InMsg.Kind = 0) and (StrPas(InMsg.Data) = cShutDownMsg + FPipeName) then begin // process shut down OutMsg.Kind := 0; OutMsg.Count := 3; OutMsg.Data := 'OK'#0; Terminate; end else begin // data send to pipe should be processed here OutMsg := InMsg; // we'll just reverse the data sent, byte-by-byte for I := 0 to Pred(InMsg.Count) do OutMsg.Data[Pred(InMsg.Count) - I] := InMsg.Data[I]; end; CalcMsgSize(OutMsg); WriteFile(FHandle, OutMsg, OutMsg.Size, Written, nil); finally DisconnectNamedPipe(FHandle); end; end; end; end; procedure TPipeServer.ShutDownServer; var BytesRead: Cardinal; OutMsg, InMsg: RPIPEMessage; ShutDownMsg: String; begin if FHandle INVALID_HANDLE_VALUE then begin // server still has pipe opened OutMsg.Size := SizeOf(OutMsg); // prepare shut down message with InMsg do begin Kind := 0; ShutDownMsg := cShutDownMsg + FPipeName; Count := Succ(Length(ShutDownMsg)); StrPCopy(Data, ShutDownMsg); end; CalcMsgSize(InMsg); // send shut down message CallNamedPipe( PChar(FPipeName), @InMsg, InMsg.Size, @OutMsg, OutMsg.Size, BytesRead, 100 ); // close pipe on server CloseHandle(FHandle); // clear handle FHandle := INVALID_HANDLE_VALUE; end; end; procedure TPipeServer.StartUpServer; begin // check whether pipe does exist if WaitNamedPipe(PChar(FPipeName), 100 {ms}) then raise Exception.Create('Requested PIPE exists already.'); // create the pipe FHandle := CreateNamedPipe( PChar(FPipeName), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE or PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, SizeOf(RPIPEMessage), SizeOf(RPIPEMessage), NMPWAIT_USE_DEFAULT_WAIT, nil ); // check if pipe was created if FHandle = INVALID_HANDLE_VALUE then raise Exception.Create('Could not create PIPE.'); end; { TPipeClient } constructor TPipeClient.Create(aServer, aPipe: String); begin inherited Create; if aServer = '' then FPipeName := Format(cPipeFormat, ['.', aPipe]) else FPipeName := Format(cPipeFormat, [aServer, aPipe]); end; function TPipeClient.ProcessMsg(aMsg: RPIPEMessage): RPIPEMessage; begin CalcMsgSize(aMsg); Result.Size := SizeOf(Result); if WaitNamedPipe(PChar(FPipeName), 10) then if not CallNamedPipe( PChar(FPipeName), @aMsg, aMsg.Size, @Result, Result.Size, Result.Size, 500 ) then raise Exception.Create('PIPE did not respond.') else else raise Exception.Create('PIPE does not exist.'); end; function TPipeClient.SendString(aStr: String): String; var Msg: RPIPEMessage; begin // prepare outgoing message Msg.Kind := 1; Msg.Count := Length(aStr); StrPCopy(Msg.Data, aStr); // send message Msg := ProcessMsg(Msg); // return data send from server Result := Copy(Msg.Data, 1, Msg.Count); end; end. A SAMPLE USING UPIPES.PAS ========================= Create a new application and add the unit uPipes.pas to the uses clause. Add the following Controls to the Main Form Checkbox: (Name: chkRunServer; Caption: Run Server) Edit: (Name: edtServer) Edit: (Name:edtTextToSend) Button: (Name: btnSend) Edit: (Name: edtResponse) Add the private variable: FServer: TPipeServer; For the OnClick Event of the chkRunServer add the following code: procedure TForm1.chkRunServerClick(Sender: TObject); begin if chkRunServer.Checked then try FServer := TPipeServer.CreatePipeServer('', 'testit', True); except on E: Exception do begin ShowMessage(E.Message); chkRunServer.Checked := False; end; end else begin FServer.Destroy; end; end; For the OnClick Event of the btnSend add the following code: procedure TForm1.btnSendClick(Sender: TObject); begin with TPipeClient.Create(edtServer.Text, 'testit') do try edtResponse.Text := SendString(edtTextToSend.Text); finally Free; end; end; Good Luck, Daniel gate(n)etwork