-
Notifications
You must be signed in to change notification settings - Fork 0
/
ipcpipe.pas
364 lines (318 loc) · 12.5 KB
/
ipcpipe.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
{ --------------------------------------------------------------------------
godaemon
IPC pipe unit
- tipcPipe: interprocess communication pipe class
- tipcPipeEndpoint: Endpoint of a tipcPipe
Used for 2-way communication between a parent and child process
Copyright (c) Michael Nixon 2015.
Please see the LICENSE file for licensing information.
-------------------------------------------------------------------------- }
{ --------------------------------------------------------------------------
-------------------------------------------------------------------------- }
unit ipcpipe;
interface
uses baseunix, unix, unixutil, sockets, sysutils, classes, pipes;
const
IPCPIPE_MAX_PACKET_SIZE = 65536;
{ message: len(packet) + packet }
IPCPIPE_MAX_MESSAGE_SIZE = 4 + IPCPIPE_MAX_PACKET_SIZE;
type
rByteArray = array[0..99999] of byte;
pByteArray = ^rByteArray;
tipcPipeEndpoint = class;
{ A class to create a pair of pipe endpoints (tipcPipeEndpoint) for IPC }
tipcPipe = class(tobject)
private
parentEndpoint: tipcPipeEndpoint;
childEndpoint: tipcPipeEndpoint;
public
constructor Create;
destructor Destroy; override;
function CreateEndpoints: boolean;
function GetParentEndpoint: tipcPipeEndpoint;
function GetChildEndpoint: tipcPipeEndpoint;
end;
{ An individual endpoint for pipe IPC }
tipcPipeEndpoint = class(tobject)
private
readPipe: TInputPipeStream;
writePipe: TOutputPipeStream;
{ Holds data read from the readPipe }
readBuffer: pByteArray;
{ Holds the packet to retrieve via GetPacket }
tempBuffer: pByteArray;
tempLength: longint;
{ Used to building output packets }
workBuffer: pByteArray;
{ Read position in readBuffer }
readPos: longint;
{ state: true if we have the packet size }
gotLength: boolean;
{ size of the packet to read in bytes }
packetLength: longint;
{ state: true if we are ready for a packet to be consumed }
packetReady: boolean;
public
constructor Create(rPipe: TInputPipeStream; wPipe: TOutputPipeStream);
destructor Destroy; override;
function Pump: boolean;
function SendPacket(size: longint; const data): boolean;
function GetPacket(var size: longint; var data): boolean;
function PeekPacketLength(var size: longint): boolean;
function SendString(s: ansistring): boolean;
function GetString(var s: ansistring): boolean;
end;
implementation
{ --------------------------------------------------------------------------
-------------------------------------------------------------------------- }
{ --------------------------------------------------------------------------
Send a string down the pipe.
The string length must be at most IPCPIPE_MAX_PACKET_SIZE bytes long.
TRUE is returned on success, and FALSE is returned on failure.
-------------------------------------------------------------------------- }
function tipcPipeEndpoint.SendString(s: ansistring): boolean;
begin
result := self.SendPacket(length(s), s[1]);
end;
{ --------------------------------------------------------------------------
Retrieve a string from the pipe.
For this to work, a packet must have been prepared by a call to <Pump>.
Returns TRUE if a string has been read and returned, or FALSE otherwise.
-------------------------------------------------------------------------- }
function tipcPipeEndpoint.GetString(var s: ansistring): boolean;
var
sLength: longint;
begin
result := false;
if not self.PeekPacketLength(sLength) then exit;
setlength(s, sLength);
result := self.GetPacket(sLength, s[1]);
end;
{ --------------------------------------------------------------------------
Check for received packets on this pipe endpoint.
The application must call Pump regularly otherwise the sender may block.
Returns TRUE if a message is ready to read with GetPacket, or FALSE if
there are no messages to read.
If a message is available but the application does not read it before
calling Pump again, the received message may be lost.
-------------------------------------------------------------------------- }
function tipcPipeEndpoint.Pump: boolean;
const
funcname = 'tipcPipeEndpoint.Pump: ';
var
bytesToRead: longint;
bytesRead: longint;
bytesAvailable: longint;
begin
result := false;
{ Need to get the packet length? }
if not gotLength then begin
{ Try to get it, drop out if we cannot read it in one go }
bytesAvailable := self.readPipe.NumBytesAvailable;
if bytesAvailable < sizeof(longint) then exit;
bytesRead := self.readPipe.Read(self.packetLength, sizeof(longint));
if bytesRead <> sizeof(longint) then begin
{ This should never happen }
raise exception.create(funcname + 'bytesRead <> sizeof(longint)');
exit;
end;
self.gotLength := true;
self.readPos := 0;
{ Zero byte packets don't need to be processed further }
if self.packetLength = 0 then begin
self.tempLength := self.packetLength;
self.packetReady := true;
self.gotLength := false;
result := true;
exit;
end;
end;
{ Reading the packet body }
bytesToRead := self.packetLength - self.readPos;
bytesAvailable := self.readPipe.NumBytesAvailable;
if bytesAvailable < bytesToRead then bytesToRead := bytesAvailable;
bytesRead := self.readPipe.Read(self.readBuffer^[self.readPos], bytesToRead);
inc(self.readPos, bytesRead);
{ If we have a full packet, make it available to read }
if self.readPos >= self.packetLength then begin
move(self.readBuffer^, self.tempBuffer^, self.packetLength);
self.tempLength := self.packetLength;
self.packetReady := true;
self.gotLength := false;
result := true;
exit;
end;
{ We don't read messages in a loop, the app needs a chance to process them }
end;
{ --------------------------------------------------------------------------
Retrieve a packet from the pipe.
For this to work, a packet must have been prepared by a call to <Pump>.
Returns TRUE if a packet has been read and returned, or FALSE otherwise.
-------------------------------------------------------------------------- }
function tipcPipeEndpoint.GetPacket(var size: longint; var data): boolean;
begin
result := false;
if not self.packetReady then exit;
size := self.tempLength;
if size <> 0 then begin
move(self.tempBuffer^, data, size);
end;
self.packetReady := false;
result := true;
end;
{ --------------------------------------------------------------------------
Peek the length of a packet waiting to be read.
For this to work, a packet must have been prepared by a call to <Pump>.
Returns TRUE if a packet can be read and <size> is valid, or FALSE otherwise.
-------------------------------------------------------------------------- }
function tipcPipeEndpoint.PeekPacketLength(var size: longint): boolean;
begin
result := false;
if not self.packetReady then exit;
size := self.tempLength;
result := true;
end;
{ --------------------------------------------------------------------------
Send a packet of data down the pipe.
<data> is the packet data, of length <size>.
Packet data must be at most IPCPIPE_MAX_PACKET_SIZE bytes long.
TRUE is returned on success, and FALSE is returned on failure.
-------------------------------------------------------------------------- }
function tipcPipeEndpoint.SendPacket(size: longint; const data): boolean;
var
bytes: pByteArray;
begin
result := false;
if (size > IPCPIPE_MAX_PACKET_SIZE) then exit;
bytes := pByteArray(@data);
move(size, self.workBuffer^[0], sizeof(longint));
if size > 0 then begin
move(bytes^[0], self.workBuffer^[sizeof(longint)], size);
end;
self.writePipe.Write(self.workBuffer^[0], size + sizeof(longint));
result := true;
end;
{ --------------------------------------------------------------------------
Return the parent endpoint.
Returns NIL if the endpoint has not been created.
-------------------------------------------------------------------------- }
function tipcPipe.GetParentEndpoint: tipcPipeEndpoint;
begin
result := self.parentEndpoint;
end;
{ --------------------------------------------------------------------------
Return the child endpoint.
Returns NIL if the endpoint has not been created.
-------------------------------------------------------------------------- }
function tipcPipe.GetChildEndpoint: tipcPipeEndpoint;
begin
result := self.childEndpoint;
end;
{ --------------------------------------------------------------------------
Create a pair of pipe endpoints.
Returns TRUE on success, or FALSE on failure.
This is a seperate function because it is bad design to throw exceptions
inside the constructor (which would have been the alternative way of
handling a failure). Failure should only happen if the OS has run out of
resources (file handles?).
-------------------------------------------------------------------------- }
function tipcPipe.CreateEndpoints: boolean;
var
parentPipeRead, childPipeRead: TInputPipeStream;
parentPipeWrite, childPipeWrite: TOutputPipeStream;
begin
result := false;
{ Can't create endpoints if we already did earlier }
if assigned(self.parentEndpoint) or assigned(self.childEndpoint) then exit;
{ Get some plumbing for those endpoints. We create a pair of pipes, because
each pipe only allows data to be sent in one direction }
try
parentPipeRead := nil;
parentPipeWrite := nil;
childPipeRead := nil;
childPipeWrite := nil;
CreatePipeStreams(parentPipeRead, childPipeWrite);
CreatePipeStreams(childPipeRead, parentPipeWrite);
except
on e: exception do begin
if assigned(parentPipeRead) then parentPipeRead.Destroy;
if assigned(parentPipeWrite) then parentPipeRead.Destroy;
if assigned(childPipeRead) then childPipeRead.Destroy;
if assigned(childPipeWrite) then childPipeWrite.Destroy;
exit;
end;
end;
{ Build endpoints for the pipes }
self.parentEndPoint := tipcPipeEndpoint.Create(parentPipeRead, parentPipeWrite);
self.childEndPoint := tipcPipeEndpoint.Create(childPipeRead, childPipeWrite);
result := true;
end;
{ --------------------------------------------------------------------------
tipcPipe constructor
-------------------------------------------------------------------------- }
constructor tipcPipe.Create;
begin
inherited Create;
self.parentEndpoint := nil;
self.childEndpoint := nil;
end;
{ --------------------------------------------------------------------------
tipcPipe destructor
CAUTION: This also destroys the pipe endpoint object pair
-------------------------------------------------------------------------- }
destructor tipcPipe.Destroy;
begin
if assigned(self.parentEndpoint) then begin
self.parentEndpoint.Destroy;
self.parentEndpoint := nil;
end;
if assigned(self.childEndpoint) then begin
self.childEndpoint.Destroy;
self.childEndpoint := nil;
end;
inherited Destroy;
end;
{ --------------------------------------------------------------------------
tipcPipe constructor
The user should never create these objects.
-------------------------------------------------------------------------- }
constructor tipcPipeEndpoint.Create(rPipe: TInputPipeStream; wPipe: TOutputPipeStream);
const
funcname = 'tipcPipeEndpoint.Create: ';
begin
inherited Create;
if (not assigned(rPipe)) or (not assigned(wPipe)) then begin
raise exception.create(funcname + 'Pipes not assigned');
exit;
end;
self.readPipe := rPipe;
self.writePipe := wPipe;
try
getmem(self.readBuffer, IPCPIPE_MAX_MESSAGE_SIZE);
getmem(self.workBuffer, IPCPIPE_MAX_MESSAGE_SIZE);
getmem(self.tempBuffer, IPCPIPE_MAX_MESSAGE_SIZE);
except
on e: exception do begin
raise exception.create(funcname + 'Out of memory allocating buffers');
end;
end;
{ Prepare the packet parser }
readPos := 0;
gotLength := false;
packetLength := 0;
packetReady := false;
end;
{ --------------------------------------------------------------------------
tipcPipe destructor
The user should never destroy these objects.
-------------------------------------------------------------------------- }
destructor tipcPipeEndpoint.Destroy;
begin
if assigned(self.readPipe) then self.readPipe.Destroy;
if assigned(self.writePipe) then self.writePipe.Destroy;
if assigned(self.readBuffer) then freemem(self.readBuffer);
if assigned(self.workBuffer) then freemem(self.workBuffer);
if assigned(self.tempBuffer) then freemem(self.tempBuffer);
inherited Destroy;
end;
end.