# HG changeset patch # User Tassilo Philipp # Date 1586713057 -7200 # Node ID 2682a627168c4c36486ddc8f5b081894024c2b2a # Parent ba47a3d709d78672d641b157505e5e3768a29661 - breaking changes: * restrict 'Z' conversions to immutable types * restrict 'p' to mutable types (and handles) diff -r ba47a3d709d7 -r 2682a627168c python/pydc/README.txt --- a/python/pydc/README.txt Sat Apr 11 20:00:25 2020 +0200 +++ b/python/pydc/README.txt Sun Apr 12 19:37:37 2020 +0200 @@ -7,6 +7,9 @@ Apr 19, 2018: update to dyncall 1.0 Apr 7, 2020: update to dyncall 1.1, Python 3 support, using the Capsule API, as well as support for python unicode strings +Apr 11, 2020: support for getting loaded library path +Apr 12, 2020: breaking change: restrict 'Z' conversions to immutable types + and 'p' to mutable types (and handles) BUILD/INSTALLATION @@ -40,30 +43,41 @@ x is positional parameter-type charcode, y is result-type charcode - SIG | FROM PYTHON 2 | FROM PYTHON 3 | C/C++ | TO PYTHON 2 | TO PYTHON 3 - ----+------------------------------------------------------+------------------------------------------+---------------------------------+--------------------------------------+------------------------------------- - 'v' | | | void | None (Py_None) (e.g. ret of "...)v") | None (Py_None) (e.g. ret of "...)v") - 'B' | bool (PyBool) | bool (PyBool)# | int/bool | bool (PyBool) | bool (PyBool)@ - 'c' | int (PyLong)%, str (single char)% | int (PyLong)%, str (single char)% | char | int (PyInt) | int (PyLong) - 'C' | int (PyLong)%, str (single char)% | int (PyLong)%, str (single char)% | unsigned char | int (PyInt) | int (PyLong) - 's' | int (PyInt)% | int (PyLong)% | short | int (PyInt) | int (PyLong) - 'S' | int (PyInt)% | int (PyLong)% | unsigned short | int (PyInt) | int (PyLong) - 'i' | int (PyInt) | int (PyLong) | int | int (PyInt) | int (PyLong) - 'I' | int (PyInt) | int (PyLong) | unsigned int | int (PyInt) | int (PyLong) - 'j' | int (PyInt) | int (PyLong) | long | int (PyInt) | int (PyLong) - 'J' | int (PyInt) | int (PyLong) | unsigned long | int (PyInt) | int (PyLong) - 'l' | int (PyInt), long (PyLong) | int (PyLongLong) | long long | long (PyLong) | int (PyLong) - 'L' | int (PyInt), long (PyLong) | int (PyLongLong) | unsigned long long | long (PyLong) | int (PyLong) - 'f' | float (PyFloat)$ | float (PyFloat)$ | float | float (PyFloat)^ | float (PyFloat)^ - 'd' | float (PyFloat) | float (PyFloat) | double | float (PyFloat) | float (PyFloat) - 'p' | str (PyUnicode/PyString), int (PyInt), long (PyLong) | str (PyUnicode), int (PyLong), (PyBytes) | void* | int,long (Py_ssize_t) | int (Py_ssize_t) - 'Z' | str (PyUnicode/PyString) | str (PyUnicode), (PyBytes) | const char* (UTF-8 for unicode) | int (PyString) | str (PyUnicode) + SIG | FROM PYTHON 2 | FROM PYTHON 3 | C/C++ | TO PYTHON 2 | TO PYTHON 3 + ----+---------------------------------+---------------------------------+---------------------------------+--------------------------------------+--------------------------------------- + 'v' | | | void | None (Py_None) (e.g. ret of "...)v") | None (Py_None) (e.g. ret of "...)v") + 'B' | bool (PyBool) | bool (PyBool) # | int/bool | bool (PyBool) | bool (PyBool) @ + 'c' | int (PyInt) % | int (PyLong) % | char | int (PyInt) | int (PyLong) + | str (with only a single char) % | str (with only a single char) % | char | int (PyInt) | int (PyLong) + 'C' | int (PyInt) % | int (PyLong) % | unsigned char | int (PyInt) | int (PyLong) + | str (with only a single char) % | str (with only a single char) % | unsigned char | int (PyInt) | int (PyLong) + 's' | int (PyInt) % | int (PyLong) % | short | int (PyInt) | int (PyLong) + 'S' | int (PyInt) % | int (PyLong) % | unsigned short | int (PyInt) | int (PyLong) + 'i' | int (PyInt) | int (PyLong) | int | int (PyInt) | int (PyLong) + 'I' | int (PyInt) | int (PyLong) | unsigned int | int (PyInt) | int (PyLong) + 'j' | int (PyInt) | int (PyLong) | long | int (PyInt) | int (PyLong) + 'J' | int (PyInt) | int (PyLong) | unsigned long | int (PyInt) | int (PyLong) + 'l' | int (PyInt) | int (PyLongLong) | long long | long (PyLong) | int (PyLong) + | long (PyLong) | - | long long | long (PyLong) | int (PyLong) + 'L' | int (PyInt) | int (PyLongLong) | unsigned long long | long (PyLong) | int (PyLong) + | long (PyLong) | - | unsigned long long | long (PyLong) | int (PyLong) + 'f' | float (PyFloat) $ | float (PyFloat) $ | float | float (PyFloat) ^ | float (PyFloat) ^ + 'd' | float (PyFloat) | float (PyFloat) | double | float (PyFloat) | float (PyFloat) + 'p' | bytearray (PyByteArray) & | bytearray (PyByteArray) & | void* | int,long (Py_ssize_t) | int (Py_ssize_t) + | int (PyInt) | int (PyLong) | void* | int,long (Py_ssize_t) | int (Py_ssize_t) + | long (PyLong) | - | void* | int,long (Py_ssize_t) | int (Py_ssize_t) + 'Z' | str (PyString) ! | str (PyUnicode) ! | const char* (UTF-8 for unicode) | int (PyString) | str (PyUnicode) + | unicode (PyUnicode) ! | - | const char* (UTF-8 for unicode) | int (PyString) | str (PyUnicode) + | - | bytes (PyBytes) ! | const char* (UTF-8 for unicode) | int (PyString) | str (PyUnicode) + | bytearray (PyByteArray) ! | bytearray (PyByteArray) ! | const char* (UTF-8 for unicode) | int (PyString) | str (PyUnicode) # converted to 1 if True and 0 otherwise @ converted to False if 0 and True otherwise -% range checked +% range/length checked $ cast to single precision ^ cast to double precision +& mutable buffer when passed to C +! immutable buffer when passed to C, as strings (in any form) are considered objects, not buffers TODO ==== diff -r ba47a3d709d7 -r 2682a627168c python/pydc/pydcext.c --- a/python/pydc/pydcext.c Sat Apr 11 20:00:25 2020 +0200 +++ b/python/pydc/pydcext.c Sun Apr 12 19:37:37 2020 +0200 @@ -325,15 +325,11 @@ dcArgDouble(gpCall, PyFloat_AsDouble(po)); break; - case DC_SIGCHAR_POINTER: + case DC_SIGCHAR_POINTER: // this will only accept integers or mutable array types (meaning only bytearray) { - PyObject* bo = NULL; DCpointer p; - if ( PyUnicode_Check(po) ) { - if((bo = PyUnicode_AsEncodedString(po, "utf-8", "strict"))) // !new ref! - p = PyBytes_AS_STRING(bo); // Borrowed pointer - } else if ( DcPyString_Check(po) ) - p = (DCpointer) DcPyString_AsString(po); + if ( PyByteArray_Check(po) ) + p = (DCpointer) PyByteArray_AsString(po); // adds an extra '\0', but that's ok #if PY_MAJOR_VERSION < 3 else if ( PyInt_Check(po) ) p = (DCpointer) PyInt_AS_LONG(po); @@ -341,25 +337,34 @@ else if ( PyLong_Check(po) ) p = (DCpointer) PyLong_AsVoidPtr(po); else - return PyErr_Format( PyExc_RuntimeError, "arg %d - expecting a promoting pointer-type (int,str)", pos ); + return PyErr_Format( PyExc_RuntimeError, "arg %d - expecting a promoting pointer-type (int, bytearray)", pos ); dcArgPointer(gpCall, p); - Py_XDECREF(bo); } break; - case DC_SIGCHAR_STRING: + case DC_SIGCHAR_STRING: // strings are considered to be immutable objects { PyObject* bo = NULL; const char* p; + char* p_; + size_t s; if ( PyUnicode_Check(po) ) { if((bo = PyUnicode_AsEncodedString(po, "utf-8", "strict"))) // !new ref! p = PyBytes_AS_STRING(bo); // Borrowed pointer - } else if ( DcPyString_Check(po) ) { - p = DcPyString_AsString(po); - } else + } else if ( DcPyString_Check(po) ) + p = DcPyString_AsString(po); //@@@ must not be modified in any way + else if ( PyByteArray_Check(po) ) + p = (DCpointer) PyByteArray_AsString(po); // adds an extra '\0', but that's ok //@@@ not sure if allowed to modify + else return PyErr_Format( PyExc_RuntimeError, "arg %d - expecting a str", pos ); - dcArgPointer(gpCall, (DCpointer) p); + + // pointer points in any case to a buffer that shouldn't be modified, so pass a copy of the string to dyncall + s = strlen(p)+1; + p_ = malloc(s); + strncpy(p_, p, s); Py_XDECREF(bo); + dcArgPointer(gpCall, (DCpointer)p_); + free(p_); } break; diff -r ba47a3d709d7 -r 2682a627168c python/pydc/setup.py --- a/python/pydc/setup.py Sat Apr 11 20:00:25 2020 +0200 +++ b/python/pydc/setup.py Sun Apr 12 19:37:37 2020 +0200 @@ -7,7 +7,7 @@ setup( name = 'pydc' -, version = '1.1' +, version = '1.1.1' , author = 'Daniel Adler' , author_email = 'dadler@dyncall.org' , maintainer = 'Daniel Adler' diff -r ba47a3d709d7 -r 2682a627168c python/pydc/test/Makefile --- a/python/pydc/test/Makefile Sat Apr 11 20:00:25 2020 +0200 +++ b/python/pydc/test/Makefile Sun Apr 12 19:37:37 2020 +0200 @@ -1,37 +1,9 @@ -#from dynload test -## path to default libc.so file, easier to do via shell than in code (see main() in dynload_plain.c) -## for compat: first gmake style, then assignment op which will use ! as part of var name on gmake<4 -## and thus not override previously set var -#DEF_C_DYLIB=$(shell ((ldd `which ls` | grep -o '/.*/libc.so[^ ]*' || ls /lib*/libc.so* || ls /usr/lib/libc.so*) | grep -v '\.a$$' | (sort -V -r || sort -t . -n -k 2)) 2>/dev/null | head -1) -#DEF_C_DYLIB!=((ldd `which ls` | grep -o '/.*/libc.so[^ ]*' || ls /lib*/libc.so* || ls /usr/lib/libc.so*) | grep -v '\.a$$' | (sort -V -r || sort -t . -n -k 2)) 2>/dev/null | head -1 -# -#APP = dynload_plain -#OBJS = dynload_plain.o -#TEST_U8_SO = dynload_plain_ß_test # @@@ unsure if every platform handles ß, here (ANSI, UTF-8, ...) -#SRCTOP = ${VPATH}/../.. -#BLDTOP = ../.. -#CFLAGS += -I${SRCTOP}/dynload -DDEF_C_DYLIB=\"${DEF_C_DYLIB}\" -#LDLIBS_D += -L${BLDTOP}/dynload -ldynload_s -# -## Works on: Darwin, NetBSD. -# Linux: add '-ldl' -#.PHONY: all clean install -#all: ${APP} ${TEST_U8_SO} -#${APP}: ${OBJS} -# ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS_D} ${LDLIBS} -o ${APP} -#${TEST_U8_SO}: -# echo 'int dynload_plain_testfunc() { return 5; }' | ${CC} -`[ \`uname\` = Darwin ] && echo dynamiclib || echo shared` -x c - -o ${TEST_U8_SO} -#clean: -# rm -f ${APP} ${OBJS} ${TEST_U8_SO} -#install: -# mkdir -p ${PREFIX}/test -# cp ${APP} ${TEST_U8_SO} ${PREFIX}/test - .PHONY: test.so test.so: - for i in char 'unsigned char' short 'unsigned short' int 'unsigned int' long 'unsigned long' 'long long' 'unsigned long long' float double 'const char*'; do \ - echo "$$i `echo $$i | sed -E 's/(^| )([a-z])[^ ]*/\2/g'`_plus_one($$i v) { return v+1; }"; \ - done | ${CC} -`[ \`uname\` = Darwin ] && echo dynamiclib || echo shared` -x c - -o $@ - echo Done! Now you can run types.py + (for i in char 'unsigned char' short 'unsigned short' int 'unsigned int' long 'unsigned long' 'long long' 'unsigned long long' float double 'const char*'; do \ + echo "$$i `echo $$i | sed -E 's/\*/ p/;s/(^| )([a-z])[^ ]*/\2/g'`_plus_one($$i v) { return v+1; }"; \ + done; \ + echo 'void cp_head_incr(char* v) { v[0]+=1; }') | ${CC} -`[ \`uname\` = Darwin ] && echo dynamiclib || echo shared` -x c - -o $@ + @echo Done! Now you can run types.py diff -r ba47a3d709d7 -r 2682a627168c python/pydc/test/types.py --- a/python/pydc/test/types.py Sat Apr 11 20:00:25 2020 +0200 +++ b/python/pydc/test/types.py Sun Apr 12 19:37:37 2020 +0200 @@ -6,11 +6,15 @@ def theader(title): - print(title) - print('%8s %20s %16s %-20s %12s %-12s -> %16s %-16s # %s' % ('DC_SIG', 'C_RET_T', 'C_FSYM', 'C_PARAMLIST', 'PYTHON_ARG_T', 'IN_ARGS', 'RET_VAL', 'PYTHON_RET_T', 'NOTES')) + print("\n"+title) + print('%8s %20s %16s %-20s %11s %-16s -> %16s %-16s %12s %-16s # %s' % ('DC_SIG', 'C_RET_T', 'C_FSYM', 'C_PARAMLIST', 'PY_ARG_T', 'IN_ARGS', 'RET_VAL', 'PY_RET_T', 'OUT_ARGS', 'OUT_ARGS_T', 'NOTES')) def t(lib, dcsig, c_rtype, c_fsym, c_paramlist, extra_msg, *args): + # before call + inarg_types = '('+','.join(map(lambda x: type(x).__name__, args))+')' + inargs = ','.join(map(str, args)) + # call try: fp = pydc.find(lib, c_fsym) r = pydc.call(fp, dcsig, *args) @@ -18,11 +22,12 @@ except: r = '[EXCEPTION]' rt = '!' - extra_msg += ' "'+str(sys.exc_info()[1])+'"' - - inarg_types = '('+','.join(map(lambda x: type(x).__name__, args))+')' - inargs = ','.join(map(str, args)) - print('%8s %20s %16s %-20s %12s %-12s -> %16.16s %-16s # %s' % (dcsig, c_rtype, c_fsym, c_paramlist, inarg_types, inargs, str(r), '('+rt+')', extra_msg)) + e = str(sys.exc_info()[1]) + extra_msg += ' "'+(e if len(e)<32 else e[0:32]+'...')+'"' + # after call + outarg_types = '('+','.join(map(lambda x: type(x).__name__, args))+')' + outargs = ','.join(map(str, args)) + print('%8s %20s %16s %-20s %11s \033[33m%-16s\033[0m -> \033[32m%16.16s\033[0m %-16s %12s \033[32m%-16s\033[0m # %s' % (dcsig, c_rtype, c_fsym, c_paramlist, inarg_types, inargs, str(r), '('+rt+')', outarg_types, outargs, extra_msg)) @@ -84,28 +89,57 @@ l = pydc.load(sys.path[0]+"/test.so") +# "long" typed test value use for Python 2 +long_i = 11234 +long_h = 0xdeadc0de +if sys.version_info < (3, 0): + long_i = long(11234) + long_h = long(0xdeadc0de) + # test all possible arg types and their conversions to and from python, with # specific focus/tests in areas where python 2 and 3 differ theader('ARG & RET TYPE CONVERSION TESTS:') t(l, "B)B", "int", "i_plus_one", "(int)", ' False => True (using int func in C)', False) + t(l, "c)c", "char", "c_plus_one", "(char)", ' "a" (97) => 98', 'a') t(l, "C)C", "unsigned char", "uc_plus_one", "(unsigned char)", ' "a" (97) => 98', 'a') t(l, "c)c", "char", "c_plus_one", "(char)", ' -2 => -1', -2) t(l, "C)C", "unsigned char", "uc_plus_one", "(unsigned char)", ' 10 => 11', 10) + t(l, "s)s", "short", "s_plus_one", "(short)", ' 10 => 11', 10) t(l, "S)S", "unsigned short", "us_plus_one", "(unsigned short)", ' 10 => 11', 10) + t(l, "i)i", "int", "i_plus_one", "(int)", ' 10 => 11', 10) t(l, "I)I", "unsigned int", "ui_plus_one", "(unsigned int)", ' 10 => 11', 10) + t(l, "j)j", "long", "l_plus_one", "(long)", ' 10 => 11', 10) t(l, "J)J", "unsigned long", "ul_plus_one", "(unsigned long)", ' 10 => 11', 10) + t(l, "l)l", "long long", "ll_plus_one", "(long long)", ' 10 => 11', 10) t(l, "L)L", "unsigned long long", "ull_plus_one", "(unsigned long long)", ' 10 => 11', 10) +t(l, "l)l", "long long", "ll_plus_one", "(long long)", ' 11234 => 11235', long_i) +t(l, "L)L", "unsigned long long", "ull_plus_one", "(unsigned long long)", ' 11234 => 11235', long_i) + t(l, "f)f", "float", "f_plus_one", "(float)", ' -1.23 => -0.23', -1.23) t(l, "d)d", "double", "d_plus_one", "(double)", ' 5.67 => 6.67', 5.67) -t(l, "Z)Z", "const char*", "cc_plus_one", "(const char*)", '"lose char" => "ose char"', 'lose char') -t(l, "p)Z", "const char*", "cc_plus_one", "(const char*)", '"w/pointer" => "/pointer"', 'w/pointer') -t(l, "p)p", "const char*", "cc_plus_one", "(const char*)", ' "x" => p+1 (retval is prob odd addr)', 'x') -t(l, "p)p", "const char*", "cc_plus_one", "(const char*)", ' 0xdeadc0de => 0xdeadc0de+1=3735929055',0xdeadc0de) + +t(l, "Z)Z", "const char*", "ccp_plus_one", "(const char*)", '"lose char" => "ose char"', 'lose char') # string object +t(l, "Z)Z", "const char*", "ccp_plus_one", "(const char*)", '"X_unicode" => "_unicode"', u'X_unicode') # string object (unicode in Python 2) +t(l, "Z)Z", "const char*", "ccp_plus_one", "(const char*)", '"1lessbyte" => "lessbyte"', b'1lessbyte') # bytes object +t(l, "Z)Z", "const char*", "ccp_plus_one", "(const char*)", ' "xY" => "Y"', bytearray(b'xY')) # bytearray object + +t(l, "p)Z", "const char*", "ccp_plus_one", "(const char*)", ' "xY" => "Y"', bytearray(b'xY')) # bytearray object +t(l, "p)p", "const char*", "ccp_plus_one", "(const char*)", ' "xY" => p+1 (~ odd addr)', bytearray(b'xY')) # bytearray object +t(l, "p)p", "const char*", "ccp_plus_one", "(const char*)", ' 0xdeadc0de => 0xdeadc0de+1=3735929055', long_h) # handle (integer interpreted as ptr) +t(l, "p)p", "const char*", "ccp_plus_one", "(const char*)", ' 0xdeadc0de => 0xdeadc0de+1=3735929055', long_h) # handle (integer interpreted as ptr, long in Python 2) + +# functions that change buffers +theader('TESTS OF IMMUTABLE AND MUTABLE PYTHON BUFFERS:') +t(l, "Z)v", "const char*", "cp_head_incr", "(const char*)", ' "string" => None / arg => "string" (not modified)"', 'string') # string object +t(l, "Z)v", "const char*", "cp_head_incr", "(const char*)", ' "UnIcOdE" => None / arg => "UnIcOdE" (not modified)"', u'UnIcOdE') # string object (unicode in Python 2) +t(l, "Z)v", "const char*", "cp_head_incr", "(const char*)", ' "BCssk#" => None / arg => "BCssk#" (not modified)"', b'BCssk#') # bytes object +t(l, "Z)v", "const char*", "cp_head_incr", "(const char*)", ' "xY" => None / arg => "xY" (not modified)"', bytearray(b'xY')) # bytearray object +t(l, "p)v", "const char*", "cp_head_incr", "(const char*)", ' "xY" => None / arg => "yY" (!MODIFIED!)"', bytearray(b'xY')) # bytearray object # tested checked value conversions theader('ARG & RET TYPE CONVERSION TESTS FOR RANGE CHECKED TYPES:') @@ -136,47 +170,8 @@ t(l, "S)S", "unsigned short", "us_plus_one", "(unsigned short)", ' -1 => input exc:', -1) t(l, "S)S", "unsigned short", "us_plus_one", "(unsigned short)", ' 65536 => input exc:', 65536) +t(l, "p)Z", "const char*", "ccp_plus_one", "(const char*)", '"w/pointer" => input exc:', 'w/pointer') # string object, not passable as 'p'ointer +t(l, "p)Z", "const char*", "ccp_plus_one", "(const char*)", '"X_unicode" => input exc:',u'X_unicode') # string object (unicode in Python 2), not passable as 'p'ointer +t(l, "p)Z", "const char*", "ccp_plus_one", "(const char*)", '"1less/ptr" => input exc:',b'1less/ptr') # bytes object, not passable as 'p'ointer +t(l, "p)p", "const char*", "ccp_plus_one", "(const char*)", ' "x" => input exc:', 'x') # string object, not passable as 'p'ointer -# int -# rand(void); -#rand() -#define DC_SIGCHAR_VOID 'v' ra -#define DC_SIGCHAR_BOOL 'B' -#define DC_SIGCHAR_CHAR 'c' 2 versions -#define DC_SIGCHAR_UCHAR 'C' -#define DC_SIGCHAR_SHORT 's' -#define DC_SIGCHAR_USHORT 'S' -#define DC_SIGCHAR_INT 'i' r -#define DC_SIGCHAR_UINT 'I' a -#define DC_SIGCHAR_LONG 'j' ra -#define DC_SIGCHAR_ULONG 'J' -#define DC_SIGCHAR_LONGLONG 'l' ra -#define DC_SIGCHAR_ULONGLONG 'L' -#define DC_SIGCHAR_FLOAT 'f' -#define DC_SIGCHAR_DOUBLE 'd' -#define DC_SIGCHAR_POINTER 'p' -#define DC_SIGCHAR_STRING 'Z' -#define DC_SIGCHAR_STRUCT 'T' -#define DC_SIGCHAR_ENDARG ')' /* also works for end struct */ - - - -# SIG | FROM PYTHON 2 | FROM PYTHON 3 @@@ | C/C++ | TO PYTHON 2 | TO PYTHON 3 @@@ -# ----+------------------------------------+------------------------------------+---------------------------------+------------------------------------+----------------------------------- -# 'v' | | | void | ?NoneType (returned for "xxx)v") | NoneType (returned for "xxx)v") -# 'B' | ?PyBool | ?PyBool | bool | ?PyBool | ?PyBool -# 'c' | ?PyInt (range checked) | ?PyLong (range checked) | char | ?PyInt | ?PyLong -# 'C' | ?PyInt (range checked) | ?PyLong (range checked) | unsigned char | ?PyInt | ?PyLong -# 's' | ?PyInt (range checked) | ?PyLong (range checked) | short | ?PyInt | ?PyLong -# 'S' | ?PyInt (range checked) | ?PyLong (range checked) | unsigned short | ?PyInt | ?PyLong -# 'i' | ?PyInt | ?PyLong | int | ?PyInt | ?PyLong -# 'I' | ?PyInt | ?PyLong | unsigned int | ?PyInt | ?PyLong -# 'j' | ?PyLong | ?PyLong | long | ?PyLong | ?PyLong -# 'J' | ?PyLong | ?PyLong | unsigned long | ?PyLong | ?PyLong -# 'l' | ?PyLongLong | ?PyLongLong | long long | ?PyLongLong | ?PyLongLong -# 'L' | ?PyLongLong | ?PyLongLong | unsigned long long | ?PyLongLong | ?PyLongLong -# 'f' | ?PyFloat (cast to single precision) | ?PyFloat (cast to single precision) | float | ?PyFloat (cast to double precision) | ?PyFloat (cast to double precision) -# 'd' | ?PyFloat | ?PyFloat | double | ?PyFloat | ?PyFloat -# 'p' | ?PyUnicode/PyString/PyLong | ?PyUnicode/PyBytes/PyLong | void* | ?Py_ssize_t | ?Py_ssize_t -# 'Z' | ?PyUnicode/PyString | ?PyUnicode/PyBytes | const char* (UTF-8 for unicode) | ?PyString | ?PyUnicode -