Skip to content

Commit

Permalink
Merge pull request #12 from opalmer/replace_dlopen
Browse files Browse the repository at this point in the history
Replace dlopen with FFI.verify
  • Loading branch information
opalmer committed Jul 26, 2015
2 parents 4c75e4b + f1506c4 commit 9850cf6
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 171 deletions.
19 changes: 19 additions & 0 deletions pywincffi/core/cdefs/headers/constants.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// This file contains constants which can be used either internally or
// by users of pywincffi.
//

#define MAX_PATH ...
#define PROCESS_CREATE_PROCESS ...
#define PROCESS_CREATE_THREAD ...
#define PROCESS_DUP_HANDLE ...
#define PROCESS_QUERY_INFORMATION ...
#define PROCESS_QUERY_LIMITED_INFORMATION ...
#define PROCESS_SET_INFORMATION ...
#define PROCESS_SET_QUOTA ...
#define PROCESS_SUSPEND_RESUME ...
#define PROCESS_TERMINATE ...
#define PROCESS_VM_OPERATION ...
#define PROCESS_VM_READ ...
#define PROCESS_VM_WRITE ...
#define SYNCHRONIZE ...
8 changes: 8 additions & 0 deletions pywincffi/core/cdefs/headers/functions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Processes
HANDLE OpenProcess(DWORD, BOOL, DWORD);

// Files
BOOL CreatePipe(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD);
BOOL CloseHandle(HANDLE);
BOOL WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED);
BOOL ReadFile(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED);
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,3 @@ typedef struct _OVERLAPPED {
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

// Processes
BOOL OpenProcess(DWORD, BOOL, DWORD);

// Files
BOOL CreatePipe(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD);
BOOL CloseHandle(HANDLE);
BOOL WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED);
BOOL ReadFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED);
1 change: 1 addition & 0 deletions pywincffi/core/cdefs/sources/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include <windows.h>
5 changes: 4 additions & 1 deletion pywincffi/core/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from six import string_types

from pywincffi.core.ffi import ffi
from pywincffi.core.ffi import Library
from pywincffi.core.logger import get_logger
from pywincffi.exceptions import WindowsAPIError, InputError

Expand Down Expand Up @@ -55,6 +55,8 @@ def error_check(api_function, code=None, expected=0):
:raises pywincffi.exceptions.WindowsAPIError:
Raised if we receive an unexpected result from a Windows API call
"""
ffi, _ = Library.load()

if code is None:
result, api_error_message = ffi.getwinerror()
else:
Expand Down Expand Up @@ -100,6 +102,7 @@ def input_check(name, value, allowed_types):
Raised if ``value`` is not an instance of ``allowed_types``
"""
assert isinstance(name, string_types)
ffi, _ = Library.load()

