4

I am creating a Python wrapper for a C-extension which is a driver for a sensor. I need to pass the Ctrl-C interrupt signal with Python to the driver so it ends the current acquisition phase and does not start a new one. I found these two related subjects : Allowing Ctrl-C to interrupt a python C-extension Allowing Ctrl-C to interrupt a python C-extension However they do not answer my question as I am using Ctypes (see below). Is there a way to interupt the C-extension using Python's threads or ctypes. I would like to avoid any change on the C code. Goal would be to stop the endless loop function

Python code:

import signal
import ctypes 
import os 
import sys 

if __name__ == "__main__" :
    libname = os.path.abspath(os.path.join(os.path.dirname(__file__),"clib.so"))
    LIBC = ctypes.CDLL(libname)
    LIBC.main.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p),]
    args=(ctypes.c_char_p * (len(sys.argv)-1))(str.encode(sys.argv[1]))
    LIBC.main(len(args),args)
    signal.signal(signal.SIGINT, lambda s,f:os.kill(os.getpid(), signal.SIGTERM))

C Code :

#include <stdlib.h>
#include <stdio.h>
#incluse <string.h>

void endless_loop()
{
    while(1){printf("Can't stop me ! \n");}
}
int main(int argc, char* argv[])
{
    endless_loop();
    return 0 ;
}

Makefile:

all: test

clean:
    rm -f *.o *.so *.html

clib.so: clib.o
    gcc -shared -o clib.so clib.c -fPIC

clib.o: clib.c
    gcc -Wall -Werror clib.c -fPIC

test: clib.so
    chmod 777 pyclib.py
    python pyclib.py 2

Thankfully,

1 Answers1

4
    LIBC.main(len(args),args)
    signal.signal(signal.SIGINT, lambda s,f:os.kill(os.getpid(), signal.SIGTERM))

If you want the signal handler to be invoked while LIBC.main is running, you must install it (via signal.signal) before LIBC.main is called, not after it returns.

But, as you noticed: It still does't work. That's because a Python signal handler doesn't get executed while the C-extension is running, and since Python on its own initiative installs a SIGINT handler, by default Ctrl-C doesn't work under this condition. In order to make it interrupt the program, restore the default signal behavior:

    signal.signal(signal.SIGINT, signal.SIG_DFL)
    LIBC.main(len(args), args)
Armali
  • 18,255
  • 14
  • 57
  • 171
  • 1
    This is a citation of your code, as one can tell from the vertical line to the left. – Armali Jul 19 '21 at 15:01
  • 1
    I amended the answer. – Armali Jul 19 '21 at 16:29
  • 1
    Thank you a lot, it is working ! May I ask you some details on your explanation, or a link if you don't want to explain (which I can understand) ? You say Python installs a SIGINT handler, why a different signal than what I guess is default ? – Hugo Castaing Jul 19 '21 at 19:03
  • I'll gladly explain, but I'm not sure how to understand your question about _a different signal than what I guess_. We are talking about just one signal, namely SIGINT. SIG_DFL ist not a signal, it's a special identifier denoting that the normal handling of the given signal is to be reinstated, which for SIGINT is to terminate the process. – Armali Jul 19 '21 at 19:11