Jump to content

Tweaking SHChangeNotifyRegister fails for me...


KaFu
 Share

Recommended Posts

HiHo Forum ;),

I was playing around with the _WinAPI_ShellChangeNotifyRegister() function from (required for the example) and hit a wall, would be great if this one could be answered.

Looking around the net, I found a definition on how to register the notifications for all filesystem objects and not just specified directories (see my adjusted _WinAPI_ShellChangeNotifyRegisterEx() below).

This works fine (commented part in the WM_SHELLCHANGENOTIFY function below), but I wanted it to take a step further. I've found this article:

http://koti.mbnet.fi/vaultec/files/miscellaneous/undocw95/notify.html

[font="Times New Roman"][size="3"]This all seems simple enough, but on NT there is a bit of a
problem. You can't just send a message to a window in a
different process, and still expect the structure pointer
contained in that message to be accessible. To get around
this, NT actually dumps all the data into a memory-mapped
file, and then sends the memory-map handle and a process
id as the parameters to the message. 
      In order to remain compatible with Windows 95, some-
body obviously has to extract the information from that
memory-map on the other side. The way this works is that
NT creates a hidden 'proxy' window whenever you call
SHChangeNotifyRegister. It is the proxy window that
receives the notification message containing the memory-
map. Its message handler then extracts all the information,
before passing on the expected data structure to your win-
dow.
      Of course, this is not exactly efficient, which is where
the [size="-1"]SHCNF_NO_PROXY[/size] flag comes in. By specifying that
flag, you are telling NT not to create the proxy window, so
the memory-map handle gets passed directly to your win-
dow. It's then up to you to extract the relevant information
from the memory-map. Fortunately there are two functions
that do all the work for you:[/size][/font]

The SHCNF_NO_PROXY flag mentioned equals the SHCNRF_NEWDELIVERY flag in the MSDN documentation.... and there's my problem. According to the article the notification is sent via a memory mapped file, which can be accessed via a call to SHChangeNotification_Lock(), and this part currently fails for me, I don't get the pointer to the PIDL.

#region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Res_requestedExecutionLevel=asInvoker
#endregion ;**** Directives created by AutoIt3Wrapper_GUI ****

#include <APIConstants.au3>
#include <WinAPIEx.au3>
#include <GUIConstantsEx.au3>

HotKeySet("{ESC}", "_Exit")

Global $hWnd, $iMsg, $ID

; http://koti.mbnet.fi/vaultec/files/miscellaneous/undocw95/notify.html

$hWnd = GUICreate('')
$iMsg = _WinAPI_RegisterWindowMessage('SHELLCHANGENOTIFY')
GUIRegisterMsg($iMsg, 'WM_SHELLCHANGENOTIFY')
$ID = _WinAPI_ShellChangeNotifyRegisterEx($hWnd, $iMsg, $SHCNE_ALLEVENTS, BitOR($SHCNRF_INTERRUPTLEVEL, $SHCNRF_SHELLLEVEL, $SHCNRF_RECURSIVEINTERRUPT, $SHCNRF_NEWDELIVERY), "", 1)
If @error Then
    MsgBox(16, 'Error', 'Window does not registered.')
    Exit
EndIf
OnAutoItExitRegister('OnAutoItExit_ShellChangeNotifyRegister')

While 1
    Sleep(1000)
WEnd

Func _Exit()
    Exit
EndFunc   ;==>_Exit

Func WM_SHELLCHANGENOTIFY($hWnd, $iMsg, $wParam, $lParam)

    #cs
        Local $Path = _WinAPI_ShellGetPathFromIDList(DllStructGetData(DllStructCreate('dword Item1; dword Item2', $wParam), 'Item1'))
        If $Path Then
        ConsoleWrite(TimerInit() & @TAB & 'Event: 0x' & Hex($lParam) & ' | Path: ' & $Path & @CR)
        Else
        ConsoleWrite(TimerInit() & @TAB & 'Event: 0x' & Hex($lParam) & @CR)
        EndIf
    #ce

    ConsoleWrite("+ " & $hWnd & @TAB & $iMsg & @TAB & $wParam & @TAB & $lParam & @CRLF)

    Local $iRes = DllCall("shell32.dll", "handle", "SHChangeNotification_Lock", "handle", $wParam, "dword", $lParam, "ptr", "", "ulong", "")
    Local $PIDLIST_ABSOLUTE = $iRes[3]
    Local $plEvent = $iRes[4]
    ConsoleWrite("! 1: " & $iRes[0] & @TAB & "2: " & $PIDLIST_ABSOLUTE & @TAB & "3: " & $plEvent & @CRLF)

    Local $Path = _WinAPI_ShellGetPathFromIDList(DllStructGetData(DllStructCreate('dword Item1; dword Item2', $PIDLIST_ABSOLUTE), 'Item1'))
    ; Local $Path = _WinAPI_ShellGetPathFromIDList($PIDLIST_ABSOLUTE)
    ConsoleWrite("- Path: " & $Path & @CRLF)

    $iRes = DllCall("shell32.dll", "int", "SHChangeNotification_Unlock", "HANDLE", $iRes[0])
    ConsoleWrite($iRes[0] & @CRLF & "============" & @CRLF & @CRLF)

