OVH Cloud OVH Cloud

SendMessage et WM_COPYDATA

9 réponses
Avatar
Caliban
Bonjour

Je cherche à envoyer à une instance d'application en cours d'exécution
un message indiquant qu'une autre instance vient de démarrer avec un nom
de fichier passé en argument. Le but est évidemment d'éviter d'avoir
deux fois la même application à l'écran, ce qui fonctionne bien, mais
aussi de signaler à celle qui tourne déjà qu'elle doit ouvrir un fichier.

Le programme contient le code suivant au début du main() :
COPYDATASTRUCT CopyData;
CopyData.dwData=0;
CopyData.cbData=strlen(ArgFile)+1;
CopyData.lpData=&ArgFile[0];
SendMessage(hWnd,WM_COPYDATA,0,(LPARAM)&CopyData);

hWnd est le handle sur l'instance déjà en cours, trouvée avec
FindWindow() et GetClassName(). Pas de problème de ce côté, ça marche
bien puisque ça donne le focus à l'instance existante avec
SetForegroundWindow(hWnd).

Côté réception, le programme contient une surcharge de DefWindowProc()
dans laquelle je fais le test suivant :
if(Msg==WM_COPYDATA)
...

Problème : le seul message que reçoit l'instance déjà en cours, c'est le
WM_SETFOCUS généré par SetForegroundWindow(). Elle ne reçoit rien
d'autre. Je suppose donc que le message n'est pas posté. Est-ce que
c'est parce que j'ai un argument WParam nul ? Si c'est bien le cas,
qu'est-ce que je dois mettre puisque je n'ai pas de handle à passer ? Le
programme appelant n'a pas le temps de créer une instance : il quitte
juste après avoir envoyé le message à celle déjà en cours.

Bref, je sèche.

Merci pour votre aide.

9 réponses

Avatar
Arnold McDonald \(AMcD\)
Caliban wrote:

hWnd est le handle sur l'instance déjà en cours, trouvée avec
FindWindow() et GetClassName().



Voilou sans doute l'erreur. Dans la doc de WM_COPYDATA il est écrit :

WM_COPYDATA
wParam = (WPARAM)(HWND) hwnd;
lParam = (LPARAM)(PCOPYDATASTRUCT) pcds;

hwnd : Handle to the window passing the data.

Passing, pas receiving...


--
Arnold McDonald (AMcD) - Help #37/2006

http://arnold.mcdonald.free.fr/
Avatar
Caliban
Arnold McDonald (AMcD) wrote:

Voilou sans doute l'erreur. Dans la doc de WM_COPYDATA il est écrit :

WM_COPYDATA
wParam = (WPARAM)(HWND) hwnd;
lParam = (LPARAM)(PCOPYDATASTRUCT) pcds;

hwnd : Handle to the window passing the data.

Passing, pas receiving...






C'est pourtant bien ce que je fais : "Window passing the data", c'est
bien celle qui envoie le message, non ? Et dans mon cas, il n'y a pas de
fenêtre puisqu'on est au tout début du main(). Donc je mets 0 faute de
mieux.

SendMessage(hWnd,WM_COPYDATA,0,(LPARAM)&CopyData);
1er argument : hWnd -> Fenêtre destinataire
3ème argument : 0 -> Fenêtre expéditrice (inexistante)

Ou alors, il faut utiliser autre chose que SendMessage() ?
Avatar
Arnold McDonald \(AMcD\)
Je te réponds plus tard, là, je Pokerize...
Avatar
Arnold McDonald \(AMcD\)
Bon. J'ai codé un rapide petit exemple.

Voici le serveur, celui qui reçoit donc :

#include <windows.h>

