Help with IFileOperationProgressSink RegisterCallback function

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
iPhilip
Posts: 822
Joined: 02 Oct 2013, 12:21

Help with IFileOperationProgressSink RegisterCallback function

14 Nov 2019, 17:15

Hi Folks,

I have been experimenting with the IFileOperation interface commands (link) and have run into a problem with the IFileOperationProgressSink notification system (link). The script below works if the ProgressSink is turned off (pProgressSink := 0). When it's turned on, the script crashes.

I am confused about why the RegisterCallback function doesn't appear to receive any parameters beyond the first one. Also, if you run it you will see that the function gets called in the following sequence:
  1. AddRef
  2. AddRef
  3. Release
  4. PreMoveItem
At which point the script crashes. I suspect that the problem has to do with the function's return value.

Code: Select all

#NoEnv

FileAppend, , foo.txt
FileCreateDir, test
Item := A_ScriptDir "\foo.txt"
Folder := A_ScriptDir "\test"

VarSetCapacity(ProgressSink, 20 * A_PtrSize, 0)
NumPut(&ProgressSink + A_PtrSize, ProgressSink, 0, "Ptr")
Loop, 19
   NumPut(RegisterCallback("Func", , , A_Index), ProgressSink, A_Index * A_PtrSize, "Ptr")
pProgressSink := &ProgressSink
; pProgressSink := 0  ; The MoveItem command works if this line in not commented

FileOperation := ComObjCreate("{3ad05575-8857-4850-9277-11b85bdb8e09}"   ; CLSID
                            , "{947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8}")  ; IID
vtbl := NumGet(FileOperation + 0, 0, "Ptr")
MoveItem := NumGet(vtbl + 0, 14 * A_PtrSize, "Ptr")
PerformOperations := NumGet(vtbl + 0, 21 * A_PtrSize, "Ptr")

r := DllCall(MoveItem, "Ptr", FileOperation
                     , "Ptr", (pItem := GetShellItem(Item))
                     , "Ptr", (pFolder := GetShellItem(Folder))
                     , "Ptr", 0
                     , "Ptr", pProgressSink
                     , "UInt")
MsgBox % r "`n" ErrorLevel "`n" A_LastError
DllCall(PerformOperations, "Ptr", FileOperation, "UInt")
MsgBox % r "`n" ErrorLevel "`n" A_LastError

FileRemoveDir, test, 1

ObjRelease(pItem)
ObjRelease(pFolder)
ObjRelease(FileOperation)
ExitApp

GetShellItem(Item) {
   VarSetCapacity(IID, 16, 0)
   DllCall("Ole32.dll\IIDFromString", "WStr", "{43826d1e-e718-42ee-bc55-a1e261c37bfe}", "Ptr", &IID, "UInt")
   DllCall("Shell32.dll\SHCreateItemFromParsingName", "WStr", Item, "Ptr", 0, "Ptr", &IID, "PtrP", pShellItem, "UInt")
   Return pShellItem
}

Func(this, params*) {
   static SinkMethods := ["QueryInterface","AddRef","Release","StartOperations","FinishOperations","PreRenameItem","PostRenameItem"
                         ,"PreMoveItem","PostMoveItem","PreCopyItem","PostCopyItem","PreDeleteItem","PostDeleteItem","PreNewItem"
                         ,"PostNewItem","UpdateProgress","ResetTimer","PauseTimer","ResumeTimer"]
   static s
   s .= SinkMethods[A_EventInfo] "-" params.MaxIndex() "`n"
   ToolTip % s
}

/*
From C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um\ShObjIdl_core.h

        HRESULT ( STDMETHODCALLTYPE *PreMoveItem )( 
            __RPC__in IFileOperationProgressSink * This,
            /* [in] */ DWORD dwFlags,
            /* [in] */ __RPC__in_opt IShellItem *psiItem,
            /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder,
            /* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszNewName);
*/
I would appreciate any insights you might have about this.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Help with IFileOperationProgressSink RegisterCallback function

14 Nov 2019, 17:32

Code: Select all

VarSetCapacity(ProgressSink, 20 * A_PtrSize, 0)
NumPut(&ProgressSink + A_PtrSize, ProgressSink, 0, "Ptr")
Loop, 19
   NumPut(RegisterCallback("Func", , , A_Index), ProgressSink, A_Index * A_PtrSize, "Ptr")
