Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Envoyer des chaînes ou des structures en asynchrone par PostMessage
En Delphi, par Andnotor

Le , par Andnotor

0PARTAGES

2  0 
Bonjour à tous !

Je vous propose aujourd'hui un nouveau tutoriel sur l'envoi de chaînes ou de structures par PostMessage en utilisant la table d'atomes.

N'hésitez pas à faire part de vos réactions

Bonne lecture !

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 09/12/2012 à 13:09
Tu peux envoyer tout ce que tu veux, entre threads ou entre processus

Voici deux exemples.

Rediriger la sortie d'une application console.
Admettons que cycliquement, tu lances en tâche de fond une application console invisible pour de la maintenance (backup, mise à jour...). Tu aimerais cependant que l'utilisateur soit informé de l'avancement ou d'une éventuelle erreur. Tu pourrais bien sûr :
  • utiliser CreateProcess et rediriger la sortie sur un Pipe. Un travail conséquent ;
  • lancer ShellExecuteEx depuis un thread et attendre la fin pour contrôler le code de sortie (ExitCode), mais pas de notification d'avancement ;
  • envoyer WM_COPYDATA mais SendMessage est bloquant et dû à la structure de taille fixe, le texte risque d'être tronqué (ou alors il faut surdimentionner le buffer).

Ici, rien de tout cela ! Il suffit de redéfinir WriteLn et d'envoyer PostTextMessage
Code : 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
program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils, Windows, Messages, MessagesEx;

procedure WriteLn(aText :string);
begin
  PostTextMessage(FindWindow('ClassName', 'WindowName'), WM_USER, aText);
end;

begin
  ClearTableOnExit := FALSE;

  try
    WriteLn('BlaBla');
    WriteLn('BlaBla');

  except
    on E: Exception do
      WriteLn(E.Message);
  end;
end.

Hook.
Tu aimerais intercepter le retour de traitement d'un message par une fenêtre quelconque. Tu vas pour cela injecter une DLL dans les processus par SetWindowsHookEx. Mais un hook peut être très pénalisant pour le système et le ralentir considérablement si le traitement à effectuer est long.
Jusqu'à Windows XP, on privilégiait le passage d'info par fichier mappé. Les lectures et écritures devant bien sûr être synchronisées par mutex. Depuis Vista et l'UAC, C'est fini ! La DLL injectée dans une application de niveau d'intégrité inférieur (par exemple Internet Explorer en mode protégé) n'aura pas accès au fichier mappé. Une violation est générée et c'est le deadlock assuré de l'application cible.
On pourrait à nouveau utiliser WM_COPYDATA, mais il faudra que notre application soit très réactive. Imagine un hook souris tellement pénalisant que le curseur avance par saccade !
Router simplement les informations par PostMessage ? Bien sûr si peu d'informations nous intéressent et tiennent dans WParam/LParam. Mais il ne faut pas espérer en récupérer plus par la suite par AttachThreadInput (UAC quand tu nous tiens...).

Intercepter le retour d'un message se fait en plaçant un hook de type WH_CALLWNDPROCRET. Un pointeur est passé dans LParam correspondant à la structure suivante :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
type
  TCwpRetStruct = record
    lResult :LRESULT;
    lParam  :LPARAM;
    wParam  :WPARAM;
    message :UINT;
    hwnd    :HWND;
  end;
Nous n'aurons donc plus qu'à faire :

Code : Sélectionner tout
1
2
3
4
5
6
7
function CWPRetProc(aCode: integer; aWParam: WPARAM; aLParam: LPARAM):LRESULT; stdcall;
begin
  if (aCode = HC_ACTION) then
    PostBufferMessage(FindWindow('ClassName', 'WindowName'), WM_USER, aLParam, SizeOf(TCwpRetStruct));

  Result := CallNextHookEx(0, aCode, aWParam, aLParam);
end;
Est-ce plus clair ?
4  0 
Avatar de Paul TOTH
Expert éminent sénior https://www.developpez.com
Le 07/12/2012 à 5:01
Bonjour,

Personnellement j'utilise un autre approche que je trouve bcp plus simple

