Page 1 of 1

WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 04:58
by just me
!!! In Bearbeitung !!!

Moin,

in diesem Beitrag habe ich angekündigt, etwas mehr über das Thema sagen zu wollen. Damit fange ich heute (16.07.20) mal an.

Für das Übermitteln von Daten von Skript zu Skript hat Microsoft die Nachricht
WM_COPYDATA
und die zugehörige Datenstruktur
COPYDATASTRUCT
entwickelt.

Um die zu nutzen braucht es Folgendes:
  • Alle beteiliegten Skripte müssen für das Senden das Handle (HWND) eines der Skriptfenster des Zielskripts kennen. Das darf auch das verborgene Hauptfenster des Skripts sein. Für Antworten kann das Zielfenster der eingehenden Nachricht entnommen werden.
  • In den empfangenden Skripten braucht man eine Funktion für die Behandlung eingehender Nachrichten

    Code: Select all

    OnMessage(0x004A, "MeineNachrichtenFunktion")
    mit den Parametern wParam und lParam.
  • Außerdem habe ich mal gelesen (finde es aber nicht mehr wieder), dass unter Umständen in emfangenden Skripten der Aufruf von

    Code: Select all

    DllCall("ChangeWindowMessageFilter", "UInt", 0x004A, "UInt", 1) ; WM_COPYDATA
    nötig sein soll, damit die Nachricht akzeptiert wird. Ich habe das aber bisher nicht gebraucht.
Und das war's auch schon.

Wie Ragnar unten angemerkt hat, gibt es für die Nutzung von WM_COPYDATA auch ein Beispiel in der Doku.

Re: WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 05:57
by just me
Daten:

Für den Datenttransport hat Microsoft die Struktur COPYDATASTRUCT bereitgestellt:

Code: Select all