pProgressSink := &ProgressSink
yeah, u can forget about this part doing anything meaningful

u have to obtain an interface pointer to a IFileOperationProgressSink(queryInterface i suppose), then patch its vtable with the function addresses of ur callbacks
iPhilip
Posts: 822
Joined: 02 Oct 2013, 12:21

Re: Help with IFileOperationProgressSink RegisterCallback function

15 Nov 2019, 02:43

swagfag wrote:
14 Nov 2019, 17:32
yeah, u can forget about this part doing anything meaningful
Thank you. It was an educated guess ... but it did result in notifications. Oh, well. :?
swagfag wrote:
14 Nov 2019, 17:32
u have to obtain an interface pointer to a IFileOperationProgressSink(queryInterface i suppose), then patch its vtable with the function addresses of ur callbacks
Thank you. I gave it a shot with the code below but it's still not working. :(

Code: Select all

#NoEnv

FileAppend, , foo.txt
FileCreateDir, test
Item := A_ScriptDir "\foo.txt"
Folder := A_ScriptDir "\test"

FileOperation := ComObjCreate("{3ad05575-8857-4850-9277-11b85bdb8e09}"   ; CLSID_FileOperation
                            , "{947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8}")  ; IID_IFileOperation
vtbl := NumGet(FileOperation + 0, 0, "Ptr")

QueryInterface    := NumGet(vtbl + 0,  0 * A_PtrSize, "Ptr")
Advise            := NumGet(vtbl + 0,  3 * A_PtrSize, "Ptr")
MoveItem          := NumGet(vtbl + 0, 14 * A_PtrSize, "Ptr")
PerformOperations := NumGet(vtbl + 0, 21 * A_PtrSize, "Ptr")

; Approach #1
; FileOperationProgressSink := ComObjQuery(FileOperation, "{04b0f1a7-9490-44bc-96e1-4296a31252e2}")  ; IID_IFileOperationProgressSink

; Approach #2
r := DllCall(QueryInterface, "Ptr", FileOperation
                           , "Ptr", IID("{04b0f1a7-9490-44bc-96e1-4296a31252e2}")  ; IID_IFileOperationProgressSink
                           , "PtrP", FileOperationProgressSink
                           , "UInt")
MsgBox % Format("0x{:X}", r) "`n" ErrorLevel "`n" A_LastError  ; E_NOINTERFACE - No such interface supported - 0x80004002

vtbl := NumGet(FileOperationProgressSink + 0, 0, "Ptr")
Loop, 19
   NumPut(RegisterCallback("Func", , , A_Index - 1), vtbl, (A_Index - 1) * A_PtrSize, "Ptr")

r := DllCall(Advise, "Ptr", FileOperation
                   , "Ptr", FileOperationProgressSink
                   , "UIntP", Cookie
                   , "UInt")
MsgBox % Cookie "`n`n" Format("0x{:X}", r) "`n" ErrorLevel "`n" A_LastError  ; 0x80070057 - E_INVALIDARG
r := DllCall(MoveItem, "Ptr", FileOperation
                     , "Ptr", (pItem := GetShellItem(Item))
                     , "Ptr", (pFolder := GetShellItem(Folder))
                     , "Ptr", 0
                     , "Ptr", 0
                     , "UInt")
MsgBox % r "`n" ErrorLevel "`n" A_LastError
DllCall(PerformOperations, "Ptr", FileOperation, "UInt")
MsgBox % r "`n" ErrorLevel "`n" A_LastError

FileRemoveDir, test, 1

ObjRelease(pItem)
ObjRelease(pFolder)
ObjRelease(FileOperation)
ObjRelease(FileOperationProgressSink)
ExitApp

IID(sIID) {
   VarSetCapacity(IID, 16, 0)
   Return DllCall("Ole32.dll\IIDFromString", "WStr", sIID, "Ptr", &IID) ? "" : &IID
}

GetShellItem(Item) {
   VarSetCapacity(IID, 16, 0)
   DllCall("Ole32.dll\IIDFromString", "WStr", "{43826d1e-e718-42ee-bc55-a1e261c37bfe}", "Ptr", &IID, "UInt")
   DllCall("Shell32.dll\SHCreateItemFromParsingName", "WStr", Item, "Ptr", 0, "Ptr", &IID, "PtrP", pShellItem, "UInt")
   Return pShellItem
}