BOOL CALLBACK DialogProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam)
{
COPYDATASTRUCT* ppp;
TCHAR sz[128];
int* p;
switch (iMsg)
{
case WM_COPYDATA :
ppp = (COPYDATASTRUCT*)(lParam);
p = (int*)ppp->lpData;
wsprintf(sz,TEXT("%d"),*p);
SetWindowText(hDlg,sz);
return TRUE;
case WM_CLOSE :
EndDialog(hDlg,0);
return TRUE;
}
return FALSE;
}

int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR pCmdLine,int
iCmdShow)
{
HGLOBAL hg;
LPDLGTEMPLATE pdt;
LPWORD pw;
LPWSTR psz;
hg = GlobalAlloc(GMEM_ZEROINIT,1024);
pdt = (LPDLGTEMPLATE)GlobalLock(hg);
pdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
pdt->cdit = 0;
pdt->x = 100;
pdt->y = 100;
pdt->cx = 100;
pdt->cy = 100;
pw = (LPWORD)(pdt+1);
psz = (LPWSTR)(pw+2);
pw += 1 + MultiByteToWideChar(CP_ACP,0,"Server",-1,psz,50);
DialogBoxIndirect(NULL,(LPDLGTEMPLATE)hg,NULL,DialogProc);
GlobalUnlock(hg);
GlobalFree(hg);
return 0;
}

J'ai pas le temps alors, j'ai fait rapidos : une boîte de dialogue en
mémoire qui attends un WM_COPYDATA et affiche dans sa barre de titre la
donnée reçue.
Et voici le client, sans fenêtre, comme toi :

#include <windows.h>
int ppp = 1655;
int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR pCmdLine,int
iCmdShow)
{
HWND h;
COPYDATASTRUCT cds;
if ( 0 != ( h = FindWindow(NULL,TEXT("Server")) ) )
{
cds.dwData = 100;
cds.cbData = sizeof(int);
cds.lpData = &ppp;
SendMessage(h,WM_COPYDATA,(WPARAM)0,(LPARAM)(LPVOID)&cds);
}
return 0;
}

Chez moi, ça marche sans problème. Si le serveur tourne, il reçoit bien la
donnée. Inspire-toi de cet exemple.

--
Arnold McDonald (AMcD) - Help #38/2006

http://arnold.mcdonald.free.fr/
Avatar
Caliban
Arnold McDonald (AMcD) wrote:

Bon. J'ai codé un rapide petit exemple.




Ben, ça marche pas mieux. J'avoue que j'ai pas codé ta partie serveur
puisque mon appli contient déjà ce qu'il faut pour intercepter les
messages, et que de toute façon c'est là-dedans que ça devra tourner.
Dans WindowProc(), au lieu de DialogProc() donc, j'ai le code suivant
(j'utilise l'encapsulation Borland de l'API et donc la surcharge de
WindowProc, mais ça revient au même) :
TResult TWin::WindowProc(UINT uMsg, WPARAM wPrm, LPARAM lPrm)
{
switch(uMsg) {
case WM_SETFOCUS:
... // Breakpoint ici
break;
case WM_COPYDATA:
... // Breakpoint ici aussi
break;
}
return TWindow::WindowProc(uMsg,wPrm,lPrm);
}

Côté client, j'ai codé la même chose que toi, et même avec des valeurs
identiques :
COPYDATASTRUCT CopyData;
int Data55;
HWND hWnd=FindWindow(PrgmClassName,0);
if(hWnd)
{
SetForegroundWindow(hWnd);
CopyData.dwData0;
CopyData.cbData=sizeof(int);
CopyData.lpData=&Data;
SendMessage(hWnd,WM_COPYDATA,(WPARAM)0,(LPARAM)(LPVOID)&CopyData);
}

1er test : une instance de l'appli tourne, que je couvre avec une autre
fenêtre. Je lance le programme depuis l'IDE : dès que ça passe sur
SetForegroundWindow() l'appli réapparaît (même si elle perd aussitôt le
focus puisque je trace dans l'IDE). Ce qui veut dire que hWnd est ok. Ca
passe sur SendMessage() puis le programme s'arrête.