typedef struct tagCOPYDATASTRUCT {
  ULONG_PTR dwData;
  DWORD     cbData;
  PVOID     lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
Diese Struktur besteht aus drei Feldern mit folgenden Inhalten:
  • ULONG_PTR dwData;
    Ein Feld in Pointergröße, das einen UINT (32-bit) Wert enthält. Hier kann Alles übergeben werden, was in 4 Bytes passt.
  • DWORD cbData;
    Die Anzahl der Bytes, auf die der im folgenden Feld lpData; enthaltene Pointer zeigt. Wenn kein Pointer übergeben wird, muss das Feld auf Null gesetzt werden.
  • PVOID lpData;
    Ein Pointer auf beliebige zusätzliche Daten oder Null.
Die ersten beiden Felder müssen in einer Länge von 4 Bytes gefüllt bzw. ausgelesen werden. Das letzte Feld enthält einen Pointer von 4 (32 Bit) bzw. 8 (64 Bit) bytes Länge. Wegen der Feldausrichtung in Strukturen ist die Distanz zwischen den Feldern immer A_PtrSize Bytes.

Code: Select all

VarSetCapacity(CDS, A_PtrSize * 3, 0) ; COPYDATASTRUCT
; Put:
NumPut(dwData, CDS, 0, "UInt")
NumPut(cbData, CDS, A_PtrSize, "UInt")
NumPut(lpData, CDS, A_PtrSize * 2, "UPtr")
; Get:
dwData := NumGet(CDS, 0, "UInt")
cbData := NumGet(CDS, A_PtrSize, "UInt")
lpData := NumGet(CDS, A_PtrSize * 2, "UPtr")
Per lpData können beliebige Inhalte übermittelt werden, Binärdaten und auch Text. Bei Text ist zu beachten, dass cbData die Länge in Bytes enthalten muss, nicht die Länge in Zeichen. Wenn alle Skripte dieselbe Sprache (ANSI / Unicode) sprechen, können Texte ohne Konvertierung übermittelt werden, anderenfalls sollten sie nach UTF-8 konvertiert werden. Für die Bestimmung, welcher Datentyp sich hinter lpData verbirgt, kann das Feld dwData benutzt werden (z.B. 1 = Text, 2 = Binärdaten).

Re: WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 05:58
by just me
Senden:

Re: WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 06:00
by just me
Empfangen:

Re: WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 06:01
by just me
Beispiele:

:arrow: Calling a function in another AHK multiple times concurrently
Master.ahk:

Code: Select all

#NoEnv
#SingleInstance, Force
OnExit, AppExit
DetectHiddenWindows, On
Global RunningScripts := {}
Global SizeT := A_IsUnicode ? 2 : 1
Global WM_COPYDATA := 0x004A
Params := "NotificationFlag`nRPA_Mode`nVariable4`nShortName`nLongname"
Script2Run := A_ScriptDir . "\Client.ahk"
; To call ChangeWindowMessageFilter is recommended by Microsoft for Win Vista+. Actually, I don't need it on Win 10.
DllCall("ChangeWindowMessageFilter", "UInt", WM_COPYDATA, "UInt", 1)
OnMessage(WM_COPYDATA, "WM_COPYDATA_Receive")
Gui, Margin, 100, 50
Gui, Add, Button, gRunScript, Run Next Client
Gui, Show, , % "Master (" . (A_ScriptHwnd + 0) . ")"
Return
GuiClose:
ExitApp
; ----------------------------------------------------------------------------------------------------------------------
RunScript:
Run, *Open %Script2Run%, , , PID
WinWait, ahk_pid %PID% ahk_class AutoHotkey, , 2
If (ErrorLevel) {
   MsgBox, 16, ERROR, Couldn't run %Script2Run%!
   Return
}
HMSG := WinExist()
If !WM_COPYDATA_Send(HMSG, Params) {
   MsgBox, 16, ERROR, Client %HMSG% refused didn't process WM_COPYDATA!
   WinClose, ahk_id %HMSG%
   Return
}
RunningScripts[HMSG] := PID
Return

AppExit:
   For HWND In RunningScripts
      WinClose, ahk_id %HWND%
ExitApp

Esc::
ExitApp

; ----------------------------------------------------------------------------------------------------------------------
WM_COPYDATA_Receive(W, L) {
   If !RunningScripts.HasKey(W)
      Return False
   RunningScripts.Delete(W)
   DataType := StrGet(L, "CP0")
   DataSize := NumGet(L + A_PtrSize, "Int")
   DataPtr := NumGet(L + (A_PtrSize * 2), "UPtr")
   If (DataType = "T") { ; we recieved text
      Data := StrGet(DataPtr, DataSize, "UTF-8")
      DataSize := StrLen(Data)
      MsgBox, Message from Client %W%`n`n%DataSize%`n`n%Data%
   }
   Else { ; we received binary data
      VarSetCapacity(Date, DataSize, 0)
      DllCall("RtlMoveMemory", "Ptr", &Data, "Ptr", DataPtr, "Ptr", DataSize)
   }
   Return True
}
; ----------------------------------------------------------------------------------------------------------------------
WM_COPYDATA_Send(HMSG, ByRef Data, DataSize := 0) {
   If DataSize Is Not Integer
      Return !(ErrorLevel := 1)
   If (DataSize < 1) {
      DataType := Asc("T")
      VarSetCapacity(UTF8, (DataSize := StrPut(Data, "UTF-8")), 0)
      StrPut(Data, &UTF8, "UTF-8")
      DataPtr := &UTF8
   }
   Else {
      DataType := Asc("B")
      DataPtr := &Data
   }
   VarSetCapacity(COPYDATA, 3 * A_PtrSize, 0)
   NumPut(DataType, COPYDATA, 0, "UChar")
   NumPut(DataSize, COPYDATA, A_PtrSize, "Int")
   NumPut(DataPtr, COPYDATA, 2 * A_PtrSize, "Ptr")
   SendMessage, WM_COPYDATA, A_ScriptHwnd, &COPYDATA, , ahk_id %HMSG%, , , , 1000
   Return (ErrorLevel = "FAIL") ? 0 : !!ErrorLevel
}
; ----------------------------------------------------------------------------------------------------------------------
/* http://msdn.microsoft.com/en-us/library/ms649010(VS.85).aspx
   typedef struct tagCOPYDATASTRUCT {
      ULONG_PTR dwData;     The data to be passed to the receiving application.
      DWORD     cbData;     The size, in bytes, of the data pointed to by the lpData member.
      PVOID     lpData;     The data to be passed to the receiving application. This member can be NULL.
   } COPYDATASTRUCT, *PCOPYDATASTRUCT;
*/
Client.ahk:

Code: Select all

#NoEnv
#SingleInstance Off
DetectHiddenWindows, On
Global HMSG := 0
Global SizeT := (A_IsUnicode ? 2 : 1)
Global Start := False
Global WM_COPYDATA := 0x004A
Global Data
Global DataSize
; To call ChangeWindowMessageFilter is recommended by Microsoft for Win Vista+. Actually, I don't need it on Win 10.
DllCall("ChangeWindowMessageFilter", "UInt", WM_COPYDATA, "UInt", 1)
OnMessage(WM_COPYDATA, "WM_COPYDATA_Receive")
While (Start = False)
   Sleep, 100
MsgBox, 0, % "Client (" . (A_ScriptHwnd + 0) . ")", Message from master %HMSG%`n`n%DataSize%`n`n%Data% ; we don't expect binary data
WM_COPYDATA_Send("This is the result sent from client " . (A_ScriptHwnd + 0))
ExitApp

Esc::
ExitApp

; ----------------------------------------------------------------------------------------------------------------------
WM_COPYDATA_Receive(W, L) {
   If (W <> HMSG) {
      If (HMSG = 0)
         HMSG := W
      Else
         Return False
   }
   DataType := StrGet(L, "CP0")
   DataSize := NumGet(L + A_PtrSize, "Int")
   DataPtr := NumGet(L + (A_PtrSize * 2), "UPtr")
   If (DataType = "T") { ; we recieved text
      Data := StrGet(DataPtr, DataSize, "UTF-8")
      DataSize := StrLen(Data)
   }
   Else { ; we received binary data
      VarSetCapacity(Data, DataSize, 0)
      DllCall("RtlMoveMemory", "Ptr", &Data, "Ptr", DataPtr, "Ptr", DataSize)
   }
   Start := True
   Return True
}
; ----------------------------------------------------------------------------------------------------------------------
WM_COPYDATA_Send(ByRef Data, DataSize := 0) {
   If DataSize Is Not Integer
      Return !(ErrorLevel := 1)
   If (DataSize < 1) {
      DataType := Asc("T")
      VarSetCapacity(UTF8, (DataSize := StrPut(Data, "UTF-8")), 0)
      StrPut(Data, &UTF8, "UTF-8")
      DataPtr := &UTF8
   }
   Else {
      DataType := Asc("B")
      DataPtr := &Data
   }
   VarSetCapacity(COPYDATA, 3 * A_PtrSize, 0)
   NumPut(DataType, COPYDATA, 0, "UChar")
   NumPut(DataSize, COPYDATA, A_PtrSize, "Int")
   NumPut(DataPtr, COPYDATA, 2 * A_PtrSize, "Ptr")
   SendMessage, WM_COPYDATA, A_ScriptHwnd, &COPYDATA, , ahk_id %HMSG%, , , , 1000
   Return (ErrorLevel = "FAIL") ? 0 : !!ErrorLevel
}
; ----------------------------------------------------------------------------------------------------------------------
/* http://msdn.microsoft.com/en-us/library/ms649010(VS.85).aspx
   typedef struct tagCOPYDATASTRUCT {
      ULONG_PTR dwData;     The data to be passed to the receiving application.
      DWORD     cbData;     The size, in bytes, of the data pointed to by the lpData member.
      PVOID     lpData;     The data to be passed to the receiving application. This member can be NULL.
   } COPYDATASTRUCT, *PCOPYDATASTRUCT;
*/

Re: WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 08:45
by Ragnar
Nur als Randinformation: Für dieses Konzept gibt es bereits Beispiele in der Doku (OnMessage-Beispiel #3 für das Senden/Empfangen von Zahlen und OnMessage-Beispiel #4 für das Senden/Empfangen von Zeichenketten).

Re: WM_COPYDATA - Datentausausch zwischen Skripten

Posted: 16 Jul 2020, 15:56
by just me
@Ragnar,
stimmt! Die Beispiele beruhen aber sehr ausgeprägt auf AHK 1.0 Konzepten.