Func(params*) {
   static SinkMethods := ["QueryInterface","AddRef","Release","StartOperations","FinishOperations","PreRenameItem","PostRenameItem","PreMoveItem","PostMoveItem","PreCopyItem","PostCopyItem","PreDeleteItem","PostDeleteItem","PreNewItem","PostNewItem","UpdateProgress","ResetTimer","PauseTimer","ResumeTimer"]
   static s
   s .= SinkMethods[A_EventInfo + 1] "-" params.MaxIndex() "`n"
   ToolTip % s
}
If you see where the problem is (I suspect the QueryInterface call), I would appreciate a pointer.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Help with IFileOperationProgressSink RegisterCallback function

15 Nov 2019, 02:57

See code comments

Code: Select all

#NoEnv
setworkingdir %a_Scriptdir%
FileAppend, , foo.txt
FileCreateDir, test
Item := A_ScriptDir "\foo.txt"
Folder := A_ScriptDir "\test"

; create the vTable
VarSetCapacity(ProgressSinkVtbl, 19 * A_PtrSize, 0) 
Loop, 19
   NumPut(RegisterCallback("Func", , , A_Index), ProgressSinkVtbl, (A_Index-1) * A_PtrSize, "Ptr")  ; note: zero-based
; create the object
VarSetCapacity(ProgressSink, a_ptrsize)	; has no other members, you can make room for ref count if you like.
numput(&ProgressSinkVtbl, ProgressSink)	; the vTable is the first member for com objs, compare to eg FileOperation object below.



FileOperation := ComObjCreate("{3ad05575-8857-4850-9277-11b85bdb8e09}"   ; CLSID
                            , "{947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8}")  ; IID
vtbl := NumGet(FileOperation + 0, 0, "Ptr")
MoveItem := NumGet(vtbl + 0, 14 * A_PtrSize, "Ptr")
PerformOperations := NumGet(vtbl + 0, 21 * A_PtrSize, "Ptr")

r := DllCall(MoveItem, "Ptr", FileOperation
                     , "Ptr", (pItem := GetShellItem(Item))
                     , "Ptr", (pFolder := GetShellItem(Folder))
                     , "Ptr", 0
                     , "Ptr", &ProgressSink ; pointer to the object
                     , "UInt")

MsgBox % r "`n" ErrorLevel "`n" A_LastError
r := DllCall(PerformOperations, "Ptr", FileOperation, "UInt")
MsgBox % r "`n" ErrorLevel "`n" A_LastError ; a_lasterror is set, idk what it is

FileRemoveDir, test, 1

ObjRelease(pItem)
ObjRelease(pFolder)
ObjRelease(FileOperation)
ExitApp

GetShellItem(Item) {
   VarSetCapacity(IID, 16, 0)
   DllCall("Ole32.dll\IIDFromString", "WStr", "{43826d1e-e718-42ee-bc55-a1e261c37bfe}", "Ptr", &IID, "UInt")
   DllCall("Shell32.dll\SHCreateItemFromParsingName", "WStr", Item, "Ptr", 0, "Ptr", &IID, "PtrP", pShellItem, "UInt")
   Return pShellItem
}

Func(this, params*) {
	; See registercallback for note on variadic*
   static SinkMethods := ["QueryInterface","AddRef","Release","StartOperations","FinishOperations","PreRenameItem","PostRenameItem"
                         ,"PreMoveItem","PostMoveItem","PreCopyItem","PostCopyItem","PreDeleteItem","PostDeleteItem","PreNewItem"
                         ,"PostNewItem","UpdateProgress","ResetTimer","PauseTimer","ResumeTimer"]
   static s
   s .= SinkMethods[A_EventInfo] "-" params.MaxIndex() "`n"
   ToolTip % s
	if (SinkMethods[A_EventInfo] = "PreMoveItem")
		sleep 2500 ; give some time for the dialog to show.
   return 0
}

/*
From C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um\ShObjIdl_core.h

        HRESULT ( STDMETHODCALLTYPE *PreMoveItem )( 
            __RPC__in IFileOperationProgressSink * This,
            /* [in] */ DWORD dwFlags,
            /* [in] */ __RPC__in_opt IShellItem *psiItem,
            /* [in] */ __RPC__in_opt IShellItem *psiDestinationFolder,
            /* [string][unique][in] */ __RPC__in_opt_string LPCWSTR pszNewName);
