Difference between revisions of "Ctrl C"

(Use `getc' instead of `SleepEx' since the later put the thread in an alertable state)
 
(7 intermediate revisions by the same user not shown)
Line 2: Line 2:
 
On Windows, the handling of SIGINT (aka Ctrl+C for a DOS prompt) is done in a different thread than the running thread. Which means that the current runtime cannot handle it properly without failing, since it throws an exception in the wrong thread. This problem is even more visible in a multithreaded application.
 
On Windows, the handling of SIGINT (aka Ctrl+C for a DOS prompt) is done in a different thread than the running thread. Which means that the current runtime cannot handle it properly without failing, since it throws an exception in the wrong thread. This problem is even more visible in a multithreaded application.
  
I've posted a question on the Microsoft newsgroups and here is the [http://groups.google.ie/group/microsoft.public.win32.programmer.kernel/browse_frm/thread/b0bdada7bae7ffa4/e4ea8cd55871b466?tvc=1&hl=en#e4ea8cd55871b466 discussion thread]. The interesting part of this is a reference to [http://groups.google.ie/group/microsoft.public.win32.programmer.kernel/browse_frm/thread/608ad10204f76515/1e175f06dca6106f?hl=en#1e175f06dca6106f  another discussion thread] where they propose a solution. A quick summary is the following code:
+
I've posted a question on the Microsoft newsgroups and here is the [http://groups.google.ie/group/microsoft.public.win32.programmer.kernel/browse_frm/thread/b0bdada7bae7ffa4/e4ea8cd55871b466?tvc=1&hl=en#e4ea8cd55871b466 discussion thread]. The interesting part of this is a reference to [http://groups.google.ie/group/microsoft.public.win32.programmer.kernel/browse_frm/thread/608ad10204f76515/1e175f06dca6106f?hl=en#1e175f06dca6106f  another discussion thread] where they propose a solution.  
 +
 
 +
One way to do this is to actually modify the context of the running thread so that it starts a different routine:
  
 
<c>
 
<c>
// Suspend the thread
+
// Suspend the thread
 
+
SuspendThread (hSorryThread);
SuspendThread(hSorryThread);
+
 
+
// Get the thread's suspended context and then
+
// update it to point to the cleanup routine ...
+
  
 +
// Get the thread's suspended context and then
 +
// update it to point to the cleanup routine ...
 
ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
 
ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
 
ctx.Eip = (DWORD) CleanupProc;                    // You define this
 
ctx.Eip = (DWORD) CleanupProc;                    // You define this
SetThreadContext(hSorryThread, &ctx);
+
SetThreadContext (hSorryThread, &ctx);
  
// and resume the thread with the new context
+
// and resume the thread with the new context
 +
ResumeThread (hSorryThread);
  
ResumeThread(hSorryThread);
+
// Note that the sketch above assumes that CleanupProc() takes no arguments and
 
+
// will exit the thread. It had better because the few lines above don't do
// Note that the sketch above assumes that CleanupProc() takes no arguments and
+
// anything with the stack.  
// will exit the thread. It had better because the few lines above don't do
+
// anything with the stack.  
+
 
</c>
 
</c>
  
 
This needs to be investigated.
 
This needs to be investigated.
 +
 +
Another solution is to use '''QueueUserAPC''', it works great however the running thread needs to be in an alertable state which is possibly hard to ensure without changing the code generation. A solution to avoid the alertable state is proposed in two Code Project samples ([http://www.codeproject.com/threads/queueuserapcex.asp sample1] and [http://www.codeproject.com/threads/QueueUserAPCEx_v2.asp sample2]). However this requires installing a special driver.
 +
 +
Yet another solution based on '''QueueUserAPC''' but without using a driver, but using NT kernel APIs '''NtAlertThread''' and '''NtResumeThread'''. This solution is mentioned in the second code project page above, under the ''NTDLL interfaces for APCs'' discussion thread.
 +
 +
Here is a C program that can be used for testing:
 +
 +
<c>
 +
#include <windows.h>
 +
#include <stdio.h>
 +
#include <signal.h>
 +
 +
static HANDLE thread;
 +
 +
VOID CALLBACK my_handler (ULONG_PTR dwParam) {
 +
printf ("I'm here\n");
 +
}
 +
 +
BOOL CtrlHandler( DWORD fdwCtrlType )
 +
{
 +
  DWORD result;
 +
  switch( fdwCtrlType )
 +
  {
 +
    // Handle the CTRL-C signal.
 +
    case CTRL_C_EVENT:
 +
      printf( "Ctrl-C event\n\n" );
 +
  result = QueueUserAPC (my_handler, thread, 0);
 +
      return( TRUE );
 +
 +
    default:
 +
      return FALSE;
 +
  }
 +
}
 +
 +
void handler (int sig) {
 +
printf ("From Signal\n");
 +
}
 +
 +
 +
DWORD WINAPI process(LPVOID lpParam)
 +
{
 +
  printf( "\nThe Control Handler is installed.\n" );
 +
  printf( "\n -- Now try pressing Ctrl+C or Ctrl+Break, or" );
 +
  printf( "\n    try logging off or closing the console...\n" );
 +
  printf( "\n(...waiting in a loop for events...)\n\n" );
 +
  while( 1 ){
 +
// SleepEx(1000, TRUE);
 +
        getc(stdin);
 +
}
 +
}
 +
 +
void main( void )
 +
{
 +
//signal (SIGINT, handler);
 +
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE );
 +
 +
thread= CreateThread(NULL, 0, process, NULL, 0, NULL);
 +
WaitForSingleObject(thread, INFINITE);
 +
}
 +
</c>

Latest revision as of 09:12, 7 June 2007

On Windows, the handling of SIGINT (aka Ctrl+C for a DOS prompt) is done in a different thread than the running thread. Which means that the current runtime cannot handle it properly without failing, since it throws an exception in the wrong thread. This problem is even more visible in a multithreaded application.

I've posted a question on the Microsoft newsgroups and here is the discussion thread. The interesting part of this is a reference to another discussion thread where they propose a solution.

One way to do this is to actually modify the context of the running thread so that it starts a different routine:

// Suspend the thread
SuspendThread (hSorryThread);
 
	// Get the thread's suspended context and then
	// update it to point to the cleanup routine ...
ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
ctx.Eip = (DWORD) CleanupProc;                    // You define this
SetThreadContext (hSorryThread, &ctx);
 
	// and resume the thread with the new context
ResumeThread (hSorryThread);
 
	// Note that the sketch above assumes that CleanupProc() takes no arguments and
	// will exit the thread. It had better because the few lines above don't do
	// anything with the stack.

This needs to be investigated.

Another solution is to use QueueUserAPC, it works great however the running thread needs to be in an alertable state which is possibly hard to ensure without changing the code generation. A solution to avoid the alertable state is proposed in two Code Project samples (sample1 and sample2). However this requires installing a special driver.

Yet another solution based on QueueUserAPC but without using a driver, but using NT kernel APIs NtAlertThread and NtResumeThread. This solution is mentioned in the second code project page above, under the NTDLL interfaces for APCs discussion thread.

Here is a C program that can be used for testing:

#include <windows.h> 
#include <stdio.h> 
#include <signal.h>
 
static HANDLE thread;
 
VOID CALLBACK my_handler (ULONG_PTR dwParam) {
	printf ("I'm here\n");
}
 
BOOL CtrlHandler( DWORD fdwCtrlType ) 
{ 
  DWORD result;
  switch( fdwCtrlType ) 
  { 
    // Handle the CTRL-C signal. 
    case CTRL_C_EVENT: 
      printf( "Ctrl-C event\n\n" );
	  result = QueueUserAPC (my_handler, thread, 0);
      return( TRUE );
 
    default: 
      return FALSE; 
  } 
} 
 
void handler (int sig) {
	printf ("From Signal\n");
}
 
 
DWORD WINAPI process(LPVOID lpParam)
{
   printf( "\nThe Control Handler is installed.\n" ); 
   printf( "\n -- Now try pressing Ctrl+C or Ctrl+Break, or" ); 
   printf( "\n    try logging off or closing the console...\n" ); 
   printf( "\n(...waiting in a loop for events...)\n\n" ); 
   while( 1 ){
	// SleepEx(1000, TRUE);
        getc(stdin);
} 
}
 
void main( void ) 
{ 
	//signal (SIGINT, handler);
	SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE );
 
	thread= CreateThread(NULL, 0, process, NULL, 0, NULL);
	WaitForSingleObject(thread, INFINITE);
}