FAQ VC++ et MFCConsultez toutes les FAQ
Nombre d'auteurs : 20, nombre de questions : 545, dernière mise à jour : 5 avril 2013 Ajouter une question
Cette faq a été réalisée pour répondre aux questions les plus fréquement posées sur le forum Développement Visual C++
Je tiens à souligner que cette faq ne garantit en aucun cas que les informations qu'elle contient sont correctes ; Les auteurs font le maximum, mais l'erreur est humaine. Si vous trouvez une erreur, ou si vous souhaitez devenir redacteur, lisez ceci.
Sur ce, je vous souhaite une bonne lecture. Farscape
- Comment créer un nouveau processus ?
- Comment attendre la fin d'exécution d'un processus ?
- comment savoir si le processus est toujours actif ?
- Comment détruire un processus ?
- Comment retrouver l'emplacement d'exécution du processus en cours ?
- Comment travailler sur des objets MFC à partir d'un thread de travail (working thread) ?
- Comment créer un thread de travail ?
- Comment arrêter un thread de travail ?
- Comment créer un programme service ?
- Comment afficher un MessageBox dans un service ?
- Comment récupérer la valeur de retour d'un exe ?
- Comment récupérer la valeur de retour d'un exe déjà ouvert ?
- Comment affecter un Thread à une unité de calcul spécifique (CPU ou Core) ?
- Comment fermer l'application à partir d'un thread de travail ?
- Comment savoir si une application est déjà lancée ?
En utilisant les fonctions :
ShellExecute() ShellExecuteEx() ou CreateProcess()
Exemples:
ShellExecute() : lancement de word
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | HINSTANCE nerror; CString strApp="WINWORD"; nerror=ShellExecute(AfxGetMainWnd()->m_hWnd, NULL, strApp, NULL, NULL, SW_SHOWNORMAL); if(reinterpret_cast<int>(nerror) <= 32) { CString mess; mess.Format("Erreur de lancement de l'application:\n%s Erreur n°:%d",(const char *)strApp,nerror); AfxMessageBox(mess,MB_ICONEXCLAMATION); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | SHELLEXECUTEINFO ExecuteInfo; memset(&ExecuteInfo, 0, sizeof(ExecuteInfo)); ExecuteInfo.cbSize = sizeof(ExecuteInfo); ExecuteInfo.fMask = 0; ExecuteInfo.hwnd = 0; ExecuteInfo.lpVerb = "open"; ExecuteInfo.lpFile = "c:\\windows\\notepad.exe"; ExecuteInfo.lpParameters = "c:\\mydir\\toto.txt"; ExecuteInfo.lpDirectory = 0; ExecuteInfo.nShow = SW_SHOW; ExecuteInfo.hInstApp = 0; if(ShellExecuteEx(&ExecuteInfo) == FALSE) { // erreur } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | STARTUPINFO siStartupInfo; PROCESS_INFORMATION piProcessInfo; memset(&siStartupInfo, 0, sizeof(siStartupInfo)); memset(&piProcessInfo, 0, sizeof(piProcessInfo)); siStartupInfo.cb = sizeof(siStartupInfo); if(CreateProcess(NULL,"c:\\windows\\notepad.exe c:\\mydir\\toto.txt",0,0,FALSE, CREATE_DEFAULT_ERROR_MODE,0,0, &siStartupInfo,&piProcessInfo) == FALSE) { // erreur } |
Dans le cas de CreateProcess() :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | STARTUPINFO siStartupInfo; PROCESS_INFORMATION piProcessInfo; memset(&siStartupInfo, 0, sizeof(siStartupInfo)); memset(&piProcessInfo, 0, sizeof(piProcessInfo)); siStartupInfo.cb = sizeof(siStartupInfo); if(CreateProcess("c:\\windows\\notepad.exe", "c:\\mydir\\toto.txt",0,0,FALSE, CREATE_DEFAULT_ERROR_MODE,0,0, &siStartupInfo,&piProcessInfo) == FALSE) { // erreur } else { // attente WaitForSingleObject(piProcessInfo.hProcess, INFINITE); ::CloseHandle(piProcessInfo.hThread); ::CloseHandle(piProcessInfo.hProcess); } |
Exemple attente que l'impression d'un document soit terminée :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | SHELLEXECUTEINFO sei; ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO ); sei.lpFile = "mondocument.doc"; // attention ce flag est important ici ! sei.fMask = SEE_MASK_NOCLOSEPROCESS; sei.lpVerb = TEXT("print"); sei.nShow = SW_SHOWNORMAL; if(ShellExecuteEx(&sei)) { WaitForSingleObject(sei.hProcess, INFINITE); } ::CloseHandle(sei.hProcess); |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | DWORD dwExitCode = 0; GetExitCodeProcess(piProcessInfo.hProcess, &dwExitCode); if(dwExitCode == STILL_ACTIVE) //process toujours présent ? { // toujours la. } |
A l'intérieur du processus lui-même :
En utilisant ExitProcess :
Code c++ : | Sélectionner tout |
1 2 | ExitProcess(0); // code d'erreur de sortie 0 . |
Code c++ : | Sélectionner tout |
1 2 | PostThreadMessage(piProcessInfo.dwThreadId, WM_QUIT, 0, 0); |
Un processus créé par CreateProcess():
Le problème c'est que le thread principal de l'application peut ne pas répondre à PostThreadMessage (voir note d'infos plus bas) dans ce cas il faut procéder un peu différemment :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | PostThreadMessage(piProcessInfo.dwThreadId, WM_QUIT, 0, 0); WaitForSingleObject(piProcessInfo.hProcess, 2000); DWORD dwExitCode = 0; GetExitCodeProcess(piProcessInfo.hProcess, &dwExitCode); if(dwExitCode == STILL_ACTIVE) //process toujours présent ? { // en dernier recours forcé à la fermeture du process avec un code d'erreur à zéro pour l'exemple TerminateProcess(piProcessInfo.hProcess, 0); } |
En utilisant la fonction GetModuleFileName:
Code c++ : | Sélectionner tout |
1 2 3 4 5 | WINAPI DWORD GetModuleFileName( HMODULE hModule, LPWSTR lpFilename, DWORD nSize); |
Exemple:
Code c++ : | Sélectionner tout |
1 2 3 4 | char szFilename[255]; GetModuleFileName(NULL,szFilename,sizeof(szFilename)); AfxMessageBox(szFilename); |
Explications :
Dans un processus win32 tous les threads travaillent dans le même espace mémoire et peuvent accéder aux variables globales.
En multithread les MFC stockent les tables sur les fenêtres et objets GDI localement sur le thread principal grâce au système TLS :thread-local-storage, justement pour éviter des accès intempestifs des différents threads.
Donc bien retenir la règle suivante :
On ne peut pas partager des objets MFC liés aux fenêtres ou d'objets GDI entre plusieurs threads de travail.
On passera le handle de fenêtre en paramètre dans la fonction AfxBeginThread pour établir le lien avec la fenêtre de traitement.
Code C++ : | Sélectionner tout |
1 2 3 4 5 | void CSdisamplesView::OnButton1() { // TODO: Add your control notification handler code here AfxBeginThread(TheThread,GetSafeHwnd(),THREAD_PRIORITY_NORMAL); } |
A partir du thread de travail on enverra des messages à la fenêtre pour réaliser les traitements
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 | UINT TheThread(LPVOID pParam) { ::PostMessage((HWND)pParam,WM_MY_MSG_PRIVE,0,0); return 0; } |
Notes : La fonction thread peut être définie dans une classe si elle est déclarée statique.
Dans le cas d'utilisation conjointe avec des boîtes de dialogue modales, il est préférable de poster (PostMessage) le message plutôt que de l'envoyer (SendMessage), afin d'éviter un problème de réentrance avec la pompe à messages du thread principal.
Il y a plusieurs manières de créer un thread de travail (worker thread) :
- _beginthreadex(),_beginthread() (C run-time library)
- CreateThread() (Win32 API)
- AfxBeginThread() (MFC)
Les exemples suivants montrent comment utiliser les différentes fonctions.
Utilisation de _beginthreadex():
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr ); |
Include :process.h Système :Win 95, Win NT
Link :Bibliothèque multithread statique Libcmt.lib ou la version dll MSVCRT.LIB.
Code c++ : | Sélectionner tout |
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 | class CWorkerThread { public: CWorkerThread() { m_uiID = 0; m_ulHandle = 0; } bool InitThread() { m_ulHandle = _beginthreadex(0, 0, ThreadFunc, this, 0, &m_uiID); if(!m_ulHandle) { // Impossible de créer le thread ! return false; } return true; } private: unsigned int m_uiID; unsigned long m_ulHandle; static unsigned int __stdcall ThreadFunc(void *pvParam); }; unsigned int __stdcall CWorkerThread::ThreadFunc(void *pvParam) { CWorkerThread *pThis=reinterpret_cast< CWorkerThread *>( pvParam) ; // Votre code // return 0; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD SIZE_T dwStackSize, // initial stack size LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier ); |
Include : Déclarée dans Winbase.h. include Windows.h.
Système:Windows NT/2000/XP: Inclus dans Windows NT 3.1 et plus
Link :Bibliothèque Kernel32.lib.
Code c++ : | Sélectionner tout |
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 | class CWorkerThread { public: CWorkerThread() { m_dwID = 0; m_hThread = 0; } ~CWorkerThread() { CloseHandle(m_hThread); } bool InitThread() { m_hThread = CreateThread(0, 0, ThreadFunc, this, 0, &m_dwID); if(!m_hThread) { // Impossible de créer le thread ! return false; } return true; } private: DWORD m_dwID; HANDLE m_hThread; static DWORD WINAPI ThreadFunc(LPVOID pvParam); }; DWORD WINAPI CWorkerThread::ThreadFunc(LPVOID pvParam) { CWorkerThread *pThis=reinterpret_cast< CWorkerThread *>( pvParam) ; return 0; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); |
Include : déclarée dans Afxwin.h
Système: Win 95, Win NT ,WINCE 2.0 et plus.
Code c++ : | Sélectionner tout |
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 | class CMyDialog : public CDialog { public: CMyDialog(CWnd* pParent = NULL) : CDialog(CMyDialog::IDD, pParent) { m_pThread = NULL; } bool InitThread() { m_pThread = AfxBeginThread(ThreadFunc, this); if(!m_pThread) { // Impossible de créer le thread ! return false; } return true; } private: CWinThread *m_pThread; static UINT ThreadFunc(LPVOID pvParam); }; UINT CMyDialog::ThreadFunc(LPVOID pvParam) { CMyDialog *pThis=reinterpret_cast< CMyDialog *>( pvParam) ; // Votre code // return 0 ; } |
Code c++ : | Sélectionner tout |
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 | class CWorkerThread { public: CWorkerThread(); // crée le thread et démarre son exécution ~CWorkerThread(); // arrêter le thread si ce n'est pas fait // demande de s'arrêter au thread // si on lui donne un timeout, et que ce dernier expire, // la fonction tue le thread et renvoie false // renvoie true si le thread s'est terminé proprement bool Stop( DWORD dwTimeoutMs = INFINITE ); // exemple de valeur retournée par le thread // son temps d'exécution en ms DWORD ExecutionTime; private: // Execute contient votre code à exécuter dans le thread UINT Execute(); private: volatile bool m_StopThread; // arrêt demandé CWinThread * m_Thread; // thread créé static UINT WorkerThreadLauncher(LPVOID pvParam); }; CWorkerThread::CWorkerThread(): m_StopThread( false ), ExecutionTime( 0 ) { // depart du thread this->m_Thread = AfxBeginThread(WorkerThreadLauncher, this); } CWorkerThread::~CWorkerThread() { this->Stop(); } bool CWorkerThread::Stop( DWORD dwTimeoutMs ) { if ( this->m_Thread != NULL ) { // demander de s'arrêter this->m_StopThread = true; // attendre qu'il soit arrêté pour la durée spécifiée if ( ::WaitForSingleObject( *this->m_Thread, dwTimeoutMs ) != WAIT_OBJECT_0 ) { // échec de l'attente : le thread ne s'est pas terminé // on le tue ::TerminateThread( *this->m_Thread, 0 ); this->m_Thread = NULL; return false; } // tout s'est bien passé this->m_Thread = NULL; } return true; } // point d'entrée du thread UINT CWorkerThread::WorkerThreadLauncher(LPVOID pvParam) { CWorkerThread *pThreadInstance = reinterpret_cast<CWorkerThread *>(pvParam); return pThreadInstance->Execute(); } // IMPORTANT: si vous appelez des fonctions membres // ou modifiez des données membres de CWorkerThread // veillez à que leur utilisation soit thread safe // car Execute() s'execute dans un autre thread contrairement // aux autres fonctions qui sont appelées depuis le thread principal // En particulier, il ne faut surtout pas appeler Stop() UINT CWorkerThread::Execute() { // exemple de code à exécuter DWORD start = ::GetTickCount(); do { // code de traitement ::Sleep( 1000 ); // simuler une opération de 1 sec } while ( this->m_StopThread == false ) ; DWORD end = ::GetTickCount(); // renvoyer un résultat : temps d'exécution this->ExecutionTime = end - start; return 0; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // créer et lancer le thread CWorkerThread thread; // l'arrêter if ( thread.Stop() ) { // récupérer le résultat CString s; s.Format( "%d ms", thread.ExecutionTime ); AfxMessageBox( s ); } else { AfxMessageBox( "Le thread a été tué" ); } |
Cette Q/R s'applique sur les threads initiés par la fonction AfxBeginThread donc dans un contexte MFC.
Pour l'arrêt d'un thread plusieurs cas sont à envisager :
- Un thread peut s'arrêter tout seul en terminant sa fonction principale.
- Le thread comporte une boucle de traitement en continu, et l'action de sortie du thread vient de l'extérieur.
Une réponse pour traiter ce cas serait d'utiliser la fonction TerminateThread. cette fonction à éviter.
Le thread est immédiatement stoppé certes mais elle comporte des inconvénients rédhibitoires :
Notamment, elle ne libère pas la mémoire des variables dynamiques :
Pour fixer les idées le code ci-dessus provoquera une perte de mémoire (memory leaks).
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | UINT CWorkerThread::ThreadFunction(LPVOID* pvParam) { CString str ; while(true) { // } ; return 0 ; } |
Les dlls chargées par le thread ne seront pas libérées !
A éviter donc.
Une technique couramment utilisée pour l'arrêt d'un thread consiste à utiliser des objets de type event :
L'exemple ci-dessus emploie deux events (objet événement) un pour signaler la fin du thread et un deuxième pour attendre que celui-ci soit effectivement terminé.
Code C++ : | Sélectionner tout |
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 | class CWorkerThread { public: CWorkerThread(); ~CWorkerThread(); private: HANDLE m_hEndThread; HANDLE m_hWaitThread; static UINT ThreadFunction(LPVOID* pvParam); }; CWorkerThread::CWorkerThread() { // Creation des events HANDLE m_EndThread = CreateEvent(0, TRUE, FALSE, 0); HANDLE m_WaitThread = CreateEvent(0, TRUE, FALSE, 0); // depart du thread AfxBeginThread(ThreadFunction, this); } CWorkerThread::~CWorkerThread() { // declenche la fin du thread ::SetEvent(m_EndThread); // attend que le thread soit terminé ::WaitForSingleObject(m_WaitThread, INFINITE); // fermeture dans handles ::CloseHandle(m_EndThread); ::CloseHandle(m_WaitThread); } UINT CWorkerThread::ThreadFunction(LPVOID* pvParam) { CWorkerThread *pThis = static_cast(pvParam); while(true) { // attente evenement de fin du thread. -> l'objet doit être ‘signalé ‘ // WaitForSingleObject renvoie WAIT_OBJECT_0 si l'objet est signalé. if(::WaitForSingleObject(pThis->m_EndThread, 0) == WAIT_OBJECT_0) { // signale l'objet event d'attente et sort. ::SetEvent(pThis->m_WaitThread); return 0; } // Votre code de traitement. } return 0; } |
Dans certains cas l'emploi de la fonction WaitForMultipleObjects s'avère nécessaire si plusieurs objets sont à contrôler voir pour exemple la classe de communication série dans la FAQ : Comment travailler avec le port série ?
Pour créer le service il faudra choisir un projet de type Win 32/Console avec support ou non des MFC .
L'exemple ci-dessus gère le processus d'installation du service.
Un squelette de service permettant l'arrêt la pause et le redémarrage du service.
Et enfin la possibilité de le désinstaller.
Le traitement du service devra être placé dans le thread de travail prévu à cet effet.
Les points clefs du service :
Le service démarre avec la fonction StartServiceCtrlDispatcher
Qui contient une structure SERVICE_TABLE_ENTRY qui comprend le nom du service et sa fonction main principale.
Celle-ci va procéder aux initialisations du service notamment ouvrir un lien avec le SCM : service control manager, le handle retourné permettra d'informer celui-ci des étapes d'initialisations ou des notifications d'arrêt du service.
Le SCM disposera d'un point d'entrée dans le programme pour signaler les événements d'arrêt, de pause ou de redémarrage du service.
Tout cela est initié par la fonction RegisterServiceCtrlHandler.
Toujours dans la fonction main principale le thread de travaille (worker thread) sera créé ainsi que les objets events permettant l'attente de fin de traitement du thread avec la fonction WaitForSingleObject.
Pour faciliter les tests en mode debug le programme est executable directement dans l'ide, le thread de travail est créé en dehors de tout contexte de service.
Pour installer le service on appelera le programme avec l'argument install.
Pour supprimer le service on utilisera l'argument delete
Le code ci-dessous installera le service TestService ,voir la variable SERVICE_NAME
Code C++ : | Sélectionner tout |
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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | // TestService.cpp : Defines the entry point for the console application. // // Exemple de service windows comprenat l'installation l'execution // et la desinstallation du service. // // Farscape le 16/09/2004 : farscape-dev@tiscali.fr // #include "stdafx.h" #include <Winsvc.h> #include "TestService.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // The one and only application object CWinApp theApp; using namespace std; BOOL SendStatusToSCM(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint); TCHAR SERVICE_NAME[]= TEXT( "TestService" ); HANDLE hTerminateEvent=NULL; HANDLE hTerminateThread=NULL; HANDLE ThreadHandle = NULL; SERVICE_STATUS_HANDLE ServiceStatusHandle; BOOL bPauseService=FALSE; BOOL bRunningService=FALSE; // ------------------------------------------------------ void ErrorHandler(const char *s,DWORD err) { cout << s << endl; cout << "Erreur Numéro: " << err << endl; ExitProcess(err); } // ------------------------------------------------------ DWORD ServiceThread(LPDWORD param) { while(1) { if(::WaitForSingleObject(hTerminateThread, 0) == WAIT_OBJECT_0) { // signale l'objet event d'attente et sort. ::SetEvent(hTerminateEvent); return 0; } // placer le code de votre service ici _sleep(5000); // juste pour l'exemple. } return 0; } // ------------------------------------------------------ BOOL InitService() { DWORD id=0; ThreadHandle = CreateThread(0,0,(LPTHREAD_START_ROUTINE)ServiceThread,NULL,0,&id); if(ThreadHandle==0) { if(ServiceStatusHandle) SendStatusToSCM(SERVICE_STOPPED,0,0,0,0); if(hTerminateEvent) CloseHandle(hTerminateEvent); if(hTerminateThread) CloseHandle(hTerminateThread); ErrorHandler("Erreur sur la mise en place du service",0); return FALSE; } bRunningService=TRUE; return TRUE; } // ------------------------------------------------------ void ResumeService() { bPauseService = FALSE; ResumeThread(ThreadHandle); } // ------------------------------------------------------ void PauseService() { bPauseService = TRUE; SuspendThread(ThreadHandle); } // ------------------------------------------------------ void StopService() { bRunningService=FALSE; SetEvent(hTerminateThread); } // ------------------------------------------------------ BOOL SendStatusToSCM(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) { BOOL bSuccess; SERVICE_STATUS ServiceStatus; ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState= dwCurrentState; ServiceStatus.dwControlsAccepted=0; if(dwCurrentState != SERVICE_START_PENDING) ServiceStatus.dwControlsAccepted= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN| SERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwWin32ExitCode=ERROR_SERVICE_SPECIFIC_ERROR; if(!dwServiceSpecificExitCode) ServiceStatus.dwWin32ExitCode=dwWin32ExitCode; ServiceStatus.dwServiceSpecificExitCode=dwServiceSpecificExitCode; ServiceStatus.dwCheckPoint = dwCheckPoint; ServiceStatus.dwWaitHint = dwWaitHint; bSuccess = SetServiceStatus(ServiceStatusHandle,&ServiceStatus); if(!bSuccess) StopService(); return bSuccess; } // ------------------------------------------------------ void ServiceCtrlHandler(DWORD controlCode) { DWORD currentState= 0; BOOL bSuccess; switch(controlCode) { case SERVICE_CONTROL_STOP: currentState= SERVICE_STOP_PENDING; bSuccess= SendStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR,0,1,5000); StopService(); return; case SERVICE_CONTROL_PAUSE: if(bRunningService && !bPauseService) { bSuccess= SendStatusToSCM(SERVICE_PAUSE_PENDING,NO_ERROR,0,1,1000); PauseService(); currentState= SERVICE_PAUSED; } break; case SERVICE_CONTROL_CONTINUE: if(bRunningService && bPauseService) { bSuccess= SendStatusToSCM(SERVICE_CONTINUE_PENDING,NO_ERROR,0,1,1000); ResumeService(); currentState= SERVICE_RUNNING; } break; case SERVICE_CONTROL_INTERROGATE:break; case SERVICE_CONTROL_SHUTDOWN:return; default:break; } SendStatusToSCM(currentState,NO_ERROR,0,0,0); } // ------------------------------------------------------ void Terminate(DWORD error) { if(ServiceStatusHandle) SendStatusToSCM(SERVICE_STOPPED,error,0,0,0); if(ThreadHandle) CloseHandle(ThreadHandle); if(hTerminateEvent) CloseHandle(hTerminateEvent); if(hTerminateThread) CloseHandle(hTerminateThread); } // ------------------------------------------------------ void AddService() { SC_HANDLE newService,scm; CString strPath; char szFilename[255]; GetModuleFileName(NULL,szFilename,sizeof(szFilename)); strPath=szFilename; scm= OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); if(!scm) ErrorHandler("Dans OpenScManager",GetLastError()); newService= CreateService(scm,SERVICE_NAME, SERVICE_NAME, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, strPath, 0,0,0,0,0); if(!newService)ErrorHandler("Dans CreateService",GetLastError()); else cout << "Service " << SERVICE_NAME << " Installé\n"; } // ------------------------------------------------------ void DeleteService() { SC_HANDLE newService,scm; CString strPath; char szFilename[255]; GetModuleFileName(NULL,szFilename,sizeof(szFilename)); strPath=szFilename; scm= OpenSCManager(0,0,SC_MANAGER_ALL_ACCESS); if(!scm) ErrorHandler("Dans OpenScManager",GetLastError()); newService=OpenService(scm,SERVICE_NAME,SERVICE_ALL_ACCESS| DELETE); if(newService)DeleteService(newService); if(!newService)ErrorHandler("Dans DeleteService",GetLastError()); else cout << "Service "<< SERVICE_NAME << " Supprimé\n"; } // ------------------------------------------------------ void ServiceMain(DWORD argc,LPTSTR *argv) { BOOL bSuccess; ServiceStatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME,(LPHANDLER_FUNCTION)ServiceCtrlHandler); if(!ServiceStatusHandle) { Terminate(GetLastError()); return; } bSuccess= SendStatusToSCM(SERVICE_START_PENDING,NO_ERROR,0,1,5000); if(!bSuccess) { Terminate(GetLastError()); return; } hTerminateEvent = CreateEvent(0,TRUE,FALSE,0); if(!hTerminateEvent) { Terminate(GetLastError()); return; } hTerminateThread = CreateEvent(0,TRUE,FALSE,0); if(!hTerminateThread) { Terminate(GetLastError()); return; } bSuccess= SendStatusToSCM(SERVICE_START_PENDING,NO_ERROR,0,2,1000); if(!bSuccess) { Terminate(GetLastError()); return; } bSuccess = InitService(); if(!bSuccess) { Terminate(GetLastError()); return; } bSuccess= SendStatusToSCM(SERVICE_RUNNING,NO_ERROR,0,0,0); if(!bSuccess) { Terminate(GetLastError()); return; } WaitForSingleObject(hTerminateEvent,INFINITE); Terminate(0); } //------------------------------------------------------------------------------------------ int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { SERVICE_TABLE_ENTRY serviceTable[]= { {SERVICE_NAME,(LPSERVICE_MAIN_FUNCTION)ServiceMain}, {NULL , NULL} }; // initialize MFC and print and error on failure if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) { // TODO: change error code to suit your needs ErrorHandler("Fatal Error: MFC initialization failed",0); return 1; } #ifdef _DEBUG hTerminateEvent = CreateEvent(0,TRUE,FALSE,0); hTerminateThread = CreateEvent(0,TRUE,FALSE,0); DWORD id=0; ThreadHandle = CreateThread(0,0,(LPTHREAD_START_ROUTINE)ServiceThread,NULL,0,&id); WaitForSingleObject(hTerminateEvent,INFINITE); ServiceStatusHandle=NULL; Terminate(0); return 0; #else CString strCmdLine; if(argc>1) strCmdLine=argv[1]; strCmdLine.MakeUpper(); if(strCmdLine=="INSTALL") { AddService(); return 0; } if(strCmdLine=="DELETE") { DeleteService(); return 0; } BOOL bSuccess; bSuccess= StartServiceCtrlDispatcher(serviceTable); if(!bSuccess) ErrorHandler("Dans StartServiceCtrlDispatcher",GetLastError()); return bSuccess; #endif } |
L'application de test : TestService.zip
Un service par définition est silencieux, il ne doit pas avoir d'interaction avec le bureau pour des raisons de sécurité.
Voir la note MSDN Interactive Services à ce sujet.
Dans le cas d'un service non interactif on pourra utiliser un MessageBox pour communiquer sur le bureau, en utilisant l'attribut MB_SERVICE_NOTIFICATION.
En utilisant l'API GetExitCodeProcess à partir du handle de process de l'application dont on veut connaitre le retour.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); if( !CreateProcess( NULL, "monExe.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ) { return 0; } WaitForSingleObject( pi.hProcess, INFINITE ); DWORD retour; GetExitCodeProcess(pi.hProcess,&retour); CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); |
On utilise la même API GetExitCodeProcess, seulement, l'obtention du handle de process de l'application est légèrement différente.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | HANDLE hSnapShot; PROCESSENTRY32 uProcess; BOOL r; short PID = 0; hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL,0); uProcess.dwSize = (DWORD) sizeof(PROCESSENTRY32); r = Process32First(hSnapShot, &uProcess); do { if ( strstr(uProcess.szExeFile, "monExe") ) // on compare au nom de l'executable PID = (short) uProcess.th32ProcessID; r = Process32Next(hSnapShot, &uProcess); } while ( r ); CloseHandle(hSnapShot); if ( PID == 0) return false; HANDLE hTemp; hTemp = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD) PID); WaitForSingleObject( hTemp, INFINITE ); DWORD retour; GetExitCodeProcess(hTemp,&retour); CloseHandle( hTemp ); |
Toutes les méthodes suivantes sont en static dans une classes COutils
Tout d'abord il faut récupérer le nombre de CPU de l'ordinateur :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | //************************************************************************** //GetCPUCount //************************************************************************** int COutils::GetCPUCount() { SYSTEM_INFO SysInfos; ::GetSystemInfo(&SysInfos); return SysInfos.dwNumberOfProcessors; } |
Code c++ : | Sélectionner tout |
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 | //************************************************************************** //Modifie le process Courant afin de pouvoir tourner sur plusieurs CPU //int nCPU : nombre de CPU estimé (incluant les dual Core) Max = 32 //************************************************************************** bool COutils::SetMultiCPUCapability(int nCPU, bool Verbose) { boolSuccess= true; intNbCPU= COutils::GetCPUCount(); if (nCPU > NbCPU) { CString Message; Message.Format("COutils::SetMultiCPUCapability -> Erreur : \ Demande de fonctionnement sur %d CPU pour une machine à %d CPU\r\n", nCPU, NbCPU); #ifdef _DEBUG TRACE (Message); #endif if (Verbose) {AfxMessageBox(Message, MB_OK); } Success = false; } else { DWORDProcessAffinity = 0xFFFFFFFF; HANDLEProcess= ::GetCurrentProcess(); ProcessAffinity= ProcessAffinity >> (32 -nCPU); //Possibilité de tourner sur les CPU 1, 2, 3 ou 4 -> jusqu'a 32 //CPU n° 4321 //0x1 -> 0001 //0x3 -> 0011 //0x7 -> 0111 //0xF -> 1111 if ( !::SetProcessAffinityMask(Process, ProcessAffinity) ) { Success = false; CString Message; //mettre un message erreur //.... #ifdef _DEBUG TRACE (Message); #endif if (Verbose) {AfxMessageBox(Message, MB_OK); } } } return Success; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //************************************************************************** //SetThreadOnCPU //************************************************************************** bool COutils::SetThreadOnCPU(HANDLE hThread, int CPUNumber) { bool Success = true; if (CPUNumber <= COutils::GetCPUCount() ) { DWORD ThreadAffinity = 0x00000001; if (CPUNumber != 1 ) { ThreadAffinity = ThreadAffinity << (CPUNumber - 1); } if ( !::SetThreadAffinityMask (hThread, ThreadAffinity) ) {Success = false;} } else {Success = false;} return Success; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | //************************************************************************** //SetThreadOnCPU //************************************************************************** bool COutils::SetThreadOnCPU (CWinThread *Thread, int CPUNumber) { return COutils::SetThreadOnCPU(Thread->m_hThread, CPUNumber); } |
Attention car si vous affectez 15 Thread au CPU n°1, Windows le fera et votre application sera encore plus lente, donc à utiliser en connaissance de cause...
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | DWORD WINAPI CWorkerThread::ThreadFunc(LPVOID pvParam) { CWorkerThread *pThis=reinterpret_cast< CWorkerThread *>( pvParam) ; // ici le traitement du thread // ...................... // sortie du thread ::PostMessage(AfxGetMainWnd()->m_hWnd,WM_CLOSE,0,0); return 0; } |
Il suffira d'envoyer un PostMessage à la MainFrame en utilisant le handle de fenêtre.
Voir explications dans ce post : Comment travailler sur des objets MFC à partir d'un thread de travail (working thread) ?
Si on connait le nom du processus, on peut par exemple regarder dans la liste de processus pour savoir s'il est lancé ou pas.
On peut utiliser cette fonction
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | bool isProcessRunning(char *processName) { HANDLE hSnapShot; PROCESSENTRY32 uProcess; BOOL r; short PID = 0; hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL,0); uProcess.dwSize = (DWORD) sizeof(PROCESSENTRY32); r = Process32First(hSnapShot, &uProcess); do { if ( strstr(uProcess.szExeFile, processName) ) PID = (short) uProcess.th32ProcessID; r = Process32Next(hSnapShot, &uProcess); } while ( r ); CloseHandle(hSnapShot); return (PID != 0); } |
Par exemple, pour savoir si Firefox est lancé, on fera :
Code C++ : | Sélectionner tout |
1 2 3 4 | if (isProcessRunning("firefox.exe")) // lancé else // non lancé |
Remarque : on utilise ici la fonction strstr pour rechercher une chaîne dans une autre, en tenant compte de la casse. On pourrait très bien envisager de changer cette fonction pour rechercher en ignorant la casse ou bien pour avoir un nom exact, etc.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.