From fcadc5ec1837c5466360c8730427a3b1b8aecc9f Mon Sep 17 00:00:00 2001 From: opalmer Date: Sun, 1 May 2016 13:19:23 -0400 Subject: [PATCH] Implement MsgWaitForMultipleObjects (#80) --- docs/source/changelog.rst | 1 + pywincffi/core/cdefs/headers/constants.h | 18 +++++ pywincffi/core/cdefs/headers/functions.h | 1 + pywincffi/core/dist.py | 7 +- pywincffi/kernel32/__init__.py | 5 +- pywincffi/kernel32/handle.py | 35 +------- pywincffi/kernel32/process.py | 3 +- pywincffi/kernel32/synchronization.py | 51 ++++++++++++ pywincffi/user32/__init__.py | 9 +++ pywincffi/user32/synchronization.py | 90 +++++++++++++++++++++ tests/test_core/test_dist.py | 8 +- tests/test_kernel32/test_handle.py | 50 +----------- tests/test_kernel32/test_synchronization.py | 47 +++++++++++ tests/test_user32/__init__.py | 0 tests/test_user32/test_synchronization.py | 65 +++++++++++++++ 15 files changed, 300 insertions(+), 90 deletions(-) create mode 100644 pywincffi/kernel32/synchronization.py create mode 100644 pywincffi/user32/__init__.py create mode 100644 pywincffi/user32/synchronization.py create mode 100644 tests/test_kernel32/test_synchronization.py create mode 100644 tests/test_user32/__init__.py create mode 100644 tests/test_user32/test_synchronization.py diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 81cb72b..e8febbb 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -23,6 +23,7 @@ Notable enhancements and changes are: * :issue:`76` - :func:`pywincffi.kernel32.process.TerminateProcess` * :issue:`78` - :func:`pywincffi.kernel32.handle.DuplicateHandle` * :issue:`79` - :func:`pywincffi.kernel32.process.ClearCommError` + * :issue:`80` - :func:`pywincffi.user32.synchronization.MsgWaitForMultipleObjects` 0.2.0 ~~~~~ diff --git a/pywincffi/core/cdefs/headers/constants.h b/pywincffi/core/cdefs/headers/constants.h index 2dab87b..11ddbbc 100644 --- a/pywincffi/core/cdefs/headers/constants.h +++ b/pywincffi/core/cdefs/headers/constants.h @@ -50,6 +50,7 @@ // Return values of WaitForSingleObject // https://msdn.microsoft.com/en-us/library/ms687032 #define WAIT_ABANDONED ... +#define WAIT_ABANDONED_0 ... #define WAIT_OBJECT_0 ... #define WAIT_TIMEOUT ... #define WAIT_FAILED ... @@ -199,6 +200,23 @@ #define CE_PTO ... #define CE_TXFULL ... +// MsgWaitForMultipleObjects +#define MAXIMUM_WAIT_OBJECTS ... +#define QS_ALLEVENTS ... +#define QS_ALLINPUT ... +#define QS_ALLPOSTMESSAGE ... +#define QS_HOTKEY ... +#define QS_INPUT ... +#define QS_KEY ... +#define QS_MOUSE ... +#define QS_MOUSEBUTTON ... +#define QS_MOUSEMOVE ... +#define QS_PAINT ... +#define QS_POSTMESSAGE ... +#define QS_RAWINPUT ... +#define QS_SENDMESSAGE ... +#define QS_TIMER ... + // For the moment, we can't define this here. When cffi // parses the header this returns -1 and cffi seems to // only handle positive integers right now. diff --git a/pywincffi/core/cdefs/headers/functions.h b/pywincffi/core/cdefs/headers/functions.h index edc839f..70069fe 100644 --- a/pywincffi/core/cdefs/headers/functions.h +++ b/pywincffi/core/cdefs/headers/functions.h @@ -37,6 +37,7 @@ DWORD WaitForSingleObject(HANDLE, DWORD); BOOL GetHandleInformation(HANDLE, LPDWORD); BOOL SetHandleInformation(HANDLE, DWORD, DWORD); BOOL DuplicateHandle(HANDLE, HANDLE, HANDLE, LPHANDLE, DWORD, BOOL, DWORD); +DWORD MsgWaitForMultipleObjects(DWORD, HANDLE, BOOL, DWORD, DWORD); // Events HANDLE CreateEvent(LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCTSTR); diff --git a/pywincffi/core/dist.py b/pywincffi/core/dist.py index 70f1c35..7e46d9c 100644 --- a/pywincffi/core/dist.py +++ b/pywincffi/core/dist.py @@ -57,6 +57,7 @@ SOURCE_FILES = ( resource_filename( "pywincffi", join("core", "cdefs", "sources", "main.c")), ) +LIBRARIES = ("kernel32", "user32") class Module(object): # pylint: disable=too-few-public-methods @@ -144,7 +145,9 @@ def _read(*paths): return output -def _ffi(module_name=MODULE_NAME, headers=HEADER_FILES, sources=SOURCE_FILES): +def _ffi( + module_name=MODULE_NAME, headers=HEADER_FILES, sources=SOURCE_FILES, + libraries=LIBRARIES): """ Returns an instance of :class:`FFI` without compiling the module. This function is used internally but also @@ -168,7 +171,7 @@ def _ffi(module_name=MODULE_NAME, headers=HEADER_FILES, sources=SOURCE_FILES): ffi = FFI() ffi.set_unicode(True) - ffi.set_source(module_name, source) + ffi.set_source(module_name, source, libraries=libraries) ffi.cdef(header) return ffi diff --git a/pywincffi/kernel32/__init__.py b/pywincffi/kernel32/__init__.py index 71411d0..8d9992b 100644 --- a/pywincffi/kernel32/__init__.py +++ b/pywincffi/kernel32/__init__.py @@ -13,8 +13,8 @@ ReadFile, WriteFile, FlushFileBuffers, MoveFileEx, CreateFile, LockFileEx, UnlockFileEx) from pywincffi.kernel32.handle import ( - CloseHandle, GetStdHandle, WaitForSingleObject, handle_from_file, - GetHandleInformation, SetHandleInformation, DuplicateHandle) + CloseHandle, GetStdHandle, GetHandleInformation, SetHandleInformation, + DuplicateHandle, handle_from_file) from pywincffi.kernel32.pipe import ( CreatePipe, PeekNamedPipe, PeekNamedPipeResult, SetNamedPipeHandleState) from pywincffi.kernel32.process import ( @@ -22,3 +22,4 @@ pid_exists, TerminateProcess) from pywincffi.kernel32.events import CreateEvent, OpenEvent, ResetEvent from pywincffi.kernel32.comms import ClearCommError +from pywincffi.kernel32.synchronization import WaitForSingleObject diff --git a/pywincffi/kernel32/handle.py b/pywincffi/kernel32/handle.py index bb2cd59..6a1f9ff 100644 --- a/pywincffi/kernel32/handle.py +++ b/pywincffi/kernel32/handle.py @@ -3,7 +3,7 @@ ------- A module containing general functions for working with handle -objects. +objects. The functions provided here are part of the ``kernel32`` library. """ from six import integer_types @@ -89,39 +89,6 @@ def CloseHandle(hObject): error_check("CloseHandle", code=code, expected=Enums.NON_ZERO) -def WaitForSingleObject(hHandle, dwMilliseconds): - """ - Waits for the specified object to be in a signaled state - or for ``dwMiliseconds`` to elapse. - - .. seealso:: - - https://msdn.microsoft.com/en-us/library/ms687032 - - :param handle hHandle: - The handle to wait on. - - :param int dwMilliseconds: - The time-out interval. - """ - input_check("hHandle", hHandle, Enums.HANDLE) - input_check("dwMilliseconds", dwMilliseconds, integer_types) - - ffi, library = dist.load() - result = library.WaitForSingleObject( - hHandle, ffi.cast("DWORD", dwMilliseconds) - ) - - if result == library.WAIT_FAILED: - raise WindowsAPIError( - "WaitForSingleObject", "Wait Failed", ffi.getwinerror()[-1], - return_code=result, expected_return_code="not %s" % result) - - error_check("WaitForSingleObject") - - return result - - def GetHandleInformation(hObject): """ Returns properties of an object handle. diff --git a/pywincffi/kernel32/process.py b/pywincffi/kernel32/process.py index e8dc801..308cbc9 100644 --- a/pywincffi/kernel32/process.py +++ b/pywincffi/kernel32/process.py @@ -20,7 +20,8 @@ from pywincffi.core import dist from pywincffi.core.checks import Enums, input_check, error_check from pywincffi.exceptions import WindowsAPIError, PyWinCFFINotImplementedError -from pywincffi.kernel32.handle import CloseHandle, WaitForSingleObject +from pywincffi.kernel32.handle import CloseHandle +from pywincffi.kernel32.synchronization import WaitForSingleObject RESERVED_PIDS = set([0, 4]) diff --git a/pywincffi/kernel32/synchronization.py b/pywincffi/kernel32/synchronization.py new file mode 100644 index 0000000..4df9989 --- /dev/null +++ b/pywincffi/kernel32/synchronization.py @@ -0,0 +1,51 @@ +""" +Synchronization +--------------- + +This module contains general functions for synchronizing objects and +events. The functions provided in this module are parts of the ``kernel32`` +library. + +.. seealso:: + + :mod:`pywincffi.user32.synchronization` +""" + +from six import integer_types + +from pywincffi.core import dist +from pywincffi.core.checks import Enums, input_check, error_check +from pywincffi.exceptions import WindowsAPIError + + +def WaitForSingleObject(hHandle, dwMilliseconds): + """ + Waits for the specified object to be in a signaled state + or for ``dwMiliseconds`` to elapse. + + .. seealso:: + + https://msdn.microsoft.com/en-us/library/ms687032 + + :param handle hHandle: + The handle to wait on. + + :param int dwMilliseconds: + The time-out interval. + """ + input_check("hHandle", hHandle, Enums.HANDLE) + input_check("dwMilliseconds", dwMilliseconds, integer_types) + + ffi, library = dist.load() + result = library.WaitForSingleObject( + hHandle, ffi.cast("DWORD", dwMilliseconds) + ) + + if result == library.WAIT_FAILED: + raise WindowsAPIError( + "WaitForSingleObject", "Wait Failed", ffi.getwinerror()[-1], + return_code=result, expected_return_code="not %s" % result) + + error_check("WaitForSingleObject") + + return result diff --git a/pywincffi/user32/__init__.py b/pywincffi/user32/__init__.py new file mode 100644 index 0000000..2252250 --- /dev/null +++ b/pywincffi/user32/__init__.py @@ -0,0 +1,9 @@ +""" +User32 Sub-Package +================== + +Provides functions, constants and utilities that wrap functions provided by +``user32.dll``. +""" + +from pywincffi.user32.synchronization import MsgWaitForMultipleObjects diff --git a/pywincffi/user32/synchronization.py b/pywincffi/user32/synchronization.py new file mode 100644 index 0000000..f4729eb --- /dev/null +++ b/pywincffi/user32/synchronization.py @@ -0,0 +1,90 @@ +""" +Synchronization +--------------- + +This module contains general functions for synchronizing objects and +events. The functions provided in this module are parts of the ``user32`` +library. + +.. seealso:: + + :mod:`pywincffi.kernel32.synchronization` +""" + +from six import integer_types + +from pywincffi.core import dist +from pywincffi.core.checks import Enums, input_check +from pywincffi.exceptions import WindowsAPIError + + +def MsgWaitForMultipleObjects( + pHandles, bWaitAll, dwMilliseconds, dwWakeMask, nCount=None): + """ + Waits until one or all of the specified objects are in a singled state + or the timeout elapses. + + .. seealso:: + + https://msdn.microsoft.com/en-us/library/ms684242 + + :param list pHandles: + A list of objects to wait on. See Microsoft's documentation for + more information about the contents of this variable. + + :param bool bWaitAll: + If True then this function will return when the states of all + objects in ``pHandles`` are signaled. + + :param int dwMilliseconds: + The timeout interval in milliseconds. + + :param int dwWakeMask: + The input types for which an input event object handle will be added + to the array of handles. See Microsoft's documentation for more + detailed information. + + :keyword int nCount: + The number of object handles in ``pHandles``. By default this will + be determined by checking the length of the input to ``pHandles``. + + :raises WindowsAPIError: + Raised if the underlying Windows function returns ``WAIT_FAILED``. + + :rtype: int + :return: + Returns the value of the event which caused the function to + return. See Microsoft's documentation for full details on what + this could be. + """ + input_check("pHandles", pHandles, (list, tuple)) + + if nCount is None: + nCount = len(pHandles) + + input_check("bWaitAll", bWaitAll, bool) + input_check("dwMilliseconds", dwMilliseconds, integer_types) + input_check("dwWakeMask", dwWakeMask, integer_types) + input_check("nCount", nCount, integer_types) + + # Iterate over all of the object in pHandles. Each object + # should be a handle. + for i, item in enumerate(pHandles): + input_check("pHandles[%d]" % i, item, Enums.HANDLE) + + ffi, library = dist.load() + + code = library.MsgWaitForMultipleObjects( + nCount, + ffi.new("const PHANDLE[%d]" % nCount, pHandles), + bWaitAll, + dwMilliseconds, + dwWakeMask + ) + + if code == library.WAIT_FAILED: + code, message = ffi.getwinerror() + raise WindowsAPIError( + "MsgWaitForMultipleObjects", message, code) + + return code diff --git a/tests/test_core/test_dist.py b/tests/test_core/test_dist.py index 4a50c14..d3709c9 100644 --- a/tests/test_core/test_dist.py +++ b/tests/test_core/test_dist.py @@ -11,8 +11,8 @@ from mock import Mock, patch from pywincffi.core.dist import ( - MODULE_NAME, HEADER_FILES, SOURCE_FILES, Module, _import_path, _ffi, - _compile, _read, load) + MODULE_NAME, HEADER_FILES, SOURCE_FILES, LIBRARIES, Module, _import_path, + _ffi, _compile, _read, load) from pywincffi.dev.testutil import TestCase from pywincffi.exceptions import ResourceNotFoundError @@ -132,7 +132,7 @@ def test_default_source_files(self): _ffi(module_name=self.module_name) mocked_set_source.assert_called_once_with( - self.module_name, _read(*SOURCE_FILES)) + self.module_name, _read(*SOURCE_FILES), libraries=LIBRARIES) def test_default_cdefs(self): with patch.object(FFI, "cdef") as mocked_cdef: @@ -147,7 +147,7 @@ def test_alternate_source_files(self): _ffi(module_name=self.module_name, sources=[path]) mocked_set_source.assert_called_once_with( - self.module_name, _read(*[path])) + self.module_name, _read(*[path]), libraries=LIBRARIES) class TestCompile(TestCase): diff --git a/tests/test_kernel32/test_handle.py b/tests/test_kernel32/test_handle.py index 3d51d05..cd74e26 100644 --- a/tests/test_kernel32/test_handle.py +++ b/tests/test_kernel32/test_handle.py @@ -8,11 +8,10 @@ from pywincffi.core import dist from pywincffi.dev.testutil import TestCase -from pywincffi.exceptions import InputError, WindowsAPIError +from pywincffi.exceptions import InputError from pywincffi.kernel32 import ( - GetStdHandle, CloseHandle, OpenProcess, WaitForSingleObject, - handle_from_file, GetHandleInformation, SetHandleInformation, - DuplicateHandle, GetCurrentProcess, CreateEvent) + GetStdHandle, CloseHandle, handle_from_file, GetHandleInformation, + SetHandleInformation, DuplicateHandle, GetCurrentProcess, CreateEvent) try: WindowsError @@ -86,49 +85,6 @@ def test_opens_correct_file_handle(self): self.fail("Expected os.close(%r) to fail" % fd) -class TestWaitForSingleObject(TestCase): - """ - Tests for :func:`pywincffi.kernel32.WaitForSingleObject` - """ - def test_wait_failed(self): - # This should cause WAIT_FAILED to be returned by the underlying - # WaitForSingleObject because we didn't request the SYNCHRONIZE - # permission. - process = self.create_python_process("import time; time.sleep(3)") - _, library = dist.load() - - hProcess = OpenProcess( - library.PROCESS_QUERY_INFORMATION, False, process.pid) - self.addCleanup(CloseHandle, hProcess) - - with self.assertRaises(WindowsAPIError) as exec_: - WaitForSingleObject(hProcess, 3) - self.assertEqual(exec_.code, library.WAIT_FAILED) - - def test_wait_on_running_process(self): - process = self.create_python_process("import time; time.sleep(1)") - _, library = dist.load() - - hProcess = OpenProcess( - library.PROCESS_QUERY_INFORMATION | library.SYNCHRONIZE, - False, process.pid) - self.addCleanup(CloseHandle, hProcess) - self.assertEqual( - WaitForSingleObject(hProcess, 0), library.WAIT_TIMEOUT) - - def test_process_dies_before_timeout(self): - process = self.create_python_process("import time; time.sleep(1)") - _, library = dist.load() - - hProcess = OpenProcess( - library.PROCESS_QUERY_INFORMATION | library.SYNCHRONIZE, - False, process.pid) - self.addCleanup(CloseHandle, hProcess) - self.assertEqual( - WaitForSingleObject(hProcess, library.INFINITE), - library.WAIT_OBJECT_0) - - class TestGetHandleInformation(TestCase): """ Tests for :func:`pywincffi.kernel32.GetHandleInformation` diff --git a/tests/test_kernel32/test_synchronization.py b/tests/test_kernel32/test_synchronization.py new file mode 100644 index 0000000..ecbc301 --- /dev/null +++ b/tests/test_kernel32/test_synchronization.py @@ -0,0 +1,47 @@ +from pywincffi.core import dist +from pywincffi.dev.testutil import TestCase +from pywincffi.exceptions import WindowsAPIError +from pywincffi.kernel32 import CloseHandle, OpenProcess, WaitForSingleObject + + +class TestWaitForSingleObject(TestCase): + """ + Tests for :func:`pywincffi.kernel32.WaitForSingleObject` + """ + def test_wait_failed(self): + # This should cause WAIT_FAILED to be returned by the underlying + # WaitForSingleObject because we didn't request the SYNCHRONIZE + # permission. + process = self.create_python_process("import time; time.sleep(3)") + _, library = dist.load() + + hProcess = OpenProcess( + library.PROCESS_QUERY_INFORMATION, False, process.pid) + self.addCleanup(CloseHandle, hProcess) + + with self.assertRaises(WindowsAPIError) as exec_: + WaitForSingleObject(hProcess, 3) + self.assertEqual(exec_.code, library.WAIT_FAILED) + + def test_wait_on_running_process(self): + process = self.create_python_process("import time; time.sleep(1)") + _, library = dist.load() + + hProcess = OpenProcess( + library.PROCESS_QUERY_INFORMATION | library.SYNCHRONIZE, + False, process.pid) + self.addCleanup(CloseHandle, hProcess) + self.assertEqual( + WaitForSingleObject(hProcess, 0), library.WAIT_TIMEOUT) + + def test_process_dies_before_timeout(self): + process = self.create_python_process("import time; time.sleep(1)") + _, library = dist.load() + + hProcess = OpenProcess( + library.PROCESS_QUERY_INFORMATION | library.SYNCHRONIZE, + False, process.pid) + self.addCleanup(CloseHandle, hProcess) + self.assertEqual( + WaitForSingleObject(hProcess, library.INFINITE), + library.WAIT_OBJECT_0) diff --git a/tests/test_user32/__init__.py b/tests/test_user32/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_user32/test_synchronization.py b/tests/test_user32/test_synchronization.py new file mode 100644 index 0000000..8097e00 --- /dev/null +++ b/tests/test_user32/test_synchronization.py @@ -0,0 +1,65 @@ +from pywincffi.core import dist +from pywincffi.dev.testutil import TestCase +from pywincffi.exceptions import InputError, WindowsAPIError +from pywincffi.kernel32 import CreateEvent, CloseHandle +from pywincffi.user32 import MsgWaitForMultipleObjects + + +class TestMsgWaitForMultipleObjects(TestCase): + """ + Tests for :func:`pywincffi.user32.WaitForSingleObject` + """ + def test_timeout(self): + _, library = dist.load() + e1 = CreateEvent(True, False) + self.addCleanup(CloseHandle, e1) + e2 = CreateEvent(True, False) + self.addCleanup(CloseHandle, e2) + result = MsgWaitForMultipleObjects( + [e1, e2], False, 0, library.QS_ALLEVENTS) + self.assertEqual(result, library.WAIT_TIMEOUT) + + def test_triggered(self): + _, library = dist.load() + e1 = CreateEvent(True, False) + self.addCleanup(CloseHandle, e1) + e2 = CreateEvent(True, True) + self.addCleanup(CloseHandle, e2) + + # The result here should be 1 because the e2 + # event is triggered by e1 is not. + result = MsgWaitForMultipleObjects( + [e1, e2], False, 0, library.QS_ALLEVENTS) + self.assertEqual(result, 1) + + def test_type_check_on_pHandles_input_not_list(self): + _, library = dist.load() + e1 = CreateEvent(True, False) + self.addCleanup(CloseHandle, e1) + + with self.assertRaises(InputError): + MsgWaitForMultipleObjects(e1, False, 0, library.QS_ALLEVENTS) + + def test_type_check_on_pHandles_value_not_handle(self): + _, library = dist.load() + + with self.assertRaises(InputError): + MsgWaitForMultipleObjects([""], False, 0, library.QS_ALLEVENTS) + + def test_wait_failed(self): + _, library = dist.load() + + events = [] + for _ in range(library.MAXIMUM_WAIT_OBJECTS + 1): + event = CreateEvent(True, False) + self.addCleanup(CloseHandle, event) + events.append(event) + + with self.assertRaises(WindowsAPIError) as error: + MsgWaitForMultipleObjects(events, False, 0, library.QS_ALLEVENTS) + + # The maximum number of events that MsgWaitForMultipleObjects can + # wait on is MAXIMUM_WAIT_OBJECTS - 1. Anything else should cause + # ERROR_INVALID_PARAMETER to be raised via WindowsAPIError. + self.assertEqual( + error.exception.errno, library.ERROR_INVALID_PARAMETER)