import ctypes class C_function: """This class wraps a Python function into its C equivalent in the given library""" types = ctypes.__dict__ @classmethod def register_type(cls, typename, obj): """Add an acceptable argument or retval type to this.""" cls.types[typename] = obj def __init__(self, dll, error_check=None): """Set the library to reference the function name against.""" self.library = ctypes.CDLL(dll) self.error_check = error_check def __call__(self, f): """Performs the actual function wrapping. This extracts function details from annotations and signature. For example, to wrap puts, the following suffices (on Linux): >>> @C_function("libc.so.6") ... def puts(my_string: "c_char_p") -> "c_int": ... return puts.c_function(my_string) ... >>> x = puts("hello, world!") hello, world! >>> x 14 Note that to call the actual C function, you just reference it by asking for the local function's .c_function attribute. """ # get the function's name, arguments, and arg types name = f.__name__ args = f.__code__.co_varnames annotations = f.__annotations__ # get the function itself function = self.library[name] # set the function's call signature argtypes = [] for arg in args: annotation = annotations[arg] if annotation.startswith("*"): argtypes.append(ctypes.POINTER(self.types[annotation[1:]])) else: argtypes.append(self.types[annotation]) function.argtypes = argtypes # and its return value restype = annotations['return'] if restype.startswith("*"): function.restype = ctypes.POINTER(self.types[restype[1:]]) else: function.restype = self.types[restype] # set its error handler if self.error_check: function.errcheck = self.error_check # set the c_function as an attribute of f f.c_function = function return f