Code : 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
type
  TMessageData = class
    UnEntier: Integer;
    UneChaine: string;
    UnStream: TStream;
    NImporteQuoi: TNImportQuoi;
  end;

  TDataMessage = record
    Msg: Integer;
    Null: Integer;
    Data: TMessageData;
   Result : Integer;
  end;

procedure SendInfos(HWnd, Msg: Integer; UnEntier, UneChaine...);
var
  data: TMessageData;
begin
  data := TMessageData.Create;
  data.UnEntier := UnEntier;
  data.UneChaine := UneChaine;
  ...
  PostMessage(HWnd, Msg, 0, Integer(@data));
end;
le destinataire du message est responsable de la suppression de "data", mais il est possible d'ajouter ces objets à une ThreadList globale qu'on purge le cas échéant quand c'est nécessaire.
1  0 
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 14/12/2012 à 18:30
Citation Envoyé par Andnotor  Voir le message
envoyer WM_COPYDATA mais SendMessage est bloquant et dû à la structure de taille fixe, le texte risque d'être tronqué (ou alors il faut surdimentionner le buffer).

WM_COPYDATA est bloquant, cela a un avantage car son retour permet de vérifier la bonne reception\compréhension du message par le receveur

Sinon WM_COPYDATA utilise une structure COPYDATASTRUCT de taille fixe mais permet justement de gérer un buffer de longueur variable via se membres cbData et lpData
On peut donc strictement définir un buffer de la taille exacte de la donnée devant être envoyé

Sinon, pourquoi ne pas avoir utiliser BinToHex qui aurait fait le travail de conversion en Hexa bien plus rapidement que les nombreuses réallocation\concaténation de Data.Bytes même si FastMM le gère bien mieux que l'ancien gestionnaire de mémoire

Tient, j'ai fait un petit dev hier que l'on m'a réclamé pour simplifier la vie de l'utilisateur
un programme A doit lancer un programme B
A étant déjà loggué, B est lancé en mode "AutoLogin" pour que l'utilisateur n'est pas retapé son Login+PW
Dans d'autres programmes qui font la même chose, le login est passé sur la ligne de commande, en clair pour certaines et d'autres en chiffré
Cela reste une jolie faille de sécurité puisque cela permet un login sans PW
Mon prédecesseur n'avait pas du tout conscience de la sécurité !

Je passe un HWND qui servirait ensuite pour un échange entre A et B d'un GUID servant d'ID de session qui aboutit à l'authentification, c'est déjà plus difficile à choper (un hook)

je viens d'en extraire une petite classe plus générique, on voit tout de suite qu'avec un peu d'effort que l'on pourrait hériter d'un TStream et utiliser un TBinaryReader\TBinaryWriter dessus

la Version C++, si j'ai le temps, je la tenterais en Delphi

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
//--------------------------------------------------------------------------- 
typedef bool __fastcall (__closure *TShaiCopyDataMessagerReadEvent)(TObject* Sender, HWND DataSender, void *Data, int DataLen); 
  
//--------------------------------------------------------------------------- 
//                          TShaiCopyDataMessager                           - 
//--------------------------------------------------------------------------- 
class TShaiCopyDataMessager : public TObject 
{ 
private: 
  // Membres Privés 
  HWND FSenderHandle; 
  HWND FRecipientHandle; 
  COPYDATASTRUCT FRawData; 
  TShaiCopyDataMessagerReadEvent FOnRead; 
  
  // Accesseurs 
  HWND __fastcall GetSenderHandle(); 
  
  // Méthodes Privées 
  void __fastcall WndProc(Messages::TMessage &Message); 
  bool DoRead(HWND DataSender, void *Data, int DataLen); 
  
public: 
  // Constructeurs Publiques 
  /*constructor*/__fastcall TShaiCopyDataMessager(); 
  /*destructor*/virtual __fastcall ~TShaiCopyDataMessager(); 
  
  // Méthodes Publiques 
  void BeginWrite(); 
  void WriteInteger(int Value); 
  void WriteString(String Value); 
  void WriteBuffer(void *Data, int DataLen); 
  bool EndWrite(); 
  void CancelWrite(); 
  
