Open darleybarreto opened 3 years ago
typedef struct tagPyCArgObject PyCArgObject;
typedef struct tagCDataObject CDataObject;
typedef PyObject *(* GETFUNC)(void *, Py_ssize_t size);
typedef PyObject *(* SETFUNC)(void *, PyObject *value, Py_ssize_t size);
typedef PyCArgObject *(* PARAMFUNC)(CDataObject *obj);
struct tagCDataObject {
PyObject_HEAD
char *b_ptr; /* pointer to memory block */
int b_needsfree; /* need _we_ free the memory? */
CDataObject *b_base; /* pointer to base object or NULL */
Py_ssize_t b_size; /* size of memory block in bytes */
Py_ssize_t b_length; /* number of references we need */
Py_ssize_t b_index; /* index of this object into base's
b_object list */
PyObject *b_objects; /* dictionary of references we need to keep, or Py_None */
union value b_value;
};
typedef struct {
PyObject_VAR_HEAD
ffi_closure *pcl_write; /* the C callable, writeable */
void *pcl_exec; /* the C callable, executable */
ffi_cif cif;
int flags;
PyObject *converters;
PyObject *callable;
PyObject *restype;
SETFUNC setfunc;
ffi_type *ffi_restype;
ffi_type *atypes[1];
} CThunkObject;
extern PyTypeObject PyCThunk_Type;
#define CThunk_CheckExact(v) Py_IS_TYPE(v, &PyCThunk_Type)
typedef struct {
/* First part identical to tagCDataObject */
PyObject_HEAD
char *b_ptr; /* pointer to memory block */
int b_needsfree; /* need _we_ free the memory? */
CDataObject *b_base; /* pointer to base object or NULL */
Py_ssize_t b_size; /* size of memory block in bytes */
Py_ssize_t b_length; /* number of references we need */
Py_ssize_t b_index; /* index of this object into base's
b_object list */
PyObject *b_objects; /* list of references we need to keep */
union value b_value;
/* end of tagCDataObject, additional fields follow */
CThunkObject *thunk;
PyObject *callable;
/* These two fields will override the ones in the type's stgdict if
they are set */
PyObject *converters;
PyObject *argtypes;
PyObject *restype;
PyObject *checker;
PyObject *errcheck;
#ifdef MS_WIN32
int index;
GUID *iid;
#endif
PyObject *paramflags;
} PyCFuncPtrObject;
/* a table entry describing a predefined ctypes type */
struct fielddesc {
char code;
SETFUNC setfunc;
GETFUNC getfunc;
ffi_type *pffi_type; /* always statically allocated */
SETFUNC setfunc_swapped;
GETFUNC getfunc_swapped;
};
typedef struct {
PyObject_HEAD
Py_ssize_t offset;
Py_ssize_t size;
Py_ssize_t index; /* Index into CDataObject's
object array */
PyObject *proto; /* a type or NULL */
GETFUNC getfunc; /* getter function if proto is NULL */
SETFUNC setfunc; /* setter function if proto is NULL */
int anonymous;
} CFieldObject;
/* A subclass of PyDictObject, used as the instance dictionary of ctypes
metatypes */
typedef struct {
PyDictObject dict; /* first part identical to PyDictObject */
Py_ssize_t size; /* number of bytes */
Py_ssize_t align; /* alignment requirements */
Py_ssize_t length; /* number of fields */
ffi_type ffi_type_pointer;
PyObject *proto; /* Only for Pointer/ArrayObject */
SETFUNC setfunc; /* Only for simple objects */
GETFUNC getfunc; /* Only for simple objects */
PARAMFUNC paramfunc;
/* Following fields only used by PyCFuncPtrType_Type instances */
PyObject *argtypes; /* tuple of CDataObjects */
PyObject *converters; /* tuple([t.from_param for t in argtypes]) */
PyObject *restype; /* CDataObject or NULL */
PyObject *checker;
int flags; /* calling convention and such */
/* pep3118 fields, pointers neeed PyMem_Free */
char *format;
int ndim;
Py_ssize_t *shape;
/* Py_ssize_t *strides; */ /* unused in ctypes */
/* Py_ssize_t *suboffsets; */ /* unused in ctypes */
} StgDictObject;
struct tagPyCArgObject {
PyObject_HEAD
ffi_type *pffi_type;
char tag;
union {
char c;
char b;
short h;
int i;
long l;
long long q;
long double D;
double d;
float f;
void *p;
} value;
PyObject *obj;
Py_ssize_t size; /* for the 'V' tag */
};
I didn't give a deeper look into types like StgDictObject
and tagPyCArgObject
.
PyTypeObject PyCArrayType_Type;
PyTypeObject PyCPointerType_Type;
PyTypeObject PyCFuncPtrType_Type;
PyTypeObject PyCStructType_Type;
PyTypeObject UnionType_Type;
PyTypeObject PyCSimpleType_Type;
All these metaclasses but PyCPointerType_Type and PyCSimpleType_Type implements CDataType_methods
:
static PyMethodDef CDataType_methods[] = {
{ "from_param", CDataType_from_param, METH_O, from_param_doc },
{ "from_address", CDataType_from_address, METH_O, from_address_doc },
{ "from_buffer", CDataType_from_buffer, METH_VARARGS, from_buffer_doc, },
{ "from_buffer_copy", CDataType_from_buffer_copy, METH_VARARGS, from_buffer_copy_doc, },
{ "in_dll", CDataType_in_dll, METH_VARARGS, in_dll_doc },
};
PyCSimpleType_Type uses PyCSimpleType_methods
:
static PyMethodDef PyCSimpleType_methods[] = {
{ "from_param", PyCSimpleType_from_param, METH_O, from_param_doc },
{ "from_address", CDataType_from_address, METH_O, from_address_doc },
{ "from_buffer", CDataType_from_buffer, METH_VARARGS, from_buffer_doc, },
{ "from_buffer_copy", CDataType_from_buffer_copy, METH_VARARGS, from_buffer_copy_doc, },
{ "in_dll", CDataType_in_dll, METH_VARARGS, in_dll_doc},
};
PyCPointerType_Type uses PyCPointerType_methods
:
static PyMethodDef PyCPointerType_methods[] = {
{ "from_address", CDataType_from_address, METH_O, from_address_doc },
{ "from_buffer", CDataType_from_buffer, METH_VARARGS, from_buffer_doc, },
{ "from_buffer_copy", CDataType_from_buffer_copy, METH_VARARGS, from_buffer_copy_doc, },
{ "in_dll", CDataType_in_dll, METH_VARARGS, in_dll_doc},
{ "from_param", (PyCFunction)PyCPointerType_from_param, METH_O, from_param_doc},
{ "set_type", (PyCFunction)PyCPointerType_set_type, METH_O },
};
Al the above classes use CDataType_as_sequence
as tp_as_sequence
(sequence protocol):
static PySequenceMethods CDataType_as_sequence = {
0, /* inquiry sq_length; */
0, /* binaryfunc sq_concat; */
CDataType_repeat, /* intargfunc sq_repeat; */
0, /* intargfunc sq_item; */
0, /* intintargfunc sq_slice; */
0, /* intobjargproc sq_ass_item; */
0, /* intintobjargproc sq_ass_slice; */
0, /* objobjproc sq_contains; */
0, /* binaryfunc sq_inplace_concat; */
0, /* intargfunc sq_inplace_repeat; */
};
PyTypeObject PyCData_Type;
PyTypeObject PyCArray_Type; // subclass of PyCArrayType_Type
PyTypeObject PyCPointer_Type; // subclass of PyCPointerType_Type
PyTypeObject PyCFuncPtr_Type; // subclass of PyCFuncPtrType_Type
PyTypeObject Struct_Type; // subclass of PyCStructType_Type
PyTypeObject Union_Type; // subclass of UnionType_Type
PyTypeObject Simple_Type; // subclass of PyCSimpleType_Type
All the above classes but PyCFuncPtr_Type
have tp_basicsize
as sizeof(CDataObject)
, PyCFuncPtr_Type
has sizeof(PyCFuncPtrObject)
. They're all subclass of PyCData_Type
.
Each class has some particular methods, which needs to be explored individually. Some of the shared methods, for instance Struct_Type
, Union_Type
, PyCArray_Type
and Simple_Type
have the same new
. Another example is that they all have the same method on tp_as_buffer
slot.
PyPy implements PyCData_Type
as a metaclass, if I'm not mistaken. CDataObject
is it's subclass, all the above classes implements CDataObject
and their respective superclasses.
/**
Name methods, members, getsets
==============================================================================
PyCStructType_Type __new__(), from_address(), __mul__(), from_param()
UnionType_Type __new__(), from_address(), __mul__(), from_param()
PyCPointerType_Type __new__(), from_address(), __mul__(), from_param(), set_type()
PyCArrayType_Type __new__(), from_address(), __mul__(), from_param()
PyCSimpleType_Type __new__(), from_address(), __mul__(), from_param()
PyCData_Type
Struct_Type __new__(), __init__()
PyCPointer_Type __new__(), __init__(), _as_parameter_, contents
PyCArray_Type __new__(), __init__(), _as_parameter_, __get/setitem__(), __len__()
Simple_Type __new__(), __init__(), _as_parameter_
PyCField_Type
PyCStgDict_Type
==============================================================================
**/
Metaclass `*Type_Type` methods
------------------------------------
It has some similarity to the byref() construct compared to pointer()
from_address(addr)
- construct an instance from a given memory block (sharing this memory block)
from_param(obj)
- typecheck and convert a Python object into a C function call parameter
The result may be an instance of the type, or an integer or tuple
(typecode, value[, obj])
PyCData_Type subclasses instance methods/properties
---------------------------------------------------
_as_parameter_
- convert self into a C function call parameter
This is either an integer, or a 3-tuple (typecode, value, obj)
functions
---------
sizeof(cdata)
- return the number of bytes the buffer contains
sizeof(ctype)
- return the number of bytes the buffer of an instance would contain
byref(cdata)
addressof(cdata)
pointer(cdata)
POINTER(ctype)
bytes(cdata)
- return the buffer contents as a sequence of bytes (which is currently a string)
PyCStructType_Type - a meta type/class. Creating a new class using this one as
__metaclass__ will call the constructor StructUnionType_new. It replaces the
tp_dict member with a new instance of StgDict, and initializes the C
accessible fields somehow.
The PyCPointerType_Type metaclass must ensure that the subclass of Pointer can be
created. It must check for a _type_ attribute in the class. Since are no
runtime created properties, a CField is probably *not* needed ?
class IntPointer(Pointer):
_type_ = "i"
The PyCPointer_Type provides the functionality: a contents method/property, a
size property/method, and the sequence protocol.
PyCArrayType_new ensures that the new Array subclass created has a _length_
attribute, and a _type_ attribute.
PyCSimpleType_new ensures that the new Simple_Type subclass created has a valid
_type_ attribute.
default __ctypes_from_outparam__ method returns self
Function to load shared libraries - unix
static PyObject *py_dl_open(PyObject *self, PyObject *args)
{
PyObject *name, *name2;
const char *name_str;
void * handle;
#if HAVE_DECL_RTLD_LOCAL
int mode = RTLD_NOW | RTLD_LOCAL;
#else
/* cygwin doesn't define RTLD_LOCAL */
int mode = RTLD_NOW;
#endif
if (!PyArg_ParseTuple(args, "O|i:dlopen", &name, &mode))
return NULL;
mode |= RTLD_NOW;
if (name != Py_None) {
if (PyUnicode_FSConverter(name, &name2) == 0)
return NULL;
name_str = PyBytes_AS_STRING(name2);
} else {
name_str = NULL;
name2 = NULL;
}
if (PySys_Audit("ctypes.dlopen", "O", name) < 0) {
return NULL;
}
handle = ctypes_dlopen(name_str, mode);
Py_XDECREF(name2);
if (!handle) {
const char *errmsg = ctypes_dlerror();
if (!errmsg)
errmsg = "dlopen() error";
PyErr_SetString(PyExc_OSError,
errmsg);
return NULL;
}
return PyLong_FromVoidPtr(handle);
}
Function to load shared libraries - windows
static const char load_library_doc[] =
"LoadLibrary(name, load_flags) -> handle\n\
\n\
Load an executable (usually a DLL), and return a handle to it.\n\
The handle may be used to locate exported functions in this\n\
module. load_flags are as defined for LoadLibraryEx in the\n\
Windows API.\n";
static PyObject *load_library(PyObject *self, PyObject *args)
{
PyObject *nameobj;
int load_flags = 0;
HMODULE hMod;
DWORD err;
if (!PyArg_ParseTuple(args, "U|i:LoadLibrary", &nameobj, &load_flags))
return NULL;
if (PySys_Audit("ctypes.dlopen", "O", nameobj) < 0) {
return NULL;
}
WCHAR *name = PyUnicode_AsWideCharString(nameobj, NULL);
if (!name)
return NULL;
Py_BEGIN_ALLOW_THREADS
/* bpo-36085: Limit DLL search directories to avoid pre-loading
* attacks and enable use of the AddDllDirectory function.
*/
hMod = LoadLibraryExW(name, NULL, (DWORD)load_flags);
err = hMod ? 0 : GetLastError();
Py_END_ALLOW_THREADS
PyMem_Free(name);
if (err == ERROR_MOD_NOT_FOUND) {
PyErr_Format(PyExc_FileNotFoundError,
("Could not find module '%.500S' (or one of its "
"dependencies). Try using the full path with "
"constructor syntax."),
nameobj);
return NULL;
} else if (err) {
return PyErr_SetFromWindowsErr(err);
}
#ifdef _WIN64
return PyLong_FromVoidPtr(hMod);
#else
return Py_BuildValue("i", hMod);
#endif
}
Function to get symbol addr from lib
static PyObject *py_dl_sym(PyObject *self, PyObject *args)
{
char *name;
void *handle;
void *ptr;
if (!PyArg_ParseTuple(args, "O&s:dlsym",
&_parse_voidp, &handle, &name))
return NULL;
if (PySys_Audit("ctypes.dlsym/handle", "O", args) < 0) {
return NULL;
}
ptr = ctypes_dlsym((void*)handle, name);
if (!ptr) {
PyErr_SetString(PyExc_OSError,
ctypes_dlerror());
return NULL;
}
return PyLong_FromVoidPtr(ptr);
}
Function to call a function from shared library
/*
* Only for debugging so far: So that we can call CFunction instances
*
* XXX Needs to accept more arguments: flags, argtypes, restype
*/
static PyObject *
call_function(PyObject *self, PyObject *args)
{
void *func;
PyObject *arguments;
PyObject *result;
if (!PyArg_ParseTuple(args,
"O&O!",
&_parse_voidp, &func,
&PyTuple_Type, &arguments))
return NULL;
if (PySys_Audit("ctypes.call_function", "nO",
(Py_ssize_t)func, arguments) < 0) {
return NULL;
}
result = _ctypes_callproc((PPROC)func,
arguments,
#ifdef MS_WIN32
NULL,
NULL,
#endif
0, /* flags */
NULL, /* self->argtypes */
NULL, /* self->restype */
NULL); /* checker */
return result;
}
Actually calling the function
/*
* Requirements, must be ensured by the caller:
* - argtuple is tuple of arguments
* - argtypes is either NULL, or a tuple of the same size as argtuple
*
* - XXX various requirements for restype, not yet collected
*/
PyObject *_ctypes_callproc(PPROC pProc,
PyObject *argtuple,
#ifdef MS_WIN32
IUnknown *pIunk,
GUID *iid,
#endif
int flags,
PyObject *argtypes, /* misleading name: This is a tuple of
methods, not types: the .from_param
class methods of the types */
PyObject *restype,
PyObject *checker)
{
Py_ssize_t i, n, argcount, argtype_count;
void *resbuf;
struct argument *args, *pa;
ffi_type **atypes;
ffi_type *rtype;
void **avalues;
PyObject *retval = NULL;
n = argcount = PyTuple_GET_SIZE(argtuple);
#ifdef MS_WIN32
/* an optional COM object this pointer */
if (pIunk)
++argcount;
#endif
if (argcount > CTYPES_MAX_ARGCOUNT)
{
PyErr_Format(PyExc_ArgError, "too many arguments (%zi), maximum is %i",
argcount, CTYPES_MAX_ARGCOUNT);
return NULL;
}
args = (struct argument *)alloca(sizeof(struct argument) * argcount);
if (!args) {
PyErr_NoMemory();
return NULL;
}
memset(args, 0, sizeof(struct argument) * argcount);
argtype_count = argtypes ? PyTuple_GET_SIZE(argtypes) : 0;
#ifdef MS_WIN32
if (pIunk) {
args[0].ffi_type = &ffi_type_pointer;
args[0].value.p = pIunk;
pa = &args[1];
} else
#endif
pa = &args[0];
/* Convert the arguments */
for (i = 0; i < n; ++i, ++pa) {
PyObject *converter;
PyObject *arg;
int err;
arg = PyTuple_GET_ITEM(argtuple, i); /* borrowed ref */
/* For cdecl functions, we allow more actual arguments
than the length of the argtypes tuple.
This is checked in _ctypes::PyCFuncPtr_Call
*/
if (argtypes && argtype_count > i) {
PyObject *v;
converter = PyTuple_GET_ITEM(argtypes, i);
v = PyObject_CallOneArg(converter, arg);
if (v == NULL) {
_ctypes_extend_error(PyExc_ArgError, "argument %zd: ", i+1);
goto cleanup;
}
err = ConvParam(v, i+1, pa);
Py_DECREF(v);
if (-1 == err) {
_ctypes_extend_error(PyExc_ArgError, "argument %zd: ", i+1);
goto cleanup;
}
} else {
err = ConvParam(arg, i+1, pa);
if (-1 == err) {
_ctypes_extend_error(PyExc_ArgError, "argument %zd: ", i+1);
goto cleanup; /* leaking ? */
}
}
}
rtype = _ctypes_get_ffi_type(restype);
resbuf = alloca(max(rtype->size, sizeof(ffi_arg)));
#ifdef _Py_MEMORY_SANITIZER
/* ffi_call actually initializes resbuf, but from asm, which
* MemorySanitizer can't detect. Avoid false positives from MSan. */
if (resbuf != NULL) {
__msan_unpoison(resbuf, max(rtype->size, sizeof(ffi_arg)));
}
#endif
avalues = (void **)alloca(sizeof(void *) * argcount);
atypes = (ffi_type **)alloca(sizeof(ffi_type *) * argcount);
if (!resbuf || !avalues || !atypes) {
PyErr_NoMemory();
goto cleanup;
}
for (i = 0; i < argcount; ++i) {
atypes[i] = args[i].ffi_type;
#ifdef CTYPES_PASS_BY_REF_HACK
size_t size = atypes[i]->size;
if (IS_PASS_BY_REF(size)) {
void *tmp = alloca(size);
if (atypes[i]->type == FFI_TYPE_STRUCT)
memcpy(tmp, args[i].value.p, size);
else
memcpy(tmp, (void*)&args[i].value, size);
avalues[i] = tmp;
}
else
#endif
if (atypes[i]->type == FFI_TYPE_STRUCT)
avalues[i] = (void *)args[i].value.p;
else
avalues[i] = (void *)&args[i].value;
}
if (-1 == _call_function_pointer(flags, pProc, avalues, atypes,
rtype, resbuf,
Py_SAFE_DOWNCAST(argcount,
Py_ssize_t,
int)))
goto cleanup;
#ifdef WORDS_BIGENDIAN
/* libffi returns the result in a buffer with sizeof(ffi_arg). This
causes problems on big endian machines, since the result buffer
address cannot simply be used as result pointer, instead we must
adjust the pointer value:
*/
/*
XXX I should find out and clarify why this is needed at all,
especially why adjusting for ffi_type_float must be avoided on
64-bit platforms.
*/
if (rtype->type != FFI_TYPE_FLOAT
&& rtype->type != FFI_TYPE_STRUCT
&& rtype->size < sizeof(ffi_arg))
{
resbuf = (char *)resbuf + sizeof(ffi_arg) - rtype->size;
}
#endif
#ifdef MS_WIN32
if (iid && pIunk) {
if (*(int *)resbuf & 0x80000000)
retval = GetComError(*(HRESULT *)resbuf, iid, pIunk);
else
retval = PyLong_FromLong(*(int *)resbuf);
} else if (flags & FUNCFLAG_HRESULT) {
if (*(int *)resbuf & 0x80000000)
retval = PyErr_SetFromWindowsErr(*(int *)resbuf);
else
retval = PyLong_FromLong(*(int *)resbuf);
} else
#endif
retval = GetResult(restype, resbuf, checker);
cleanup:
for (i = 0; i < argcount; ++i)
Py_XDECREF(args[i].keep);
return retval;
}
In the snippet bellow we can see that _CFuncPtr
uses _argtypes_
, _restype_
and _flags_
...
class CDLL(object):
...
class _FuncPtr(_CFuncPtr):
_flags_ = flags
_restype_ = self._func_restype_
...
...
_c_functype_cache = {}
def CFUNCTYPE(restype, *argtypes, **kw):
...
try:
return _c_functype_cache[(restype, argtypes, flags)]
except KeyError:
class CFunctionType(_CFuncPtr):
_argtypes_ = argtypes
_restype_ = restype
_flags_ = flags
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
return CFunctionType
...
As far I can tell, these are represented as static types in the backend. They use StgDictObject
, It's seems to be a tweaked version of a dict
to handle callable symbols inside of the .so
.
PyCFuncPtr_Type
is _CFuncPtr
. This class has these getters and setters:
static PyGetSetDef PyCFuncPtr_getsets[] = {
{ "errcheck", (getter)PyCFuncPtr_get_errcheck, (setter)PyCFuncPtr_set_errcheck,
"a function to check for errors", NULL },
{ "restype", (getter)PyCFuncPtr_get_restype, (setter)PyCFuncPtr_set_restype,
"specify the result type", NULL },
{ "argtypes", (getter)PyCFuncPtr_get_argtypes,
(setter)PyCFuncPtr_set_argtypes,
"specify the argument types", NULL },
{ NULL, NULL }
};
Looking at argtypes
, we see:
static int
PyCFuncPtr_set_argtypes(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
{
PyObject *converters;
if (ob == NULL || ob == Py_None) {
Py_CLEAR(self->converters);
Py_CLEAR(self->argtypes);
} else {
converters = converters_from_argtypes(ob);
if (!converters)
return -1;
Py_XSETREF(self->converters, converters);
Py_INCREF(ob);
Py_XSETREF(self->argtypes, ob);
}
return 0;
}
static PyObject *
PyCFuncPtr_get_argtypes(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
{
StgDictObject *dict;
if (self->argtypes) {
Py_INCREF(self->argtypes);
return self->argtypes;
}
dict = PyObject_stgdict((PyObject *)self);
assert(dict); /* Cannot be NULL for PyCFuncPtrObject instances */
if (dict->argtypes) {
Py_INCREF(dict->argtypes);
return dict->argtypes;
} else {
Py_RETURN_NONE;
}
}
And converters_from_argtypes
:
static PyObject *
converters_from_argtypes(PyObject *ob)
{
_Py_IDENTIFIER(from_param);
PyObject *converters;
Py_ssize_t i;
Py_ssize_t nArgs;
ob = PySequence_Tuple(ob); /* new reference */
if (!ob) {
PyErr_SetString(PyExc_TypeError,
"_argtypes_ must be a sequence of types");
return NULL;
}
nArgs = PyTuple_GET_SIZE(ob);
converters = PyTuple_New(nArgs);
if (!converters) {
Py_DECREF(ob);
return NULL;
}
/* I have to check if this is correct. Using c_char, which has a size
of 1, will be assumed to be pushed as only one byte!
Aren't these promoted to integers by the C compiler and pushed as 4 bytes?
*/
for (i = 0; i < nArgs; ++i) {
PyObject *cnv;
PyObject *tp = PyTuple_GET_ITEM(ob, i);
if (_PyObject_LookupAttrId(tp, &PyId_from_param, &cnv) <= 0) {
Py_DECREF(converters);
Py_DECREF(ob);
if (!PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"item %zd in _argtypes_ has no from_param method",
i+1);
}
return NULL;
}
PyTuple_SET_ITEM(converters, i, cnv);
}
Py_DECREF(ob);
return converters;
}
which checks if the element on _argtypes_
has from_param
, if it does, than place it on converters
tuple (idk the purpose of this field yet)
This is the
#1
(first) tracking issue w.r.t the implementation of_ctypes
. The main goal is to discuss the core ideas of the some interpreters such as CPython, PyPy and IronPython.