Accueil > Forum > > > > problème multithreading pourtant simple sous Visual C++ & MFC
problème multithreading pourtant simple sous Visual C++ & MFC
mercredi 1 septembre 2004 à 01:40:06 |
problème multithreading pourtant simple sous Visual C++ & MFC

kelly
|
Au secours ! Je m'arrache les cheveux depuis des jours en essayant de comprendre d'ou vient le probleme : Mon application dispose de boutons type magnétophone : Play, Pause, Stop. Un clic sur PLAY lance un thread, jusque là aucun probleme. Un clic sur STOP devrait signaler au thread précédemment lancé qu'il est temps pour celui-ci de se terminer. Pour ce faire, la fonction exécutée par le clic sur STOP change l'état d'une variable booleenne globale : m_PleaseStop=true. La thread PLAY vérifie à chaque tour de boucle (c'est un algo qui tourne en boucle) la valeur de m_PleaseStop. Quand m_PleaseStop==true, la fonction de la thread PLAY se dirige vers la sortie et juste avant de finir, réaffecte m_PleaseStop=false, pour la forme. Bref, mon problème est que mon programme se bloque de manière aléatoire quand PLAY est en route et que je clique sur STOP (la boucle PLAY s'arrete effectivement puis je n'ai plus la main, la thread étant toujours existante). J'ai remarqué qu'en éliminant une ligne UpdataData(false) présente dans la boucle PLAY, le problème disparait et tout fonctionne normalement, la thread PLAY se ferme et tout va bien. J'ai alors essayé de mettre des sections critiques, des temporisations etc. mais rien n'y fait, et quoi de plus normal puisque la procédure STOP n'utilise aucun objet spécial. Je ne comprends pas, aidez moi SVPPPPPPPPPP !
|
|
mercredi 1 septembre 2004 à 01:56:42 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

AlexMAN
|
Je sais pas si ca pourrait marcher, mais essaye, sait on jamais :
#define WM_ENDTHRPLAY WM_USER + 1
int Stop (...) { ... PostMessage(hwnd, WM_ENDTHRPLAY, 0, 0); ... }
LRESULT CALLBACK WndProc(...) { ... case WM_ENDTHRPLAY: TerminateThread(hthreadplay); CloseHandle(hthreadplay); return 0; ... }
Vraiment po sur de la fonctionnalité du code ci dessus, teste et envoie nous une réponse ;)
Allez, bonne nuit a toi
++
|
|
mercredi 1 septembre 2004 à 11:07:39 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

BruNews
|
AlexMAN, c'est du MFC son affaire. Jamais de TerminateThread(), c'est une fonction d'extreme urgence qui engendrerait des enormes fuites memoire. Son thread doit finir proprement.
ciao... BruNews, Admin CS, MVP VC++
|
|
mercredi 1 septembre 2004 à 11:16:38 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

AlexMAN
|
Je sais que TerminateThread ne doit pas etre utilisé (libere pas les ressources...etc), faut il utiliser ExitThread a la place ?
|
|
mercredi 1 septembre 2004 à 11:17:37 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

AlexMAN
|
Et dsl, j'avais pas vu que c'etait du MFC, dsl.
|
|
mercredi 1 septembre 2004 à 22:51:04 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

kelly
|
La méthode d'Alexman doit en effet fonctionner et je l'en remercie, mais c'est vrai que si je trouvais le moyen de terminer la thread proprement (par sa propre volonté) , ca m'arrangerait...
Je répète cependant mon observation sur le comportement de mon code : si je supprime les appels UpdateData(false) réalisés par la thread PLAY, tout fonctionne comme sur des roulettes...
Mes threads freezent sur des UpdateData(false) quoi... et il n'y a meme pas de section critique ou autre à mettre en oeuvre puisque STOP ne demande rien au systeme ! pourquoi ? pourquoi moi ??
|
|
mercredi 1 septembre 2004 à 22:57:35 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

BruNews
|
Tu ne serais pas en MFC j'y aurais jete un oeil mais la desole... Faut bien remonter toutes les actions engendrees par UpdateData(false) dans ton code, y a que ça a faire.
ciao... BruNews, Admin CS, MVP VC++
|
|
jeudi 2 septembre 2004 à 12:32:09 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