  int ReadInteger(void *Data, /*out*/int &Value); 
  int ReadString(void *Data, /*out*/String &Value); 
  
  // Propriétés Publiques 
  __property HWND SenderHandle = {read=GetSenderHandle}; 
  __property HWND RecipientHandle = {read=FRecipientHandle, write=FRecipientHandle}; 
  __property TShaiCopyDataMessagerReadEvent OnRead = {read=FOnRead, write=FOnRead}; 
};
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
//--------------------------------------------------------------------------- 
//                          TShaiCopyDataMessager                           - 
//--------------------------------------------------------------------------- 
/*constructor*/__fastcall TShaiCopyDataMessager::TShaiCopyDataMessager() 
{ 
  
} 
  
//--------------------------------------------------------------------------- 
/*destructor*/__fastcall TShaiCopyDataMessager::~TShaiCopyDataMessager() 
{ 
  CancelWrite(); 
  
  if (FSenderHandle != 0) 
  { 
    DeallocateHWnd(FSenderHandle); 
    FSenderHandle = 0; 
  } 
} 
  
//--------------------------------------------------------------------------- 
HWND __fastcall TShaiCopyDataMessager::GetSenderHandle() 
{ 
  if ( ! FSenderHandle) 
    FSenderHandle = AllocateHWnd(WndProc); 
  
  return FSenderHandle; 
} 
  
//--------------------------------------------------------------------------- 
void TShaiCopyDataMessager::BeginWrite() 
{ 
  CancelWrite(); 
} 
  
//--------------------------------------------------------------------------- 
void TShaiCopyDataMessager::WriteInteger(int Value) 
{ 
  WriteBuffer(&Value, sizeof(Value)); 
} 
  
//--------------------------------------------------------------------------- 
void TShaiCopyDataMessager::WriteString(String Value) 
{ 
  int Len = Value.Length(); 
  WriteInteger(Len); 
  
  if (Len > 0) 
  { 
    int Size = Len * sizeof(Char); // gère que la table BMP de UTF-16 
    WriteBuffer(Value.c_str(), Size); 
  } 
} 
  
//--------------------------------------------------------------------------- 
void TShaiCopyDataMessager::WriteBuffer(void *Data, int DataLen) 
{ 
  int Position = FRawData.cbData; 
  FRawData.cbData += DataLen; 
  if (Position > 0) 
    ReallocMemory(FRawData.lpData, FRawData.cbData); 
  else 
    FRawData.lpData = GetMemory(FRawData.cbData); 
  CopyMemory((Byte*)FRawData.lpData + Position, Data, DataLen); 
} 
  
//--------------------------------------------------------------------------- 
bool TShaiCopyDataMessager::EndWrite() 
{ 
  bool Result = SendMessage(FRecipientHandle, WM_COPYDATA, (WPARAM)FSenderHandle, (LPARAM)&FRawData); 
  CancelWrite(); 
  return Result; 
} 
  
//--------------------------------------------------------------------------- 
void TShaiCopyDataMessager::CancelWrite() 
{ 
  FreeMemory(FRawData.lpData); 
  ZeroMemory(&FRawData, sizeof(FRawData)); 
} 
  
//--------------------------------------------------------------------------- 
int TShaiCopyDataMessager::ReadInteger(void *Data, /*out*/int &Value) 
{ 
  int Result = -1; 
  if ( ! IsBadReadPtr(Data, sizeof(Value))) 
  { 
    Value = *(int*)Data; 
    Result = sizeof(Value); 
  } 
  return Result; 
} 
  
//--------------------------------------------------------------------------- 
int TShaiCopyDataMessager::ReadString(void *Data, /*out*/String &Value) 
{ 
  int Result = -1; 
  int Len = 0; 
  int TmpResult = ReadInteger(Data, Len); 
  if (TmpResult > 0) 
  { 
    Result = TmpResult; 
    if (Len > 0) 
    { 
      Value = UnicodeString((WideChar*)((Byte*)Data + TmpResult), Len); 
      int Size = Len * sizeof(Char); // gère que la table BMP de UTF-16 
      Result += Size; 
    } 
    else 
      Value = ""; 
  } 
  return Result; 
} 
  
