[pypy-svn] r61799 - pypy/trunk/pypy/lib/_ctypes
afa at codespeak.net
afa at codespeak.net
Thu Feb 12 17:57:19 CET 2009
Author: afa
Date: Thu Feb 12 17:57:16 2009
New Revision: 61799
Modified:
pypy/trunk/pypy/lib/_ctypes/function.py
Log:
Great progress toward a usable comtypes module on pypy:
our ctypes module can now call COM functions and methods.
Support for OUT parameters has been added, it's probably incomplete.
Next: support the _type_='X' (BSTR) objects.
Everything in pure python: _rawffi really rocks.
Modified: pypy/trunk/pypy/lib/_ctypes/function.py
==============================================================================
--- pypy/trunk/pypy/lib/_ctypes/function.py (original)
+++ pypy/trunk/pypy/lib/_ctypes/function.py Thu Feb 12 17:57:16 2009
@@ -31,6 +31,10 @@
callable = None
_ptr = None
_buffer = None
+ # win32 COM properties
+ _paramflags = None
+ _com_index = None
+ _com_iid = None
def _getargtypes(self):
return self._argtypes_
@@ -70,11 +74,13 @@
argument = args[0]
if isinstance(argument, (int, long)):
+ # direct construction from raw address
ffiargs, ffires = self._ffishapes(self._argtypes_, self._restype_)
self._ptr = _rawffi.FuncPtr(argument, ffiargs, ffires,
self._flags_)
self._buffer = self._ptr.byptr()
elif callable(argument):
+ # A callback into python
self.callable = argument
ffiargs, ffires = self._ffishapes(self._argtypes_, self._restype_)
self._ptr = _rawffi.CallbackPtr(self._wrap_callable(argument,
@@ -82,6 +88,7 @@
ffiargs, ffires)
self._buffer = self._ptr.byptr()
elif isinstance(argument, tuple) and len(argument) == 2:
+ # function exported from a shared library
import ctypes
self.name, self.dll = argument
if isinstance(self.dll, str):
@@ -90,12 +97,23 @@
ptr = self._getfuncptr([], ctypes.c_int)
self._buffer = ptr.byptr()
+ elif (sys.platform == 'win32' and
+ len(args) >= 2 and isinstance(args[0], (int, long))):
+ # A COM function call, by index
+ ffiargs, ffires = self._ffishapes(self._argtypes_, self._restype_)
+ self._com_index = args[0] + 0x1000
+ self.name = args[1]
+ if len(args) > 2:
+ self._paramflags = args[2]
+ # XXX ignored iid = args[3]
+
elif len(args) == 0:
+ # Empty function object.
# this is needed for casts
self._buffer = _rawffi.Array('P')(1)
return
else:
- raise TypeError("Unknown constructor %s" % (argument,))
+ raise TypeError("Unknown constructor %s" % (args,))
def _wrap_callable(self, to_call, argtypes):
def f(*args):
@@ -118,25 +136,26 @@
return res
return
argtypes = self._argtypes_
+
+ if self._com_index:
+ from ctypes import cast, c_void_p, POINTER
+ thisarg = cast(args[0], POINTER(POINTER(c_void_p))).contents
+ argtypes = [c_void_p] + list(argtypes)
+ args = list(args)
+ args[0] = args[0].value
+ else:
+ thisarg = None
+
if argtypes is None:
argtypes = self._guess_argtypes(args)
- else:
- dif = len(args) - len(argtypes)
- if dif < 0:
- raise TypeError("Not enough arguments")
- if dif > 0:
- cut = len(args) - dif
- argtypes = argtypes[:] + self._guess_argtypes(args[cut:])
+ argtypes, argsandobjs = self._wrap_args(argtypes, args)
+
restype = self._restype_
- funcptr = self._getfuncptr(argtypes, restype)
- argsandobjs = self._wrap_args(argtypes, args)
+ funcptr = self._getfuncptr(argtypes, restype, thisarg)
resbuffer = funcptr(*[arg._buffer for _, arg in argsandobjs])
- if restype is not None:
- if not isinstance(restype, _CDataMeta):
- return restype(resbuffer[0])
- return restype._CData_retval(resbuffer)
+ return self._build_result(restype, resbuffer, argtypes, argsandobjs)
- def _getfuncptr(self, argtypes, restype):
+ def _getfuncptr(self, argtypes, restype, thisarg=None):
if self._ptr is not None:
return self._ptr
if restype is None or not isinstance(restype, _CDataMeta):
@@ -151,6 +170,11 @@
self._ptr = ptr
return ptr
+ if self._com_index:
+ # extract the address from the object's virtual table
+ ptr = thisarg[self._com_index - 0x1000]
+ return _rawffi.FuncPtr(ptr, argshapes, resshape, self._flags_)
+
cdll = self.dll._handle
try:
return cdll.ptr(self.name, argshapes, resshape, self._flags_)
@@ -196,11 +220,70 @@
return res
def _wrap_args(self, argtypes, args):
- try:
- return [argtype._CData_input(arg) for argtype, arg in
- zip(argtypes, args)]
- except (UnicodeError, TypeError), e:
- raise ArgumentError(str(e))
+ wrapped_args = []
+ consumed = 0
+ for i, argtype in enumerate(argtypes):
+ if i > 0 and self._paramflags is not None:
+ idlflag, name = self._paramflags[i-1]
+ if idlflag == 2: # OUT
+ import ctypes
+ arg = ctypes.byref(argtype._type_())
+ wrapped_args.append((arg, arg))
+ continue
+
+ if consumed == len(args):
+ raise TypeError("Not enough arguments")
+ arg = args[consumed]
+ try:
+ wrapped = argtype._CData_input(arg)
+ except (UnicodeError, TypeError), e:
+ raise ArgumentError(str(e))
+ wrapped_args.append(wrapped)
+ consumed += 1
+
+ if len(wrapped_args) < len(args):
+ extra = args[len(wrapped_args):]
+ extra_types = self._guess_argtypes(extra)
+ for arg, argtype in zip(extra, extra_types):
+ try:
+ wrapped = argtype._CData_input(arg)
+ except (UnicodeError, TypeError), e:
+ raise ArgumentError(str(e))
+ wrapped_args.append(wrapped)
+ argtypes.extend(extra_types)
+ return argtypes, wrapped_args
+
+ def _build_result(self, restype, resbuffer, argtypes, argsandobjs):
+ """Build the function result:
+ If there is no OUT parameter, return the actual function result
+ If there is one OUT parameter, return it
+ If there are many OUT parameters, return a tuple"""
+ results = []
+ if self._paramflags:
+ for argtype, (_, obj), paramflag in zip(argtypes[1:], argsandobjs[1:],
+ self._paramflags):
+ idlflag, name = paramflag
+ if idlflag == 2: # OUT
+ val = obj.contents
+
+ # XXX find a better way to detect pointers to pointers
+ basetype = argtype._type_._type_
+ if isinstance(basetype, str) and basetype != 'P':
+ val = val.value
+
+ results.append(val)
+
+ if results:
+ if len(results) == 1:
+ return results[0]
+ else:
+ return tuple(results)
+
+ # No output parameter, return the actual function result.
+ if restype is not None:
+ if not isinstance(restype, _CDataMeta):
+ return restype(resbuffer[0])
+ return restype._CData_retval(resbuffer)
def __del__(self):
if self._needs_free:
More information about the Pypy-commit
mailing list