logger.debug(
"input_check(name=%r, value=%r, allowed_types=%r",
Expand Down
134 changes: 57 additions & 77 deletions pywincffi/core/ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
"""

from errno import ENOENT
from os.path import join

from cffi import FFI
from pkg_resources import resource_filename

from pywincffi.core.logger import get_logger
from pywincffi.exceptions import HeaderNotFoundError
from pywincffi.exceptions import ResourceNotFoundError


logger = get_logger("core.ffi")
Expand All @@ -25,94 +26,73 @@ class Library(object):
loaded multiple times causing an exception to be raised when
a function is redefined.
"""
CACHE = {}
CACHE = None
VERIFY_LIBRARIES = ("kernel32", )
HEADERS_ROOT = resource_filename(
"pywincffi", join("core", "cdefs", "headers"))
SOURCES_ROOT = resource_filename(
"pywincffi", join("core", "cdefs", "sources"))
HEADERS = (
join(HEADERS_ROOT, "constants.h"),
join(HEADERS_ROOT, "structs.h"),
join(HEADERS_ROOT, "functions.h")
)
SOURCES = (
join(SOURCES_ROOT, "main.c"),
)

@staticmethod
def _load_header(header_name):
def _load_files(filepaths):
"""
For the given ``header_name`` locate the path on disk and
attempt to load it. This function will search the `headers`
directory under the root of the pywincffi project.
Given a tuple of file paths open each file and return a string
containing the contents of all the files loaded.
:param str header_name:
The name of the header to load, kernel32.h for example. This
will be passed along to :func:`resource_filename` to construct
the path.
:param tuple filepaths:
A tuple of file paths to load
:returns:
Returns the loaded header file or ``None`` if no header for
for the given name could be found.
"""
header_name = "headers/" + header_name
logger.debug("Searching for header %r", header_name)
path = resource_filename("pywincffi", header_name)

logger.debug("Loading header for %s from %s", header_name, path)

try:
with open(path, "rb") as stream:
header_data = stream.read().decode()
except (OSError, IOError, WindowsError) as error:
if error.errno == ENOENT:
logger.error("No such header %s", path)
return None
raise
return header_data
:raises ResourceNotFoundError:
Raised if one of the paths in ``filepaths`` could not
be loaded.
@classmethod
def load(cls, library_name, ffi_instance=None, header=None):
:return:
Returns a string containing all the loaded paths.
"""
Loads the given ``library_name`` and returns a bound instance of
:class:`FFI`. The return value may either a new instance or
a cached result we've seen before.
:param str library_name:
The name of the library to load (ex. kernel32)
:keyword cffi.api.FFI ffi_instance:
The optional instance of :class:`FFI` to bind to. If no value
is provided we'll use the global ``pywincffi.core.ffi.ffi`
instance instead.
:keyword str header:
An optional header to provide the definitions for the
given ``library_name``. This keyword is mainly used
when testing and when not provided we use :meth:`_find_header`
to locate the path to the header file for the given
library_name.
"""
if ffi_instance is None:
ffi_instance = ffi
output = ""

cached = cls.CACHE.get(ffi_instance, None)
if cached is not None and library_name in cached:
logger.debug(
"Returning cached library %s for %s",
library_name, ffi_instance
)
return cached[library_name]
for path in filepaths:
logger.debug("Reading %s", path)
try:
with open(path, "r") as open_file:
output += open_file.read()

logger.debug("Loading %s onto %s", library_name, ffi_instance)
except (OSError, IOError, WindowsError) as error:
if error.errno == ENOENT:
raise ResourceNotFoundError("Failed to locate %s" % path)

if header is None:
header = cls._load_header(library_name + ".h")
return output

if header is None:
raise HeaderNotFoundError(
"Failed to locate header for %s" % library_name
)
@classmethod
def load(cls, cached=True):
"""
Main function which loads up an instance of the library.
:returns:
Returns a tuple of :class:`FFI` and the loaded library.
"""
if cached and cls.CACHE is not None:
return cls.CACHE

ffi_instance.cdef(header)
library = ffi_instance.dlopen(library_name)
instance_cache = cls.CACHE.setdefault(ffi_instance, {})
instance_cache[library_name] = library
return library
# Read in headers
csource = cls._load_files(cls.HEADERS)
source = cls._load_files(cls.SOURCES)

# Compile
# TODO: this should do something slightly different if pre-compiled
ffi = FFI()
ffi.set_unicode(True)
ffi.cdef(csource)
library = ffi.verify(source, libraries=cls.VERIFY_LIBRARIES)

def new_ffi():
"""Returns an instance of :class:`FFI`"""
ffi_instance = FFI()
ffi_instance.set_unicode(True)
return ffi_instance
cls.CACHE = (ffi, library)

ffi = new_ffi()
return cls.CACHE
5 changes: 3 additions & 2 deletions pywincffi/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Custom exceptions that ``pywincffi`` can throw.
"""


class PyWinCFFIError(Exception):
"""
The base class for all custom exceptions that pywincffi can throw.
Expand Down Expand Up @@ -57,5 +58,5 @@ def __init__(self, api_function, api_error_message, code, expected_code):
super(WindowsAPIError, self).__init__(self.message)


class HeaderNotFoundError(PyWinCFFIError):
"""Raised when we fail to locate a specific header file"""
class ResourceNotFoundError(PyWinCFFIError):
"""Raised when we fail to locate a specific resource"""
24 changes: 15 additions & 9 deletions pywincffi/kernel32/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@

from six import integer_types

from pywincffi.core.ffi import Library, ffi
from pywincffi.core.ffi import Library
from pywincffi.core.checks import Enums, input_check, error_check, NoneType

kernel32 = Library.load("kernel32")


def CreatePipe(nSize=0, lpPipeAttributes=None):
"""
Creates an anonymous pipe and returns the read and write handles.
>>> from pywincffi.core.ffi import ffi
>>> from pywincffi.core.ffi import Library
>>> ffi, library = Library.load()
>>> lpPipeAttributes = ffi.new(
... "SECURITY_ATTRIBUTES[1]", [{
... "nLength": ffi.sizeof("SECURITY_ATTRIBUTES"),
Expand Down Expand Up @@ -49,14 +48,15 @@ def CreatePipe(nSize=0, lpPipeAttributes=None):
"""
input_check("nSize", nSize, int)
input_check("lpPipeAttributes", lpPipeAttributes, (NoneType, dict))
ffi, library = Library.load()

hReadPipe = ffi.new("PHANDLE")
hWritePipe = ffi.new("PHANDLE")

if lpPipeAttributes is None:
lpPipeAttributes = ffi.NULL

code = kernel32.CreatePipe(hReadPipe, hWritePipe, lpPipeAttributes, nSize)
code = library.CreatePipe(hReadPipe, hWritePipe, lpPipeAttributes, nSize)
error_check("CreatePipe", code=code, expected=Enums.NON_ZERO)

return hReadPipe[0], hWritePipe[0]
Expand All @@ -74,8 +74,9 @@ def CloseHandle(hObject):
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211
"""
input_check("hObject", hObject, Enums.HANDLE)
ffi, library = Library.load()

code = kernel32.CloseHandle(hObject)
code = library.CloseHandle(hObject)
error_check("CloseHandle", code=code, expected=Enums.NON_ZERO)


Expand Down Expand Up @@ -112,6 +113,8 @@ def WriteFile(hFile, lpBuffer, lpOverlapped=None):
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747
"""
ffi, library = Library.load()

if lpOverlapped is None:
lpOverlapped = ffi.NULL

Expand All @@ -124,7 +127,7 @@ def WriteFile(hFile, lpBuffer, lpOverlapped=None):
lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToWrite, lpBuffer)
bytes_written = ffi.new("LPDWORD")