//--------------------------------------------------------------------------- 
void __fastcall TShaiCopyDataMessager::WndProc(Messages::TMessage &Message) 
{ 
  if (Message.Msg == WM_COPYDATA) 
  { 
    PCOPYDATASTRUCT PRawData = (PCOPYDATASTRUCT)Message.LParam; 
    if (PRawData && PRawData->cbData) 
      Message.Result = DoRead((HWND)Message.WParam, PRawData->lpData, PRawData->cbData); 
    else 
      Message.Result = false; 
  } 
  else 
    Message.Result = DefWindowProc(FSenderHandle, Message.Msg, Message.WParam, Message.LParam); 
} 
  
//--------------------------------------------------------------------------- 
bool TShaiCopyDataMessager::DoRead(HWND DataSender, void *Data, int DataLen) 
{ 
  bool Result = false; 
  if (FOnRead) 
    Result = FOnRead(this, DataSender, Data, DataLen); 
  
  return Result; 
}

//---------------------------------------------------------------------------

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
void __fastcall TSystemManipForm::BtnCopyDataWhoIAmClick(TObject *Sender) 
{ 
  if ( ! FCopyDataMessager) 
  { 
    FCopyDataMessager = new TShaiCopyDataMessager(); 
    FCopyDataMessager->OnRead = CopyDataMessagerReadEventHandler; 
  } 
  
  EditCopyDataWhoIAm->Text = UIntToStr((unsigned)FCopyDataMessager->SenderHandle); 
} 
  
//--------------------------------------------------------------------------- 
bool __fastcall TSystemManipForm::CopyDataMessagerReadEventHandler(TObject *Sender, HWND DataSender, void *Data, int DataLen) 
{ 
  bool Result = false; 
  
  TShaiCopyDataMessager* Messager = dynamic_cast<TShaiCopyDataMessager*>(Sender); 
  if (Messager) 
  { 
    String S1, S2, S3; 
  
    int OffSet = Messager->ReadString(Data, S1); 
    Data = (Byte*)Data + OffSet; 
    OffSet = Messager->ReadString(Data, S2); 
    Data = (Byte*)Data + OffSet; 
    OffSet = Messager->ReadString(Data, S3); 
  
    MemoCopyDataRecv->Lines->Add(S1 + S2 + S3); 
    Result = Pos("bad", S2) <= 0; 
  } 
  
  return Result; 
} 
  
//--------------------------------------------------------------------------- 
void __fastcall TSystemManipForm::BtnCopyDataSendToClick(TObject *Sender) 
{ 
  if ( ! FCopyDataMessager) 
  { 
    FCopyDataMessager = new TShaiCopyDataMessager(); 
    FCopyDataMessager->OnRead = CopyDataMessagerReadEventHandler; 
  } 
  
  FCopyDataMessager->RecipientHandle = (HWND)StrToInt(EditCopyDataSendTo->Text); 
  FCopyDataMessager->BeginWrite(); 
  FCopyDataMessager->WriteString("La Chaine est : \""); 
  FCopyDataMessager->WriteString(EditCopyDataSendToText->Text); 
  FCopyDataMessager->WriteString("\" !"); 
  if ( ! FCopyDataMessager->EndWrite()) 
    MemoCopyDataRecv->Lines->Add("bad est refusé"); 
} 
//---------------------------------------------------------------------------
Code dfm : 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
object BtnCopyDataWhoIAm: TButton 
  Left = 3 
  Top = 3 
  Width = 75 
  Height = 25 
  Caption = 'Who I Am ?' 
  TabOrder = 0 
  OnClick = BtnCopyDataWhoIAmClick 
end 
object EditCopyDataWhoIAm: TEdit 
  Left = 84 
  Top = 5 
  Width = 121 
  Height = 21 
  Alignment = taRightJustify 
  NumbersOnly = True 
  ReadOnly = True 
  TabOrder = 1 
  Text = '?' 
end 
object BtnCopyDataSendTo: TButton 
  Left = 3 
  Top = 34 
  Width = 75 
  Height = 25 
  Caption = 'Send To' 
  TabOrder = 2 
  OnClick = BtnCopyDataSendToClick 