2ème test : je lance une instance de l'appli depuis l'IDE, et une fois
qu'elle est à l'écran, peinte et tout le tintouin, je pose deux
breakpoints dans WindowProc(), l'un après case WM_SETFOCUS et l'autre
après case WM_COPYDATA. Ensuite je lance une 2ème instance. Le
breakpoint après case WM_SETFOCUS réagit immédiatement. Je relance : le
second breakpoint après case WM_COPYDATA ne réagit pas.

3ème test : je mets mon code côté serveur dans DefWindowProc() au lieu
de WindowProc() parce que je suis pas sûr d'avoir pas compris la
différence profonde entre les deux, et je mets un breakpoint tout en
haut de la fonction une fois que l'appli est lancée. Quand je lance la
2ème instance, le breakpoint réagit sur un message WM_SETFOCUS, puis sur
un WM_KILLFOCUS (puisque l'IDE a repris le focus entretemps à cause du
1er break). Il n'y a AUCUN autre message qui arrive dans DefWindowProc()
après ces deux-là.

Je conclus sans en être tout à fait sûr que c'est côté client que ça
coince et que SendMessage() n'a pas fonctionné. Pourtant, hWnd n'a pas
changé entre le moment où j'appelle SetForegroundWindow() et
SendMessage(), j'ai vérifié. D'ailleurs, si j'appelle
SetForegroundWindow() APRES SendMessage(), le focus est toujours bien
donné à la 1ère instance. Donc hWnd est ok.

Déroutant, non ?
Avatar
Caliban
J'ajoute si ça peut aider que SendMessage() retourne 0.
Avatar
Arnold McDonald \(AMcD\)
Caliban wrote:

Arnold McDonald (AMcD) wrote:

Bon. J'ai codé un rapide petit exemple.



Ben, ça marche pas mieux.



Ha si, désolé, ça marche très bien chez moi.

J'avoue que j'ai pas codé ta partie serveur
puisque mon appli contient déjà ce qu'il faut pour intercepter les
messages, et que de toute façon c'est là-dedans que ça devra tourner.



En informatique, il n'y a pas de "toute façon" ; si un problème existe, il
faut le corriger non ? Si c'est le serveur qui foire, faudra bien le recoder
!

Dans WindowProc(), au lieu de DialogProc() donc, j'ai le code suivant
(j'utilise l'encapsulation Borland de l'API et donc la surcharge de
WindowProc, mais ça revient au même) :



Oui, Dialogproc() appelle un WindowProc() en interne, c'est strictement la
même chose.

Côté client, j'ai codé la même chose que toi, et même avec des valeurs
identiques :
COPYDATASTRUCT CopyData;
int Data55;
HWND hWnd=FindWindow(PrgmClassName,0);
if(hWnd)
{
SetForegroundWindow(hWnd);
CopyData.dwData0;
CopyData.cbData=sizeof(int);
CopyData.lpData=&Data;
SendMessage(hWnd,WM_COPYDATA,(WPARAM)0,(LPARAM)(LPVOID)&CopyData);
}



Non, ce n'est pas du tout pareil que moi. D'abord, je n'utilise pas de
SetForeground(). Ensuite, moi, je cherche avec le titre de la fenêtre, pas
avec le nom de la classe de fenêtre. Si tu cherches qu'avec la classe de
fenêtre, il est très possible, surtout avec des produits daubesques comme
Delphi et autres, que plusieurs applications aient cette classe de fenêtre,
donc, difficile d'être sûr que ce soit la bonne qui recevra ton
WM_COPYDATA...

Le mieux, pour gérer l'unicité des instances, c'est d'utiliser des mutex.

Je conclus sans en être tout à fait sûr que c'est côté client que ça
coince et que SendMessage() n'a pas fonctionné.



Ce qui serait une première depuis que Windows existe :-).

Déroutant, non ?



