J'aimerais terminer un process cree avec CreateProcess avec ExitProcess et non TerminateProcess.
ExitProcess termine le process appelant. Comment utiliser dans ExitProcess le HANDLE retourne par CreateProcess ?
merci
Version imprimable
J'aimerais terminer un process cree avec CreateProcess avec ExitProcess et non TerminateProcess.
ExitProcess termine le process appelant. Comment utiliser dans ExitProcess le HANDLE retourne par CreateProcess ?
merci
Il y a un moyen, mais c'est crade.
Mais paradoxalement, moins crade que TerminateProcess().
Néanmoins, ni l'un ni l'autre ne sont recommandés. La bonne méthode, c'est de dire à l'autre processus de se terminer (par un message Windows ou un autre mécanisme d'IPC).
2 points me genent:
* la doc de msdn dit explicitement d'utiliser ExitProcess(). C'est tres bizarre qu'on ne puisse pas l'utiliser
* dans la mesure ou CreateProcess() peut lancer n'importe quelle application, je ne peux pas savoir comment l'appli se termine (je fais un wrapper autour de CreateProcess, donc la commande qu'on lui passe n'est pas connue a l'avance)
Ben non, c'est pour quitter le processus courant. Elle recommande sans doute que le processus destination utiliser ExitProcess() pour quitter, c'est son droit (même si personnellement je trouve ça sale: Dans le cas idéal, un processus se termine quand tous ses threads se sont terminés, mais de nos jours il y a tellement de fonctions qui créent un thread en arrière-plan que ça n'est plus réaliste, et le code appelle automatiquement ExitProcess() quand la fonction main() retourne).
Et pourtant, tu autorises à tuer le processus créé? Ben tu ne peux pas avoir le beurre et l'argent du beurre, ni être sâle et propre à la fois. D'un autre côté, tu peux essayer plusieurs méthodes successives, comme le fait le gestionnaire de tâches: Envoyer des messages de fin à ses fenêtres, puis si ça ne marche pas, à son thread principal, puis si ça ne marche toujours pas, tuer le processus...Citation:
* dans la mesure ou CreateProcess() peut lancer n'importe quelle application, je ne peux pas savoir comment l'appli se termine (je fais un wrapper autour de CreateProcess, donc la commande qu'on lui passe n'est pas connue a l'avance)
le thread principal, c'est celui qui se trouve dans le PROCESS_INFORMATION rempli par CreateProcess ?
Oui.
Disons que c'est le premier thread créé, et que c'est généralement celui utilisé pour l'interface utilisateur (je pense notamment que ça n'est pas le cas pour les applications Java).
C'est surtout que si ce thread principal se termine, ton process se termine également. Donc, les autres threads du processus se font flinguer au passage...
En règle générale, pour qu'il ne se termine pas sans prévenir, on y met justement la boucle de messages Windows, ça occupe utilement le thread principal justement.
Seulement s'il se termine par un retour du main.
On peut explicitement le terminer avec ExitThread() pour éviter cela, mais ça n'est pas recommandé parce que certains composants du systême (comme COM) peuvent créer des threads sur lesquels tu n'as aucun contrôle, et qui empêchent le processus de se terminer naturellement quand tous les threads "à toi" sont terminés.
Edit: En bonus, l'article du lendemain. Quand je disais que ExitProcess() était sale...
Le thread principal, c'est celui exécutant le main, justement...
Faudrait essayer pour le fun, mais je doute fortement que flinguer le thread principal (via TerminateThread, tant qu'à faire soyons crades jusqu'au bout) depuis un thread / processus à part permette au processus de continuer à tourner.
Je l'ai déjà fait: Thread principal lance un second thread, second thread fait un truc (comme afficher des messages toutes les secondes pendant dix secondes), premier thread appelle ExitThread() au lieu de sortir du main()...
PS: Doc à l'appui
"ExitThread()", c'est "propre" (enfin... c'est relatif, hein) par rapport à "TerminateThread()"... Enfin, ça reste juste de la curiosité quoi qu'il en soit vu que cela reste du code "crade" si ce n'est pas fait correctement, avec la coopération propre du processus que l'on veut terminer.
donc pour résumer, j'appelle CreateProcess. Si pi est la structure PROCESS_INFORMATION remplie par CreateProcess, j'appelle CloseHandle sur pi.hProcess (je n'en ai pas besoin). Puis si plus tard je veux arreter l'application lancee par CreateProcess:
- J'appelle CloseHandle(pi.hThread)
- Si ca foire, J'appelle TerminateProcess(pi.ProcessId, 0)
c'est ca ? Notez que j'appelle CloseHandle et pas ExitThread car c'est ce qui est indique dans la doc de CreateProcess
Non.
Vous demandez au process que vous avez créé de se terminer. Le canal de communication avec le process créé est fonction de celui-ci.
On ne "demande" pas à un programme de se terminer par un TerminateThread() sur son premier thread. Entre autres, c'est une des choses qui laissera tourner les autres threads, puisque le premier thread n'aura pas appelé ExitProcess().
La méthode que je propose, c'est plus:
- EnumWindows() + GetWindowThreadProcessId() + PostMessage(WM_CLOSE)
- et si ça ne marche pas, PostThreadMessage(WM_QUIT)
- si ça ne marche pas, et si tu en as la volonté, on peut tester plus sournois: CreateRemoteThread(ExitProcess) mais ça a tendance à planter certaines applications, et donc à lancer DrWatson. Étrangement, comme ça prévient les DLLs, je me demande parfois si ça n'est pas plus propre que TerminateProcess()...
- Quand tout le reste a échoué, TerminateProcess().
PS: TerminateProcess() nécessite un handle, pas un ID.
Un code comme ceci pour le callback ?
Je ne suis pas sur si il faut que je passe NULL pour les attributs de securite de CreateRemoteThread.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 static DWORD WINAPI _ecore_exe_thread_procedure(LPVOID data) { ExitProcess(0); return 1; } static BOOL CALLBACK _ecore_exe_enum_windows_procedure(HWND window, LPARAM data) { Ecore_Exe *exe; DWORD thread_id; exe = (Ecore_Exe *)data; thread_id = GetWindowThreadProcessId(window, NULL); if (thread_id == exe->thread_id) { if (PostMessage(window, WM_CLOSE, 0, 0)) return FALSE; if (PostThreadMessage(thread_id, WM_QUIT, 0, 0)) return FALSE; if (CreateRemoteThread(exe->process, NULL, 0, _ecore_exe_thread_procedure, NULL, 0, NULL)) return FALSE; } return TRUE; }
Concernant l'utilisation de TerminateProcess(), comme je fais un port Windows d'une bibliotheque Linux et que celle-ci utilise les signaux d'interruption, je ne sais pas s'il faut que je l'utilise tout le temps ou pas. Je demanderai au developpeur principal.
Au passage, j'ai utilise OpenProcess() apres CreateProcess pour modifier les attributs du processus. Dois-je toujours appeler CloseHandle sur le HANDLE retourne par cette donction ? Ou bien ceci est gerer par certains appels du code ci-dessus ?
merci en tout cas !
Non, la Callback doit être directement ExitProcess().
Et normalement, il faudrait regarder où se trouve la fonction dans la DLL (calculer son offset) et où se trouve la DLL dans le processus destination. Mais bon, comme il s'agit de Kernel32, on peut s'en passer (du moins sous XP).
Note aussi que cette tactique, peut-être même avec le calcul d'offset, peut être invalidée par l'introduction de mécanismes d'ASLR (Address Space Layout Randomization) sous Windows Vista.
Edit: Apparemment, l'ASLR de Vista ne change que l'emplacement des DLLs en mémoire, donc un calcul d'offset devrait contourner la protection.
Pour calculer l'offset, ceci devrait suffire:
Code:ptrdiff_t offsetExit = (char const*)GetModuleHandle("kernel32.dll") - (char const*)&ExitProcess;
Ensuite, il faut trouver la DLL dans l'autre processus. À l'époque, j'avais utilisé CreateToolHelp32Snapshot() et Module32First()/Module32Next(), mais EnumProcessModules() suffit peut-être.
Heu, je veux peut-être dire une connerie mais si la version Linux fait des Kill -9 sur ces fils, il peut faire aussi crade sous Windows.
Si c'est des signaux USR1 ou USR2, pourquoi ne pas câbler les routines d'interruption sur signal sous Linux dans la pompe à message de la fenêtre principale sur les messages WM_APP et WM_APP+1 ?
C'est juste pour faire aussi basic que sous Linux.
Mais nux peut aussi faire kill -15 au lieu de kill -9...
plus j'y reflechis, plus je me demande ou utiliser cet offset :-)
comme je fais un port d'un wrapper sur fork/exec et kill, je dois en effet emuler, plus ou moins bien, kill(). Or kill() permet de terminer un process de 4 facons differentes, dont une qui est la plus crade (SIGKILL). Neanmoins les implementations de kill() avec SIGKILL essaient toujours de terminer proprement les applis, avant, si necessaire, de tuer le process brutalement.
Je dois aussi emuler USR1 et USR2. Je vais regarder si je peux utiliser WM_APP
merci :-)
Ben tu l'ajoutes à l'emplacement de kernel32 dans le processus destination, pour obtenir l'emplacement de ExitProcess dans le processus destination...
Mais je ne vois pas trop ce que tu pourras faire pour WM_APP, vu que le programme n'est pas fait pour quitter en le recevant.
Franchement, je serais plutôt parti sur un thread en attente d'un sémaphore dans le processus-fils, sémaphore signalé par le processus-père pour demander la fin du fils.
Côté fils, une fois le thread réveillé, il va demander l'interruption du traitement de façon propre : par exemple, utilisation de booléen globaux d'arrêt (un par thread). Chaque thread / boucle du programme-fils rajoute ces flags en OR de chaque condition d'arrêt, et s'arrête proprement (même si le traitement n'est bien sûr pas terminé totalement !) si le flag d'arrêt est positionné, avec libération bien sûr de tous les mutex / sémaphores existants.
Ainsi, ton processus-fils s'arrête proprement, en interrompant tout simplement son travail en cours. En plus, c'est 100% portable si tu utilises les bonnes librairies/couches d'abstraction d'OS...
Pour rappel, la notion même de signal n'existe pas sous Windows, et les messages requièrent une boucle de message pour être traités.
Le problème, c'est qu'on n'est pas ici dans une logique de "processus fils contrôlé et écrit par le même auteur". On est dans une logique de "pouvoir lancer et tuer n'importe quoi"...
Ah, si on fait dans le crade, alors, pourquoi s'embêter à tenter de "proprifier" quelque chose qui ne peut pas l'être ?
Quadruple séquence WM_CLOSE, puis WM_QUIT (même tech), puis GenerateConsoleCtrlEvent (via CreateRemoteThread), et enfin TerminateProcess si rien ne répond et puis zou...
Le code que propose Mac LAK sur déclenchement sémaphore devrait correspondre au code de gestion des signaux dans le code Linux, non ?
dois-je, comme dans le code que j'ai posté, mettre GenerateConsoleCtrlEvent() dans _ecore_exe_thread_procedure() ?
Edit: voile le code que j'ai écrit pour simuler plus ou moins les 4 signaux unix:
SIGINT : sous unix: Ctrl-C ou Break (sous Windows Ctrl-C ou Ctrl-Break)
SIGQUIT: idem sauf qu'il y a un core dump. Je ne sais pas s'il y a quelque chose de similaire sous Windows que le debugeur de visual studio peut utiliser
SIGTERM : on ferme le process proprement (sous Windows, dans l'ordre, les 2 messages ci-dessus, les messages WM_CLOSE et WM_QUIT, puis ExitProcess())
SIGKILL : on ferme le process brutalement, en essayant de le fermer proprement avant (en faisant les opperations ci-dessus avant TerminateProcess() )
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 typedef enum { ECORE_EXE_WIN32_SIGINT, ECORE_EXE_WIN32_SIGQUIT, ECORE_EXE_WIN32_SIGTERM, ECORE_EXE_WIN32_SIGKILL } Ecore_Exe_Win32_Signal; EAPI void ecore_exe_interrupt(Ecore_Exe *exe) { if (!ECORE_MAGIC_CHECK(exe, ECORE_MAGIC_EXE)) { ECORE_MAGIC_FAIL(exe, ECORE_MAGIC_EXE, "ecore_exe_interrupt"); return; } CloseHandle(exe->process); exe->sig = ECORE_EXE_WIN32_SIGINT; while (EnumWindows(_ecore_exe_enum_windows_procedure, (LPARAM)exe)); } EAPI void ecore_exe_quit(Ecore_Exe *exe) { if (!ECORE_MAGIC_CHECK(exe, ECORE_MAGIC_EXE)) { ECORE_MAGIC_FAIL(exe, ECORE_MAGIC_EXE, "ecore_exe_quit"); return; } CloseHandle(exe->process); exe->sig = ECORE_EXE_WIN32_SIGQUIT; while (EnumWindows(_ecore_exe_enum_windows_procedure, (LPARAM)exe)); } EAPI void ecore_exe_terminate(Ecore_Exe *exe) { if (!ECORE_MAGIC_CHECK(exe, ECORE_MAGIC_EXE)) { ECORE_MAGIC_FAIL(exe, ECORE_MAGIC_EXE, "ecore_exe_terminate"); return; } CloseHandle(exe->process); exe->sig = ECORE_EXE_WIN32_SIGTERM; while (EnumWindows(_ecore_exe_enum_windows_procedure, (LPARAM)exe)); } EAPI void ecore_exe_kill(Ecore_Exe *exe) { if (!ECORE_MAGIC_CHECK(exe, ECORE_MAGIC_EXE)) { ECORE_MAGIC_FAIL(exe, ECORE_MAGIC_EXE, "ecore_exe_kill"); return; } CloseHandle(exe->process); exe->sig = ECORE_EXE_WIN32_SIGKILL; while (EnumWindows(_ecore_exe_enum_windows_procedure, (LPARAM)exe)); } static DWORD WINAPI _ecore_exe_thread_procedure(LPVOID data) { GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0); return 1; } static BOOL CALLBACK _ecore_exe_enum_windows_procedure(HWND window, LPARAM data) { Ecore_Exe *exe; DWORD thread_id; exe = (Ecore_Exe *)data; thread_id = GetWindowThreadProcessId(window, NULL); if (thread_id == exe->thread_id) { /* Ctrl-C or Ctrl-Break */ if (CreateRemoteThread(exe->process, NULL, 0, (LPTHREAD_START_ROUTINE)_ecore_exe_thread_procedure, NULL, 0, NULL)) return FALSE; if ((exe->sig == ECORE_EXE_WIN32_SIGINT) || (exe->sig == ECORE_EXE_WIN32_SIGQUIT)) return FALSE; /* WM_CLOSE message */ PostMessage(window, WM_CLOSE, 0, 0); if (WaitForSingleObject(exe->process, ECORE_EXE_WIN32_TIMEOUT) == WAIT_OBJECT_0) return FALSE; /* WM_QUIT message */ PostMessage(window, WM_QUIT, 0, 0); if (WaitForSingleObject(exe->process, ECORE_EXE_WIN32_TIMEOUT) == WAIT_OBJECT_0) return FALSE; /* Exit process */ if (CreateRemoteThread(exe->process, NULL, 0, (LPTHREAD_START_ROUTINE)ExitProcess, NULL, 0, NULL)) return FALSE; if (exe->sig == ECORE_EXE_WIN32_SIGTERM) return FALSE; TerminateProcess(exe->process, 0); return FALSE; } return TRUE; }
Plus ou moins : sous Windows, c'est "propre", mais cela demande un gestionnaire adapté pour y répondre. Toutefois, SANS gestionnaire, il n'y a aucun effet (contrairement à ce qu'il se passe avec un signal Unix non pris en charge qui termine le process, comme on le sait tous).
Une autre différence est que sous Windows, cet arrêt est coopératif, mais on peut aussi faire une cascade de tentatives d'arrêt jusqu'au TerminateProcess, bien entendu. Les signaux Unix, eux, sont préemptifs et peuvent ne pas toujours permettre de reprendre le cours du programme, ou d'arrêter réellement proprement tout ce qui est en cours
Sous Windows, on peut par exemple coder une DLL qui ne fait que créer des couples (thread+sémaphore) nommés, et dont le rôle est juste de s'attacher à un processus pour intercepter des "kill" à destination du processus afin de l'arrêter. Chaque thread n'a plus qu'à tester une bête fonction de la DLL renvoyant un booléen indiquant si l'arrêt a été demandé ou pas, afin de tenter au maximum un arrêt propre.
Côté utilisateur, par contre, ça revient très exactement au même qu'un "kill" Unix, la seule différence étant qu'avant de flinguer le processus bourrinement, on va tenter par plusieurs méthodes un arrêt "propre".
Tu utilises cette fonction pour les applications console (c'est à dire celles qui n'ont pas répondu aux messages WM), en lieu et place de "ExitProcess".
Éventuellement, si l'event console ne marche pas non plus, tu peux tenter un remote thread avec ExitProcess avant d'utiliser TerminateProcess : ça rajoute une étape, certes, mais c'est un poil plus propre que de flinguer sans préavis...
Dernier point à vérifier : tu peux créer un snapshot sur le process et vérifier s'il n'a pas de processus-fils engendrés, afin de les terminer eux aussi. Cela équivaut à la commande "Terminer l'arborescence du processus" dans le gestionnaire de tâches, mais en plus propre.
Mais comment savoir si Ctrl-C (ou d'ailleurs une des autres méthodes) a réellement tué le process ? Faire à nouveau un EnumWindows ?
comprends pas.. :-)Citation:
Dernier point à vérifier : tu peux créer un snapshot sur le process et vérifier s'il n'a pas de processus-fils engendrés, afin de les terminer eux aussi. Cela équivaut à la commande "Terminer l'arborescence du processus" dans le gestionnaire de tâches, mais en plus propre.
Voir la fonction CreateToolHelp32Snapshot (et les exemples qui vont avec).
Cela permet, notamment, de trouver les processus fils d'un processus quelconque.