end 
object EditCopyDataSendTo: TEdit 
  Left = 84 
  Top = 36 
  Width = 121 
  Height = 21 
  Alignment = taRightJustify 
  NumbersOnly = True 
  TabOrder = 3 
  Text = '?' 
end 
object EditCopyDataSendToText: TEdit 
  Left = 211 
  Top = 36 
  Width = 121 
  Height = 21 
  TabOrder = 4 
  Text = 'Bonjour !' 
end 
object MemoCopyDataRecv: TMemo 
  Left = 3 
  Top = 65 
  Width = 329 
  Height = 274 
  Lines.Strings = ( 
    'Receive :') 
  ReadOnly = True 
  ScrollBars = ssBoth 
  TabOrder = 5 
end
1  0 
Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 07/12/2012 à 10:24
C'est possible en effet

Par record, il faudra prendre soin de faire appel à GetMem/FreeMem (New/Dispose) et pas simplement déclarer une variable locale à la fonction !
Mon approche se veut cependant plus universelle et autorise l'envoi entre processus
0  0 
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 07/12/2012 à 14:19
J'ai jamais eu le temps de m'occuper de [QR] Comment passer une string dans un message Windows ? qui utilise WM_COPYDATA qui fonctionnee en inter-processus
0  0 
Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 07/12/2012 à 14:24
...mais qui est obligatoirement synchrone par SendMessage
0  0 
Avatar de BuzzLeclaire
Membre éprouvé https://www.developpez.com
Le 08/12/2012 à 22:55
@AndNotY,

Salut, j'ai parcouru tout ton article, et à la fin je me suis demandé à quoi cela peut servir en cas réel ? (je préfère passer pour un idiot, mais je pose quand même la question...)

Est-ce un moyen de transmettre des données (tableau, chaîne, nombre) entre 2 applications ?

0  0 
Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 14/12/2012 à 19:21
Citation Envoyé par ShaiLeTroll Voir le message
WM_COPYDATA est bloquant, cela a un avantage car son retour permet de vérifier la bonne reception\compréhension du message par le receveur
C'est clair qu'à partir du moment où un dialogue est nécessaire, cette technique n'est pas applicable. Ce n'est pas pour rien que j'ai pris l'exemple de WH_CALLWNDPROCRET dont la donnée ne peut être modifiée

Maintenant, mon but était vraiment de faire de l'asynchrone...

Citation Envoyé par ShaiLeTroll Voir le message
Sinon, pourquoi ne pas avoir utiliser BinToHex qui aurait fait le travail de conversion en Hexa bien plus rapidement que les nombreuses réallocation\concaténation de Data.Bytes
J'aurais pu Comme j'aurais pu aussi définir une chaîne de 2x la taille de la donnée et travailler sur l'index du caractère.
Mais je suis un pervers et au départ je comptais convertir un octet sur 10 bits (actuellement 16 bits) pour utiliser au mieux les 255 octets à disposition. Puisqu'une des contraintes est : pas de byte à "0" son codage aurait été 1000010000 (en ajoutant toujours "1". Mais ça devenait un peu compliqué et le tuto n'aurait plus été "Confirmé", mais "Expert"

Mais j'aurais dû plus simplifier !

Citation Envoyé par ShaiLeTroll Voir le message
Je passe un HWND qui servirait ensuite pour un échange entre A et B d'un GUID servant d'ID de session qui aboutit à l'authentification, c'est déjà plus difficile à choper (un hook)
J'ai aussi utiliser ce genre de technique en envoyant un message que l'application ne sait pas gérer (RegisterWindowMessage, donc inconnu par la cible)
mais récupéré par hook pour exécuter du code dans le processus cible
0  0 
Avatar de MIWAN
Membre régulier https://www.developpez.com
Le 30/12/2012 à 18:29
c'est Noël
Dans l'endroit et au bon moment que ce tutoriel car je passais des heures pour apprendre quelques choses sur les HOOK(s).

Merci pour ce tutoriel et les interventions des chères membres
0  0