Can you mock a C function using ctypes?
Eryk Sun
eryksun at gmail.com
Fri Sep 16 01:51:00 EDT 2022
On 9/15/22, Grant Edwards <grant.b.edwards at gmail.com> wrote:
>
> Does the pointer have to be passed? Or can it be "poked" into a global
> variable?
If the library exports the function pointer as a global variable, it
can be set in ctypes using the in_dll() method. Unfortunately, as far
as I know, a ctypes function prototype can't be used directly for
this, at least not easily, since the function pointer it creates
doesn't have a `value` or `contents` attribute to set the target
address. Instead, the exported global can be accessed as a void
pointer. It's clunky, but it works.
For example, Python's REPL supports a callback for reading a line from
the terminal. Normally it's either hooked by a C extension, such as
the readline module, or set to the default function
PyOS_StdioReadline(). We can use a ctypes callback to hook into this
and chain to the previous function.
import ctypes
PyOS_RFP = ctypes.CFUNCTYPE(
ctypes.c_void_p,
ctypes.c_void_p, # stdin (FILE *)
ctypes.c_void_p, # stdout (FILE *)
ctypes.c_char_p, # prompt
)
@PyOS_RFP
def readline_hook(stdin, stdout, prompt):
print('HOOKED: ', end='', flush=True)
return prev_rfp(stdin, stdout, prompt)
rfp_vp = ctypes.c_void_p.in_dll(ctypes.pythonapi,
'PyOS_ReadlineFunctionPointer')
if rfp_vp:
# there's a previous RFP that can be hooked
prev_rfp = ctypes.cast(rfp_vp, PyOS_RFP)
rfp_vp.value = ctypes.cast(readline_hook, ctypes.c_void_p).value
In this trivial example, "HOOKED: " is printed to the terminal before
each line is read:
HOOKED: >>> def f():
HOOKED: ... pass
HOOKED: ...
HOOKED: >>>
Note that I defined the return type of PyOS_RFP to be c_void_p. Using
c_char_p as the return type would convert the result to a bytes
object, which is wrong in this case. If the setfunc of the callback's
return type has to keep a reference to a converted Python object, such
as a bytes object, then the callback has no choice but to leak the
reference in order to keep the object alive indefinitely, which
usually causes a memory leak.
More information about the Python-list
mailing list