-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathPJPipe.pas
More file actions
380 lines (346 loc) · 13 KB
/
Copy pathPJPipe.pas
File metadata and controls
380 lines (346 loc) · 13 KB
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
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
{
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/
*
* Copyright (C) 2007-2014, Peter Johnson (www.delphidabbler.com).
*
* Class that encapsulates an unnamed pipe and can read and write the pipe.
}
unit PJPipe;
{$UNDEF COMPILERSUPPORTED}
{$UNDEF STRICT}
{$UNDEF RTLNAMESPACES}
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24.0} // Delphi XE3 and later
{$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives
{$IFEND}
{$IF CompilerVersion >= 23.0} // Delphi XE2 ad later
{$DEFINE RTLNAMESPACES}
{$IFEND}
{$IF CompilerVersion >= 17.0} // Delphi 2005 and later
{$DEFINE STRICT}
{$IFEND}
{$IF CompilerVersion >= 15.0} // Delphi 7 and later
{$DEFINE COMPILERSUPPORTED}
{$IFEND}
{$ENDIF}
{$IFNDEF COMPILERSUPPORTED}
{$MESSAGE FATAL 'Minimum compiler version is Delphi 7'}
{$ENDIF}
{$WARN UNSAFE_CODE OFF}
interface
uses
// Delphi
{$IFNDEF RTLNAMESPACES}
SysUtils, Classes, Windows;
{$ELSE}
System.SysUtils, System.Classes, Winapi.Windows;
{$ENDIF}
// Ensure TBytes is defined
{$IF not Declared(TBytes)}
type
TBytes = array of Byte;
{$IFEND}
type
/// <summary>
/// Class that encapsulates an unamed pipe and can read and write the pipe.
/// </summary>
TPJPipe = class(TObject)
{$IFDEF STRICT}strict{$ENDIF}
private
/// Handle used to read the pipe.
fReadHandle: THandle;
/// Handle used to write the pipe.
fWriteHandle: THandle;
/// <summary>Checks that the pipe's write handle hasn't been closed.
/// </summary>
/// <remarks>Raises EInOurError if handle is 0.</remarks>
procedure CheckWriteHandle;
/// <summary>Creates a pipe using Windows API and records pipe handles.
/// </summary>
/// <param name="Size">Longword [in] Size of pipe. If 0 the default pipe
/// size is used.</param>
/// <param name="Security">PSecurityAttributes [in] Pointer to security
/// structure. May be nil.</param>
/// <remarks>Raises EInOutError if pipe can't be created.</remarks>
procedure CreatePipe(const Size: LongWord;
const Security: PSecurityAttributes);
public
/// <summary>Object constructor. Creates a pipe with specified size.
/// </summary>
/// <param name="Size">LongWord [in] Required size of pipe. If Size is 0
/// then default pipe size used.</param>
/// <param name="Inheritable">Boolean [in] Indicates whether pipe handles
/// are to be inheritable.</param>
/// <remarks>Raises EInOutError if pipe can't be created.</remarks>
constructor Create(const Size: LongWord; const Inheritable: Boolean = True);
overload;
/// <summary>Object constructor. Creates a pipe object with default size.
/// </summary>
/// <param name="Inheritable">Boolean [in] Indicates whether pipe handles
/// are to be inheritable.</param>
/// <remarks>Raises EInOutError if pipe can't be created.</remarks>
constructor Create(const Inheritable: Boolean = True);
overload;
/// <summary>Object constructor. Creates a pipe object with specified size
/// and security.</summary>
/// <param name="Size">LongWord [in] Required size of pipe. If Size is 0
/// then default pipe size used.</param>
/// <param name="Security">TSecurityAttributes [in] Required security for
/// pipe. If handles to be inheritable set the bInheritHandle field to
/// true.</param>
/// <remarks>Raises EInOutError if pipe can't be created.</remarks>
constructor Create(const Size: LongWord;
const Security: TSecurityAttributes);
overload;
/// <summary>Object constructor. Creates a pipe object with default size
/// pipe and specified security.</summary>
/// <param name="Security">TSecurityAttributes [in] Required security for
/// pipe. If handles to be inheritable set the bInheritHandle field to
/// true.</param>
/// <remarks>Raises EInOutError if pipe can't be created.</remarks>
constructor Create(const Security: TSecurityAttributes);
overload;
/// <summary>Object destructor. Tears down object.</summary>
destructor Destroy; override;
/// <summary>Returns size of data, in bytes, available for reading from
/// pipe.</summary>
/// <remarks>EInOutError raised if there is an error peeking pipe.
/// </remarks>
function AvailableDataSize: LongWord;
/// <summary>Reads data from pipe into a buffer.</summary>
/// <param name="Buf">Untyped [out] Buffer that receives data. Must have
/// capacity of at least BufSize bytes.</param>
/// <param name="BufSize">LongWord [in] Size of buffer or number of bytes
/// requested.</param>
/// <param name="BytesRead">LongWord [out] Receives number of bytes
/// actually read.</param>
/// <returns>True if some data was read, false if not.</returns>
/// <remarks>EInOutError raised if there is an error peeking or reading
/// pipe.</remarks>
function ReadData(out Buf; const BufSize: LongWord;
out BytesRead: LongWord): Boolean;
/// <summary>Reads data from pipe into a byte array.</summary>
/// <param name="Count">LongWord [in] Number of bytes to read from pipe. If
/// Count = 0 or greater than number of available bytes then all the data
/// from the pipe is read.</param>
/// <returns>Byte array containing data.</returns>
/// <remarks>EInOutError raised if there is an error peeking or reading
/// pipe.</remarks>
function ReadBytes(const Count: LongWord = 0): TBytes;
/// <summary>Copies data from pipe to a stream.</summary>
/// <param name="Stm">TStream [in] Stream that receives data.</param>
/// <param name="Count">LongWord [in] Number of bytes to copy. If 0 then
/// all remaining data in pipe is copied to stream.</param>
/// <remarks>
/// <para>EInOutError raised if there is an error peeking or reading pipe.
/// </para>
/// <para>EWriteError raised if fail to write to stream.</para>
/// </remarks>
procedure CopyToStream(const Stm: TStream; Count: LongWord = 0);
/// <summary>Copies data from a stream into the pipe.</summary>
/// <param name="Stm">TStream [in] Stream from which to copy data.</param>
/// <param name="Count">LongWord [in] Number of bytes to copy. If 0 then
/// all remaining data in stream is copied.</param>
/// <remarks>
/// <para>EInOutError raised if pipe's write handle has been closed or if
/// can't write to pipe.</para>
/// <para>EReadError raised if fail to read from stream.</para>
/// </remarks>
procedure CopyFromStream(const Stm: TStream; Count: LongWord = 0);
/// <summary>Writes the whole content of a byte array to pipe.</summary>
/// <param name="Bytes">TBytes [in] Array of bytes to write to pipe.
/// </param>
/// <remarks>EInOutError raised if pipe's write handle has been closed or
/// if can't write to pipe.</remarks>
procedure WriteBytes(const Bytes: TBytes);
/// <summary>Writes data from buffer to pipe.</summary>
/// <param name="Buf">Untyped [in] Buffer containing data to be written.
/// </param>
/// <param name="BufSize">LongWord [in] Number of bytes to write from
/// buffer. Buf must have capacity of at least BufSize bytes.</param>
/// <returns>Number of bytes actually written.</returns>
/// <remarks>EInOutError raised if pipe's write handle has been closed or
/// if can't write to pipe.</remarks>
function WriteData(const Buf; const BufSize: LongWord): LongWord;
/// <summary>Closes the pipe's write handle if it is open.</summary>
/// <remarks>Closing the write handle effectively signals EOF to any reader
/// of the pipe. After calling this method no further data may be written
/// to the pipe.</remarks>
procedure CloseWriteHandle;
/// <summary>Handle used to read data from the pipe. Should be non-zero.
/// </summary>
property ReadHandle: THandle read fReadHandle;
/// <summary>Handle used to write data to the pipe. Should not be used
/// when 0.</summary>
/// <remarks>CloseWriteHandle closes and zeros this handle.</remarks>
property WriteHandle: THandle read fWriteHandle;
end;
implementation
resourcestring
// Error messages
sCantCreatePipe = 'Can''t create pipe: %s';
sCantPeekPipe = 'Can''t read pipe: peek attempt failed';
sPipeReadError = 'Error reading pipe';
sBadWriteHandle = 'Can''t write pipe: handle closed';
sPipeWriteError = 'Error writing pipe';
{ TPJPipe }
function TPJPipe.AvailableDataSize: LongWord;
begin
if not PeekNamedPipe(fReadHandle, nil, 0, nil, @Result, nil) then
raise EInOutError.Create(sCantPeekPipe);
end;
procedure TPJPipe.CheckWriteHandle;
begin
if fWriteHandle = 0 then
raise EInOutError.Create(sBadWriteHandle);
end;
procedure TPJPipe.CloseWriteHandle;
begin
if fWriteHandle <> 0 then
begin
CloseHandle(fWriteHandle);
fWriteHandle := 0;
end;
end;
procedure TPJPipe.CopyFromStream(const Stm: TStream; Count: LongWord);
var
BytesToWrite: LongWord; // adjusted number of bytes to write
Buf: array[0..4095] of Byte; // buffer used in copying from pipe to stream
begin
// Determine how much to copy
if Count = 0 then
Count := Stm.Size - Stm.Position;
// Copy data one bufferful at a time
while Count > 0 do
begin
if Count > SizeOf(Buf) then
BytesToWrite := SizeOf(Buf)
else
BytesToWrite := Count;
Stm.ReadBuffer(Buf, BytesToWrite);
WriteData(Buf, BytesToWrite);
Dec(Count, BytesToWrite);
end;
end;
procedure TPJPipe.CopyToStream(const Stm: TStream; Count: LongWord);
var
AvailBytes: LongWord; // number of bytes in pipe
BytesToRead: LongWord; // decreasing count of remaining bytes
BytesRead: LongWord; // bytes read in each loop
Buf: array[0..4095] of Byte; // buffer used to read from stream
begin
// Determine how much should be read
AvailBytes := AvailableDataSize;
if (Count = 0) or (Count > AvailBytes) then
Count := AvailBytes;
// Copy data one bufferful at a time
while Count > 0 do
begin
if Count > SizeOf(Buf) then
BytesToRead := SizeOf(Buf)
else
BytesToRead := Count;
ReadData(Buf, BytesToRead, BytesRead);
if BytesRead <> BytesToRead then
raise EInOutError.Create(sPipeReadError);
Stm.WriteBuffer(Buf, BytesRead);
Dec(Count, BytesRead);
end;
end;
constructor TPJPipe.Create(const Size: LongWord; const Inheritable: Boolean);
var
Security: TSecurityAttributes; // file's security attributes
begin
inherited Create;
if Inheritable then
begin
// Set up security structure so file handle is inheritable (for Windows NT)
Security.nLength := SizeOf(Security);
Security.lpSecurityDescriptor := nil;
Security.bInheritHandle := True;
CreatePipe(Size, @Security);
end
else
CreatePipe(Size, nil);
end;
constructor TPJPipe.Create(const Size: LongWord;
const Security: TSecurityAttributes);
begin
inherited Create;
CreatePipe(Size, @Security);
end;
constructor TPJPipe.Create(const Security: TSecurityAttributes);
begin
Create(0, Security);
end;
constructor TPJPipe.Create(const Inheritable: Boolean);
begin
Create(0, Inheritable);
end;
procedure TPJPipe.CreatePipe(const Size: LongWord;
const Security: PSecurityAttributes);
begin
{$IFNDEF RTLNAMESPACES}
if not Windows.CreatePipe(fReadHandle, fWriteHandle, Security, Size) then
{$ELSE}
if not Winapi.Windows.CreatePipe(
fReadHandle, fWriteHandle, Security, Size
) then
{$ENDIF}
raise EInOutError.CreateFmt(
sCantCreatePipe, [SysErrorMessage(GetLastError)]
);
end;
destructor TPJPipe.Destroy;
begin
CloseHandle(fReadHandle);
CloseWriteHandle;
inherited;
end;
function TPJPipe.ReadBytes(const Count: LongWord): TBytes;
var
AvailBytes: LongWord; // number of bytes in pipe
BytesRead: LongWord; // number of bytes actually read from pipe
begin
AvailBytes := AvailableDataSize;
if (Count = 0) or (Count > AvailBytes) then
SetLength(Result, AvailBytes)
else
SetLength(Result, Count);
ReadData(Pointer(Result)^, Length(Result), BytesRead);
if BytesRead <> LongWord(Length(Result)) then
raise EInOutError.Create(sPipeReadError);
end;
function TPJPipe.ReadData(out Buf; const BufSize: LongWord;
out BytesRead: LongWord): Boolean;
var
BytesToRead: DWORD; // number of bytes to actually read
begin
BytesToRead := AvailableDataSize;
if BytesToRead > 0 then
begin
if BytesToRead > BufSize then
BytesToRead := BufSize;
// we don't check return value: sometimes fails with BytesRead = 0
ReadFile(fReadHandle, Buf, BytesToRead, BytesRead, nil);
Result := BytesRead > 0;
end
else
begin
Result := False;
BytesRead := 0;
end;
end;
procedure TPJPipe.WriteBytes(const Bytes: TBytes);
begin
WriteData(Pointer(Bytes)^, Length(Bytes));
end;
function TPJPipe.WriteData(const Buf; const BufSize: LongWord): LongWord;
begin
CheckWriteHandle;
if not WriteFile(fWriteHandle, Buf, BufSize, Result, nil) then
raise EInOutError.Create(sPipeWriteError);
end;
end.