| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| 1 #include <stdexcept> | 1 #include <stdexcept> |
| 2 #include <functional> | |
| 3 #include <wctype.h> | |
| 4 // <thread> is C++11, but implemented in VS2012 | |
| 5 #include <thread> | |
| 2 | 6 |
| 3 #include "process.h" | 7 #include "process.h" |
| 4 | 8 |
| 9 //------------------------------------------------------- | |
| 10 // Windows_Handle | |
| 11 //------------------------------------------------------- | |
| 5 Windows_Handle::Windows_Handle( HANDLE h ) | 12 Windows_Handle::Windows_Handle( HANDLE h ) |
| 6 : handle( h ) | 13 : handle( h ) |
| 7 { | 14 { |
| 15 validate_handle() ; | |
| 16 } | |
| 17 | |
| 18 Windows_Handle::~Windows_Handle() | |
| 19 { | |
| 20 CloseHandle( handle ) ; | |
| 21 } | |
| 22 | |
| 23 void Windows_Handle::operator=( HANDLE h ) | |
| 24 { | |
| 25 this -> ~Windows_Handle() ; | |
| 26 handle = h ; | |
| 27 validate_handle() ; | |
| 28 } | |
| 29 | |
| 30 void Windows_Handle::validate_handle() | |
| 31 { | |
| 8 if ( handle == INVALID_HANDLE_VALUE ) | 32 if ( handle == INVALID_HANDLE_VALUE ) |
| 9 { | 33 { |
| 10 throw std::runtime_error( "Invalid handle" ) ; | 34 throw std::runtime_error( "Invalid handle" ) ; |
| 11 } | 35 } |
| 12 } | 36 } |
| 13 | 37 |
| 14 Windows_Handle::~Windows_Handle() | 38 //------------------------------------------------------- |
| 15 { | 39 // process_by_name_CI |
| 16 CloseHandle( handle ) ; | 40 //------------------------------------------------------- |
| 17 } | 41 process_by_name_CI::process_by_name_CI( const wchar_t * name ) |
| 42 : name( name ), length( wcslen( name ) ) | |
| 43 {} | |
| 44 | |
| 45 bool process_by_name_CI::operator()( const PROCESSENTRY32W & process ) | |
| 46 { | |
| 47 return 0 == wcsncmpi( process.szExeFile, name, length ) ; | |
| 48 } | |
| 49 | |
| 50 //------------------------------------------------------- | |
| 51 // process_by_any_exe_name_CI | |
| 52 //------------------------------------------------------- | |
| 53 bool process_by_any_exe_name_CI::operator()( const PROCESSENTRY32W & process ) | |
| 54 { | |
| 55 return names.find( process.szExeFile ) != names.end() ; | |
| 56 } | |
| 57 | |
| 58 //------------------------------------------------------- | |
| 59 // wcscmpi | |
| 60 //------------------------------------------------------- | |
| 61 int wcscmpi( const wchar_t * s1, const wchar_t * s2 ) | |
|
Oleksandr
2014/03/12 19:26:04
Why don't we just use _wcsicmp instead? http://msd
Eric
2014/03/17 12:26:46
We could use an MS-specific library function, I su
| |
| 62 { | |
| 63 // Note: Equality of character sequences is case-insensitive in all predicates below. | |
| 64 // Loop invariant: s1[0..j) == s2[0..j) | |
| 65 const size_t LIMIT( 4294967295 ) ; // Runaway limit of 2^32 - 1 should be acce ptably long. | |
|
Eric
2014/03/17 12:26:46
This limit ought to be 2^16 - 1. I don't know why
| |
| 66 for ( size_t j = 0 ; j < LIMIT ; ++j ) | |
| 67 { | |
| 68 wchar_t c1 = towupper( *s1++ ) ; | |
| 69 wchar_t c2 = towupper( *s2++ ) ; | |
| 70 if ( c1 != c2 ) | |
| 71 { | |
| 72 // Map to -1/+1 because c2 - c1 may not fit into an 'int'. | |
| 73 return ( c1 < c2 ) ? -1 : 1 ; | |
| 74 } | |
| 75 else | |
| 76 { | |
| 77 if ( c1 == L'\0' ) | |
| 78 { | |
| 79 // Assert length( s1 ) == length( s2 ) == j | |
| 80 // Assert strings are equal at length < n | |
| 81 return 0 ; | |
| 82 } | |
| 83 } | |
| 84 } | |
| 85 // Assert j == LIMIT | |
| 86 // Assert s1[0..LIMIT) == s2[0..LIMIT) | |
| 87 // Both strings are longer than 64K, which violates the precondition | |
| 88 throw std::runtime_error( "String arguments too long for wcscmpi" ) ; | |
| 89 } | |
| 90 | |
| 91 //------------------------------------------------------- | |
| 92 // wcsncmpi | |
| 93 //------------------------------------------------------- | |
| 94 int wcsncmpi( const wchar_t * s1, const wchar_t * s2, unsigned int n ) | |
|
Oleksandr
2014/03/12 19:26:04
_wcsnicmp? http://msdn.microsoft.com/en-us/library
| |
| 95 { | |
| 96 // Note: Equality of character sequences is case-insensitive in all predicates below. | |
| 97 // Loop invariant: s1[0..j) == s2[0..j) | |
| 98 for ( unsigned int j = 0 ; j < n ; ++j ) | |
| 99 { | |
| 100 wchar_t c1 = towupper( *s1++ ) ; | |
| 101 wchar_t c2 = towupper( *s2++ ) ; | |
| 102 if ( c1 != c2 ) | |
| 103 { | |
| 104 // Map to -1/+1 because c2 - c1 may not fit into an 'int'. | |
| 105 return ( c1 < c2 ) ? -1 : 1 ; | |
| 106 } | |
| 107 else | |
| 108 { | |
| 109 if ( c1 == L'\0' ) | |
| 110 { | |
| 111 // Assert length( s1 ) == length( s2 ) == j | |
| 112 // Assert strings are equal at length < n | |
| 113 return 0 ; | |
| 114 } | |
| 115 } | |
| 116 } | |
| 117 // Assert j == n | |
| 118 // Assert s1[0..n) == s2[0..n) | |
| 119 // The semantics of n-compare ignore everything after the first 'n' characters . | |
| 120 return 0 ; | |
| 121 } | |
| 122 | |
| 123 //------------------------------------------------------- | |
| 124 // creator_process | |
| 125 //------------------------------------------------------- | |
| 126 DWORD creator_process( HWND window ) | |
| 127 { | |
| 128 DWORD pid ; | |
| 129 DWORD r = GetWindowThreadProcessId( window, & pid ) ; | |
| 130 if ( r == 0 ) | |
| 131 { | |
| 132 // Assert GetWindowThreadProcessId returned an error | |
| 133 // If the window handle is invalid, we end up here. | |
| 134 throw std::runtime_error( "" ) ; | |
| 135 } | |
| 136 return pid ; | |
| 137 } | |
| 138 | |
| 139 //------------------------------------------------------- | |
| 140 // Snapshot | |
| 141 //------------------------------------------------------- | |
| 142 Snapshot::Snapshot() | |
| 143 : handle( ::CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ) ) | |
| 144 { | |
| 145 process.dwSize = sizeof( PROCESSENTRY32W ) ; | |
| 146 } | |
| 147 | |
| 148 PROCESSENTRY32W * Snapshot::begin() | |
| 149 { | |
| 150 return ::Process32FirstW( handle, & process ) ? ( & process ) : 0 ; | |
| 151 } | |
| 152 | |
| 153 PROCESSENTRY32W * Snapshot::next() | |
| 154 { | |
| 155 return ::Process32NextW( handle, & process ) ? ( & process ) : 0 ; | |
| 156 } | |
| 157 | |
| 158 void Snapshot::refresh() | |
| 159 { | |
| 160 handle = ::CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ) ; | |
| 161 } | |
| 162 | |
| 163 | |
| 164 //------------------------------------------------------- | |
| 165 // send_message, send_endsession_messages | |
| 166 //------------------------------------------------------- | |
| 167 /** | |
| 168 * Default process exit wait time (per message) 5000 ms | |
| 169 * | |
| 170 * 5 seconds is time that the system will wait before it considers a process non -responsive. | |
| 171 */ | |
| 172 static const unsigned int timeout = 5000 ; // milliseconds | |
| 173 | |
| 174 /** | |
| 175 * An function object to process the results of sending window messages in send_ message. | |
| 176 * | |
| 177 * We are using send_message within a system iteration over windows. | |
| 178 * The system has its own convention for continuing/breaking the iteration. | |
| 179 * This convention is assured consistently in send_message, which also provides default behavior. | |
| 180 * This class provides the base for any variation from the default behavior. | |
| 181 */ | |
| 182 struct message_accumulator | |
| 183 : public std::binary_function< DWORD_PTR, BOOL, bool > | |
| 184 { | |
| 185 virtual result_type operator()( first_argument_type result, second_argument_ty pe return_value ) = 0 ; | |
| 186 virtual ~message_accumulator() {} ; | |
| 187 } ; | |
| 188 | |
| 189 /** | |
| 190 * Iteration action to send a message to a window and accumulate results. | |
| 191 * | |
| 192 * An error sending the message is not a failure for the function a whole. | |
| 193 * The goal is to close the process, and if the window is no longer present, the n the process may have already closed. | |
| 194 * Therefore, we're ignoring both the return value and the result. | |
| 195 */ | |
| 196 class send_message | |
| 197 { | |
| 198 UINT message ; ///< Message type for windows message | |
| 199 WPARAM p1 ; ///< Generic parameter 1 for windows message | |
| 200 LPARAM p2 ; ///< Generic parameter 2 for windows message | |
| 201 message_accumulator * f ; ///< Processor for results of sending the messag e. | |
| 202 | |
| 203 public: | |
| 204 /** | |
| 205 * Full contructor gathers message parameters and a message accumulator. | |
| 206 */ | |
| 207 send_message( UINT message, WPARAM p1, LPARAM p2, message_accumulator & f ) | |
| 208 : message( message ), p1( p1 ), p2( p2 ), f( & f ) | |
| 209 {} | |
| 210 | |
| 211 /** | |
| 212 * Abbreviated contructor gathers only message parameters. | |
| 213 * The message accumulator is absent. | |
| 214 */ | |
| 215 send_message( UINT message, WPARAM p1, LPARAM p2 ) | |
| 216 : message( message ), p1( p1 ), p2( p2 ), f( 0 ) | |
| 217 {} | |
| 218 | |
| 219 /* | |
| 220 * Enumeration function applied to each window. | |
| 221 */ | |
| 222 bool operator()( HWND window ) | |
| 223 { | |
| 224 DWORD_PTR result ; | |
| 225 BOOL rv = SendMessageTimeoutW( window, message, p1, p2, SMTO_BLOCK, timeout, & result ) ; | |
| 226 /* | |
| 227 * If we have no message accumulator, the default behavior is to iterate eve rything. | |
| 228 * If we do have one, we delegate to it the decision whether to break or to continue. | |
| 229 */ | |
| 230 if ( ! f ) | |
| 231 { | |
| 232 return true ; | |
| 233 } | |
| 234 return ( * f )( result, rv ) ; | |
| 235 } | |
| 236 } ; | |
| 237 | |
| 238 /** | |
| 239 * Send WM_QUERYENDSESSION and WM_ENDSESSION to a window. | |
| 240 * | |
| 241 * This window processor tries to shut down each application individually. | |
| 242 * The alternative, gathering all the query results first and only then ending s essions, cannot be done with a single window enumeration. | |
| 243 */ | |
| 244 class send_endsession_messages | |
| 245 { | |
| 246 public: | |
| 247 /* | |
| 248 * Enumeration function applied to each window. | |
| 249 */ | |
| 250 bool operator()( HWND window ) | |
| 251 { | |
| 252 DWORD_PTR result ; | |
| 253 if ( ! SendMessageTimeoutW( window, WM_QUERYENDSESSION, 0, ENDSESSION_CLOSEA PP, SMTO_BLOCK, timeout, & result ) ) | |
| 254 { | |
| 255 // Assert sending the message failed | |
| 256 // Ignore failure, just as with send_message(). | |
| 257 return true ; | |
| 258 } | |
| 259 // Assert result is FALSE if the process has refused notice that it should s hut down. | |
| 260 if ( ! result ) | |
| 261 { | |
| 262 /* | |
| 263 * Returning false terminates iteration over windows. | |
| 264 * Since this process is refusing to shut down, we can't close all the pro cesses and the operation fails. | |
| 265 */ | |
| 266 return false ; | |
| 267 } | |
| 268 SendMessageTimeoutW( window, WM_ENDSESSION, 0, ENDSESSION_CLOSEAPP, SMTO_BLO CK, timeout, 0 ) ; | |
| 269 return true ; | |
| 270 } | |
| 271 } ; | |
| 272 | |
| 273 /** | |
| 274 * Accumulator for query-endsession message. | |
| 275 * | |
| 276 * Implements a conditional-conjunction of the query results. | |
| 277 * All answers must be true in order for this result to be true, | |
| 278 * and the calculation is terminated at the first answer 'false'. | |
| 279 * As usual, errors sending messages are ignored. | |
| 280 */ | |
| 281 struct endsession_accumulator : | |
| 282 public message_accumulator | |
| 283 { | |
| 284 bool permit_end_session ; ///< Accumulator variable yields final result. | |
| 285 | |
| 286 /** | |
| 287 * Enumeration function applied to each window. | |
| 288 */ | |
| 289 bool operator()( DWORD_PTR result, BOOL return_value ) | |
| 290 { | |
| 291 if ( ( ! return_value ) || result ) | |
| 292 { | |
| 293 // 1. If the result is true, then the process will permit WM_ENDSESSION | |
| 294 // 2. An error sending the message counts as "no new information" | |
| 295 return true ; | |
| 296 } | |
| 297 // The first false is the result of the calculation. | |
| 298 // The second false means to terminate enumeration early. | |
| 299 permit_end_session = false ; | |
| 300 return false ; | |
| 301 } | |
| 302 | |
| 303 /** | |
| 304 * Ordinary constructor. | |
| 305 */ | |
| 306 endsession_accumulator() | |
| 307 : permit_end_session( true ) | |
| 308 {} | |
| 309 } ; | |
| 310 | |
| 311 //------------------------------------------------------- | |
| 312 // Process_Closer | |
| 313 //------------------------------------------------------- | |
| 314 /** | |
| 315 * Shut down all the processes in the pid_list. | |
| 316 * | |
| 317 * The method used here uses blocking system calls to send messages to target pr ocesses. | |
| 318 * Message processing delays, therefore, are sequential and the total delay is t heir sum. | |
| 319 * Windows has non-blocking message calls available, and using a multi-threaded implementation would shorten that delay. | |
| 320 * The code, hwoever, is significantly simpler without multi-threading. | |
| 321 * The present use of this method is not closing dozens of applications, so dela y performance is not critical. | |
| 322 * | |
| 323 * \return | |
| 324 * The negation of is_running. | |
| 325 * If is_running() was true at the beginning, then this function will have run refresh() before returning. | |
| 326 * | |
| 327 * \sa | |
| 328 * - MSDN [WM_QUERYENDSESSION message](http://msdn.microsoft.com/en-us/library /windows/desktop/aa376890%28v=vs.85%29.aspx) | |
| 329 * - MSDN [WM_ENDSESSION message](http://msdn.microsoft.com/en-us/library/wind ows/desktop/aa376889%28v=vs.85%29.aspx) | |
| 330 */ | |
| 331 bool Process_Closer::shut_down() | |
| 332 { | |
| 333 /* | |
| 334 * If we're not running, we don't need to shut down. | |
| 335 */ | |
| 336 if ( ! is_running() ) | |
| 337 { | |
| 338 return true ; | |
| 339 } | |
| 340 | |
| 341 /* | |
| 342 * Shutting down is a structure as an escalating series of attempts to shut do wn. | |
| 343 * After each one, we wait to see if the shut down has completed. | |
| 344 * Even though we're using a blocking call to send messages, applications need not block before exiting. | |
| 345 * Internet Explorer, in particular, does not. | |
| 346 * | |
| 347 * Note that termination occurs inside the default case within the switch stat ement | |
| 348 */ | |
| 349 for ( unsigned int stage = 1 ; ; ++ stage ) | |
| 350 { | |
| 351 // Assert is_running() | |
| 352 switch( stage ) | |
| 353 { | |
| 354 case 1 : | |
| 355 /* | |
| 356 * Send WM_QUERYENDSESSION to every admissible window. | |
| 357 * Send WM_ENDSESSION if all processes are ready to shut down. | |
| 358 * We try this technique first, since this allows an application to restor e its application state when it starts up again. | |
| 359 */ | |
| 360 { | |
| 361 endsession_accumulator acc ; | |
| 362 send_message m1( WM_QUERYENDSESSION, 0, ENDSESSION_CLOSEAPP, acc ) ; | |
| 363 iterate_our_windows( m1 ) ; | |
| 364 | |
| 365 if ( acc.permit_end_session ) | |
| 366 { | |
| 367 send_message m2( WM_ENDSESSION, 0, 0 ) ; | |
| 368 iterate_our_windows( m2 ) ; | |
| 369 } | |
| 370 } | |
| 371 break ; | |
| 372 | |
| 373 case 2 : | |
| 374 { | |
| 375 /* | |
| 376 * Send WM_QUERYENDSESSION and WM_ENDSESSION to every admissible window singly, not accumulating results. | |
| 377 */ | |
| 378 send_endsession_messages m ; | |
| 379 iterate_our_windows( m ) ; | |
| 380 } | |
| 381 break ; | |
| 382 | |
| 383 case 3 : | |
| 384 { | |
| 385 /* | |
| 386 * Send WM_CLOSE to every admissible window. | |
| 387 */ | |
| 388 send_message m( WM_CLOSE, 0, 0 ) ; | |
| 389 iterate_our_windows( m ) ; | |
| 390 } | |
| 391 break ; | |
| 392 | |
| 393 default : | |
| 394 /* | |
| 395 * We're out of ways to try to shut down. | |
| 396 */ | |
| 397 return false ; | |
| 398 } | |
| 399 | |
| 400 /* | |
| 401 * Wait loop. | |
| 402 */ | |
| 403 for ( unsigned int j = 0 ; j < 50 ; ++ j ) | |
| 404 { | |
| 405 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ) ; | |
| 406 refresh() ; | |
| 407 if ( ! is_running() ) | |
| 408 { | |
| 409 return true ; | |
| 410 } | |
| 411 } | |
| 412 // Assert is_running() | |
| 413 } | |
| 414 // No control path leaves the for-loop. | |
| 415 } ; | |
| 416 | |
| OLD | NEW |