Skip to content

Commit

Permalink
Implement MsgWaitForMultipleObjects (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
opalmer committed May 1, 2016
1 parent 9038106 commit fcadc5e
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 90 deletions.
1 change: 1 addition & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~
Expand Down
18 changes: 18 additions & 0 deletions pywincffi/core/cdefs/headers/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 ...
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions pywincffi/core/cdefs/headers/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions pywincffi/core/dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions pywincffi/kernel32/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
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 (
GetProcessId, GetCurrentProcess, OpenProcess, GetExitCodeProcess,
pid_exists, TerminateProcess)
from pywincffi.kernel32.events import CreateEvent, OpenEvent, ResetEvent
from pywincffi.kernel32.comms import ClearCommError
from pywincffi.kernel32.synchronization import WaitForSingleObject
35 changes: 1 addition & 34 deletions pywincffi/kernel32/handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion pywincffi/kernel32/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
51 changes: 51 additions & 0 deletions pywincffi/kernel32/synchronization.py
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions pywincffi/user32/__init__.py
Original file line number Diff line number Diff line change
@@ -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
90 changes: 90 additions & 0 deletions pywincffi/user32/synchronization.py
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions tests/test_core/test_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down
50 changes: 3 additions & 47 deletions tests/test_kernel32/test_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
Loading

0 comments on commit fcadc5e

Please sign in to comment.