Je pense que tu cherches "mal" ton serveur et que le message n'est pas
envoyé à qui de droit. Enfin, il faudrait un code plus détaillé, parce
qu'avec ce que tu donnes, difficile de faire de la précision.

--
Arnold McDonald (AMcD)

http://arnold.mcdonald.free.fr/
Avatar
Caliban
Arnold McDonald (AMcD) wrote:


Ha si, désolé, ça marche très bien chez moi.



Je n'en doute pas, mais malheureusement c'est chez moi que ça coince.
J'aurais préféré l'inverse ;-)


Non, ce n'est pas du tout pareil que moi. D'abord, je n'utilise pas de
SetForeground().



Cet appel à SetForegroundWindow() me sert entre autres à vérifier que
j'ai bien chopé la bonne fenêtre. Comme elle réapparaît, je me sens à
peu près en position d'affirmer que c'est bien elle. De toute façon,
cette fonction n'altère pas hWnd et si je l'enlève, ça va pas mieux.


Ensuite, moi, je cherche avec le titre de la fenêtre, pas
avec le nom de la classe de fenêtre.



Je l'ai fait aussi, bien que si je vérifie quelle fenêtre je tiens dans
mon hWnd, je sais que c'est la bonne (je peux lire son titre). De toute
façon, c'est pas mieux avec FindWindow(0,"Machin").


Si tu cherches qu'avec la classe de fenêtre, il est très possible, surtout
avec des produits daubesques comme Delphi et autres, que plusieurs
applications aient cette classe de fenêtre,



Bon, j'utilise pas Delphi mais Borland C++. Ensuite, je n'ai que 2
applications avec fenêtre : mon IDE et l'appli qui tourne. Enfin, dans
la GetClassName(), je retourne le nom du programme avec son chemin
d'accès, c'est à dire l'Arg[0] du main(). La possibilité de confusion me
paraît limitée de ce côté.


Le mieux, pour gérer l'unicité des instances, c'est d'utiliser des mutex.



Ok, mais ça marche bien comme ça pour empêcher de lancer 2 instances de
la même appli correspondant au même programme. Si les 2 sont dans les
répertoires différents, alors elles ont le droit de cohabiter. Je vais
pas recoder un truc qui marche bien et de toute façon c'est pas le problème.


Je pense que tu cherches "mal" ton serveur et que le message n'est pas
envoyé à qui de droit. Enfin, il faudrait un code plus détaillé, parce
qu'avec ce que tu donnes, difficile de faire de la précision.



J'ai essayé d'utiliser WM_COPYDATA cette fois entre deux applis
différentes : marche pas mieux, mais il semble y avoir un temps de
traitement de SendMessage() - qui retourne 0 après 1 seconde. A mon
avis, le hWnd et le SendMessage() sont bons, mais c'est côté réception
que ça déraille. J'en conclus donc maintenant que quelque chose hooke le
message avant que je le chope dans WindowProc(). C'est curieux, parce
que, encore une fois, je reçois bien le WM_SETFOCUS correspondant à
l'appel de SetForegroundWindow(hWnd) et le WM_KILLFOCUS correspondant au
retour dans mon IDE à cause du breakpoint.


Bon, ne nous énervons pas, hein. J'ai sûrement une connerie quelque
part, ok. Mais franchement, je vois pas où.
Avatar
Arnold McDonald \(AMcD\)
Caliban wrote:

Bon, ne nous énervons pas, hein. J'ai sûrement une connerie quelque
part, ok. Mais franchement, je vois pas où.



J'ai testé différentes versions, même une où le programme se ferme
directement après avoir envoyé le WM_COPYDATA au serveur. Pas de problème,
le serveur le reçoit quand même ! Quel que soit le truc testé, ça marche
toujours.

Alors, désolé, mais je peux plus rien pour toi :-).

--
Arnold McDonald (AMcD)

http://arnold.mcdonald.free.fr/