Differentiate between variadic and non-variadic functions in ctypes

Proposal

Currently, ctypes._CFuncPtr objects always allows you to call it with more arguments than the length of .argtypes. I find this behavior strange because it means accidently passing extra arguments can go unnoticed due to the lack of errors. For example:

libc.puts.argtypes = [ctypes.c_char_p]
libc.puts.restype = ctypes.c_int
libc.puts(b"Hello, World!")  # works
libc.puts(b"foo", b"bar", b"baz")  # still works even though it's wrong

I propose that the behavior be changed so that once .argtypes has been set, the number of arguments in a call must be equal to the length of .argtypes unless the last element of .argtypes is the built-in Ellipsis object (in which case the number of arguments should be greater than or equal to len(.argtypes) - 1).

So to declare a variadic function, you must either use Ellipsis as the last element of .argtypes, or not set .argtypes at all. For example:

libc.puts.argtypes = [ctypes.c_char_p]  # non-variadic function
libc.puts.restype = ctypes.c_int
libc.puts(b"Hello, World!")  # works
libc.puts(b"foo", b"bar", b"baz")  # too many arguments, raises an exception

libc.printf.argtypes = [ctypes.c_char_p, ...]  # variadic function
libc.printf.restype = ctypes.c_int
libc.printf(b"Hello, World!\n")  # works
libc.printf(b"%d %lf %s\n", 25, ctypes.c_double(3.14), b"foo")  # also works

I know this would break backwards-compatibility, so if this gets accepted, maybe add it as a __future__ feature for now so that people have to opt-in to it?

Reasons for the Proposal

  1. This stricter behavior for non-variadic functions can help catch mistakes in code early.
  2. Ending the parameter list of the function prototype with an ellipsis matches C’s syntax for declaring variadic functions, so it’s very intuitive.
1 Like

To clarify, I realized later that foreign functions using the stdcall calling convention (loaded through ctypes.WinDLL or ctypes.OleDLL) don’t allow variadic functions at all, and always expects the same number of arguments as the length of .argtypes.

So my proposal only applies to the behavior of cdecl functions (loaded through ctypes.CDLL), which currently offer no way to specify that a function is not variadic. I would edit the post, but it seems that the allowed time period for editing has passed.