EndFunc   ;==>WM_SHELLCHANGENOTIFY

Func OnAutoItExit_ShellChangeNotifyRegister()
    _WinAPI_ShellChangeNotifyDeregister($ID)
EndFunc   ;==>OnAutoItExit_ShellChangeNotifyRegister


Func _WinAPI_ShellChangeNotifyRegisterEx($hWnd, $iMsg, $iEvents, $iSources, $aPaths, $fRecursive = 0)

    Local $tEntry, $Path = $aPaths, $Struct = '', $iCount

    If $aPaths Then
        If IsArray($aPaths) Then
            If UBound($aPaths, 2) Then
                Return SetError(1, 0, 0)
            EndIf
        Else
            Dim $aPaths[1] = [$Path]
        EndIf
        For $i = 0 To UBound($aPaths) - 1
            If Not _WinAPI_PathIsDirectory($aPaths[$i]) Then
                Return SetError(1, 0, 0)
            EndIf
        Next
        For $i = 0 To UBound($aPaths) - 1
            $Struct &= 'ptr;int;'
        Next
        $tEntry = DllStructCreate($Struct)
        For $i = 0 To UBound($aPaths) - 1
            $aPaths[$i] = _WinAPI_ShellILCreateFromPath(_WinAPI_PathSearchAndQualify($aPaths[$i]))
            DllStructSetData($tEntry, 2 * $i + 1, $aPaths[$i])
            DllStructSetData($tEntry, 2 * $i + 2, $fRecursive)
        Next

        Local $Ret = DllCall('shell32.dll', 'ulong', 'SHChangeNotifyRegister', 'hwnd', $hWnd, 'int', $iSources, 'long', $iEvents, 'uint', $iMsg, 'int', UBound($aPaths), 'ptr', DllStructGetPtr($tEntry))

    Else

        $tEntry = DllStructCreate('ptr;int;')
        DllStructSetData($tEntry, 1, "")
        DllStructSetData($tEntry, 2, 1)
        Local $Ret = DllCall('shell32.dll', 'ulong', 'SHChangeNotifyRegister', 'hwnd', $hWnd, 'int', $iSources, 'long', $iEvents, 'uint', $iMsg, 'int', 1, 'ptr', DllStructGetPtr($tEntry))

    EndIf

    If (Not @error) And ($Ret[0]) Then
        $Ret = $Ret[0]
    Else
        $Ret = 0
    EndIf
    For $i = 0 To UBound($aPaths) - 1
        _WinAPI_CoTaskMemFree($aPaths[$i])
    Next
    Return SetError(Number($Ret = 0), 0, $Ret)
EndFunc   ;==>_WinAPI_ShellChangeNotifyRegisterEx
Link to comment
Share on other sites

Works now for me. And as mentioned in the MSDN documentation for SHChangeNotifyRegister, "SHCNRF_NewDelivery: We recommend this flag because it provides a more robust delivery method. All clients should specify this flag."

#region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Res_requestedExecutionLevel=asInvoker
#endregion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <APIConstants.au3>
#include <WinAPIEx.au3>
#include <GUIConstantsEx.au3>
HotKeySet("{ESC}", "_Exit")
Global $hWnd, $iMsg, $ID
; [url="http://koti.mbnet.fi/vaultec/files/miscellaneous/undocw95/notify.html"]http://koti.mbnet.fi/vaultec/files/miscellaneous/undocw95/notify.html[/url]
$hWnd = GUICreate('')
$iMsg = _WinAPI_RegisterWindowMessage('SHELLCHANGENOTIFY')
GUIRegisterMsg($iMsg, 'WM_SHELLCHANGENOTIFY')
$ID = _WinAPI_ShellChangeNotifyRegisterEx($hWnd, $iMsg, $SHCNE_ALLEVENTS, BitOR($SHCNRF_INTERRUPTLEVEL, $SHCNRF_SHELLLEVEL, $SHCNRF_RECURSIVEINTERRUPT, $SHCNRF_NEWDELIVERY), "", 1)
If @error Then
 MsgBox(16, 'Error', 'Window does not registered.')
 Exit
EndIf
OnAutoItExitRegister('OnAutoItExit_ShellChangeNotifyRegister')
While 1
 Sleep(1000)
WEnd
Func _Exit()
 Exit
