How this C function was called through ctypes this way?

eryk sun eryksun at
Thu Feb 4 06:33:59 EST 2016

On Thu, Feb 4, 2016 at 3:33 AM,  <jfong at> wrote:
> class DoubleArrayType:
>     def from_param(self, param):
> [snip]
> DoubleArray = DoubleArrayType()
> _avg = _mod.avg
> _avg.argtypes = (DoubleArray, ctypes.c_int)
> [snip]
> What confuse me are:
> (1) at line: _avg.argtypes = (DoubleArray, ctypes.c_int)
> The "DoubleArry" is an instance of the class "DoubleArrayType",
> Can it appear at where a type was expected?

ctypes generally (notwithstanding out parameters defined via
paramflags) requires only that an object set in argtypes has a
from_param callable to check and convert the corresponding argument.
Usually classes are set in argtypes, so usually from_param is a
classmethod. In this case the author chose to set an instance in
argtypes and use an instance method. While unusual, this works all the

> (2) How the method "from_param" was invoked? I can't see any
> mechanism to reach it from the "_avg(values, len(values))" call.

A ctypes function pointer is a callable implemented in C. Its tp_call
slot function is PyCFuncPtr in Modules/_ctypes/_ctypes.c, which in
turn calls _ctypes_callproc in Modules/_ctypes/callproc.c. Here's the
snippet of code from _ctypes_callproc that's responsible for calling
the from_param converter:

    converter = PyTuple_GET_ITEM(argtypes, i);
    v = PyObject_CallFunctionObjArgs(converter, arg, NULL);

Note that "argtypes" in the above is a misnomer; it's actually the
tuple of from_param converters.

Each from_param return value is passed to ConvParam, which handles
ctypes objects, integers, strings, and None. If the object isn't of
those but has an _as_parameter_ attribute, ConvParam calls itself
recursively using the _as_parameter_ value.

> _mod = ctypes.cdll.LoadLibrary(_path)

Calling ctypes.CDLL directly is preferable since it allows passing
parameters such as "mode" and "use_errno".

IMO, the ctypes.cdll and ctypes.windll loaders should be avoided in
general, especially on Windows, since their attribute-based access
(e.g. windll.user32) caches libraries, which in turn cache
function-pointer attributes. You don't want function pointer instances
being shared across unrelated packages. They may not use compatible
prototypes and errcheck functions. Each package, module, or script
should create private instances of CDLL, PyDLL, and WinDLL for a given
shared library.

More information about the Python-list mailing list