Author: Daniel Wischnewski
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 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 not 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!
1 unit uPipes;
2
3 interface
4
5 uses
6 Classes, Windows;
7
8 const
9 cShutDownMsg = 'shutdown pipe ';
10 cPipeFormat = '\\%s\pipe\%s';
11
12 type
13 RPIPEMessage = record
14 Size: DWORD;
15 Kind: Byte;
16 Count: DWORD;
17 Data: array[0..8095] of Char;
18 end;
19
20 TPipeServer = class(TThread)
21 private
22 FHandle: THandle;
23 FPipeName: string;
24
25 protected
26 public
27 constructor CreatePipeServer(aServer, aPipe: string; StartServer: Boolean);
28 destructor Destroy; override;
29
30 procedure StartUpServer;
31 procedure ShutDownServer;
32 procedure Execute; override;
33 end;
34
35 TPipeClient = class
36 private
37 FPipeName: string;
38 function ProcessMsg(aMsg: RPIPEMessage): RPIPEMessage;
39 protected
40 public
41 constructor Create(aServer, aPipe: string);
42
43 function SendString(aStr: string): string;
44 end;
45
46 implementation
47
48 uses
49 SysUtils;
50
51 procedure CalcMsgSize(var Msg: RPIPEMessage);
52 begin
53 Msg.Size :=
54 SizeOf(Msg.Size) +
55 SizeOf(Msg.Kind) +
56 SizeOf(Msg.Count) +
57 Msg.Count +
58 3;
59 end;
60
61 { TPipeServer }
62
63 constructor TPipeServer.CreatePipeServer(
64 aServer, aPipe: string; StartServer: Boolean
65 );
66 begin
67 if aServer = '' then
68 FPipeName := Format(cPipeFormat, ['.', aPipe])
69 else
70 FPipeName := Format(cPipeFormat, [aServer, aPipe]);
71 // clear server handle
72 FHandle := INVALID_HANDLE_VALUE;
73 if StartServer then
74 StartUpServer;
75 // create the class
76 Create(not StartServer);
77 end;
78
79 destructor TPipeServer.Destroy;
80 begin
81 if FHandle <> INVALID_HANDLE_VALUE then
82 // must shut down the server first
83 ShutDownServer;
84 inherited Destroy;
85 end;
86
87 procedure TPipeServer.Execute;
88 var
89 I, Written: Cardinal;
90 InMsg, OutMsg: RPIPEMessage;
91 begin
92 while not Terminated do
93 begin
94 if FHandle = INVALID_HANDLE_VALUE then
95 begin
96 // suspend thread for 250 milliseconds and try again
97 Sleep(250);
98 end
99 else
100 begin
101 if ConnectNamedPipe(FHandle, nil) then
102 try
103 // read data from pipe
104 InMsg.Size := SizeOf(InMsg);
105 ReadFile(FHandle, InMsg, InMsg.Size, InMsg.Size, nil);
106 if
107 (InMsg.Kind = 0) and
108 (StrPas(InMsg.Data) = cShutDownMsg + FPipeName) then
109 begin
110 // process shut down
111 OutMsg.Kind := 0;
112 OutMsg.Count := 3;
113 OutMsg.Data := 'OK'#0;
114 Terminate;
115 end
116 else
117 begin
118 // data send to pipe should be processed here
119 OutMsg := InMsg;
120 // we'll just reverse the data sent, byte-by-byte
121 for I := 0 to Pred(InMsg.Count) do
122 OutMsg.Data[Pred(InMsg.Count) - I] := InMsg.Data[I];
123 end;
124 CalcMsgSize(OutMsg);
125 WriteFile(FHandle, OutMsg, OutMsg.Size, Written, nil);
126 finally
127 DisconnectNamedPipe(FHandle);
128 end;
129 end;
130 end;
131 end;
132
133 procedure TPipeServer.ShutDownServer;
134 var
135 BytesRead: Cardinal;
136 OutMsg, InMsg: RPIPEMessage;
137 ShutDownMsg: string;
138 begin
139 if FHandle <> INVALID_HANDLE_VALUE then
140 begin
141 // server still has pipe opened
142 OutMsg.Size := SizeOf(OutMsg);
143 // prepare shut down message
144 with InMsg do
145 begin
146 Kind := 0;
147 ShutDownMsg := cShutDownMsg + FPipeName;
148 Count := Succ(Length(ShutDownMsg));
149 StrPCopy(Data, ShutDownMsg);
150 end;
151 CalcMsgSize(InMsg);
152 // send shut down message
153 CallNamedPipe(
154 PChar(FPipeName), @InMsg, InMsg.Size, @OutMsg, OutMsg.Size, BytesRead, 100
155 );
156 // close pipe on server
157 CloseHandle(FHandle);
158 // clear handle
159 FHandle := INVALID_HANDLE_VALUE;
160 end;
161 end;
162
163 procedure TPipeServer.StartUpServer;
164 begin
165 // check whether pipe does exist
166 if WaitNamedPipe(PChar(FPipeName), 100 {ms}) then
167 raise Exception.Create('Requested PIPE exists already.');
168 // create the pipe
169 FHandle := CreateNamedPipe(
170 PChar(FPipeName), PIPE_ACCESS_DUPLEX,
171 PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE or PIPE_WAIT,
172 PIPE_UNLIMITED_INSTANCES, SizeOf(RPIPEMessage), SizeOf(RPIPEMessage),
173 NMPWAIT_USE_DEFAULT_WAIT, nil
174 );
175 // check if pipe was created
176 if FHandle = INVALID_HANDLE_VALUE then
177 raise Exception.Create('Could not create PIPE.');
178 end;
179
180 { TPipeClient }
181
182 constructor TPipeClient.Create(aServer, aPipe: string);
183 begin
184 inherited Create;
185 if aServer = '' then
186 FPipeName := Format(cPipeFormat, ['.', aPipe])
187 else
188 FPipeName := Format(cPipeFormat, [aServer, aPipe]);
189 end;
190
191 function TPipeClient.ProcessMsg(aMsg: RPIPEMessage): RPIPEMessage;
192 begin
193 CalcMsgSize(aMsg);
194 Result.Size := SizeOf(Result);
195 if WaitNamedPipe(PChar(FPipeName), 10) then
196 if not CallNamedPipe(
197 PChar(FPipeName), @aMsg, aMsg.Size, @Result, Result.Size, Result.Size, 500
198 ) then
199 raise Exception.Create('PIPE did not respond.')
200 else
201 else
202 raise Exception.Create('PIPE does not exist.');
203 end;
204
205 function TPipeClient.SendString(aStr: string): string;
206 var
207 Msg: RPIPEMessage;
208 begin
209 // prepare outgoing message
210 Msg.Kind := 1;
211 Msg.Count := Length(aStr);
212 StrPCopy(Msg.Data, aStr);
213 // send message
214 Msg := ProcessMsg(Msg);
215 // return data send from server
216 Result := Copy(Msg.Data, 1, Msg.Count);
217 end;
218
219 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:
220 procedure TForm1.chkRunServerClick(Sender: TObject);
221 begin
222 if chkRunServer.Checked then
223 try
224 FServer := TPipeServer.CreatePipeServer('', 'testit', True);
225 except
226 on E: Exception do
227 begin
228 ShowMessage(E.message);
229 chkRunServer.Checked := False;
230 end;
231 end
232 else
233 begin
234 FServer.Destroy;
235 end;
236 end;
237
238 //For the OnClick Event of the btnSend add the following code:
239
240 procedure TForm1.btnSendClick(Sender: TObject);
241 begin
242 with TPipeClient.Create(edtServer.Text, 'testit') do
243 try
244 edtResponse.Text := SendString(edtTextToSend.Text);
245 finally
246 Free;
247 end;
248 end;
|