Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GetOverlappedResult implementation #115

Merged
merged 12 commits into from
Aug 1, 2016
Merged
14 changes: 14 additions & 0 deletions pywincffi/core/cdefs/headers/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,20 @@ BOOL WINAPI CreateProcess(
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);


///////////////////////
// Overlapped
///////////////////////

// https://msdn.microsoft.com/en-us/ms683209
BOOL WINAPI GetOverlappedResult(
_In_ HANDLE hFile,
_In_ LPOVERLAPPED lpOverlapped,
_Out_ LPDWORD lpNumberOfBytesTransferred,
_In_ BOOL bWait
);


// Used internally to reset the last error to 0
// in cases where pywincffi is the cause of the
// error and we choose to ignore the error.
Expand Down
1 change: 1 addition & 0 deletions pywincffi/kernel32/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@
from pywincffi.kernel32.events import CreateEvent, OpenEvent, ResetEvent
from pywincffi.kernel32.comms import ClearCommError
from pywincffi.kernel32.synchronization import WaitForSingleObject
from pywincffi.kernel32.overlapped import GetOverlappedResult
3 changes: 2 additions & 1 deletion pywincffi/kernel32/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ def WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite=None, lpOverlapped=None):
wintype_to_cdata(hFile), lpBuffer, nNumberOfBytesToWrite,
bytes_written, wintype_to_cdata(lpOverlapped)
)
error_check("WriteFile", code=code, expected=NON_ZERO)
expected = NON_ZERO if lpOverlapped is None else 0
error_check("WriteFile", code=code, expected=expected)

return bytes_written[0]

Expand Down
71 changes: 71 additions & 0 deletions pywincffi/kernel32/overlapped.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Overlapped
----------

A module containing Windows functions for working with OVERLAPPED objects.
"""

from pywincffi.core import dist
from pywincffi.core.checks import NON_ZERO, input_check, error_check
from pywincffi.exceptions import WindowsAPIError
from pywincffi.wintypes import HANDLE, OVERLAPPED, wintype_to_cdata


def GetOverlappedResult(hFile, lpOverlapped, bWait):
"""
Retrieves the results of an overlapped operation on the specified file,
named pipe, or communications device. To specify a timeout interval or
wait on an alertable thread, use GetOverlappedResultEx.

.. seealso::

https://msdn.microsoft.com/en-us/library/ms683209

:param pywincffi.wintypes.HANDLE hFile:
A handle to the file, named pipe, or communications device.
This is the same handle that was specified when the overlapped
operation was started by a call to the ReadFile, WriteFile,
ConnectNamedPipe, TransactNamedPipe, DeviceIoControl, or WaitCommEvent
function.

:param pywincffi.wintypes.OVERLAPPED lpOverlapped:
The an OVERLAPPED object that was specified when the overlapped
operation was started

:param bool bWait:
If this parameter is TRUE, and the Internal member of the lpOverlapped
structure is STATUS_PENDING, the function does not return until the
operation has been completed. If this parameter is FALSE and the
operation is still pending, the function returns FALSE and the
GetLastError function returns ERROR_IO_INCOMPLETE

:returns:
The number of bytes that were actually transferred by a read or write
operation. For a TransactNamedPipe operation, this is the number of
bytes that were read from the pipe. For a DeviceIoControl operation,
this is the number of bytes of output data returned by the device
driver. For a ConnectNamedPipe or WaitCommEvent operation, this value
is undefined.
"""
input_check("hFile", hFile, HANDLE)
input_check("lpOverlapped", lpOverlapped, OVERLAPPED)
input_check("bWait", bWait, allowed_values=(True, False))

ffi, library = dist.load()

lpNumberOfBytesTransferred = ffi.new("DWORD[1]")

result = library.GetOverlappedResult(
wintype_to_cdata(hFile),
wintype_to_cdata(lpOverlapped),
lpNumberOfBytesTransferred,
ffi.cast("BOOL", bWait),
)

try:
error_check("GetOverlappedResult", result, NON_ZERO)
except WindowsAPIError as error:
if error.errno != library.ERROR_ALREADY_EXISTS:
Copy link
Owner

@opalmer opalmer Jul 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not checked the tests yet, but you might be missing coverage here. The reason I think this might be the case is because there's a cleanup step in the tests which calls GetLastError() to see there are any unhandled errors that need to be reset:

https://github.com/opalmer/pywincffi/blob/master/pywincffi/dev/testutil.py#L222

Looking at your test results, that check does not appear to be failing.

And of course had I read your description first before doing the code review, I would have seen this was in fact covered. Sorry!

raise

return int(lpNumberOfBytesTransferred[0])
70 changes: 70 additions & 0 deletions tests/test_kernel32/test_overlapped.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os
import shutil
import tempfile

from six import text_type

from pywincffi.dev.testutil import TestCase

from pywincffi.core import dist

from pywincffi.kernel32 import (
CreateFile, WriteFile, CloseHandle, CreateEvent, GetOverlappedResult)
from pywincffi.wintypes import OVERLAPPED


class TestOverlappedWriteFile(TestCase):
"""
Tests for :func:`pywincffi.kernel32.GetOverlappedResult`
"""
def test_overlapped_write_file(self):
# Test outline:
# - Create a temp dir.
# - CreateFile for writing with FILE_FLAG_OVERLAPPED.
# - WriteFile in overlapped mode.
# - Use GetOverlappedResult to wait for IO completion.

temp_dir = tempfile.mkdtemp(prefix="pywincffi-test-ovr-")
self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)

filename = text_type(os.path.join(temp_dir, "overlapped-write-file"))
file_contents = b"hello overlapped world"

_, lib = dist.load()
handle = CreateFile(
lpFileName=filename,
dwDesiredAccess=lib.GENERIC_WRITE,
dwCreationDisposition=lib.CREATE_NEW,
dwFlagsAndAttributes=lib.FILE_FLAG_OVERLAPPED,
)

# Prepare overlapped write
ovr = OVERLAPPED()
ovr.hEvent = CreateEvent(bManualReset=True, bInitialState=False)

# Go for overlapped WriteFile. Should result in:
# - num_bytes_written == 0
# - GetLastError() == ERROR_IO_PENDING

# HOWEVER, https://msdn.microsoft.com/en-us/library/aa365683 states:
# "Further, the WriteFile function will sometimes return TRUE with a
# GetLastError value of ERROR_SUCCESS, even though it is using an
# asynchronous handle (which can also return FALSE with
# ERROR_IO_PENDING).
# Test strategy:
# - Disregard WriteFile return result.
# - Assert GetLastError is either ERROR_IO_PENDING or ERROR_SUCCESS.
# - Later validate that the correct number of bytes was written.

_ = WriteFile(handle, file_contents, lpOverlapped=ovr)
error_code, _ = self.GetLastError()
self.assertIn(error_code, (lib.ERROR_IO_PENDING, 0))
Copy link
Owner

@opalmer opalmer Jul 31, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, I merged some code yesterday you could use here:

https://github.com/opalmer/pywincffi/blob/master/pywincffi/dev/testutil.py#L295

Basically, replace assertIn and SetLastError with:

self.maybe_assert_last_error(lib.ERROR_IO_PENDING)


# Reset last error so that TestCase cleanups don't error out.
self.SetLastError(0)

# Block until async write is completed.
num_bytes_written = GetOverlappedResult(handle, ovr, bWait=True)
self.assertEqual(num_bytes_written, len(file_contents))

CloseHandle(handle)