Bonjour à tous,
Je suis coincé depuis plusieurs jours sur un problème qui viens probablement d'une corruption du tas. Je suis en train d'écrire (ou plutôt de réécrire) une librairie qui permettra à un programme utilisateur de lancer une interface graphique Qt de manière asynchrone.
Après avoir testé pas mal de trucs, j'en suis arrivé à la conclusion que je devais faire un exécutable à part qui contient l'interface graphique et que je devais communiquer avec lui depuis un thread séparé.
Actuellement, le programme qui importe la librairie appelle une fonction pour initialiser (ça lance le thread qui lance l'exécutable). Il appelle ensuite une fonction qui envoi des données à l'interface et attend (de manière synchrone) des données en retour. Enfin, l'appelant libère les ressources (thread, interface, …).
Voici le code de l'exécutable qui appelle la librairie :
1 2 3 4 5 6 7 8 | int main(int argc, char* argv[]) { unsigned long id; InitialiserDll(&id); DisplayCorrection(id); DechargerDll(id); return 0; } |
Le code de la librairie :
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 Controler { public: std::thread fccThread; BlockingQueue<std::string> putter; BlockingQueue<std::string> getter; Controler(); ~Controler(); }; Controler::Controler() { // On démarre le thread ici pour être sûr que putter et getter sont bien initialisés fccThread = std::thread(threadFun, std::ref(putter), std::ref(getter)); } Controler::~Controler(){ putter.push("!!quit!!"); fccThread.join(); } std::map<unsigned long, std::shared_ptr<Controler>> controlers; unsigned long nextId = 0; unsigned long newControler(){ controlers.emplace(nextId, std::make_shared<Controler>()); return nextId++; } int InitialiserDll(unsigned long *id){ *id = newControler(); return 1; } int DechargerDll(unsigned long id){ controlers.erase(id); return 1; } int DisplayCorrection(unsigned long id){ auto controler = controlers[id]; // on envoie un message et on en attend un en retour controler->putter.push("controler.xml"); std::string corrected = controler->getter.pop(); std::cout << corrected << std::endl; return 1; } |
Le code de la BlockingQueue (que j'ai pompé sur StackOverflow) :
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 | template<typename T> class BlockingQueue { private: std::mutex mut; std::condition_variable cv; std::deque<T> queue; public: void push(T const &value){ { std::unique_lock<std::mutex> lock(mut); queue.push_front(value); } cv.notify_one(); } T pop(){ std::unique_lock<std::mutex> lock(mut); cv.wait(lock, [this]{ return !this->queue.empty(); }); T rc(std::move(this->queue.back())); this->queue.pop_back(); return rc; } bool tryPop(T &v){ std::unique_lock<std::mutex> lock(mut); if(!cv.wait_for(lock, std::chrono::milliseconds(1), [this]{ return !this->queue.empty(); })){ return false; } v = std::move(this->queue.back()); this->queue.pop_back(); return true; } }; |
Et enfin ce qui correspond au thread :
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 | class MessageManager : public QObject { Q_OBJECT BlockingQueue<std::string> &getter; BlockingQueue<std::string> &putter; QLocalServer serv; QLocalSocket *sock; QProcess fcc; QTimer timer; public: MessageManager(BlockingQueue<std::string> &getter, BlockingQueue<std::string> &putter, QObject *parent = 0); signals: void correct(std::string); public slots: void tic(); void newConnection(); void corrected(); }; void threadFun(BlockingQueue<std::string> &getter, BlockingQueue<std::string> &putter) { int argc = 0; QCoreApplication app(argc, NULL); MessageManager messageManager(getter, putter); app.exec(); } MessageManager::MessageManager(BlockingQueue<std::string> &getter, BlockingQueue<std::string> &putter, QObject *parent) : QObject(parent), getter(getter), putter(putter), serv(this), fcc(this), timer(this) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis('a', 'z'); // génération d'un nom aléatoire de 10 caractères QString serverName; for(int i=0; i<10; i++){ serverName += dis(gen); } connect(&serv, SIGNAL(newConnection()), this, SLOT(newConnection())); serv.listen(serverName); QStringList args; args << serverName; fcc.start("executable.exe", args); connect(&timer, SIGNAL(timeout()), this, SLOT(tic())); } // tic est exécuté périodiquement une fois que l'on est connecté à l'exécutable void MessageManager::tic(){ std::string mess; if(getter.tryPop(mess)){ sock->write(mess.c_str()); if(mess == "!!quit!!"){ QCoreApplication::exit(); } } } void MessageManager::newConnection(){ sock = serv.nextPendingConnection(); connect(sock, SIGNAL(readyRead()), this, SLOT(corrected())); timer.start(10); } void MessageManager::corrected(){ QString correction = sock->readAll(); putter.push(correction.toStdString()); } |
Voilà, j'ai essayé de supprimer tout ce qui était spécifique à mon utilisation et le code tel que présenté pose quand même problème.
Suivant les exécutions, j'ai une jolie fenêtre de Windows qui me dit qu'une assertion n'est pas respecté (_pFirstBlock == _pHead
) dans son fichier dbgheap.c
. D'où le fait que je soupçonne très fortement une corruption du tas. Les autres erreurs d'exécution que j'ai eu sont :
- Plantage lors de la désallocation du lock de
tryPop
au moment de libérer le mutex. - Plantage à la fin de l'exécution du slot
corrected()
lors de la désallocation de la std::string créé parcorrection.toStdString
.
Dernière info qui est peut-être significative : je compile tout avec visual studio c++ 2012 (j'ai malheureusement pas le choix).
Du coup, est-ce que quelqu'un vois d'où pourrais venir le problème? Autrement, si vous voyez des trucs pas bien dans mon code, hésitez pas à le signaler non plus.
Merci d'avance aux courageux qui se pencheront sur le problème.