*/
I am confused about why the RegisterCallback function doesn't appear to receive any parameters beyond the first one
See registercallback, specifically,
Receiving parameters by address [v1.0.90+]: If the function is declared as variadic, its final parameter is assigned the address of the first callback parameter which was not assigned to a script parameter.
Cheers.
iPhilip
Posts: 822
Joined: 02 Oct 2013, 12:21

Re: Help with IFileOperationProgressSink RegisterCallback function

15 Nov 2019, 04:36

Hi @Helgef :wave:

Thank you for your comments. As posted by you above, the PerformOperations command does not execute properly, which is why A_LastError is set, and the file is not moved to the folder. Without the ProgressSink the file is moved as intended. Thus, I am still puzzled about the solution. :?

As to your comments,
Helgef wrote:

Code: Select all

; create the vTable
VarSetCapacity(ProgressSinkVtbl, 19 * A_PtrSize, 0) 
Loop, 19
   NumPut(RegisterCallback("Func", , , A_Index), ProgressSinkVtbl, (A_Index-1) * A_PtrSize, "Ptr")  ; note: zero-based
; create the object
VarSetCapacity(ProgressSink, a_ptrsize)	; has no other members, you can make room for ref count if you like.
numput(&ProgressSinkVtbl, ProgressSink)	; the vTable is the first member for com objs, compare to eg FileOperation object below.
If I am not mistaken, my original version does the same. I just packed the memory differently. My understanding from this post by @Lexicos is that
The only requirement for a COM interface pointer is that at this address, there is a pointer to the vtable.
Also, when you say
Helgef wrote: ... you can make room for ref count if you like.
I assume you are referring to the ref count of the COM object. Can you point me to a reference that describes the structure of a COM object? I would like to learn more.
Helgef wrote: the vTable is the first member for com objs, compare to eg FileOperation object below.
Isn't the first member of the FileOperation object also the vTable? I just want to make sure I understood your comment correctly.
Helgef wrote: See registercallback, specifically,
Receiving parameters by address [v1.0.90+]: If the function is declared as variadic, its final parameter is assigned the address of the first callback parameter which was not assigned to a script parameter.
I re-read the documentation for RegisterCallback and I see that, in this case, params* is not an array. Rather, params is the address of the first callback parameter which was not assigned to the function parameters.

Thank you for your help.

Cheers!
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Help with IFileOperationProgressSink RegisterCallback function

15 Nov 2019, 04:39

nevermind, this is an interface that u have to extend urself, so u cant use queryInteface or instantiate it. ur original code is fine like it is. no crashes here. and yeah, params is a pointer to the first unbound parameter, not an AHK array.
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Help with IFileOperationProgressSink RegisterCallback function

15 Nov 2019, 04:48

As far as I understand the docs, you have to specify the actual number of parameters for variadic callback functions.

Also, I prefer to define special functions to be used as callbacks for the methods, e.g.

Code: Select all

; IFileOperationProgressSink
IFOPPS_PreMoveItem(IFOP, Flags, Item, DestinationFolder, NewName) {
   ; DWORD dwFlags,
   ; IShellItem *psiItem,
   ; IShellItem *psiDestinationFolder,
   ; LPCWSTR pszNewName
   MsgBox, 0, %A_ThisFunc%, % StrGet(NewName)
   Return 0
}
*not tested*
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Help with IFileOperationProgressSink RegisterCallback function

15 Nov 2019, 06:38

Hi. Sorry, I misread your code, and it actually didn't call the callbacks on the first try, so I assumed you got it wrong. But it works when I add setworkingdir, but that is only due to a local problem, not an error on your part.
the file is not moved to the folde
It does get moved on both our versions, for me. The function does return 0 (S_OK), indicating success.
assume you are referring to the ref count of the COM object. Can you point me to a reference that describes the structure of a COM object? I would like to learn more.
Yes I mean that, you can implement it in which ever way you like. For example, allocate space for an integer in object, and increment it when addref is called, and decrement it when release is called, eg,

Code: Select all

varsetcapacity(myobj, a_ptrsize + 4, 0)
varsetcapacity(myobjVt, a_ptrsize * 3)
numput(&myobjVt, myobj)				

