The ctypes function prototype
specification says
The first item is an integer containing a combination of direction flags for the parameter: [...]
4: Input parameter which defaults to the integer zero.
and shortly after,
The optional third item is the default value for this parameter.
Why does "type 4" exist when the exact same thing, seemingly, can be specified by writing 0
in the third tuple element? Are they indeed equivalent? Why would one be preferred over the other?
In fact, there's some evidence that they aren't equivalent: if I define WlanRegisterNotification like
proto = ctypes.WINFUNCTYPE(
ctypes.wintypes.DWORD,
ctypes.wintypes.HANDLE,
ctypes.wintypes.DWORD,
ctypes.wintypes.BOOL,
WLAN_NOTIFICATION_CALLBACK,
ctypes.wintypes.LPVOID,
ctypes.wintypes.LPVOID,
ctypes.POINTER(ctypes.wintypes.DWORD),
)
fun = proto(
('WlanRegisterNotification', wlanapi),
(
(IN, 'hClientHandle'),
(IN, 'dwNotifSource'),
(IN, 'bIgnoreDuplicate'),
(IN | DEFAULT_ZERO, 'funcCallback'),
(IN | DEFAULT_ZERO, 'pCallbackContext'),
(IN | DEFAULT_ZERO, 'pReserved'),
(OUT, 'pdwPrevNotifSource'),
),
)
It behaves nonsensically when passed values for the first four parameters:
TypeError: call takes exactly 3 arguments (4 given)
Why should it assume that there are exactly three arguments, when arguments four, five and six are given defaults (but should accept explicit values)? Removing DEFAULT_ZERO
and adding , None
solves the problem, but is not a satisfying answer.
DEFAULT_ZERO
seems to mean "don't pass a parameter...use a default as input", so it only makes sense to use for pReserved
. The other two parameters need valid defaults. Note that int(0)
is not a valid default for a callback, and None
raises expected WinFunctionType instance instead of NoneType
, so I passed a default null instance of the notification callback type.
Functional example:
import ctypes
import ctypes.wintypes
PWLAN_NOTIFICATION_DATA = ctypes.c_void_p
WLAN_NOTIFICATION_CALLBACK = ctypes.WINFUNCTYPE(None, PWLAN_NOTIFICATION_DATA, ctypes.wintypes.LPVOID)
null_callback = WLAN_NOTIFICATION_CALLBACK()
# valid callback
@WLAN_NOTIFICATION_CALLBACK
def callback(param1, param2):
print(param1, param2)
IN = 1
OUT = 2
DEFAULT_ZERO = 4
wlanapi = ctypes.WinDLL('wlanapi')
proto = ctypes.WINFUNCTYPE(
ctypes.wintypes.DWORD,
ctypes.wintypes.HANDLE,
ctypes.wintypes.DWORD,
ctypes.wintypes.BOOL,
WLAN_NOTIFICATION_CALLBACK,
ctypes.wintypes.LPVOID,
ctypes.wintypes.LPVOID,
ctypes.POINTER(ctypes.wintypes.DWORD),
)
fun = proto(
('WlanRegisterNotification', wlanapi),
(
(IN, 'hClientHandle'),
(IN, 'dwNotifSource'),
(IN, 'bIgnoreDuplicate'),
(IN, 'funcCallback', null_callback),
(IN, 'pCallbackContext', None),
(IN | DEFAULT_ZERO, 'pReserved'),
(OUT, 'pdwPrevNotifSource'),
),
)
fun.errcheck = lambda result, func, args: (result, args[5])
print(fun(0,0,0))
print(fun(0,0,0,callback))
print(fun(0,0,0,callback,None))
print(fun(0,0,0,callback,None,None)) # this will fail. pReserved can't be passed.
Output (note that ERROR_INVALID_PARAMETER = 87
since I passed garbage params, but it makes it passed ctypes parameter validation):
(87, 0)
(87, 0)
(87, 0)
Traceback (most recent call last):
File "C:\test.py", line 44, in <module>
print(fun(0,0,0,callback,None,None))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: call takes exactly 5 arguments (6 given)
Frankly I avoid paramFlags and use .argtypes
and .restype
to declare arguments and return type and write wrappers to make the C API exactly the way I want it if needed:
import ctypes as ct
import ctypes.wintypes as w
PWLAN_NOTIFICATION_DATA = ct.c_void_p
WLAN_NOTIFICATION_CALLBACK = ct.WINFUNCTYPE(None, PWLAN_NOTIFICATION_DATA, w.LPVOID)
null_callback = WLAN_NOTIFICATION_CALLBACK()
# valid callback
@WLAN_NOTIFICATION_CALLBACK
def callback(param1, param2):
print(param1, param2)
wlanapi = ct.WinDLL('wlanapi')
wlanapi.WlanRegisterNotification.argtypes = w.HANDLE, w.DWORD, w.BOOL, WLAN_NOTIFICATION_CALLBACK, w.LPVOID, w.LPVOID, ct.POINTER(w.DWORD)
wlanapi.WlanRegisterNotification.restype = w.DWORD
def fun(h, src, ignore, cb=null_callback, context=None):
prev = w.DWORD()
result = wlanapi.WlanRegisterNotification(h, src, ignore, cb, context, None, ct.byref(prev))
return result, prev.value
print(fun(0, 0, 0))
print(fun(0, 0, 0, callback))
print(fun(0, 0, 0, callback, None))
print(fun(0, 0, 0, callback, None, None)) # this will fail. pReserved can't be passed.