diff --git a/.gitignore b/.gitignore index 0bd2ad90..8424ff82 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ htmlcov/ .coverage .coverage.* .cache +.pytest_cache/ nosetests.xml coverage.xml *,cover diff --git a/CHANGES.rst b/CHANGES.rst index acd0c43c..f217448a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,13 @@ Changes ======= +0.0.8 (2018-03-18) +------------------ + +* FEATURE: Support for structures and pointers as return values, see issue #14. +* FEATURE: (Limited) support for call back functions (function pointers) as DLL argument types, see issues #3 and #4. +* FIX: ``argtypes`` definitions (with one single argument) were not raising a ``TypeError`` like ``ctypes`` does if not passed as a tuple or list, see issue #21. + 0.0.7 (2018-03-05) ------------------ @@ -20,7 +27,7 @@ Changes 0.0.5 (2017-11-13) ------------------ -* Added support for light-weight pointers (``ctypes.byref``) +* FEATURE: Support for light-weight pointers (``ctypes.byref``) * FIX: Elements within structures are properly synchronized even if they are not a pointer on their own. * FIX: Structure objects in arrays of structures are properly initialized. * FIX: Links in ``README.rst`` work when rendered on PyPI. @@ -28,16 +35,16 @@ Changes 0.0.4 (2017-11-05) ------------------ -* Implemented full support for multidimensional fixed length arrays +* FEATURE: Full support for multidimensional fixed length arrays 0.0.3 (2017-11-02) ------------------ -* Implemented fixed length 1D arrays +* FEATURE: Fixed length 1D arrays * Refactored argument packing and unpacking code * Plenty of cleanups based on static code analysis * Introduced ``Python``'s ``any`` functions in a number of places -* Fixed lots of typos and grammar issues in documentation +* FIX: Lots of typos and grammar issues in documentation 0.0.2 (2017-07-28) ------------------ diff --git a/demo_dll/demo_dll.c b/demo_dll/demo_dll.c index 25ea686f..326d2449 100644 --- a/demo_dll/demo_dll.c +++ b/demo_dll/demo_dll.c @@ -8,7 +8,7 @@ Calling routines in Windows DLLs from Python scripts running on unixlike systems Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -112,6 +112,18 @@ double __stdcall DEMODLL cookbook_distance( } +/* Function involving a C data structure - returning a pointer to a variable */ +double __stdcall DEMODLL *cookbook_distance_pointer( + cookbook_point *p1, + cookbook_point *p2 + ) +{ + double *distance_p = (double *)malloc(sizeof(double)); + *distance_p = hypot(p1->x - p2->x, p1->y - p2->y); + return distance_p; +} + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // zugbruecke demo // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -187,6 +199,37 @@ void __stdcall DEMODLL gauss_elimination( } +vector3d __stdcall DEMODLL *vector3d_add( + vector3d *v1, + vector3d *v2 + ) +{ + + vector3d *v3 = malloc(sizeof(vector3d)); + + v3->x = v1->x + v2->x; + v3->y = v1->y + v2->y; + v3->z = v1->z + v2->z; + + return v3; + +} + + +int16_t __stdcall DEMODLL sqrt_int( + int16_t a + ) +{ + return a * a; +} + + +int16_t __stdcall DEMODLL get_const_int(void) +{ + return sqrt(49); +} + + float __stdcall DEMODLL simple_demo_routine( float param_a, float param_b @@ -221,6 +264,29 @@ void __stdcall DEMODLL complex_demo_routine( } +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// zugbruecke demo: callback +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int16_t __stdcall DEMODLL sum_elements_from_callback( + int16_t len, + conveyor_belt get_data + ) +{ + + int16_t sum = 0; + int16_t i; + + for(i = 0; i < len; i++) + { + sum += get_data(i); + } + + return sum; + +} + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // DLL infrastructure // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/demo_dll/demo_dll.h b/demo_dll/demo_dll.h index 59ac5041..eb27ed17 100644 --- a/demo_dll/demo_dll.h +++ b/demo_dll/demo_dll.h @@ -8,7 +8,7 @@ Calling routines in Windows DLLs from Python scripts running on unixlike systems Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -88,6 +88,10 @@ double __stdcall DEMODLL cookbook_distance( cookbook_point *p2 ); +double __stdcall DEMODLL *cookbook_distance_pointer( + cookbook_point *p1, + cookbook_point *p2 + ); // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // zugbruecke demo @@ -109,6 +113,21 @@ void __stdcall DEMODLL gauss_elimination( float (*x)[3] ); +typedef struct vector3d { + int16_t x, y, z; +} vector3d; + +vector3d __stdcall DEMODLL *vector3d_add( + vector3d *p1, + vector3d *p2 + ); + +int16_t __stdcall DEMODLL sqrt_int( + int16_t a + ); + +int16_t __stdcall DEMODLL get_const_int(void); + struct test { char el_char; @@ -132,6 +151,18 @@ void __stdcall DEMODLL complex_demo_routine( ); +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// zugbruecke demo: callback +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +typedef int16_t __stdcall (*conveyor_belt)(int16_t index); + +int16_t __stdcall DEMODLL sum_elements_from_callback( + int16_t len, + conveyor_belt get_data + ); + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // DLL infrastructure // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/demo_dll/makefile b/demo_dll/makefile index 69f663a9..3d816fc9 100644 --- a/demo_dll/makefile +++ b/demo_dll/makefile @@ -6,7 +6,7 @@ # # Required to run on platform / side: [UNIX] # -# Copyright (C) 2017 Sebastian M. Ernst +# Copyright (C) 2017-2018 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/docs/Makefile b/docs/Makefile index 2efe9268..bb361a35 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,7 +6,7 @@ # # Required to run on platform / side: [UNIX] # -# Copyright (C) 2017 Sebastian M. Ernst +# Copyright (C) 2017-2018 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/docs/conf.py b/docs/conf.py index 98cedee1..852cbcf6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -77,7 +77,7 @@ def fetch_version_string(): # General information about the project. project = 'zugbruecke' -copyright = '2017 Sebastian M. Ernst' +copyright = '2017-2018 Sebastian M. Ernst' author = 'Sebastian M. Ernst' # The version info for the project you're documenting, acts as replacement for diff --git a/examples/demo_call_win.py b/examples/demo_call_win.py index f16d2dc8..6baf27e3 100755 --- a/examples/demo_call_win.py +++ b/examples/demo_call_win.py @@ -11,7 +11,7 @@ Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/examples/test_callback.py b/examples/test_callback.py new file mode 100755 index 00000000..e2d3c5f9 --- /dev/null +++ b/examples/test_callback.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + examples/test_callback.py: Demonstrates callback routines as arguments + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from sys import platform + +if any([platform.startswith(os_name) for os_name in ['linux', 'darwin', 'freebsd']]): + + f = open('.zugbruecke.json', 'w') + f.write('{"log_level": 10}') + f.close() + + import zugbruecke as ctypes + +elif platform.startswith('win'): + + import ctypes + +else: + + raise # TODO unsupported platform + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# RUN +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +if __name__ == '__main__': + + DATA = [1, 6, 8, 4, 9, 7, 4, 2, 5, 2] + + conveyor_belt = ctypes.WINFUNCTYPE(ctypes.c_int16, ctypes.c_int16) + + @conveyor_belt + def get_data(index): + print((index, DATA[index])) + return DATA[index] + + dll = ctypes.windll.LoadLibrary('demo_dll.dll') + sum_elements_from_callback = dll.sum_elements_from_callback + sum_elements_from_callback.argtypes = (ctypes.c_int16, conveyor_belt) + sum_elements_from_callback.restype = ctypes.c_int16 + + test_sum = sum_elements_from_callback(len(DATA), get_data) + print(('sum', 48, test_sum)) diff --git a/examples/test_cookbook.py b/examples/test_cookbook.py index bab0a1ac..8b99ddf2 100755 --- a/examples/test_cookbook.py +++ b/examples/test_cookbook.py @@ -11,7 +11,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/examples/test_zugbruecke.py b/examples/test_zugbruecke.py index c66e7242..44babc73 100755 --- a/examples/test_zugbruecke.py +++ b/examples/test_zugbruecke.py @@ -11,7 +11,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/examples/test_zugbruecke_perf.py b/examples/test_zugbruecke_perf.py index c0385742..50278855 100755 --- a/examples/test_zugbruecke_perf.py +++ b/examples/test_zugbruecke_perf.py @@ -11,7 +11,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/makefile b/makefile index e916583e..642563e4 100644 --- a/makefile +++ b/makefile @@ -6,7 +6,7 @@ # # Required to run on platform / side: [UNIX] # -# Copyright (C) 2017 Sebastian M. Ernst +# Copyright (C) 2017-2018 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/scripts/wine-pip b/scripts/wine-pip index 87c2f13a..984cff1f 100755 --- a/scripts/wine-pip +++ b/scripts/wine-pip @@ -8,7 +8,7 @@ # # Required to run on platform / side: [UNIX] # -# Copyright (C) 2017 Sebastian M. Ernst +# Copyright (C) 2017-2018 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/scripts/wine-pytest b/scripts/wine-pytest index 6655f42f..e0ae28f9 100644 --- a/scripts/wine-pytest +++ b/scripts/wine-pytest @@ -8,7 +8,7 @@ # # Required to run on platform / side: [UNIX] # -# Copyright (C) 2017 Sebastian M. Ernst +# Copyright (C) 2017-2018 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/scripts/wine-python b/scripts/wine-python index 2a1c4467..3099f91e 100755 --- a/scripts/wine-python +++ b/scripts/wine-python @@ -8,7 +8,7 @@ # # Required to run on platform / side: [UNIX] # -# Copyright (C) 2017 Sebastian M. Ernst +# Copyright (C) 2017-2018 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/setup.py b/setup.py index 0c5ee8ff..efc00c4e 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -46,7 +46,7 @@ # Bump version HERE! -_version_ = '0.0.7' +_version_ = '0.0.8' # List all versions of Python which are supported diff --git a/src/zugbruecke/__init__.py b/src/zugbruecke/__init__.py index b0bee484..4f7f726f 100644 --- a/src/zugbruecke/__init__.py +++ b/src/zugbruecke/__init__.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/_server_.py b/src/zugbruecke/_server_.py index 7e16f5c5..ea0c5d7f 100755 --- a/src/zugbruecke/_server_.py +++ b/src/zugbruecke/_server_.py @@ -11,7 +11,7 @@ Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -51,6 +51,9 @@ parser.add_argument( '--port_socket_ctypes', type = int, nargs = 1 ) + parser.add_argument( + '--port_socket_callback', type = int, nargs = 1 + ) parser.add_argument( '--port_socket_log_main', type = int, nargs = 1 ) @@ -73,6 +76,7 @@ 'log_level': args.log_level[0], 'log_server': False, 'port_socket_ctypes': args.port_socket_ctypes[0], + 'port_socket_callback': args.port_socket_callback[0], 'port_socket_log_main': args.port_socket_log_main[0] } diff --git a/src/zugbruecke/_wrapper_.py b/src/zugbruecke/_wrapper_.py index c3c45f90..a489a583 100644 --- a/src/zugbruecke/_wrapper_.py +++ b/src/zugbruecke/_wrapper_.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -30,7 +30,10 @@ # IMPORT: Unix ctypes members required by wrapper # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from ctypes import DEFAULT_MODE +from ctypes import ( + _FUNCFLAG_CDECL, + DEFAULT_MODE + ) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -46,15 +49,13 @@ from ctypes import CDLL as ctypes_CDLL_class -from ctypes import CFUNCTYPE as __CFUNCTYPE__ -from ctypes import _c_functype_cache as __c_functype_cache__ - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# IMPORT: zugbruecke core +# IMPORT: zugbruecke core and missing ctypes flags # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from .core.session_client import session_client_class +from .core.session_client import session_client_class # EXPORT +from .core.const import _FUNCFLAG_STDCALL # EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -73,12 +74,6 @@ class HRESULT: # EXPORT def _check_HRESULT(result): # EXPORT pass # TODO stub - method for HRESULT, checks error bit, raises error if true. Needs reimplementation. -def WINFUNCTYPE(restype, *argtypes, **kw): # EXPORT - pass # TODO stub - -# Used in ctypes __init__.py by WINFUNCTYPE. Needs to be exposed? -_win_functype_cache = {} # EXPORT # TODO stub - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # zugbruecke session @@ -104,15 +99,16 @@ def WINFUNCTYPE(restype, *argtypes, **kw): # EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# Compatibility to CFUNCTYPE on Wine side must be implemented +# CFUNCTYPE & WINFUNCTYPE # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# Need to handle c functions in DLLs -CFUNCTYPE = __CFUNCTYPE__ # EXPORT -_c_functype_cache = __c_functype_cache__ # EXPORT +# CFUNCTYPE and WINFUNCTYPE function pointer factories +CFUNCTYPE = current_session.ctypes_CFUNCTYPE # EXPORT +WINFUNCTYPE = current_session.ctypes_WINFUNCTYPE # EXPORT -# Just in case ... -_FUNCFLAG_STDCALL = 0 # EXPORT +# Used as cache by CFUNCTYPE and WINFUNCTYPE +_c_functype_cache = current_session.cache_dict['func_type'][_FUNCFLAG_CDECL] # EXPORT +_win_functype_cache = current_session.cache_dict['func_type'][_FUNCFLAG_STDCALL] # EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/zugbruecke/core/__init__.py b/src/zugbruecke/core/__init__.py index 963e746c..60bafced 100644 --- a/src/zugbruecke/core/__init__.py +++ b/src/zugbruecke/core/__init__.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/arg_contents.py b/src/zugbruecke/core/arg_contents.py index 7547ac0c..39873a2f 100644 --- a/src/zugbruecke/core/arg_contents.py +++ b/src/zugbruecke/core/arg_contents.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -39,8 +39,11 @@ FLAG_POINTER, GROUP_VOID, GROUP_FUNDAMENTAL, - GROUP_STRUCT + GROUP_STRUCT, + GROUP_FUNCTION ) +from .callback_client import callback_translator_client_class +from .callback_server import callback_translator_server_class # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -62,6 +65,29 @@ def arg_list_unpack(self, args_package_list, argtypes_list): return [self.__unpack_item__(a[1], d) for a, d in zip(args_package_list, argtypes_list)] + def return_msg_pack(self, return_value, returntype_dict): + + if return_value is None: + return None + + return self.__pack_item__(return_value, returntype_dict) + + + def return_msg_unpack(self, return_msg, returntype_dict): + + if return_msg is None: + return None + + # If this is not a fundamental datatype or if there is a pointer involved, just unpack + if not returntype_dict['g'] == GROUP_FUNDAMENTAL or FLAG_POINTER in returntype_dict['f']: + return self.__unpack_item__(return_msg, returntype_dict) + + # The original ctypes strips away ctypes datatypes for fundamental + # (non-pointer, non-struct) return values and returns plain Python + # data types instead - the unpack result requires stripping + return self.__item_value_strip__(self.__unpack_item__(return_msg, returntype_dict)) + + def arg_list_sync(self, old_arguments_list, new_arguments_list, argtypes_list): # Step through arguments @@ -72,7 +98,6 @@ def arg_list_sync(self, old_arguments_list, new_arguments_list, argtypes_list): old_arg, new_arg, arg_def_dict ) - def __item_pointer_strip__(self, arg_in): # Handle pointer object @@ -113,6 +138,10 @@ def __pack_item__(self, arg_in, arg_def_dict): elif arg_def_dict['g'] == GROUP_STRUCT: # Reclusively call this routine for packing structs return self.__pack_item_struct__(arg_in, arg_def_dict) + # Handle functions + elif arg_def_dict['g'] == GROUP_FUNCTION: + # Packs functions and registers them at RPC server + return self.__pack_item_function__(arg_in, arg_def_dict) # Handle everything else ... likely pointers handled by memsync else: # Just return None - will (hopefully) be overwritten by memsync @@ -161,6 +190,36 @@ def __pack_item_array__(self, arg_in, arg_def_dict, flag_index_start = 0): return arg_in + def __pack_item_function__(self, func_ptr, func_def_dict): + + # HACK if on server, just return None + if self.is_server: + return None + + # Use memory address of function pointer as unique name/ID + func_name = 'func_%x' % id(func_ptr) + + # Has callback translator been built before? + if func_name in self.cache_dict['func_handle'].keys(): + + # Just return its name + return func_name + + # Generate and store callback translator in cache + self.cache_dict['func_handle'][func_name] = callback_translator_client_class( + self, func_name, func_ptr, func_def_dict['_argtypes_'], func_def_dict['_restype_'] + ) + + # Register translator at RPC server + self.callback_server.register_function( + self.cache_dict['func_handle'][func_name], + public_name = func_name + ) + + # Return name of callback entry + return func_name + + def __pack_item_struct__(self, struct_raw, struct_def_dict): # Return parameter message list - MUST WORK WITH PICKLE @@ -261,6 +320,9 @@ def __unpack_item__(self, arg_raw, arg_def_dict): # Handle structs elif arg_def_dict['g'] == GROUP_STRUCT: arg_rebuilt = self.__unpack_item_struct__(arg_raw, arg_def_dict) + # Handle functions + elif arg_def_dict['g'] == GROUP_FUNCTION: + arg_rebuilt = self.__unpack_item_function__(arg_raw, arg_def_dict) # Handle voids (likely mensync stuff) elif arg_def_dict['g'] == GROUP_VOID: # Return a placeholder @@ -332,7 +394,7 @@ def __unpack_item_array__(self, arg_in, arg_def_dict, flag_index = 0): arg_type = getattr(ctypes, arg_def_dict['t']) * flag arg_in = arg_type(*arg_in) elif arg_def_dict['g'] == GROUP_STRUCT: - arg_type = self.struct_type_dict[arg_def_dict['t']] * flag + arg_type = self.cache_dict['struct_type'][arg_def_dict['t']] * flag arg_in = arg_type(*(self.__unpack_item_struct__(e, arg_def_dict) for e in arg_in)) else: raise # TODO @@ -340,10 +402,34 @@ def __unpack_item_array__(self, arg_in, arg_def_dict, flag_index = 0): return arg_type, arg_in + def __unpack_item_function__(self, func_name, func_def_dict): + + # HACK if this function is called on the client, just return None + if not self.is_server: + return None + + # Has callback translator been built? + if func_name in self.cache_dict['func_handle'].keys(): + + # Just return handle + return self.cache_dict['func_handle'][func_name] + + # Generate, decorate and store callback translator in cache + self.cache_dict['func_handle'][func_name] = func_def_dict['_factory_type_']( + callback_translator_server_class( + self, func_name, getattr(self.callback_client, func_name), + func_def_dict['_argtypes_'], func_def_dict['_restype_'] + ) + ) + + # Return name of callback entry + return self.cache_dict['func_handle'][func_name] + + def __unpack_item_struct__(self, args_list, struct_def_dict): # Generate new instance of struct datatype - struct_inst = self.struct_type_dict[struct_def_dict['t']]() + struct_inst = self.cache_dict['struct_type'][struct_def_dict['t']]() # Step through arguments for field_def_dict, field_arg in zip(struct_def_dict['_fields_'], args_list): diff --git a/src/zugbruecke/core/arg_definition.py b/src/zugbruecke/core/arg_definition.py index f3f4dbe2..7a17b25d 100644 --- a/src/zugbruecke/core/arg_definition.py +++ b/src/zugbruecke/core/arg_definition.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -38,7 +38,8 @@ FLAG_POINTER, GROUP_VOID, GROUP_FUNDAMENTAL, - GROUP_STRUCT + GROUP_STRUCT, + GROUP_FUNCTION ) from .lib import ( reduce_dict @@ -52,9 +53,6 @@ class arg_definition_class(): - struct_type_dict = {} - - def apply_memsync_to_argtypes_definition(self, memsync, argtypes_d): # Start empty handle list @@ -149,7 +147,7 @@ def __generate_struct_from_definition__(self, struct_d_dict): )) # Generate actual class - self.struct_type_dict[struct_d_dict['t']] = type( + self.cache_dict['struct_type'][struct_d_dict['t']] = type( struct_d_dict['t'], # Potenial BUG: Ends up in __main__ scope, problematic? (ctypes.Structure,), {'_fields_': fields} @@ -224,8 +222,10 @@ def __pack_definition_dict__(self, datatype, field_name = None): elif group_name == 'PyCStructType': # Keep track of datatype on client side - if type_name not in self.struct_type_dict.keys(): - self.struct_type_dict[type_name] = datatype + if type_name not in self.cache_dict['struct_type'].keys(): + self.cache_dict['struct_type'][type_name] = datatype + + # TODO: For speed, cache packed struct definitions for known structs return { 'f': flag_list, @@ -240,6 +240,24 @@ def __pack_definition_dict__(self, datatype, field_name = None): ] } + # Function pointers + elif group_name == 'PyCFuncPtrType': + + # TODO: For speed, cache packed function definitions for known functions + + return { + 'f': flag_list, + 's': flag_scalar, + 'd': flag_array_depth, + 'p': flag_pointer, + 'n': field_name, # kw + 't': (datatype._restype_, datatype._argtypes_, datatype._flags_).__hash__(), + 'g': GROUP_FUNCTION, + '_argtypes_': self.pack_definition_argtypes(datatype._argtypes_), + '_restype_': self.pack_definition_returntype(datatype._restype_), + '_flags_': datatype._flags_ + } + # UNKNOWN stuff, likely pointers - handled without datatype else: @@ -266,6 +284,11 @@ def __unpack_definition_dict__(self, datatype_d_dict): return self.__unpack_definition_struct_dict__(datatype_d_dict) + # Function pointers (PyCFuncPtrType) + elif datatype_d_dict['g'] == GROUP_FUNCTION: + + return self.__unpack_definition_function_dict__(datatype_d_dict) + # Handle generic pointers elif datatype_d_dict['g'] == GROUP_VOID: @@ -300,6 +323,34 @@ def __unpack_definition_flags__(self, datatype, flag_list, is_void_pointer = Fal return datatype + def __unpack_definition_function_dict__(self, datatype_d_dict): + + # TODO BUG only works on Wine Python, must not be called on Unix side + if not self.is_server: + raise # TODO + + # Figure out which "factory" to use, i.e. calling convention + if not(datatype_d_dict['_flags_'] & ctypes._FUNCFLAG_STDCALL): + FACTORY = ctypes.CFUNCTYPE + elif datatype_d_dict['_flags_'] & ctypes._FUNCFLAG_STDCALL: + FACTORY = ctypes.WINFUNCTYPE + else: + raise # TODO + + # Generate function pointer type (used as parameter type and as decorator for Python function) + factory_type = FACTORY( + self.unpack_definition_returntype(datatype_d_dict['_restype_']), + *self.unpack_definition_argtypes(datatype_d_dict['_argtypes_']), + use_errno = datatype_d_dict['_flags_'] & ctypes._FUNCFLAG_USE_ERRNO, + use_last_error = datatype_d_dict['_flags_'] & ctypes._FUNCFLAG_USE_LASTERROR + ) + + # Store function pointer type for subsequent use as decorator + datatype_d_dict['_factory_type_'] = factory_type + + return factory_type + + def __unpack_definition_fundamental_dict__(self, datatype_d_dict): # Return type class or type pointer @@ -313,11 +364,11 @@ def __unpack_definition_fundamental_dict__(self, datatype_d_dict): def __unpack_definition_struct_dict__(self, datatype_d_dict): # Generate struct class if it does not exist yet - if datatype_d_dict['t'] not in self.struct_type_dict.keys(): + if datatype_d_dict['t'] not in self.cache_dict['struct_type'].keys(): self.__generate_struct_from_definition__(datatype_d_dict) # Return type class or type pointer return self.__unpack_definition_flags__( - self.struct_type_dict[datatype_d_dict['t']], # struct class + self.cache_dict['struct_type'][datatype_d_dict['t']], # struct class datatype_d_dict['f'] # flags ) diff --git a/src/zugbruecke/core/arg_memory.py b/src/zugbruecke/core/arg_memory.py index 4da81a64..a4b376d7 100644 --- a/src/zugbruecke/core/arg_memory.py +++ b/src/zugbruecke/core/arg_memory.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/callback_client.py b/src/zugbruecke/core/callback_client.py new file mode 100644 index 00000000..ad384ba8 --- /dev/null +++ b/src/zugbruecke/core/callback_client.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + src/zugbruecke/core/callback_client.py: Classes for managing callback routines + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from pprint import pformat as pf +import traceback + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CALLBACK CLIENT CLASS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class callback_translator_client_class: + + + def __init__(self, parent_routine, routine_name, routine_handler, argtypes_d, restype_d): + + # Store my own name + self.name = routine_name + + # Store handler + self.handler = routine_handler + + # Store handle on parent routine + self.parent_routine = parent_routine + + # Get handle on log + self.log = self.parent_routine.log + + # Store definition of argument types + self.argtypes_d = argtypes_d + + # Store definition of return value type + self.restype_d = restype_d + + # Store handlers on packing/unpacking routines + self.arg_list_unpack = self.parent_routine.arg_list_unpack + self.arg_list_pack = self.parent_routine.arg_list_pack + self.return_msg_pack = self.parent_routine.return_msg_pack + + + def __call__(self, arg_message_list): + + # Log status + self.log.out('[callback-client] Trying to call callback routine "%s" ...' % self.name) + + # Unpack arguments + args_list = self.arg_list_unpack(arg_message_list, self.argtypes_d) + + # Default return value + return_value = None + + # This is risky + try: + + # Call actual callback function (ctypes function pointer) + return_value = self.handler(*args_list) + + # Get new arg message list + arg_message_list = self.arg_list_pack(args_list, self.argtypes_d) + + # Pack return value + return_message = self.return_msg_pack(return_value, self.restype_d) + + # Log status + self.log.out('[routine-client] ... done.') + + # Ship data back to Wine side + return { + 'args': arg_message_list, + 'return_value': return_message, # TODO handle memory allocated by callback in "free form" pointers + 'memory': None, # TODO memsync not included + 'success': True + } + + except: + + # Log status + self.log.out('[routine-client] ... failed!') + + # Push traceback to log + self.log.err(traceback.format_exc()) + + # Pack return package and return it + return { + 'args': arg_message_list, + 'return_value': return_value, + 'memory': None, # TODO memsync not included + 'success': False + } diff --git a/src/zugbruecke/core/callback_server.py b/src/zugbruecke/core/callback_server.py new file mode 100644 index 00000000..1d670a6c --- /dev/null +++ b/src/zugbruecke/core/callback_server.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + src/zugbruecke/core/callback_server.py: Classes for managing callback routines + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from pprint import pformat as pf +import traceback + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CALLBACK SERVER CLASS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class callback_translator_server_class: + + + def __init__(self, parent_routine, routine_name, routine_handler, argtypes_d, restype_d): + + # Store my own name + self.name = routine_name + + # Store handler + self.handler = routine_handler + + # Store handle on parent routine + self.parent_routine = parent_routine + + # Get handle on log + self.log = self.parent_routine.log + + # Store definition of argument types + self.argtypes_d = argtypes_d + + # Store definition of return value type + self.restype_d = restype_d + + # Store handlers on packing/unpacking routines + self.arg_list_pack = self.parent_routine.arg_list_pack + self.arg_list_sync = self.parent_routine.arg_list_sync + self.arg_list_unpack = self.parent_routine.arg_list_unpack + self.return_msg_unpack = self.parent_routine.return_msg_unpack + + + def __call__(self, *args): + + # Log status + self.log.out('[callback-server] Trying to call callback routine "%s" ...' % self.name) + + # Log status + self.log.out('[callback-server] ... parameters are "%r". Packing and pushing to client ...' % (args,)) + + # Pack arguments and call RPC callback function (packed arguments are shipped to Unix side) + return_dict = self.handler(self.arg_list_pack(args, self.argtypes_d)) + + # Log status + self.log.out('[callback-server] ... received feedback from client, unpacking ...') + + # Unpack return dict (for pointers and structs) + self.arg_list_sync( + args, + self.arg_list_unpack(return_dict['args'], self.argtypes_d), + self.argtypes_d + ) + + # Unpack return value + return_value = self.return_msg_unpack(return_dict['return_value'], self.restype_d) + + # Log status + self.log.out('[callback-server] ... unpacked, return.') + + # Return data directly to DLL routine + return return_value diff --git a/src/zugbruecke/core/config.py b/src/zugbruecke/core/config.py index c7293287..d088a251 100644 --- a/src/zugbruecke/core/config.py +++ b/src/zugbruecke/core/config.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/const.py b/src/zugbruecke/core/const.py index 6b3fb206..577ba705 100644 --- a/src/zugbruecke/core/const.py +++ b/src/zugbruecke/core/const.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -35,3 +35,12 @@ GROUP_VOID = 1 GROUP_FUNDAMENTAL = 2 GROUP_STRUCT = 4 +GROUP_FUNCTION = 8 + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CTYPES FLAGS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +# Required for WINFUNCTYPE +_FUNCFLAG_STDCALL = 0 # EXPORT diff --git a/src/zugbruecke/core/dll_client.py b/src/zugbruecke/core/dll_client.py index 0edbb435..db71ee3e 100644 --- a/src/zugbruecke/core/dll_client.py +++ b/src/zugbruecke/core/dll_client.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -108,13 +108,13 @@ def __attach_to_routine__(self, name): if isinstance(name, str): # Set attribute for future use - setattr(self, name, self.routines[name].handle_call) + setattr(self, name, self.routines[name]) # Log status self.log.out('[dll-client] ... return handler.') # Return handler - return self.routines[name].handle_call + return self.routines[name] def __getattr__(self, name): @@ -128,13 +128,10 @@ def __getitem__(self, name_or_ordinal): if name_or_ordinal in self.routines.keys(): # Return handle - return self.routines[name_or_ordinal].handle_call + return self.routines[name_or_ordinal] - # Is is unknown? - else: - - # Generate new handle - return self.__attach_to_routine__(name_or_ordinal) + # Generate new handle and return + return self.__attach_to_routine__(name_or_ordinal) def __repr__(self): diff --git a/src/zugbruecke/core/dll_server.py b/src/zugbruecke/core/dll_server.py index 234a6a5a..a4d04ce5 100644 --- a/src/zugbruecke/core/dll_server.py +++ b/src/zugbruecke/core/dll_server.py @@ -10,7 +10,7 @@ Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -125,7 +125,7 @@ def __register_routine__(self, routine_name): # Export call and configration directly self.session.server.register_function( - self.routines[routine_name].__handle_call__, + self.routines[routine_name], self.hash_id + '_' + str(routine_name) + '_handle_call' ) self.session.server.register_function( diff --git a/src/zugbruecke/core/interpreter.py b/src/zugbruecke/core/interpreter.py index dcf3dc6c..3a29953c 100644 --- a/src/zugbruecke/core/interpreter.py +++ b/src/zugbruecke/core/interpreter.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/lib.py b/src/zugbruecke/core/lib.py index 283000e4..a20026d8 100644 --- a/src/zugbruecke/core/lib.py +++ b/src/zugbruecke/core/lib.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -31,11 +31,14 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +from ctypes import _FUNCFLAG_CDECL import hashlib import os import random import socket +from .const import _FUNCFLAG_STDCALL + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # LIBRARY ROUTINES @@ -70,6 +73,18 @@ def get_randhashstr(dig): return (('%0' + str(dig) + 'x') % random.randrange(16**dig)) +def generate_cache_dict(): + + return { + 'func_type': { + _FUNCFLAG_CDECL: {}, + _FUNCFLAG_STDCALL: {} + }, + 'func_handle': {}, + 'struct_type': {} + } + + def generate_session_id(): # A session id by default is an 8 digit hash string diff --git a/src/zugbruecke/core/log.py b/src/zugbruecke/core/log.py index 2df0748a..eaab7c89 100644 --- a/src/zugbruecke/core/log.py +++ b/src/zugbruecke/core/log.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/memory.py b/src/zugbruecke/core/memory.py index f0415a30..2fec7f0f 100644 --- a/src/zugbruecke/core/memory.py +++ b/src/zugbruecke/core/memory.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/path.py b/src/zugbruecke/core/path.py index 72dcc655..90ae6ce8 100644 --- a/src/zugbruecke/core/path.py +++ b/src/zugbruecke/core/path.py @@ -10,7 +10,7 @@ Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/routine_client.py b/src/zugbruecke/core/routine_client.py index 339fb16f..003a013a 100644 --- a/src/zugbruecke/core/routine_client.py +++ b/src/zugbruecke/core/routine_client.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -68,20 +68,22 @@ def __init__(self, parent_dll, routine_name): # Store my own name self.name = routine_name + # Required by arg definitions and contents + self.cache_dict = self.session.cache_dict + self.callback_server = self.session.callback_server + self.is_server = False + # Set call status self.called = False - # Turn a bound method into a function ... HACK? - self.handle_call = partial(self.__handle_call__) - # By default, there is no memory to sync - self.handle_call.memsync = [] + self.__memsync__ = [] # By default, assume no arguments - self.handle_call.argtypes = [] + self.__argtypes__ = [] # By default, assume c_int return value like ctypes expects - self.handle_call.restype = ctypes.c_int + self.__restype__ = ctypes.c_int # Get handle on server-side configure self.__configure_on_server__ = getattr( @@ -94,7 +96,7 @@ def __init__(self, parent_dll, routine_name): ) - def __handle_call__(self, *args): + def __call__(self, *args): """ TODO Optimize for speed! """ @@ -138,6 +140,9 @@ def __handle_call__(self, *args): self.argtypes_d ) + # Unpack return value of routine + return_value = self.return_msg_unpack(return_dict['return_value'], self.restype_d) + # Unpack memory self.client_unpack_memory_list(return_dict['memory'], memory_transport_handle) @@ -145,42 +150,73 @@ def __handle_call__(self, *args): self.log.out('[routine-client] ... unpacked, return.') # Return result. return_value will be None if there was not a result. - return return_dict['return_value'] + return return_value def __configure__(self): - # Processing argument and return value types on first call TODO proper sanity check - if hasattr(self.handle_call, 'memsync'): - self.memsync = self.handle_call.memsync - if hasattr(self.handle_call, 'argtypes'): - self.argtypes = self.handle_call.argtypes - if hasattr(self.handle_call, 'restype'): - self.restype = self.handle_call.restype - # Prepare list of arguments by parsing them into list of dicts (TODO field name / kw) - self.argtypes_d = self.pack_definition_argtypes(self.argtypes) + self.argtypes_d = self.pack_definition_argtypes(self.__argtypes__) # Parse return type - self.restype_d = self.pack_definition_returntype(self.restype) + self.restype_d = self.pack_definition_returntype(self.__restype__) # Fix missing ctypes in memsync - self.client_fix_memsync_ctypes(self.memsync) + self.client_fix_memsync_ctypes(self.__memsync__) # Reduce memsync for transfer - self.memsync_d = self.pack_definition_memsync(self.memsync) + self.memsync_d = self.pack_definition_memsync(self.__memsync__) # Generate handles on relevant argtype definitions for memsync, adjust definitions with void pointers - self.memsync_handle = self.apply_memsync_to_argtypes_definition(self.memsync, self.argtypes_d) + self.memsync_handle = self.apply_memsync_to_argtypes_definition(self.__memsync__, self.argtypes_d) # Log status - self.log.out(' memsync: \n%s' % pf(self.memsync)) - self.log.out(' argtypes: \n%s' % pf(self.argtypes)) + self.log.out(' memsync: \n%s' % pf(self.__memsync__)) + self.log.out(' argtypes: \n%s' % pf(self.__argtypes__)) self.log.out(' argtypes_d: \n%s' % pf(self.argtypes_d)) - self.log.out(' restype: \n%s' % pf(self.restype)) + self.log.out(' restype: \n%s' % pf(self.__restype__)) self.log.out(' restype_d: \n%s' % pf(self.restype_d)) # Pass argument and return value types as strings ... result = self.__configure_on_server__( self.argtypes_d, self.restype_d, self.memsync_d ) + + + @property + def argtypes(self): + + return self.__argtypes__ + + + @argtypes.setter + def argtypes(self, value): + + if not isinstance(value, list) and not isinstance(value, tuple): + raise TypeError # original ctypes does that + + self.__argtypes__ = value + + + @property + def restype(self): + + return self.__restype__ + + + @restype.setter + def restype(self, value): + + self.__restype__ = value + + + @property + def memsync(self): + + return self.__memsync__ + + + @memsync.setter + def memsync(self, value): + + self.__memsync__ = value diff --git a/src/zugbruecke/core/routine_server.py b/src/zugbruecke/core/routine_server.py index c42b6263..e7939129 100644 --- a/src/zugbruecke/core/routine_server.py +++ b/src/zugbruecke/core/routine_server.py @@ -10,7 +10,7 @@ Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -64,11 +64,16 @@ def __init__(self, parent_dll, routine_name, routine_handler): # Store my own name self.name = routine_name + # Required by arg definitions and contents + self.cache_dict = self.session.cache_dict + self.callback_client = self.session.callback_client + self.is_server = True + # Set routine handler self.handler = routine_handler - def __handle_call__(self, arg_message_list, arg_memory_list): + def __call__(self, arg_message_list, arg_memory_list): """ TODO: Optimize for speed! """ @@ -97,13 +102,16 @@ def __handle_call__(self, arg_message_list, arg_memory_list): # Get new arg message list arg_message_list = self.arg_list_pack(args_list, self.argtypes_d) + # Get new return message list + return_message = self.return_msg_pack(return_value, self.restype_d) + # Log status self.log.out('[routine-server] ... done.') # Pack return package and return it return { 'args': arg_message_list, - 'return_value': return_value, # TODO allow & handle pointers + 'return_value': return_message, # TODO handle memory allocated by DLL in "free form" pointers 'memory': arg_memory_list, 'success': True } @@ -119,7 +127,7 @@ def __handle_call__(self, arg_message_list, arg_memory_list): # Pack return package and return it return { 'args': arg_message_list, - 'return_value': return_value, # TODO allow & handle pointers + 'return_value': return_value, 'memory': arg_memory_list, 'success': False } diff --git a/src/zugbruecke/core/rpc.py b/src/zugbruecke/core/rpc.py index 23cec637..608f1ced 100644 --- a/src/zugbruecke/core/rpc.py +++ b/src/zugbruecke/core/rpc.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/core/session_client.py b/src/zugbruecke/core/session_client.py index ce40593c..b2efc012 100644 --- a/src/zugbruecke/core/session_client.py +++ b/src/zugbruecke/core/session_client.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -32,20 +32,29 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import atexit +from ctypes import ( + _CFuncPtr, + _FUNCFLAG_CDECL, + _FUNCFLAG_USE_ERRNO, + _FUNCFLAG_USE_LASTERROR + ) import os import signal import time +from .const import _FUNCFLAG_STDCALL from .config import get_module_config from .dll_client import dll_client_class from .interpreter import interpreter_session_class from .lib import ( + generate_cache_dict, get_free_port, get_location_of_file ) from .log import log_class from .rpc import ( - mp_client_class + mp_client_class, + mp_server_class ) from .wineenv import ( create_wine_prefix, @@ -116,6 +125,58 @@ def ctypes_WinError(self, code = None, descr = None): return self.client.ctypes_WinError(code, descr) + def ctypes_CFUNCTYPE(self, restype, *argtypes, **kw): + + # If in stage 1, fire up stage 2 + if self.stage == 1: + self.__init_stage_2__() + + return self.get_callback_decorator(_FUNCFLAG_CDECL, restype, *argtypes, **kw) + + + def ctypes_WINFUNCTYPE(self, restype, *argtypes, **kw): # EXPORT + + # If in stage 1, fire up stage 2 + if self.stage == 1: + self.__init_stage_2__() + + return self.get_callback_decorator(_FUNCFLAG_STDCALL, restype, *argtypes, **kw) + + + def get_callback_decorator(self, functype, restype, *argtypes, **kw): + + # If in stage 1, fire up stage 2 + if self.stage == 1: + self.__init_stage_2__() + + flags = functype + + if kw.pop("use_errno", False): + flags |= _FUNCFLAG_USE_ERRNO + if kw.pop("use_last_error", False): + flags |= _FUNCFLAG_USE_LASTERROR + if kw: + raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) + + try: + + # There already is a matching function pointer type available + return self.cache_dict['func_type'][functype][(restype, argtypes, flags)] + + except KeyError: + + # Create new function pointer type class + class FunctionType(_CFuncPtr): + + _argtypes_ = argtypes + _restype_ = restype + _flags_ = flags + + # Store the new type and return + self.cache_dict['func_type'][functype][(restype, argtypes, flags)] = FunctionType + return FunctionType + + def load_library(self, dll_name, dll_type, dll_param = {}): # If in stage 1, fire up stage 2 @@ -208,6 +269,9 @@ def terminate(self): # Tell server via message to terminate self.client.terminate() + # Terminate callback server + self.callback_server.terminate() + # Destruct interpreter session self.interpreter_session.terminate() @@ -240,6 +304,9 @@ def __init_stage_1__(self, parameter, force_stage_2): # Store current working directory self.dir_cwd = os.getcwd() + # Set up a cache dict (packed and unpacked types) + self.cache_dict = generate_cache_dict() + # Set up a dict for loaded dlls self.dll_dict = {} @@ -274,6 +341,9 @@ def __init_stage_2__(self): self.dir_wineprefix = set_wine_env(self.p['dir'], self.p['arch']) create_wine_prefix(self.dir_wineprefix) + # Start RPC server for callback routines + self.__start_callback_server__() + # Prepare python command for ctypes server or interpreter self.__prepare_python_command__() @@ -290,6 +360,22 @@ def __init_stage_2__(self): self.log.out('[session-client] STARTED (STAGE 2).') + def __start_callback_server__(self): + + # Get socket for callback bridge + self.p['port_socket_callback'] = get_free_port() + + # Create server + self.callback_server = mp_server_class( + ('localhost', self.p['port_socket_callback']), + 'zugbruecke_callback_main', + log = self.log + ) + + # Start server into its own thread + self.callback_server.server_forever_in_thread() + + def __start_ctypes_client__(self): # Log status @@ -373,6 +459,7 @@ def __prepare_python_command__(self): ), '--id', self.id, '--port_socket_ctypes', str(self.p['port_socket_ctypes']), + '--port_socket_callback', str(self.p['port_socket_callback']), '--port_socket_log_main', str(self.p['port_socket_log_main']), '--log_level', str(self.p['log_level']), '--logwrite', str(int(self.p['logwrite'])) diff --git a/src/zugbruecke/core/session_server.py b/src/zugbruecke/core/session_server.py index 1c293146..7018f873 100644 --- a/src/zugbruecke/core/session_server.py +++ b/src/zugbruecke/core/session_server.py @@ -10,7 +10,7 @@ Required to run on platform / side: [WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -35,9 +35,13 @@ import traceback from .dll_server import dll_server_class +from .lib import generate_cache_dict from .log import log_class from .path import path_class -from .rpc import mp_server_class +from .rpc import ( + mp_client_class, + mp_server_class + ) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -62,6 +66,9 @@ def __init__(self, session_id, parameter): # Mark session as up self.up = True + # Create dict for struct type definitions + self.cache_dict = generate_cache_dict() + # Offer methods for converting paths path = path_class() self.path_unix_to_wine = path.unix_to_wine @@ -77,6 +84,12 @@ def __init__(self, session_id, parameter): 'oledll': ctypes.OleDLL } + # Connect to callback server + self.callback_client = mp_client_class( + ('localhost', self.p['port_socket_callback']), + 'zugbruecke_callback_main' + ) + # Create server self.server = mp_server_class( ('localhost', self.p['port_socket_ctypes']), diff --git a/src/zugbruecke/core/wineenv.py b/src/zugbruecke/core/wineenv.py index e582eadb..c7e257dd 100644 --- a/src/zugbruecke/core/wineenv.py +++ b/src/zugbruecke/core/wineenv.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/util.py b/src/zugbruecke/util.py index 8abeb288..b3f90233 100644 --- a/src/zugbruecke/util.py +++ b/src/zugbruecke/util.py @@ -14,7 +14,7 @@ https://github.com/python/cpython/blob/master/Lib/ctypes/util.py https://github.com/python/cpython/commit/a76f014278bd1643e93fdfa9e88f9414ce8354a6 - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/zugbruecke/wintypes.py b/src/zugbruecke/wintypes.py index 39829b67..8db4271e 100644 --- a/src/zugbruecke/wintypes.py +++ b/src/zugbruecke/wintypes.py @@ -18,7 +18,7 @@ https://github.com/python/cpython/blob/3.6/LICENSE Modifications from original: - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_avg.py b/tests/test_avg.py index 62657139..51dd7fe0 100644 --- a/tests/test_avg.py +++ b/tests/test_avg.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_bubblesort.py b/tests/test_bubblesort.py index bf58453b..e9f41571 100644 --- a/tests/test_bubblesort.py +++ b/tests/test_bubblesort.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_callback_simple.py b/tests/test_callback_simple.py new file mode 100644 index 00000000..0a63fb27 --- /dev/null +++ b/tests/test_callback_simple.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + tests/test_callback_simple.py: Demonstrates callback routines as arguments + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +# import pytest + +from sys import platform +if any([platform.startswith(os_name) for os_name in ['linux', 'darwin', 'freebsd']]): + import zugbruecke as ctypes +elif platform.startswith('win'): + import ctypes + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CLASSES AND ROUTINES +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class sample_class: + + + def __init__(self): + + self.__dll__ = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + + conveyor_belt = ctypes.WINFUNCTYPE(ctypes.c_int16, ctypes.c_int16) + + self.__sum_elements_from_callback__ = self.__dll__.sum_elements_from_callback + self.__sum_elements_from_callback__.argtypes = (ctypes.c_int16, conveyor_belt) + self.__sum_elements_from_callback__.restype = ctypes.c_int16 + + self.DATA = [1, 6, 8, 4, 9, 7, 4, 2, 5, 2] + + @conveyor_belt + def get_data(index): + print((index, self.DATA[index])) + return self.DATA[index] + + self.__get_data__ = get_data + + + def sum_elements_from_callback(self): + + return self.__sum_elements_from_callback__(len(self.DATA), self.__get_data__) + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def test_callback_simple(): + + sample = sample_class() + + assert 48 == sample.sum_elements_from_callback() diff --git a/tests/test_devide.py b/tests/test_devide.py index c5c8f8eb..a5606d7d 100644 --- a/tests/test_devide.py +++ b/tests/test_devide.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_distance.py b/tests/test_distance.py index 84c652c2..f43179a0 100644 --- a/tests/test_distance.py +++ b/tests/test_distance.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -66,6 +66,16 @@ def __init__(self): self.distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point)) self.distance.restype = ctypes.c_double + # double *distance_pointer(Point *, Point *) + self.__distance_pointer__ = self.__dll__.cookbook_distance_pointer + self.__distance_pointer__.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point)) + self.__distance_pointer__.restype = ctypes.POINTER(ctypes.c_double) + + + def distance_pointer(self, in1, in2): + + return self.__distance_pointer__(in1, in2).contents.value + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # TEST(s) @@ -79,3 +89,13 @@ def test_distance(): p2 = Point(4, 5) assert pytest.approx(4.242640687119285, 0.0000001) == sample.distance(p1, p2) + + +def test_distance_pointer(): + + sample = sample_class() + + p1 = Point(1, 2) + p2 = Point(4, 5) + + assert pytest.approx(4.242640687119285, 0.0000001) == sample.distance_pointer(p1, p2) diff --git a/tests/test_error_argtypes.py b/tests/test_error_argtypes.py new file mode 100644 index 00000000..356ac810 --- /dev/null +++ b/tests/test_error_argtypes.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + tests/test_error_argtypes.py: Checks for proper error handling of argtypes definition + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import pytest + +from sys import platform +if any([platform.startswith(os_name) for os_name in ['linux', 'darwin', 'freebsd']]): + import zugbruecke as ctypes +elif platform.startswith('win'): + import ctypes + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def test_argtypes_neither_list_nor_tuple(): + + dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + sqrt_int = dll.sqrt_int + + with pytest.raises(TypeError): + sqrt_int.argtypes = ctypes.c_int16 diff --git a/tests/test_gauss_elimination.py b/tests/test_gauss_elimination.py index ce76db80..9bc44813 100644 --- a/tests/test_gauss_elimination.py +++ b/tests/test_gauss_elimination.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_gdc.py b/tests/test_gdc.py index 1c016d52..833be684 100644 --- a/tests/test_gdc.py +++ b/tests/test_gdc.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_get_const_int.py b/tests/test_get_const_int.py new file mode 100644 index 00000000..ec06aef6 --- /dev/null +++ b/tests/test_get_const_int.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + tests/test_sqrt_int.py: Test function call without parameters + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import pytest + +from sys import platform +if any([platform.startswith(os_name) for os_name in ['linux', 'darwin', 'freebsd']]): + import zugbruecke as ctypes +elif platform.startswith('win'): + import ctypes + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def test_sqrt_int(): + + dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + + get_const_int = dll.get_const_int + get_const_int.restype = ctypes.c_int16 + + assert 7 == get_const_int() + + +def test_sqrt_int_with_tuple(): + + dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + + get_const_int = dll.get_const_int + get_const_int.argtypes = tuple() + get_const_int.restype = ctypes.c_int16 + + assert 7 == get_const_int() + + +def test_sqrt_int_with_list(): + + dll = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + + get_const_int = dll.get_const_int + get_const_int.argtypes = [] + get_const_int.restype = ctypes.c_int16 + + assert 7 == get_const_int() diff --git a/tests/test_in_mantel.py b/tests/test_in_mantel.py index 7ad48e54..5ba14894 100644 --- a/tests/test_in_mantel.py +++ b/tests/test_in_mantel.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_mix_rgb_colors.py b/tests/test_mix_rgb_colors.py index e0d51439..d125040c 100644 --- a/tests/test_mix_rgb_colors.py +++ b/tests/test_mix_rgb_colors.py @@ -10,7 +10,7 @@ Required to run on platform / side: [UNIX, WINE] - Copyright (C) 2017 Sebastian M. Ernst + Copyright (C) 2017-2018 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_sqrt_int.py b/tests/test_sqrt_int.py new file mode 100644 index 00000000..09db6a9f --- /dev/null +++ b/tests/test_sqrt_int.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + tests/test_sqrt_int.py: Test function with single parameter + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import pytest + +from sys import platform +if any([platform.startswith(os_name) for os_name in ['linux', 'darwin', 'freebsd']]): + import zugbruecke as ctypes +elif platform.startswith('win'): + import ctypes + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CLASSES AND ROUTINES +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class sample_class: + + + def __init__(self): + + self.__dll__ = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + + self.sqrt_int = self.__dll__.sqrt_int + self.sqrt_int.argtypes = (ctypes.c_int16,) + self.sqrt_int.restype = ctypes.c_int16 + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def test_sqrt_int(): + + sample = sample_class() + + assert 9 == sample.sqrt_int(3) diff --git a/tests/test_vector3d_add.py b/tests/test_vector3d_add.py new file mode 100644 index 00000000..52bdb2c7 --- /dev/null +++ b/tests/test_vector3d_add.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +""" + +ZUGBRUECKE +Calling routines in Windows DLLs from Python scripts running on unixlike systems +https://github.com/pleiszenburg/zugbruecke + + tests/test_vector3d_add.py: Tests pointer to struct type return value + + Required to run on platform / side: [UNIX, WINE] + + Copyright (C) 2017-2018 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/zugbruecke/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + + +""" + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import pytest + +from sys import platform +if any([platform.startswith(os_name) for os_name in ['linux', 'darwin', 'freebsd']]): + import zugbruecke as ctypes +elif platform.startswith('win'): + import ctypes + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CLASSES AND ROUTINES +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class vector3d(ctypes.Structure): + + + _fields_ = [ + ('x', ctypes.c_int16), + ('y', ctypes.c_int16), + ('z', ctypes.c_int16) + ] + + +class sample_class: + + + def __init__(self): + + self.__dll__ = ctypes.windll.LoadLibrary('tests/demo_dll.dll') + + # vector3d *distance(vector3d *, vector3d *) + self.__vector3d_add__ = self.__dll__.vector3d_add + self.__vector3d_add__.argtypes = (ctypes.POINTER(vector3d), ctypes.POINTER(vector3d)) + self.__vector3d_add__.restype = ctypes.POINTER(vector3d) + + + def vector3d_add(self, v1, v2): + + def struct_from_dict(in_dict): + return vector3d(*tuple(in_dict[key] for key in ['x', 'y', 'z'])) + def dict_from_struct(in_struct): + return {key: getattr(in_struct.contents, key) for key in ['x', 'y', 'z']} + + return dict_from_struct(self.__vector3d_add__( + struct_from_dict(v1), struct_from_dict(v2) + )) + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def test_vector3d_add(): + + v1 = {'x': 5, 'y': 7, 'z': 2} + v2 = {'x': 1, 'y': 9, 'z': 8} + added = {'x': 6, 'y': 16, 'z': 10} + + sample = sample_class() + + assert added == sample.vector3d_add(v1, v2)