EndFunc   ;==>_Exit
Func WM_SHELLCHANGENOTIFY($hWnd, $iMsg, $wParam, $lParam)
 #cs
  Local $Path = _WinAPI_ShellGetPathFromIDList(DllStructGetData(DllStructCreate('dword Item1; dword Item2', $wParam), 'Item1'))
  If $Path Then
  ConsoleWrite(TimerInit() & @TAB & 'Event: 0x' & Hex($lParam) & ' | Path: ' & $Path & @CR)
  Else
  ConsoleWrite(TimerInit() & @TAB & 'Event: 0x' & Hex($lParam) & @CR)
  EndIf
 #ce
 ; ConsoleWrite("+ " & $hWnd & @TAB & $iMsg & @TAB & $wParam & @TAB & $lParam & @CRLF)
 ;Local $PIDLIST_ABSOLUTE, $plEvent
 Local $iRes = DllCall("shell32.dll", "hwnd", "SHChangeNotification_Lock", "wparam", $wParam, "lparam", $lParam, "ptr*", "", "ulong*", "")
 Local $PIDLIST_ABSOLUTE = $iRes[3]
 Local $plEvent = $iRes[4]
 ; ConsoleWrite("! 1: " & $iRes[0] & @TAB & "2: " & $PIDLIST_ABSOLUTE & @TAB & "3: " & $plEvent & @CRLF)
 Local $Path = _WinAPI_ShellGetPathFromIDList(DllStructGetData(DllStructCreate('dword Item1; dword Item2', $PIDLIST_ABSOLUTE), 'Item1'))
 ; Local $Path = _WinAPI_ShellGetPathFromIDList($PIDLIST_ABSOLUTE)
 ConsoleWrite("+ Event: 0x" & Hex($plEvent) & @tab & "Path: " & $Path & @CRLF)
 $iRes = DllCall("shell32.dll", "int", "SHChangeNotification_Unlock", "HANDLE", $iRes[0])
 ; ConsoleWrite($iRes[0] & @CRLF & "============" & @CRLF & @CRLF)
EndFunc   ;==>WM_SHELLCHANGENOTIFY
Func OnAutoItExit_ShellChangeNotifyRegister()
 _WinAPI_ShellChangeNotifyDeregister($ID)
EndFunc   ;==>OnAutoItExit_ShellChangeNotifyRegister

Func _WinAPI_ShellChangeNotifyRegisterEx($hWnd, $iMsg, $iEvents, $iSources, $aPaths, $fRecursive = 0)
 Local $tEntry, $Path = $aPaths, $Struct = '', $iCount
 If $aPaths Then
  If IsArray($aPaths) Then
   If UBound($aPaths, 2) Then
    Return SetError(1, 0, 0)
   EndIf
  Else
   Dim $aPaths[1] = [$Path]
  EndIf
  For $i = 0 To UBound($aPaths) - 1
   If Not _WinAPI_PathIsDirectory($aPaths[$i]) Then
    Return SetError(1, 0, 0)
   EndIf
  Next
  For $i = 0 To UBound($aPaths) - 1
   $Struct &= 'ptr;int;'
  Next
  $tEntry = DllStructCreate($Struct)
  For $i = 0 To UBound($aPaths) - 1
   $aPaths[$i] = _WinAPI_ShellILCreateFromPath(_WinAPI_PathSearchAndQualify($aPaths[$i]))
   DllStructSetData($tEntry, 2 * $i + 1, $aPaths[$i])
   DllStructSetData($tEntry, 2 * $i + 2, $fRecursive)
  Next
  Local $Ret = DllCall('shell32.dll', 'ulong', 'SHChangeNotifyRegister', 'hwnd', $hWnd, 'int', $iSources, 'long', $iEvents, 'uint', $iMsg, 'int', UBound($aPaths), 'ptr', DllStructGetPtr($tEntry))
 Else
  $tEntry = DllStructCreate('ptr;int;')
  DllStructSetData($tEntry, 1, "")
  DllStructSetData($tEntry, 2, 1)
  Local $Ret = DllCall('shell32.dll', 'ulong', 'SHChangeNotifyRegister', 'hwnd', $hWnd, 'int', $iSources, 'long', $iEvents, 'uint', $iMsg, 'int', 1, 'ptr', DllStructGetPtr($tEntry))
 EndIf
 If (Not @error) And ($Ret[0]) Then
  $Ret = $Ret[0]
 Else
  $Ret = 0
 EndIf
 For $i = 0 To UBound($aPaths) - 1
  _WinAPI_CoTaskMemFree($aPaths[$i])
 Next
 Return SetError(Number($Ret = 0), 0, $Ret)
EndFunc   ;==>_WinAPI_ShellChangeNotifyRegisterEx
Link to comment
Share on other sites

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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...