2010-06-23 3 views
0

J'ai une classe Observer et une classe Subscriber.
Pour des fins de test, l'observateur crée un thread qui génère des messages faux et appelle CServerCommandObserver::NotifySubscribers(), qui ressemble à ceci:Erreur ESP lors de l'envoi de messages de fenêtre entre threads

void CServerCommandObserver::NotifySubscribers(const Command cmd, void const * const pData) 
{ 
    // Executed in worker thread // 

    for (Subscribers::const_iterator it = m_subscribers.begin(); it != m_subscribers.end(); ++it) 
    { 
     const CServerCommandSubscriber * pSubscriber = *it; 

     const HWND hWnd = pSubscriber->GetWindowHandle(); 
     if (!IsWindow(hWnd)) { ASSERT(FALSE); continue; } 

     SendMessage(hWnd, WM_SERVERCOMMAND, cmd, reinterpret_cast<LPARAM>(pData)); 
    } 
} 

L'abonné est une classe dérivée CDialog, qui hérite aussi de CServerCommandSubscriber.

Dans la classe dérivée, j'ai ajouté une entrée de mappe de message, qui route les commandes du serveur vers le gestionnaire de classe d'abonné.

// Derived dialog class .cpp 
ON_REGISTERED_MESSAGE(CServerCommandObserver::WM_SERVERCOMMAND, HandleServerCommand) 

// Subscriber base class .cpp 
void CServerCommandSubscriber::HandleServerCommand(const WPARAM wParam, const LPARAM lParam) 
{ 
    const Command cmd = static_cast<Command>(wParam); 

    switch (cmd) 
    { 
    case something: 
     OnSomething(SomethingData(lParam)); // Virtual method call 
     break; 
    case // ... 
    }; 
} 

Le problème est que je vois des accidents étranges dans la méthode HandleServerCommand():

Il ressemble à ceci:

Erreur de débogage!

Programme: c: \ myprogram.exe
Module:
fichier: i386 \ chkesp.c
Ligne: 42

La valeur de l'ESP n'a pas été correctement enregistrée à travers un appel de fonction. Ceci est généralement le résultat de l'appel d'une fonction déclarée avec une convention appelant avec un pointeur de fonction déclaré avec une convention d'appel différente.

J'ai vérifié le pointeur de la fonction AfxBeginThread() veut avoir:

typedef UINT (AFX_CDECL *AFX_THREADPROC)(LPVOID); // AFXWIN.H 

static UINT AFX_CDECL MessageGeneratorThread(LPVOID pParam); // My thread function 

Pour moi, cela semble compatible, non?

Je ne sais pas, quoi d'autre je dois rechercher. Des idées?

J'ai fait une autre observation étrange, qui pourrait être lié: Dans la méthode NotifySubscribers, je l'appelle IsWindow() pour vérifier si la fenêtre à laquelle les points de poignée, existe. Apparemment, c'est le cas. Mais l'appel CWnd::FromHandlePermanent() renvoie un pointeur NULL.

+0

J'ai décidé de contourner le problème (voir ma propre réponse) et ne pourra pas essayer d'autres solutions car le code a beaucoup changé. – foraidt

+0

'void const * const pData' - qu'est-ce que cela signifie? 'const void * pData' ne suffirait pas? Qu'est-ce que 'Command'? Je suppose que c'est un type simple puisque vous le passez comme valeur et non comme référence. –

+0

@AOI 1: 'void const * const' signifie que le pointeur lui-même est garanti de ne pas être modifié dans sa portée. Le plus 'const 'le mieux mais oui, cela fonctionnerait avec un seul, aussi. 2: Oui, 'Command 'est juste un type' enum'. – foraidt

Répondre

1

J'ai finalement décidé de le faire sans messages de fenêtre et je poste maintenant mon contournement ici. Peut-être que cela aidera quelqu'un d'autre. Au lieu de laisser l'observateur poster des messages de fenêtre à ses abonnés, je laisse l'observateur mettre des données dans des tampons d'abonné synchronisés. L'abonné de la classe de dialogue utilise un temporisateur pour vérifier périodiquement ses tampons et appeler les gestionnaires appropriés s'ils ne sont pas vides.
Il y a quelques inconvénients:

  • Il est plus d'efforts de codage parce que pour chaque type de données, un élément tampon doit être ajouté à l'abonné.
  • Cela prend également plus d'espace, car les données existent pour chaque abonné et pas une seule fois pendant l'appel SendMessage().
  • On doit également faire la synchronisation manuellement au lieu de compter sur le thread d'observateur suspendu pendant que les messages sont traités.

A - IMO - énorme avantage est qu'il a une meilleure sécurité de type. Il n'est pas nécessaire de convertir des valeurs lParam en pointeurs en fonction de la valeur de wParam. Pour cette raison, je pense que cette solution de contournement est très acceptable si elle n'est même pas supérieure à mon approche originale.

1

Pour moi, cela semble compatible, n'est-ce pas il?

Syntactiquement, cela ressemble à cela. Je ne sais pas, quoi d'autre je dois regarder pour. Des idées? Oui: J'ai rencontré le même problème lors de la compilation d'une bibliothèque de plug-in avec les paramètres de débogage et utilisée dans une application compilée par la version. Fondamentalement, le problème ressemble à une corruption de la pile.

Depuis que vous utilisez NotifySubscribers dans un thread séparé, envisagez d'utiliser PostMessage (ou PostThreadMessage) au lieu de SendMessage.

Cela ne peut être la cause réelle de l'accident, mais le changement devrait être fait de toute façon (comme vous passez des contextes threading en utilisant SendMessage sans GARDIENNAGE des données que ce soit

+0

J'ai aussi essayé 'PostMessage()' mais ça a eu le même effet. La raison de l'utilisation de 'SendMessage()' était que l'expéditeur est bloqué jusqu'à ce que le destinataire ait fait son travail. J'ai supposé que cela me sauverait l'effort d'avoir à stocker les données, que lParam pointe vers, dans un endroit spécial et avoir à comprendre quand la mémoire peut être libérée. – foraidt

2

De afxmsg_.h.

// for Registered Windows messages 
#define ON_REGISTERED_MESSAGE(nMessageVariable, memberFxn) \ 
    { 0xC000, 0, 0, 0, (UINT_PTR)(UINT*)(&nMessageVariable), \ 
     /*implied 'AfxSig_lwl'*/ \ 
     (AFX_PMSG)(AFX_PMSGW) \ 
     (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \ 
     (memberFxn)) }, 

Ainsi, la signature est LRESULT ClassName::FunctionName(WPARAM, LPARAM), alors que le vôtre est void ClassName::FunctionName(const WPARAM, const LPARAM). Cela ne devrait pas compiler, au moins sous VS2008 il ne fonctionne pas.

Quelle est votre HandleServerCo mmand déclaration dans la classe CServerCommandSubscriber (dans le fichier d'en-tête)?

+0

Vous avez raison, c'était faux (et ne devrait pas avoir compilé, mais je suis lié à un ancien compilateur). Le corriger à 'LRESULT HandleServerCommand (WPARAM wParam, LPARAM lParam); 'n'a pas résolu le problème, cependant. – foraidt

Questions connexes