Jump to content
Advert

WMCDIPC ( x32/x64, user/admin, self triggering, slow WM_COPYDATA IPC )


Go to solution Solved by argumentum,

Recommended Posts

Posted (edited)

The example:

Spoiler
#NoTrayIcon
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseX64=y
#AutoIt3Wrapper_AU3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
#AutoIt3Wrapper_Run_Au3Stripper=y ; or not
#Au3Stripper_Parameters=/rm /sv /rsln /pe
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#cs ----------------------------------------------------------------------------
 AutoIt Version: 3.3.16.1
#ce ----------------------------------------------------------------------------

;~ #RequireAdmin ; with or without or mixed

#include <WMCDIPC.au3> ; that's the name I came up with
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIError.au3>

Global $hGui, $idEdit, $g_DebugStr = "", $iGuiCount = WinList("WMCDIPC-example")[0][0]
If $iGuiCount > 20 Then Exit

If StringInStr($CmdLineRaw, "/AsSender") Then Exit ThaGui("AsSender")
ThaGui("AsReceiver")

Func ThaGui($sActAs)
    If WinExists("WMCDIPC-example-AsReceiver") Then $sActAs = "AsSender" ; ..in case you to load more times
    Local $iSleep = 0, $iS = 40, $iW = 950, $iH = 220, $iT = $iS + ($iGuiCount * ($iH + $iS)), $iL = $iS
    #forceref $iSleep
    If @DesktopHeight < ($iH * ($iGuiCount + 2)) Then
        $iT = $iS + ($iGuiCount * $iS)
        $iL = $iT ; use this if the GUIs don't fit in the monitor
    EndIf

    $hGui = GUICreate("WMCDIPC-example-" & $sActAs, $iW, $iH, $iL, $iT, _
            BitOR($GUI_SS_DEFAULT_GUI, $WS_MAXIMIZEBOX, $WS_SIZEBOX, $WS_THICKFRAME, $WS_TABSTOP), _
            BitOR($WS_EX_TOPMOST, $WS_EX_WINDOWEDGE)) ; ..or use your own GUI. WM_COPYDATA needs a GUI.
    GUISetFont(10, 400, 0, "Terminal")
    $idEdit = GUICtrlCreateEdit("..just a sec..", 0, 0, $iW, $iH)
    GUICtrlSetResizing(-1, $GUI_DOCKLEFT + $GUI_DOCKRIGHT + $GUI_DOCKTOP + $GUI_DOCKBOTTOM)
    GUISetState()

;~  Opt("GUIOnEventMode", 1)
;~  $iSleep = 100
;~  GUISetOnEvent($GUI_EVENT_CLOSE, ExitScript) ; just in case you GUIOnEventMode = 1

    ; these lines are all you need + the IPC user function

    WMCDIPC_PrintCallback(myConsoleWrite) ; You'd set something like this for debug. Not really necessary.
    ;           WMCDIPC_PrintCallback() returns the current function.
    ;           WMCDIPC_PrintCallback(Default) resets it back to the default handler "ConsoleWrite()".
    ;           WMCDIPC_PrintCallback("") to disable it.
    ;           When compiled it is automatically disabled, unless later declared by user.

    WMCDIPC_MSGFLT_AllAllowed() ; to get msg from every level, elevated or not.