code = kernel32.WriteFile(
code = library.WriteFile(
hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_written, lpOverlapped)
error_check("WriteFile", code=code, expected=Enums.NON_ZERO)

Expand All @@ -145,7 +148,8 @@ def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None):
documentation for intended usage and below for an example of this
struct.
>>> from pywincffi.core.ffi import ffi
>>> from pywincffi.core.ffi import Library
>>> ffi, library = Library.load()
>>> reader = None # normally, this would be a handle
>>> struct = ffi.new(
... "OVERLAPPED[1]", [{
Expand All @@ -161,6 +165,8 @@ def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None):
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467
"""
ffi, library = Library.load()

if lpOverlapped is None:
lpOverlapped = ffi.NULL

Expand All @@ -170,7 +176,7 @@ def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None):

lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToRead)
bytes_read = ffi.new("LPDWORD")
code = kernel32.ReadFile(
code = library.ReadFile(
hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_read, lpOverlapped
)
error_check("ReadFile", code=code, expected=Enums.NON_ZERO)
Expand Down
21 changes: 3 additions & 18 deletions pywincffi/kernel32/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,9 @@

import six

from pywincffi.core.ffi import Library, ffi
from pywincffi.core.ffi import Library
from pywincffi.core.checks import input_check, error_check

kernel32 = Library.load("kernel32")

PROCESS_CREATE_PROCESS = 0x0080
PROCESS_CREATE_THREAD = 0x0002
PROCESS_DUP_HANDLE = 0x0040
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
PROCESS_SET_INFORMATION = 0x0200
PROCESS_SET_QUOTA = 0x0100
PROCESS_SUSPEND_RESUME = 0x0800
PROCESS_TERMINATE = 0x0001
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0008
PROCESS_VM_WRITE = 0x0020
SYNCHRONIZE = 0x00100000


def OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId):
"""
Expand All @@ -62,8 +46,9 @@ def OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId):
input_check("dwDesiredAccess", dwDesiredAccess, six.integer_types)
input_check("bInheritHandle", bInheritHandle, bool)
input_check("dwProcessId", dwProcessId, six.integer_types)
ffi, library = Library.load()

handle_id = kernel32.OpenProcess(
handle_id = library.OpenProcess(
ffi.cast("DWORD", dwDesiredAccess),
ffi.cast("BOOL", bInheritHandle),
ffi.cast("DWORD", dwProcessId)
Expand Down
Loading

0 comments on commit 9850cf6

Please sign in to comment.