I believe the canonical implementation reads like this (in Delphi):
// Returns when <BytesToRead> bytes are read into <Buffer> or when the timer <TimeoutTimer> expires,
// whatever comes first. The number of bytes read is returned.
// I/O errors are reported as EOSError exception.
function TTestObj.Read(TimeoutTimer: TWaitableTimer; out Buffer; BytesToRead: cardinal): cardinal;
var
Handles: array [0..1] of THandle;
begin
// ReadFile can complete immediately:
if not Windows.ReadFile(FHandle, Buffer, BytesToRead, Result, @FOverlapped) then begin
Win32Check(Windows.GetLastError = ERROR_IO_PENDING, 'ReadFile');
// ReadFile will complete asynchronous => wait for it with a timeout:
Handles[0] := FOverlapped.hEvent;
Handles[1] := TimeoutTimer.Handle;
case Windows.WaitForMultipleObjects(length(Handles), pointer(@Handles), false, INFINITE) of
WAIT_OBJECT_0 + 0: // ReadFile is complete
{do nothing};
WAIT_OBJECT_0 + 1: // timeout is expired => terminate the ReadFile operation:
Win32Check( Windows.CancelIo(FHandle), 'CancelIo');
else
RaiseWin32Error(Windows.GetLastError, 'WaitForMultipleObjects');
end;
// Get the result of the ReadFile operation. ERROR_OPERATION_ABORTED means CancelIO was called, but even in this case,
// we want to report the number of bytes read:
if not Windows.GetOverlappedResult(FHandle, FOverlapped, Result, false) and (Windows.GetLastError <> ERROR_OPERATION_ABORTED) then
Win32Check(false, 'GetOverlappedResult/ReadFile');
end;
end;
FHandle and FOverlapped are instance members. Win32Check() raises the EOSError exception when the condition (the first argument) is not true. <Result> is the implicit variable that represents the return value of the function.
I use this code in a productive application without problems.