numput(registercallback("addref"), myobjVt, a_ptrsize)	
numput(registercallback("release"), myobjVt, a_ptrsize*2)

msgbox % objaddref(&myobj)
msgbox % objaddref(&myobj)
msgbox % objrelease(&myobj)
msgbox % objrelease(&myobj)

addref(this){
	static ref_count_offset := a_ptrsize + 4
	ref_count := numget(this+ref_count_offset, "uint")
	numput(++ref_count, this+ref_count_offset, "uint")
	return ref_count
}
release(this){
	static ref_count_offset := a_ptrsize + 4
	ref_count := numget(this+ref_count_offset, "uint")
	if (ref_count == 1) {
		msgbox "deleted"
		return 0
	}
	numput(--ref_count, this+ref_count_offset, "uint")
	return ref_count
}
Isn't the first member of the FileOperation object also the vTable? I just want to make sure I understood your comment correctly.
Yes, that was the point, but you already got it ofc.

I agree with just me, although I assume you only did it this way for your initial testing.

Cheers.

Edit: Separating the vtable and the instance of the object, as I did, makes it easier to reuse, i.e, several instances can use the same vtable, you only need to create it and destroy it once.
iPhilip
Posts: 822
Joined: 02 Oct 2013, 12:21

Re: Help with IFileOperationProgressSink RegisterCallback function

21 Nov 2019, 00:04

just me wrote:
15 Nov 2019, 04:48
As far as I understand the docs, you have to specify the actual number of parameters for variadic callback functions.
Hi @just me,

I just re-read the documentation for the RegisterCallback and saw that.
https://www.autohotkey.com/docs/commands/RegisterCallback.htm#Indirect wrote: Most callbacks use the stdcall calling convention, which requires a fixed number of parameters. In those cases, ParamCount must be set to the size of the parameter list, where Int64 and Double count as two 32-bit parameters.
Thank you for pointing it out. I did that and the script is now working great. :)

just me wrote:
15 Nov 2019, 04:48
Also, I prefer to define special functions to be used as callbacks for the methods, e.g.

Code: Select all

; IFileOperationProgressSink
IFOPPS_PreMoveItem(IFOP, Flags, Item, DestinationFolder, NewName) {
   ; DWORD dwFlags,
   ; IShellItem *psiItem,
   ; IShellItem *psiDestinationFolder,
   ; LPCWSTR pszNewName
   MsgBox, 0, %A_ThisFunc%, % StrGet(NewName)
   Return 0
}
*not tested*
Thank you for suggesting that. I am in the process of defining all 19 functions and I have a question: can you point me to a way to use the Item/psiItem parameter
to access the IShellItem::GetDisplayName method?

Thank you.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Help with IFileOperationProgressSink RegisterCallback function

21 Nov 2019, 05:57

Perhaps:

Code: Select all

GetDisplayName := NumGet(NumGet(Item + 0, "UPtr"), A_PtrSize * 5, "UPtr") ; IShellItem
DisplayName := ""
If !DllCall(GetDisplayName, "Ptr", Item, "UInt", 0, "PtrP", StrPtr) { ; SIGDN_NORMALDISPLAY = 0
   DisplayName := StrGet(StrPtr, "UTF-16")
   DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
}
; ObjRelease(Item) ; ???
MsgBox, DisplayName: %DisplayName%
iPhilip
Posts: 822
Joined: 02 Oct 2013, 12:21

Re: Help with IFileOperationProgressSink RegisterCallback function

21 Nov 2019, 12:11

just me wrote:
21 Nov 2019, 05:57
Perhaps:

Code: Select all

GetDisplayName := NumGet(NumGet(Item + 0, "UPtr"), A_PtrSize * 5, "UPtr") ; IShellItem
DisplayName := ""
If !DllCall(GetDisplayName, "Ptr", Item, "UInt", 0, "PtrP", StrPtr) { ; SIGDN_NORMALDISPLAY = 0
   DisplayName := StrGet(StrPtr, "UTF-16")
   DllCall("Ole32.dll\CoTaskMemFree", "Ptr", StrPtr)
}
; ObjRelease(Item) ; ???
MsgBox, DisplayName: %DisplayName%
Thank you. That works perfectly. :)
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Araphen and 430 guests