ymca2003
|
je suppose que tu passe en param de ton thread un pointeur sur le CDialog et que tu utilise ce pointeur pour faire UpdateData.
voici ce qui est dit dans les sources MFC :
"wincore.cpp" ligne 886 void CWnd::AssertValid() const
// Note: if either of the above asserts fire and you are // writing a multithreaded application, it is likely that // you have passed a C++ object from one thread to another // and have used that object in a way that was not intended. // (only simple inline wrapper functions should be used) // // In general, CWnd objects should be passed by HWND from // one thread to another. The receiving thread can wrap // the HWND with a CWnd object by using CWnd::FromHandle. // // It is dangerous to pass C++ objects from one thread to // another, unless the objects are designed to be used in // such a manner.
en gros, dans le thread il faut éviter d'utiliser des fonctions spéciales MFC et n'utiliser que les fonctions API déguisées (implémentée en inline avec le HWND implicite).
|
|
jeudi 2 septembre 2004 à 18:50:07 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

kelly
|
Merci quand meme Brunews! -> ymca2003 : En effet, c'est très exactement la manipulation que j'ai faite. D'ailleurs si je compile en debug, une librairie interne responsable de UpdateData crie (assertion invalide) que la classe appelante n'est pas la meme que le CDialog dont je veux rafraichir le contenu des controles standards (je détaille au cas ou qqun d'autre est intéressé par le probleme). Ce qui est bizarre puisque l'appel se présente comme suit : Appel lors du clic sur PLAY : void CMonAppDlg::OnBRestart() { ... AfxBeginThread( g_PseudoThreadFunction, (LPVOID)this ) ; }
|
Pseudo fonction thread définie en global : UINT g_PseudoThreadFunction(LPVOID pParam) { ((CMonAppDlg*)pParam) -> m_ThreadFunc() ; return 0 ; }
|
Fonction thread effectivement appelée : void CMonAppDlg::m_ThreadFunc() { ... UpdateData(false) ; ... return ; // ferme proprement le thread en remontant les appels }
|
D'ou vient alors cette assertion invalide ? Pourquoi en compilant en release, tout passe bien, et le programme ne freeze d'ailleurs "que" 9 fois sur 10 ? Une chose est sure, le freeze venait d'une routine au plus profond de l'OS. Merci de me faire encore profiter de tes lumieres !
|
|
jeudi 2 septembre 2004 à 22:42:25 |
Re : problème multithreading pourtant simple sous Visual C++ & MFC

ymca2003
|
pour t'en sortir, il suffit d'associer chacun des contrôles dont tu as besoin de la boîte de dialogues à leur classe respectives (et pas int , CString ou autre variables). ensuite, au lieu d'appeler UpdateData, tu te sert de ton pointeur sur le dialogue pour récuper les controles et faire doit même les mises à jour (UpdateData est censé simplifié le travail en associant un CString a un Edit par exemple, mais il est aussi simple d'affecter soi-même le nouveau texte à l'Edit, ex (chrono): //*************************************************************************************** // MFCThreadDlg.h : header file // //***************************************************************************************
#ifndef AFX_MFCTHREADDLG_H_INCLUDED_ #define AFX_MFCTHREADDLG_H_INCLUDED_
//*************************************************************************************** // Classe CMFCThreadDlg. //*************************************************************************************** class CMFCThreadDlg : public CDialog { //======================================================================================= // Membres privés. //======================================================================================= private :
// icone associée à la boîte de dialogue HICON m_hIcon;
// police de caractère pour l'affichage CFont* m_pFont;
// heure de départ du chronomètre, offset éventuel à rajouter DWORD m_dwStartTime; DWORD m_dwOffsetTime;
// thread du chronomètre, états (m_bThreadRunning est volatile pour que sa valeur // soit évaluée à chaque boucle et éviter des optimisations du compilateur menant // à un deadlock) CWinThread* m_pThread; BOOL m_bPause; volatile BOOL m_bThreadRunning;
// synchronisation des threads (pour qu'un seul ai accès aux variables partagées // à un instant donné) CRITICAL_SECTION m_cs;
//======================================================================================= // Méthodes privées. //======================================================================================= private : // met à jour l'état des boutons void UpdateButtons();
// point d'entrée du thread du chronomètre static UINT ThreadFunc(LPVOID lpData);
//======================================================================================= // Méthodes publiques. //======================================================================================= public : // constructeur CMFCThreadDlg(CWnd* pParentWnd = NULL);
//======================================================================================= // Données de la boîte de dialogue. //======================================================================================= protected : //{{AFX_DATA(CMFCThreadDlg) enum { IDD = IDD_MFCTHREAD }; CStatic m_StcCounter; CButton m_BtnStart; CButton m_BtnStop; CButton m_BtnPause; //}}AFX_DATA
//======================================================================================= // Méthodes virtuelles redéfinies. //======================================================================================= //{{AFX_VIRTUAL(CMFCThreadDlg) protected : virtual void DoDataExchange(CDataExchange* pDX); //}}AFX_VIRTUAL
//======================================================================================= // Traitement des messages. //======================================================================================= protected : //{{AFX_MSG(CMFCThreadDlg) virtual BOOL OnInitDialog(); afx_msg void OnBtnStart(); afx_msg void OnBtnStop(); afx_msg void OnBtnPause(); afx_msg void OnDestroy(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
#endif // AFX_MFCTHREADDLG_H_INCLUDED_
|
//*************************************************************************************** // MFCThreadDlg.cpp : implementation file // //***************************************************************************************
#include "StdAfx.h" #include "MFCThread.h"
#include "MFCThreadDlg.h"
//======================================================================================= // Tables des messages pour CMFCThreadDlg. //======================================================================================= BEGIN_MESSAGE_MAP(CMFCThreadDlg, CDialog) //{{AFX_MSG_MAP(CMFCThreadDlg) ON_BN_CLICKED(IDC_MFCTHREAD_BTN_START, OnBtnStart) ON_BN_CLICKED(IDC_MFCTHREAD_BTN_STOP, OnBtnStop) ON_BN_CLICKED(IDC_MFCTHREAD_BTN_PAUSE, OnBtnPause) ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP()
//*************************************************************************************** // Constructeur. //*************************************************************************************** CMFCThreadDlg::CMFCThreadDlg(CWnd* pParentWnd) : CDialog(CMFCThreadDlg::IDD, pParentWnd) { //{{AFX_DATA_INIT(CMFCThreadDlg) //}}AFX_DATA_INIT
// initialisation des paramètres m_pFont = NULL; m_dwStartTime = 0; m_dwOffsetTime = 0; m_pThread = NULL; m_bThreadRunning = FALSE; m_bPause = FALSE;
// chargement de l'icone asociée à la boîte de dialogue m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
//*************************************************************************************** // DoDataExchange : //*************************************************************************************** void CMFCThreadDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMFCThreadDlg) DDX_Control(pDX, IDC_MFCTHREAD_STC_COUNTER, m_StcCounter); DDX_Control(pDX, IDC_MFCTHREAD_BTN_START, m_BtnStart); DDX_Control(pDX, IDC_MFCTHREAD_BTN_STOP, m_BtnStop); DDX_Control(pDX, IDC_MFCTHREAD_BTN_PAUSE, m_BtnPause); //}}AFX_DATA_MAP }
//*************************************************************************************** // UpdateButtons : //*************************************************************************************** void CMFCThreadDlg::UpdateButtons() { // bouton [Démarrer] m_BtnStart.EnableWindow(m_pThread == NULL);
// bouton [Arrêter] m_BtnStop.EnableWindow(m_pThread != NULL);
// bouton [Pause\Reprendre] m_BtnPause.SetWindowText(m_bPause ? _T("Reprendre") : _T("Pause")); m_BtnPause.EnableWindow(m_pThread != NULL); }
//*************************************************************************************** // ThreadFunc : //*************************************************************************************** UINT CMFCThreadDlg::ThreadFunc(LPVOID lpData) { // récupération objet CMFCThreadDlg associé CMFCThreadDlg* pMFCThreadDlg = (CMFCThreadDlg*)lpData;
// tant que le thread doit tourner (m_bThreadRunning est volatile pour que sa valeur // soit évaluée à chaque boucle et éviter des optimisations du compilateur menant // à un deadlock. A priori inutile si utilisée à travers un pointeur) while(pMFCThreadDlg->m_bThreadRunning) { // pause de 5 ms Sleep(5);
// calcul de la nouvelle valeur du chronomètre EnterCriticalSection(&pMFCThreadDlg->m_cs); DWORD dwTime = GetTickCount()-pMFCThreadDlg->m_dwStartTime+ pMFCThreadDlg->m_dwOffsetTime; LeaveCriticalSection(&pMFCThreadDlg->m_cs);
// décomposition des minutes, secondes et centièmes de seconde int nMinutes = dwTime/60000; int nSeconds = (dwTime-60000*nMinutes)/1000; int nHundreth = (dwTime%1000)/10;
// formatage chaîne et affichage (sauf si le thread a été arrêté entre temps) if(pMFCThreadDlg->m_bThreadRunning) { CString strTime; strTime.Format(_T("%02d:%02d:%02d"), nMinutes, nSeconds, nHundreth); pMFCThreadDlg->m_StcCounter.SetWindowText(strTime); } } return 0; }
//======================================================================================= // Gestionnaire de messages pour CMFCThreadDlg. //=======================================================================================
//*************************************************************************************** // OnInitDialog : //*************************************************************************************** BOOL CMFCThreadDlg::OnInitDialog() { // appel fonction de la classe de base CDialog::OnInitDialog();
// affectation des icones (grande et petite) SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE);
// création police de caractères (3/4 de la hauteur du CStatic) CRect rcStcCounter; m_StcCounter.GetClientRect(&rcStcCounter); m_pFont = new CFont(); m_pFont->CreateFont(3*rcStcCounter.Height()/4, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, 0, 0, 0, 0, NULL);
// affectation au contrôle CStatic m_StcCounter.SetFont(m_pFont, FALSE);
// initialisation section critique InitializeCriticalSection(&m_cs);
// mise à jour de l'état des boutons UpdateButtons();
// retour return TRUE; }
//*************************************************************************************** // OnDestroy : //*************************************************************************************** void CMFCThreadDlg::OnDestroy() { // destruction de la police de caractères ASSERT(m_pFont != NULL); m_pFont->DeleteObject(); delete m_pFont;
// on stoppe le chronomètre OnBtnStop();
// destruction section critique DeleteCriticalSection(&m_cs);
// appel fonction de la classe de base CDialog::OnDestroy(); }
//*************************************************************************************** // OnBtnStart : //*************************************************************************************** void CMFCThreadDlg::OnBtnStart() { // si un thread est déjà en cours if(m_pThread != NULL) return;
// heure de début, offset m_dwStartTime = GetTickCount(); m_dwOffsetTime = 0;
// création d'un nouveau thread (on met m_bAutoDelete car on gèrera la destruction) m_bPause = FALSE; m_bThreadRunning = TRUE; m_pThread = AfxBeginThread(CMFCThreadDlg::ThreadFunc, this, THREAD_PRIORITY_BELOW_NORMAL, 0, CREATE_SUSPENDED, NULL); m_pThread->m_bAutoDelete = FALSE; m_pThread->ResumeThread();
// mise à jour de l'état des boutons UpdateButtons(); }
//*************************************************************************************** // OnBtnStop : //*************************************************************************************** void CMFCThreadDlg::OnBtnStop() { // si aucun thread en cours if(m_pThread == NULL) return; // arrêt du thread, si le thread était suspendu, on le reprend m_bThreadRunning = FALSE; if(m_bPause) m_pThread->ResumeThread(); m_bPause = FALSE;
// on attend que le thread s'arrête (on traite les messages en attente car le thread // est susceptible d'envoyer des messages aux contrôles créé par le thread principal) while(::WaitForSingleObject(m_pThread->m_hThread, 10) != WAIT_OBJECT_0) { // traiter les messages en attente MSG msg; if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } }
// destruction de l'objet CWinThread delete m_pThread; m_pThread = NULL;
// mise à jour de l'état des boutons UpdateButtons(); }
//*************************************************************************************** // OnBtnPause : //*************************************************************************************** void CMFCThreadDlg::OnBtnPause() { // si aucun thread en cours if(m_pThread == NULL) return;
// pause/reprise du thread if(m_bPause) { // on affecte la nouvelle heure de départ EnterCriticalSection(&m_cs); m_dwStartTime = GetTickCount(); m_pThread->ResumeThread(); LeaveCriticalSection(&m_cs); } else { // on sauvegarde la valeur actuelle pour s'en servir d'offset EnterCriticalSection(&m_cs); m_pThread->SuspendThread(); m_dwOffsetTime = GetTickCount()-m_dwStartTime+m_dwOffsetTime; LeaveCriticalSection(&m_cs); } m_bPause = !m_bPause;
// mise à jour de l'état des boutons UpdateButtons(); }
|
IDD_MFCTHREAD DIALOGEX 0, 0, 250, 110 STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "MFCThread" FONT 8, "MS Sans Serif" BEGIN CTEXT "00:00:00",IDC_MFCTHREAD_STC_COUNTER,10,10,230,70, SS_CENTERIMAGE PUSHBUTTON "Démarrer",IDC_MFCTHREAD_BTN_START,10,90,50,14 PUSHBUTTON "Arrêter",IDC_MFCTHREAD_BTN_STOP,70,90,50,14 PUSHBUTTON "Pause",IDC_MFCTHREAD_BTN_PAUSE,130,90,50,14 PUSHBUTTON "Fermer",IDCANCEL,190,90,50,14 END
|
|
|
Cette discussion est classée dans : problème, thread, stop, play, pleasestop
Répondre à ce message
Sujets en rapport avec ce message
PROBLEME DE THREAD (CreateThread -->ERROR) [ par neub ]
Salut à tous, j'ecris un module de visionnement d'image etje souhaiterai pouvoir stopper ma fonction OnPlay au moment voulu mais j'ai un pb a la creat
Problème de boucle dans un muli-thread [ par vinvay ]
Alors voila, j'ai 4 threads qui tournent en même temps, ceux-ci utilisant la même fonction.Dans cette fonction, il y a une première boucle for suivie
Problème Thread + HElppppppppppppppppppppppppppppppppppp [ par emmanuel9 ]
Bonjour à tous, En faite j'ai un tread et je fais une boucle sur une ArrayList de personne et en faite dans la fonction UpdateProgressBar j'aurais b
bouton stop [ par rlope ]
BonjourJ'ai un petit problème avec un bouton STOP que je voudrais créer. (Visual C++ boite de dialogue)Explication :Avec un bouton de ma boite de dial
Thread, Client/Serveur VC++.Net et avec WinForms ( pas d'MFC ) [ par lacousine ]
Bonjour,j'ai développé un serveur qui accept plusieurs clients avec des sockets. Voici mes problèmes: lorsque je veux mettre fin à mon serveur et qu'i
Problème de .h débutant [ par matt22 ]
Bonjour à tous.j'ai quelques notions en c++ mais de gros problème à bien comprendre les .h et comment bien les utiliser.J'avais une application avec j
Question sur Thread dans mon serveur multithread [ par Nixeus ]
Bonjour a tous !J'ai récupérer un code source d' un serveur multithread multi client, en mode console.J'ai repris les classes et j'ai porté ce program
Problème avec un thread [ par Mini92 ]
Bonsoir (ou bonjour),J'ai un ti soucis avec un thread, en fait, la fonction qui se trouve à l'intérieur ne s'exécute pas... Je comprend pas, pourtant
Problème de fermeture de connexions [ par Mr.X ]
Bonjours. Enfait mon problème c'est que lorsque je lance un programme que j'ai créé celui-ci se déroule parfaitement bien. Mais après sa fermeture et
Livres en rapport
|
Derniers Blogs
POUR RAPPEL ! LES SPéCIFICATIONS DES PROTOCOLES OFFICE ET SHAREPOINT SONT DISPONIBLES SUR MSDNPOUR RAPPEL ! LES SPéCIFICATIONS DES PROTOCOLES OFFICE ET SHAREPOINT SONT DISPONIBLES SUR MSDN par neodante
Quelle est le point commun entre : Microsoft il y a 10 ans et Apple aujourd'hui ? Réponse: avoir une politique de protocoles propriétaires et fermés :) Car pour rappel (si si je vous assure c'est important de le rappeler), la majorité des spécifications e...
Cliquez pour lire la suite de l'article par neodante JOYEUX ANNIVERSAIRE NIXJOYEUX ANNIVERSAIRE NIX par ebartsoft
Souhaitons un bon et joyeux anniversaire à notre hôte à tous, Nix.
Je ne le répéterais jamais assez mais sans lui rien ne serait possible. Il défit en permanence les lois de la gravité et comme il le dit si bien, si tu lui fais confiance ça devra...
Cliquez pour lire la suite de l'article par ebartsoft IMAGINE CUP 2012, MAKE A SIGN EN FINALEIMAGINE CUP 2012, MAKE A SIGN EN FINALE par junarnoalg
Voilà qui est fait, la nouvelle est officielle ! L'équipe belge "Make a Sign" va au pays des kangourous défendre son projet dans la catégorie Software Design. http://www.imaginecup.com/CompetitionsContent/Competition/WorldwideFinalists.aspx V...
Cliquez pour lire la suite de l'article par junarnoalg KINECT 1.5 IS OUT !KINECT 1.5 IS OUT ! par Vko
La version 1.5 du Kinect For Microsoft vient tout juste de sortir ! Plein de nouveautés: Tracking de squelette en Near Mode Détection en position assise Détection faciale avec un SDK dédié Documentation et des guideline (enfin) Un out...
Cliquez pour lire la suite de l'article par Vko LES ACTUALITéS DE LA SEMAINE SUR C2I.FR (14 MAI - 20 MAI) LES ACTUALITéS DE LA SEMAINE SUR C2I.FR (14 MAI - 20 MAI) par richardc
Mise à jour des Web API du 14 Mai
Réservez dès maintenant votre journée du 20 juin pour le Windows Azure Dev Camp 2012 à Paris
Mise à jour de Team Foundation Service
MechCommander 2 sur Windows 8
Entity Framework 5 Release Candidate e...
Cliquez pour lire la suite de l'article par richardc
Forum
MATLAB PROGRAMME MATLAB PROGRAMME par wahab1087
Cliquez pour lire la suite par wahab1087 RGB2GRAYRGB2GRAY par musa18
Cliquez pour lire la suite par musa18
Logiciels
sDEVIS-FACTURES vlPRO (8.1.0.3)SDEVIS-FACTURES VLPRO (8.1.0.3)sDEVIS-FACTURES vlPRO a été mis au point pour les particuliers, créateurs, entrepreneurs, artisa... Cliquez pour télécharger sDEVIS-FACTURES vlPRO 974 Application Server (12.2.4.6)974 APPLICATION SERVER (12.2.4.6)Développez de puissantes applications dans un environnement de 'cloud computing', clusterisé, séc... Cliquez pour télécharger 974 Application Server vPicture (1.4.2.1)VPICTURE (1.4.2.1)Avec vPicture, hébergez vos images facilement et rapidement.
vPicture est un utilitaire simple, ... Cliquez pour télécharger vPicture Easy-Planning (2.2.1.6)EASY-PLANNING (2.2.1.6)Easy-Planning permet de créer des plannings sous la représentation de diagrammes et est adapté au... Cliquez pour télécharger Easy-Planning COM-BACKUP (2.0)COM-BACKUP (2.0)
COM-BACKUP est un logiciel de sauvegarde qui permet de planifier les sauvegardes de vos dossiers ...
Cliquez pour télécharger COM-BACKUP
|