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