Introduction
I am working on a code generator that would generate functions around ctypes.cdll
loaded functions. The generator would take information about the ctypes
of the arguments and the return value, and generate something that behaves (and, to some extent, looks) like this:
func = getattr(dll, 'UglyLongAndUselessCName')
func.argtypes = [ctypes.c_uint32, ctypes.c_int8, ctypes.c_char_p]
func.restype = ctypes.c_int16
def nice_python_name(handle: int, arg1: int, arg2: str) -> int:
return func(handle, arg1, arg2)
Notice how python type annotations play nicely with the ctypes
data types of the function arguments. Also notice that there is no conversion code between the python types in nice_python_name
function and func
function. This is what my question is about.
Getting close to the problem
The ctypes
docs say that if the argtypes
attribute of a loaded DLL function is specified using "fundamental data types", then when calling the loaded DLL functions, ctypes
will do the conversion to python types for you. This is great, because in this case my generated code will look like the example above - I won't need to explicitly convert ctypes
objects to python-typed values for returned values, and the reverse for the arguments.
However, the docs also say that for "subclasses of the fundamental data types" this trick will not work, and calling a loaded DLL function will require ctypes
objects for arguments, and the result will be a ctypes
object.
This is the excerpt from the ctypes
docs about it:
Fundamental data types, when returned as foreign function call results, or, for example, by retrieving structure field members or array items, are transparently converted to native Python types. In other words, if a foreign function has a
restype
ofc_char_p
, you will always receive a Python bytes object, not ac_char_p
instance.Subclasses of fundamental data types do not inherit this behavior. So, if a foreign functions
restype
is a subclass ofc_void_p
, you will receive an instance of this subclass from the function call. Of course, you can get the value of the pointer by accessing the value attribute.
So, I would like to get around this.
It seems that I need to know if a type is "fundamental" or a "subclass". This will help me define the way the code is generated, i.e. for "fundamental" types the generated code would look similar to the above example, and for "subclasses of fundamental" types it will have the extra conversion from ctypes
objects into reasonable python types (or the generator will just throw an exception saying "this is not supported").
Question:
How can I distinguish between "fundamental ctypes
data types" and "subclasses of fundamental ctypes
data types"?
I looked into the code of ctypes
python module and discovered that both c_void_p
and c_char_p
are subclasses of ctypes._SimpleCData
, hence one is not a subclass of the other in any way.
Also, do I misunderstand it that this problem also applies to the input arguments, or is this only the deal for the returned values?