;~  WMCDIPC_UseQueue(1) ; default is 0 ( no ). Set to 1 to enable.
;~  WMCDIPC_AdlibTime(10) ; default is 20 mSec but, have your fun ?. Look at "TimerDiff($aDataArray[$eWMCDIPC_TimerInit])" for hints.
;~  WMCDIPC_AdlibFunc("") ; process the messages in the loop. It does respond faster than Adlib when using GUIOnEventMode=0 ( as in this example )
;~  WMCDIPC_AdlibFunc(My_IPC_Func) ; process the messages in this function ; don't set as string, must be the func name for AdlibRegister() to work.
    WMCDIPC_AdlibRegister(My_IPC_Func, 10) ; this alternate function is the same as the above two func, all in one.
    WMCDIPC_Register() ; register the message handler
    myConsoleWrite('> WMCDIPC_AdlibFunc = ' & (StringLen(FuncName(WMCDIPC_AdlibFunc())) = "" ? '""' : FuncName(WMCDIPC_AdlibFunc()) & '()') & _
            ' and GUIOnEventMode = ' & Opt("GUIOnEventMode") & @CRLF) ; debug ?

    ; This section will load senders ( to call it something ) to make some chatter among them.
    Local $sBase = @AutoItExe
    If Not WinExists("WMCDIPC-example-AsSender") Then
        For $n = 1 To 2
            Sleep(100)
            If Not WMCDIPC_Compiled() Then $sBase = StringLeft(@AutoItExe, StringInStr(@AutoItExe, "\", 0, -1)) & (Mod($n, 2) ? "autoit3_x64.exe" : "autoit3.exe")
;~          ShellExecute($sBase, '"' & @ScriptFullPath & '" /AsSender', "", (Mod($n, 2) ? "" : "RunAs")) ; to test as admin
            ShellExecute($sBase, '"' & @ScriptFullPath & '" /AsSender')
        Next
    EndIf

    If $sActAs = "AsSender" Then
        Sleep(500)
        WMCDIPC_Send($hGui, WinGetHandle("WMCDIPC-example-AsReceiver"), 5, "Yellow there !")
    EndIf

    While 1
;~      If $iSleep Then Sleep($iSleep) ; if "GUIOnEventMode = 1", set $iSleep to maybe 100
;~      If WMCDIPC_MsgData()[0] Then My_IPC_Func() ; must declare WMCDIPC_AdlibFunc("") to run in the loop
;~      If WMCDIPC_Queue() Then My_IPC_Func() ; must declare WMCDIPC_AdlibFunc("") and WMCDIPC_UseQueue(1) to run in the loop
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                myCloseThemAll() ; Tell the other scripts to exit.
                GUIDelete()
                Return
        EndSwitch
    WEnd
EndFunc   ;==>ThaGui

Func My_IPC_Func()
;~  If Not WMCDIPC_Queue() Then Return ; nothing to do if the queue is empty ( when using WMCDIPC_UseQueue(1) )
;~  Local $aDataArray = WMCDIPC_Queue("POP")
    Local $aDataArray = WMCDIPC_MsgData() ; will AdlibUnRegister() and get the message data
    Local Enum $eWMCDIPC_Adlib, $eWMCDIPC_receiver, $eWMCDIPC_iMsg, $eWMCDIPC_sender, $eWMCDIPC_int, $eWMCDIPC_string, _
            $eWMCDIPC_TimerDiff, $eWMCDIPC_TimerInit, $eWMCDIPC_MsgCounter, $eWMCDIPC_CaughtCounter, $eWMCDIPC_UBound
    ; these are repeated here due to Au3Stripper

    ; --------- your code here ------8<---------
    Local $sStr = (@AutoItX64 ? "64bit" : "32bit") & " / " & (IsAdmin() ? "Admin" : "User") & ' level  -  ( UDF ver.: ' & WMCDIPC_Version() & ' )' & @CRLF & @CRLF
    $sStr &= "-   receiver >" & $aDataArray[$eWMCDIPC_receiver] & '<' & @CRLF
    $sStr &= "-       iMsg >0x" & Hex($aDataArray[$eWMCDIPC_iMsg], 4) & '<' & @CRLF
    $sStr &= "-     sender >" & $aDataArray[$eWMCDIPC_sender] & '<' & @CRLF
    $sStr &= "-       data >0x" & Hex($aDataArray[$eWMCDIPC_int], 4) & '< ' & ($aDataArray[$eWMCDIPC_int] = 0xFADE ? "the other script failed to act on this" : (Mod($aDataArray[$eWMCDIPC_int], 5) ? "" : " will return ""sup !""")) & @CRLF
    $sStr &= "-     string >" & $aDataArray[$eWMCDIPC_string] & '<' & @CRLF & @CRLF
    $sStr &= "- MsgHandler >" & $aDataArray[$eWMCDIPC_TimerDiff] & '< time spent receiving' & @CRLF
    $sStr &= "- MsgHandler >" & TimerDiff($aDataArray[$eWMCDIPC_TimerInit]) & '< time it took to get here' & @CRLF
    $sStr &= "- MsgHandler >" & $aDataArray[$eWMCDIPC_MsgCounter] & '< Incoming messages count' & @CRLF        ; These two should
    $sStr &= "- MsgHandler >" & $aDataArray[$eWMCDIPC_CaughtCounter] & '< Messages caught count' & @CRLF ; be the same count.
    $sStr &= @CRLF
    $g_DebugStr &= $sStr
    GUICtrlSetData($idEdit, $g_DebugStr) ; if "not Mod(number,5)" send data back. is just an idea for the example.
    If Not Mod($aDataArray[$eWMCDIPC_int], 5) Then WMCDIPC_send($aDataArray[$eWMCDIPC_receiver], $aDataArray[$eWMCDIPC_sender], 6, "sup !")
    If $aDataArray[$eWMCDIPC_int] = 7 Then Exit ; This line, will save you a click or two and close it for you  =)
    ; --------- your code here ------>8---------

    Sleep(200) ; to create a busy state for the demo and return "busy"
    ;Note: "$aDataArray[$eWMCDIPC_int] = 64222" is returned to the sender when is busy/unable to act on the message received,
    ; hence "64222" ( 0xFADE as in fade away ) is reserved by the UDF.

    WMCDIPC_MsgData(1) ; at the end to allow a next message. Not needed when using WMCDIPC_UseQueue(1)
EndFunc   ;==>My_IPC_Func

Func myConsoleWrite($sStr) ; You'd code something like this for debug. Not really necessary.
    ConsoleWrite($sStr)
    $g_DebugStr &= $sStr
    GUICtrlSetData($idEdit, $g_DebugStr)
EndFunc   ;==>myConsoleWrite

Func myCloseThemAll() ; ..thought it'd be nice to close one and close them all.
    Local $n, $aArray = WinList("WMCDIPC-example")
    For $n = 1 To $aArray[0][0]
        If $aArray[$n][1] = $hGui Then ContinueLoop
        WMCDIPC_Send($hGui, $aArray[$n][1], 7, "Bye bye")
    Next
    Sleep(100) ; if in the loop instead of addlib, it may need a delay
EndFunc   ;==>myCloseThemAll

Func ExitScript()
    myCloseThemAll() ; Tell the other scripts to exit.
    Exit
EndFunc   ;==>ExitScript

 

The UDFish:

Spoiler
#AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7
#include-once ; #include <WMCDIPC.au3> ; https://www.autoitscript.com/forum/topic/212541-wmcdipc-x32x64-useradmin-self-triggering-slow-wm_copydata-ipc/
;~ #include <_GUIRegisterMsgTell.au3> ; https://www.autoitscript.com/forum/topic/213746-_guiregistermsgtell-a-wrapper-for-guiregistermsg/

#Au3Stripper_Off

Global Enum $eWMCDIPC_Adlib, $eWMCDIPC_receiver, $eWMCDIPC_iMsg, $eWMCDIPC_sender, $eWMCDIPC_int, $eWMCDIPC_string, $eWMCDIPC_TimerDiff, $eWMCDIPC_TimerInit, $eWMCDIPC_MsgCounter, $eWMCDIPC_CaughtCounter, $eWMCDIPC_UseQueue, $eWMCDIPC_UBound
Global Const $_g__WMCDIPC_tagCOPYDATA = 'ulong_ptr dwData;dword cbData;ptr lpData'
Global Const $_g__WMCDIPC_WM_COPYDATA = 0x004A, $_g__WMCDIPC_MSGFLT_ALLOW = 1
Global $_g__WMCDIPC_tData, $_g__WMCDIPC_tBuffer, $_g__WMCDIPC_AdlibTime = 20 ; mSec
Global $_g__WMCDIPC_Version = "2.2.0", $_g__WMCDIPC_message[$eWMCDIPC_UBound] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Global $_g__WMCDIPC_AdlibFunc = WMCDIPC_UserAdlibFuncExample ; example of the func that would do whatever you need done
Global $_g__WMCDIPC_hPrintCallback = (WMCDIPC_Compiled() ? "" : ConsoleWrite)

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_Compiled
; Description ...: Checks if the script is currently running as a compiled executable.
; Syntax.........: WMCDIPC_Compiled()
; Return values .: True if compiled to exe, False otherwise.
; ===============================================================================================================================
Func WMCDIPC_Compiled() ; for debug ?. Don't know if is worth it but will not hurt anyone
    Local Static $bIsCompiled = (@AutoItExe = @ScriptFullPath)
    Return $bIsCompiled
EndFunc   ;==>WMCDIPC_Compiled

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_Version
; Description ...: Returns the current version of the WMCDIPC UDF.
; Syntax.........: WMCDIPC_Version()
; Return values .: String containing the version number.
; ===============================================================================================================================
Func WMCDIPC_Version() ; just in case there are future versions
    Return $_g__WMCDIPC_Version
EndFunc   ;==>WMCDIPC_Version

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_UseQueue
; Description ...: Getter/Setter for the IPC queue state index.
; Syntax.........: WMCDIPC_UseQueue([$iVal = Default])
; Parameters ....: $iVal - [optional] If provided, sets the queue state index. If omitted, returns current value.
; Return values .: Returns the current state index from the global message array.
; ===============================================================================================================================
Func WMCDIPC_UseQueue($iVal = Default)
    If Not IsKeyword($iVal) Then
        $_g__WMCDIPC_message[$eWMCDIPC_UseQueue] = Int($iVal)
    EndIf
    Return $_g__WMCDIPC_message[$eWMCDIPC_UseQueue]
EndFunc   ;==>WMCDIPC_UseQueue

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_Queue
; Description ...: Fixed-size Ring Buffer for FIFO IPC messaging. For sporadic, bursty data handling.
; Syntax.........: WMCDIPC_Queue([$vData = Default])
; Parameters ....: $vData - Behavior changes based on the data type:
;                  | Default : Returns the count of unread messages currently in the queue.
;                  | "POP"   : Retrieves and removes the oldest array payload from the queue.
;                  | Array   : Enqueues the provided array into the buffer.
; Return values .: Success - Returns count (Default), Status 1 (Enqueue), or the popped Array (POP).
;                  Failure - Returns "" or 0 and sets @error:
;                  | 1 - Queue Full (Max capacity of 100 reached).
;                  | 2 - Queue Empty (Tried to Pop, but no messages exist).
;                  | 3 - Invalid parameter (Type not supported).
; ===============================================================================================================================
Func WMCDIPC_Queue($vData = Default)
    Local Const $iMAX = 100
    Local Static $aQueue[$iMAX]
    Local Static $iHead = 0, $iTail = 0, $iCount = 0, $aNothing[$eWMCDIPC_UBound] = [0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0]

    If $vData = Default Then Return $iCount

    If IsArray($vData) Then ; ENQUEUE (Push)
        If $iCount >= $iMAX Then Return SetError(1, 0, 1)
        $aQueue[$iTail] = $vData
        $iTail = Mod($iTail + 1, $iMAX)
        $iCount += 1
        Return 0
    EndIf

    If $vData = "POP" Then ; DEQUEUE (Pop)
        If $iCount = 0 Then Return SetError(2, 0, $aNothing)
        Local $vResult = $aQueue[$iHead]
        $aQueue[$iHead] = ""
        $iHead = Mod($iHead + 1, $iMAX)
        $iCount -= 1
        Return $vResult
    EndIf

    Return SetError(3, 0, 0) ; oops
EndFunc   ;==>WMCDIPC_Queue

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_PrintCallback
; Description ...: Sets or unsets the function used for internal debug printing.
; Syntax.........: WMCDIPC_PrintCallback([$hPrintCallback = Null])
; Parameters ....: $hPrintCallback - [optional] "" to disable, Default for ConsoleWrite, or a valid function name.
; Return values .: Success - Returns the assigned callback function.
;                  Failure - Sets @error to 1 if the provided callback is not a valid function.
; ===============================================================================================================================
Func WMCDIPC_PrintCallback($hPrintCallback = Null)
    Switch $hPrintCallback
        Case "" ; disable it
            $_g__WMCDIPC_hPrintCallback = ""
        Case Default ; ConsoleWrite
            $_g__WMCDIPC_hPrintCallback = ConsoleWrite
        Case Null
        Case Else ; your own "ConsoleWrite" function
            If IsFunc($hPrintCallback) Then
                $_g__WMCDIPC_hPrintCallback = $hPrintCallback
            Else
                __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_PrintCallback: the func was not found !!!" & @CRLF)
                SetError(1)
            EndIf
    EndSwitch
    Return $_g__WMCDIPC_hPrintCallback
EndFunc   ;==>WMCDIPC_PrintCallback

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_MsgData
; Description ...: Manages the internal Adlib state and retrieves the current message data array.
; Syntax.........: WMCDIPC_MsgData([$iDone = 0])
; Parameters ....: $iDone - [optional] Set to 1 to signal that message processing is complete. Defaults to 0.
; Return values .: Returns the $_g__WMCDIPC_message array containing message details.
; ===============================================================================================================================
Func WMCDIPC_MsgData($iDone = 0) ; data back and forth via this function
    Local $iFuncsCurrRegistered = -1
    If $iDone Then
        $_g__WMCDIPC_message[$eWMCDIPC_Adlib] = 0
    Else
        $iFuncsCurrRegistered = ($_g__WMCDIPC_AdlibFunc = "" ? -2 : AdlibUnRegister(FuncName($_g__WMCDIPC_AdlibFunc)))
    EndIf
    Return SetError(0, $iFuncsCurrRegistered, $_g__WMCDIPC_message)
EndFunc   ;==>WMCDIPC_MsgData

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_Register
; Description ...: Registers the WM_COPYDATA message handler.
; Syntax.........: WMCDIPC_Register()
; Return values .: Success - Returns the result of _GUIRegisterMsgTell.
;                  Failure - Sets @error if registration failed. @extended contains warnings for GUIOnEventMode.
; ===============================================================================================================================
Func WMCDIPC_Register() ; register it to receive messages
    Local $iWarning = 0
    If Opt("GUIOnEventMode") And Not IsFunc($_g__WMCDIPC_AdlibFunc) Then
        __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_Register: GUIOnEventMode without a func !!!" & @CRLF)
        $iWarning = 1
    ElseIf Opt("GUIOnEventMode") And $_g__WMCDIPC_AdlibFunc = WMCDIPC_UserAdlibFuncExample Then
        __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_Register: GUIOnEventMode without a user func !!!" & @CRLF)
        $iWarning = 2
    EndIf
    Local $iRet = GUIRegisterMsg($_g__WMCDIPC_WM_COPYDATA, WMCDIPC_MsgHandler) ; use this to exclude the _GUIRegisterMsgTell UDF
;~  Local $iRet = _GUIRegisterMsgTell($_g__WMCDIPC_WM_COPYDATA, WMCDIPC_MsgHandler)
    Return SetError(Not $iRet, $iWarning, $iRet)
EndFunc   ;==>WMCDIPC_Register

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_AdlibFunc
; Description ...: Sets or retrieves the specific function to be called via Adlib when a message is received.
; Syntax.........: WMCDIPC_AdlibFunc([$function = Default])
; Parameters ....: $function - [optional] A valid user function pointer, or "" to disable.
; Return values .: Returns the currently assigned Adlib function.
; ===============================================================================================================================
Func WMCDIPC_AdlibFunc($function = Default) ; function to handle the message received
    If $function <> Default Then
        If StringLen($function) Then
            __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_AdlibFunc: FunName as string !!!" & @CRLF)
            SetError(3)
        EndIf
        If IsFunc($function) = 1 Then
            $_g__WMCDIPC_AdlibFunc = $function
        ElseIf $function = "" Then
            $_g__WMCDIPC_AdlibFunc = ""
            If Opt("GUIOnEventMode") Then
                __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_AdlibFunc: GUIOnEventMode=1 !!!" & @CRLF)
                SetError(2) ; flag that it will fail ? v2.1.1 ?
            EndIf
        Else
            SetError(1)
        EndIf
    EndIf
    Return $_g__WMCDIPC_AdlibFunc
EndFunc   ;==>WMCDIPC_AdlibFunc

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_AdlibTime
; Description ...: Sets or retrieves the Adlib trigger time delay.
; Syntax.........: WMCDIPC_AdlibTime([$iTimeInMSec = Default])
; Parameters ....: $iTimeInMSec - [optional] Time in milliseconds. Default is 20mSec.
; Return values .: Returns the currently assigned Adlib time.
; ===============================================================================================================================
Func WMCDIPC_AdlibTime($iTimeInMSec = Default) ; AdlibRegister's, time to trigger. 20 mSec worked fine on my PC.
    ; Note: the time parameter should be used carefully to avoid CPU load. ( from the help file )
    If Int($iTimeInMSec) > 0 Then
        $_g__WMCDIPC_AdlibTime = Int($iTimeInMSec)
    ElseIf Int($iTimeInMSec) < 1 Then
        __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_AdlibTime: time needs a positive integer of X mSec !!!" & @CRLF)
        Return SetError(1)
    EndIf
    Return $_g__WMCDIPC_AdlibTime
EndFunc   ;==>WMCDIPC_AdlibTime

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_AdlibRegister
; Description ...: Helper function to set both the Adlib function and its trigger time simultaneously.
; Syntax.........: WMCDIPC_AdlibRegister([$function = Default[, $iTimeInMSec = Default]])
; Parameters ....: $function     - [optional] User function pointer.
;                  $iTimeInMSec - [optional] Delay in milliseconds.
; Return values .: Returns the assigned function. Sets @error bitmask (1 = Func Error, 2 = Time Error).
; ===============================================================================================================================
Func WMCDIPC_AdlibRegister($function = Default, $iTimeInMSec = Default)
    Local $newFunc, $newTime, $iError = 0
    If $function <> Default Then
        $newFunc = WMCDIPC_AdlibFunc($function)
        If @error Then $iError += 1
    EndIf
    If $iTimeInMSec <> Default Then
        $newTime = WMCDIPC_AdlibTime($iTimeInMSec)
        If @error Then $iError += 2
    EndIf
    Return SetError($iError, $newTime, $newFunc)
EndFunc   ;==>WMCDIPC_AdlibRegister

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_MSGFLT_AllAllowed
; Description ...: Changes the window message filter to allow WM_COPYDATA, bypassing UIPI limitations for elevated scripts.
; Syntax.........: WMCDIPC_MSGFLT_AllAllowed()
; Return values .: None. Sets @error to 1 if it fails to change the filter.
; ===============================================================================================================================
Func WMCDIPC_MSGFLT_AllAllowed() ; to have user and admin level interaction, if that is what you need
    If IsAdmin() And Not __WMCDIPC_WinAPI_ChangeWindowMessageFilterEx(0, $_g__WMCDIPC_WM_COPYDATA, $_g__WMCDIPC_MSGFLT_ALLOW) Then Return SetError(1)
EndFunc   ;==>WMCDIPC_MSGFLT_AllAllowed

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_Send
; Description ...: Sends an inter-process communication message to another window using WM_COPYDATA.
; Syntax.........: WMCDIPC_Send($hSender, $hReciever, $iMsg, $sMsg)
; Parameters ....: $hSender   - Handle of the sending GUI.
;                  $hReciever - Handle of the receiving GUI.
;                  $iMsg      - Integer data to send.
;                  $sMsg      - String data to send.
; Return values .: Success - Returns True.
;                  Failure - Returns False and sets @error (1 = Invalid Receiver, 2 = Empty Message).
; ===============================================================================================================================
Func WMCDIPC_Send($hSender, $hReciever, $iMsg, $sMsg)
    If Not IsHWnd($hReciever) Then Return SetError(1, 0, False)
    If StringStripWS($sMsg, 8) = '' Then Return SetError(2, 0, False)
    If Not IsInt($iMsg) Then $iMsg = 0
    $_g__WMCDIPC_tBuffer = DllStructCreate('wchar cdata[' & StringLen($sMsg) + 1 & ']')
    DllStructSetData($_g__WMCDIPC_tBuffer, 'cdata', $sMsg)
    $_g__WMCDIPC_tData = DllStructCreate($_g__WMCDIPC_tagCOPYDATA)
    DllStructSetData($_g__WMCDIPC_tData, 'dwData', $iMsg)
    DllStructSetData($_g__WMCDIPC_tData, 'cbData', DllStructGetSize($_g__WMCDIPC_tBuffer))
    DllStructSetData($_g__WMCDIPC_tData, 'lpData', DllStructGetPtr($_g__WMCDIPC_tBuffer))
    __WMCDIPC_SendMessage($hReciever, $_g__WMCDIPC_WM_COPYDATA, $hSender, DllStructGetPtr($_g__WMCDIPC_tData))
    Return Not @error
EndFunc   ;==>WMCDIPC_Send

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_MsgHandler
; Description ...: Internal message handler registered to catch WM_COPYDATA events.
; Syntax.........: WMCDIPC_MsgHandler($hWnd, $iMsg, $wParam, $lParam)
; Parameters ....: $hWnd   - The window handle.
;                  $iMsg   - The message ID.
;                  $wParam - The WParam (Sender's GUI handle).
;                  $lParam - The LParam (Pointer to COPYDATASTRUCT).
; Return values .: Returns 'GUI_RUNDEFMSG' to allow normal message processing to continue.
; ===============================================================================================================================
Func WMCDIPC_MsgHandler($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg
    Local $iBusy = 0, $hTimer = TimerInit()
    $_g__WMCDIPC_message[$eWMCDIPC_MsgCounter] += 1
    $_g__WMCDIPC_tData = DllStructCreate($_g__WMCDIPC_tagCOPYDATA, $lParam)
    $_g__WMCDIPC_tBuffer = DllStructCreate('wchar cdata[' & DllStructGetData($_g__WMCDIPC_tData, 'cbData') / 2 & ']', DllStructGetData($_g__WMCDIPC_tData, 'lpData'))
    Local $iInt = DllStructGetData($_g__WMCDIPC_tData, 'dwData'), $sStr = DllStructGetData($_g__WMCDIPC_tBuffer, 'cdata')
    If $_g__WMCDIPC_message[$eWMCDIPC_Adlib] And Not $_g__WMCDIPC_message[$eWMCDIPC_UseQueue] Then ; return to sender a busy signal and time spent since last message caught
        $iBusy = 1
        WMCDIPC_Send($hWnd, $wParam, 0xFADE, Round(TimerDiff($_g__WMCDIPC_message[$eWMCDIPC_TimerInit])) & " (mSec Busy)")
    Else
        $_g__WMCDIPC_message[$eWMCDIPC_receiver] = $hWnd ; Self GUI
        $_g__WMCDIPC_message[$eWMCDIPC_iMsg] = $iMsg
        $_g__WMCDIPC_message[$eWMCDIPC_sender] = $wParam ; Sender's GUI
        $_g__WMCDIPC_message[$eWMCDIPC_int] = $iInt
        $_g__WMCDIPC_message[$eWMCDIPC_string] = $sStr
        $_g__WMCDIPC_message[$eWMCDIPC_TimerDiff] = TimerDiff($hTimer)
        $_g__WMCDIPC_message[$eWMCDIPC_TimerInit] = $hTimer
        $_g__WMCDIPC_message[$eWMCDIPC_CaughtCounter] += 1
        $_g__WMCDIPC_message[$eWMCDIPC_Adlib] = ($_g__WMCDIPC_AdlibFunc = "" ? 1 : AdlibRegister(FuncName($_g__WMCDIPC_AdlibFunc), $_g__WMCDIPC_AdlibTime))
        If $_g__WMCDIPC_message[$eWMCDIPC_UseQueue] Then WMCDIPC_Queue($_g__WMCDIPC_message)
        ; Note: The adlib function should be kept simple as during this time the main script is paused. ( from the help file )
    EndIf
    __WMCDIPC_Print('"WMCDIPC.au3" (' & @ScriptLineNumber & ") : WMCDIPC_MsgHandler (" & $_g__WMCDIPC_message[$eWMCDIPC_CaughtCounter] & "/" & _
            $_g__WMCDIPC_message[$eWMCDIPC_MsgCounter] & "):  Timer: " & Round(TimerDiff($_g__WMCDIPC_message[$eWMCDIPC_TimerInit])) & _
            " mSec ( Busy: " & $iBusy & " )" & @TAB & "UseQueue: " & $_g__WMCDIPC_message[$eWMCDIPC_UseQueue] & @TAB & '0x' & Hex($iInt, 4) & @TAB & $sStr & @CRLF)
    Return 'GUI_RUNDEFMSG'
EndFunc   ;==>WMCDIPC_MsgHandler

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __WMCDIPC_Print
; Description ...: Triggers the configured debug print callback.
; ===============================================================================================================================
Func __WMCDIPC_Print($sText)
    If $_g__WMCDIPC_hPrintCallback = "" Then Return
    If IsFunc($_g__WMCDIPC_hPrintCallback) Then $_g__WMCDIPC_hPrintCallback($sText)
EndFunc   ;==>__WMCDIPC_Print


#Region from UDFs

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __WMCDIPC_SendMessage
; Description ...: Internal wrapper for the user32.dll SendMessage API.
; ===============================================================================================================================
Func __WMCDIPC_SendMessage($hWnd, $iMsg, $wParam = 0, $lParam = 0, $iReturn = 0, $wParamType = "wparam", $lParamType = "lparam", $sReturnType = "lresult") ; same as SendMessage()
    Local $aCall = DllCall("user32.dll", $sReturnType, "SendMessageW", "hwnd", $hWnd, "uint", $iMsg, $wParamType, $wParam, $lParamType, $lParam)
    If @error Then Return SetError(@error + 10, @extended, "")
    If $iReturn >= 0 And $iReturn <= 4 Then Return $aCall[$iReturn]
    Return $aCall
EndFunc   ;==>__WMCDIPC_SendMessage

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __WMCDIPC_WinAPI_ChangeWindowMessageFilterEx
; Description ...: Internal wrapper for the user32.dll ChangeWindowMessageFilter(Ex) API.
; ===============================================================================================================================
Func __WMCDIPC_WinAPI_ChangeWindowMessageFilterEx($hWnd, $iMsg, $iAction) ; same as _WinAPI_ChangeWindowMessageFilterEx()
    Local $tCFS, $aCall
    If $hWnd And (__WMCDIPC_WinAPI_GetVersion() > 6.0) Then
        Local Const $tagCHANGEFILTERSTRUCT = 'dword cbSize; dword ExtStatus'
        $tCFS = DllStructCreate($tagCHANGEFILTERSTRUCT)
        DllStructSetData($tCFS, 1, DllStructGetSize($tCFS))
        $aCall = DllCall('user32.dll', 'bool', 'ChangeWindowMessageFilterEx', 'hwnd', $hWnd, 'uint', $iMsg, 'dword', $iAction, 'struct*', $tCFS)
    Else
        $tCFS = 0
        $aCall = DllCall('user32.dll', 'bool', 'ChangeWindowMessageFilter', 'uint', $iMsg, 'dword', $iAction)
    EndIf
    If @error Or Not $aCall[0] Then Return SetError(@error + 10, @extended, 0)
    Return SetExtended(DllStructGetData($tCFS, 2), 1)
EndFunc   ;==>__WMCDIPC_WinAPI_ChangeWindowMessageFilterEx

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: __WMCDIPC_WinAPI_GetVersion
; Description ...: Internal wrapper to retrieve the OS version.
; ===============================================================================================================================
Func __WMCDIPC_WinAPI_GetVersion() ; same as _WinAPI_GetVersion()
    Local Const $tagOSVERSIONINFO = "struct;dword OSVersionInfoSize;dword MajorVersion;dword MinorVersion;dword BuildNumber;dword PlatformId;wchar CSDVersion[128];endstruct"
    Local $NUMBER_DOUBLE = 3, $tOSVI = DllStructCreate($tagOSVERSIONINFO)
    DllStructSetData($tOSVI, 1, DllStructGetSize($tOSVI))
    Local $aCall = DllCall('kernel32.dll', 'bool', 'GetVersionExW', 'struct*', $tOSVI)
    If @error Or Not $aCall[0] Then Return SetError(@error, @extended, 0)
    Return Number(DllStructGetData($tOSVI, 2) & "." & DllStructGetData($tOSVI, 3), $NUMBER_DOUBLE)
EndFunc   ;==>__WMCDIPC_WinAPI_GetVersion

#EndRegion from UDFs

#Region example ( and default handler )

; #FUNCTION# ====================================================================================================================
; Name...........: WMCDIPC_UserAdlibFuncExample
; Description ...: Example handler function that demonstrates parsing and using the received IPC data.
; Syntax.........: WMCDIPC_UserAdlibFuncExample()
; Return values .: None. Outputs debug information to the console.
; ===============================================================================================================================
Func WMCDIPC_UserAdlibFuncExample() ; example of what to do with the data received
    Local $aDataArray = WMCDIPC_MsgData() ; "WMCDIPC_MsgData()" will AdlibUnRegister() and get the message data

    ; --------- your code here ------8<---------
    Local $sStr = (@AutoItX64 ? "64bit" : "32bit") & " / " & (IsAdmin() ? "Admin" : "User") & ' ( UDF ver.: ' & WMCDIPC_Version() & ' )' & @CRLF & @CRLF
    $sStr &= "-   receiver >" & $aDataArray[$eWMCDIPC_receiver] & '<' & @CRLF
    $sStr &= "-       iMsg >0x" & Hex($aDataArray[$eWMCDIPC_iMsg], 4) & '<' & @CRLF
    $sStr &= "-     sender >" & $aDataArray[$eWMCDIPC_sender] & '<' & @CRLF
    $sStr &= "-       data >" & $aDataArray[$eWMCDIPC_int] & '< ' & Mod($aDataArray[$eWMCDIPC_int], 2) & @CRLF
    $sStr &= "-     string >" & $aDataArray[$eWMCDIPC_string] & '<' & @CRLF & @CRLF
    $sStr &= "- MsgHandler >" & $aDataArray[$eWMCDIPC_TimerDiff] & '< time spent receiving' & @CRLF
    $sStr &= "- MsgHandler >" & TimerDiff($aDataArray[$eWMCDIPC_TimerInit]) & '< time it took to get here' & @CRLF
    $sStr &= "- MsgHandler >" & $aDataArray[$eWMCDIPC_MsgCounter] & '< Messages in count' & @CRLF
    $sStr &= "- MsgHandler >" & $aDataArray[$eWMCDIPC_CaughtCounter] & '< Messages caught count' & @CRLF
    $sStr &= @CRLF
    If Not WMCDIPC_Compiled() Then ConsoleWrite((WMCDIPC_Compiled() ? '@@ Debug' : '"WMCDIPC.au3"') & ' (' & @ScriptLineNumber & ') : ' & $sStr)
    ; --------- your code here ------>8---------

    WMCDIPC_MsgData(1) ; "WMCDIPC_MsgData(1)" at the end to allow a next message
EndFunc   ;==>WMCDIPC_UserAdlibFuncExample

#EndRegion example ( and default handler )

#Au3Stripper_On

 

The back story
I put together "Win 11 - My own border color" and looking at the logs I've found that I had a handle leak. The leak got fixed but I wanted ( out of OCD ? ) to have the script report on it's status. Ok, easy enough, I'll IPC the request. But I don't wanna have it in the main loop, constantly asking "are we there yet ?, are we there yet ?,  ..." .
I could not find a copy'n'paste UDF, that did that I wanted to have, so I had to code it 🥲
Now you can copy'n'paste it ;)

How does it work:
When WM_COPYDATA gets a message, it puts the data in a global array that gets accessed by any user function triggered by AdlibRegister() after X mSec.
You can set the AdlibRegister() time with WMCDIPC_AdlibTime(). Without a parameter it will return the current value.
Can set the user function with WMCDIPC_AdlibFunc()**. Without a parameter it will return the current value. The UDF has notes that will hint it's use.

Hope you find it useful.

Edit: This is v2.0 ( yey ! )
** there now is a WMCDIPC_AdlibRegister(func,time) that can do that too.
This version allows running all Au3Stripper arguments, so that's good.
Added WMCDIPC_PrintCallback() to handle the one line of debug in the UDF.
Also added a return in case the script is already busy running in Adlib, therefore unable to process the IPC request right there and then. The int return is "0xFADE" and the string has how many mSec it's been busy. Giving the user a chance to know it "FADEd away" and formulate a resend or what not.
Since is a new version, added WMCDIPC_Version(), just in case of a future one.
But all in all, I think that this UDF is complete and needs no other functionality for the scenario it would be used at.

Edit: This is v2.1 ( wow ! )
Added in the loop. Why ?. Well, the project that I wrote this for is GUIOnEventMode=1. Having the "are we there yet ?" is much slower given that a long Sleep() is common in that option.
But the example I posted is GUIOnEventMode=0. It does use GUIGetMsg() to handle messages and CPU usage so, why not have the trigger right there. So that's what I added in this version. And obviously responds much faster than scheduling an Adlib.

Edit: ..and this is v2.1.1 ( child proofing ? )
I was thinking that it would be nice to tell, the new to this UDF, that a set of choices would not work ( not as extreme as in MsgBox_Extn() but, something ).
Also to run ControlViewer just in case of an "oops".

Edit: v2.2.0 ( what !? )
Needed to queue the messages. So I added a function to store the messages in a FIFO queue.
That way I can get a burst of data and work on it in the the receiving process without delaying the sender.

Spoiler

image.thumb.png.3f87b54499533afedbc39a52f0cb6f68.png

where you can select a script, press DEL, and process close it, if something went wrong. ( and I use it a lot :lol: )
So there: for all those that should be sleeping at 3 AM but want to code and screw up, because the brain is sleeping regardless of will.

Edited by argumentum
v2.2.0

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Advert
Posted

Thanks @argumentum , I am quite sure I will have a deeper look into this 👌 .

Best regards
Sven

==> AutoIt related: 🔗 Organization AutoIt Community🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet🔗 autoit-webdriver-boilerplate

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Posted (edited)

hmmm, .. v2.1.1 ( child roof )
( ..am the child most of the times when I screw up something obvious )


..when I do something wrong in SQLite it tells me what and where.
Most users don't add error checking so having a console warning is quite helpful.

Edited by argumentum

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

  • 11 months later...
  • Solution
Posted (edited)

In v2.2.0 now the messages can be queued to... in my case ( and reason for the addition ), get a burst of data and deal with that data without slowing down the sender.
The default is up to 100 messages in queue.
If you need to store more, rethink the IPC ?

Edited by argumentum

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...