From b296c7442be97600dc87819e885e037313883d8e Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Sun, 3 Jul 2022 20:58:02 +0200 Subject: gh-92869: ctypes: Add c_time_t (#92870) Adds `ctypes.c_time_t` to represent the C `time_t` type accurately as its size varies. Primarily-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Gregory P. Smith [Google] --- Doc/library/ctypes.rst | 37 ++++++++++++++++++---- Lib/ctypes/__init__.py | 8 +++++ Lib/test/test_ctypes/test_sizes.py | 3 ++ .../2022-05-17-06-27-39.gh-issue-92869.t8oBkw.rst | 2 ++ Modules/_ctypes/_ctypes.c | 1 + 5 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-05-17-06-27-39.gh-issue-92869.t8oBkw.rst diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 52950b5..4757371 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -148,15 +148,14 @@ Calling functions ^^^^^^^^^^^^^^^^^ You can call these functions like any other Python callable. This example uses -the ``time()`` function, which returns system time in seconds since the Unix -epoch, and the ``GetModuleHandleA()`` function, which returns a win32 module -handle. +the ``rand()`` function, which takes no arguments and returns a pseudo-random integer:: -This example calls both functions with a ``NULL`` pointer (``None`` should be used -as the ``NULL`` pointer):: + >>> print(libc.rand()) # doctest: +SKIP + 1804289383 + +On Windows, you can call the ``GetModuleHandleA()`` function, which returns a win32 module +handle (passing ``None`` as single argument to call it with a ``NULL`` pointer):: - >>> print(libc.time(None)) # doctest: +SKIP - 1150640792 >>> print(hex(windll.kernel32.GetModuleHandleA(None))) # doctest: +WINDOWS 0x1d000000 >>> @@ -247,6 +246,8 @@ Fundamental data types | :class:`c_ssize_t` | :c:type:`ssize_t` or | int | | | :c:type:`Py_ssize_t` | | +----------------------+------------------------------------------+----------------------------+ +| :class:`c_time_t` | :c:type:`time_t` | int | ++----------------------+------------------------------------------+----------------------------+ | :class:`c_float` | :c:type:`float` | float | +----------------------+------------------------------------------+----------------------------+ | :class:`c_double` | :c:type:`double` | float | @@ -447,6 +448,21 @@ By default functions are assumed to return the C :c:type:`int` type. Other return types can be specified by setting the :attr:`restype` attribute of the function object. +The C prototype of ``time()`` is ``time_t time(time_t *)``. Because ``time_t`` +might be of a different type than the default return type ``int``, you should +specify the ``restype``:: + + >>> libc.time.restype = c_time_t + +The argument types can be specified using ``argtypes``:: + + >>> libc.time.argtypes = (POINTER(c_time_t),) + +To call the function with a ``NULL`` pointer as first argument, use ``None``:: + + >>> print(libc.time(None)) # doctest: +SKIP + 1150640792 + Here is a more advanced example, it uses the ``strchr`` function, which expects a string pointer and a char, and returns a pointer to a string:: @@ -2275,6 +2291,13 @@ These are the fundamental ctypes data types: .. versionadded:: 3.2 +.. class:: c_time_t + + Represents the C :c:type:`time_t` datatype. + + .. versionadded:: 3.12 + + .. class:: c_ubyte Represents the C :c:type:`unsigned char` datatype, it interprets the value as diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 26135ad..b94b337 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -11,6 +11,7 @@ from _ctypes import CFuncPtr as _CFuncPtr from _ctypes import __version__ as _ctypes_version from _ctypes import RTLD_LOCAL, RTLD_GLOBAL from _ctypes import ArgumentError +from _ctypes import SIZEOF_TIME_T from struct import calcsize as _calcsize @@ -563,4 +564,11 @@ for kind in [c_ushort, c_uint, c_ulong, c_ulonglong]: elif sizeof(kind) == 8: c_uint64 = kind del(kind) +if SIZEOF_TIME_T == 8: + c_time_t = c_int64 +elif SIZEOF_TIME_T == 4: + c_time_t = c_int32 +else: + raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}") + _reset_cache() diff --git a/Lib/test/test_ctypes/test_sizes.py b/Lib/test/test_ctypes/test_sizes.py index 4ceacbc..bf8d6ea 100644 --- a/Lib/test/test_ctypes/test_sizes.py +++ b/Lib/test/test_ctypes/test_sizes.py @@ -28,6 +28,9 @@ class SizesTestCase(unittest.TestCase): def test_ssize_t(self): self.assertEqual(sizeof(c_void_p), sizeof(c_ssize_t)) + def test_time_t(self): + self.assertEqual(sizeof(c_time_t), SIZEOF_TIME_T) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2022-05-17-06-27-39.gh-issue-92869.t8oBkw.rst b/Misc/NEWS.d/next/Library/2022-05-17-06-27-39.gh-issue-92869.t8oBkw.rst new file mode 100644 index 0000000..7787f34 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-05-17-06-27-39.gh-issue-92869.t8oBkw.rst @@ -0,0 +1,2 @@ +Added :class:`~ctypes.c_time_t` to :mod:`ctypes`, which has the same size as +the :c:type:`time_t` type in C. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 2c629d7..a3c7c8c 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -5784,6 +5784,7 @@ _ctypes_add_objects(PyObject *mod) MOD_ADD("RTLD_GLOBAL", PyLong_FromLong(RTLD_GLOBAL)); MOD_ADD("CTYPES_MAX_ARGCOUNT", PyLong_FromLong(CTYPES_MAX_ARGCOUNT)); MOD_ADD("ArgumentError", Py_NewRef(PyExc_ArgError)); + MOD_ADD("SIZEOF_TIME_T", PyLong_FromSsize_t(SIZEOF_TIME_T)); return 0; #undef MOD_ADD } -- cgit v0.12