1

I am trying to implement the IFileOperation interface and need help with understanding the punkItems parameter of the IFileOperation::DeleteItems method. On the documentation page the punkItems parameter is described as a

Pointer to the IUnknown of the IShellItemArray, IDataObject, or IEnumShellItems object which represents the group of items to be copied.

The following code shows how I am using the SHCreateShellItemArrayFromIDLists() function to generate the IShellItemArray:

Files := [A_ScriptDir "\foo1.txt", A_ScriptDir "\foo2.txt"]
IID := "{947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8}"
CLSID := "{3ad05575-8857-4850-9277-11b85bdb8e09}"
FileOperation := ComObjCreate(CLSID, IID)
VTBL := NumGet(FileOperation + 0, 0, "Ptr")
DeleteItems := NumGet(VTBL + 0, 19 * A_PtrSize, "Ptr") ; IFileOperation::DeleteItems
PerformOperations := NumGet(VTBL + 0, 21 * A_PtrSize, "Ptr") ; IFileOperation::PerformOperations

VarSetCapacity(PCIDLIST, A_PtrSize * Files.Count(), 0)
PIDLISTS := []
for Each, File in Files {
   PIDLISTS[Each] := DllCall("Shell32.dll\ILCreateFromPath", "Str", File, "Ptr")
   NumPut(PIDLISTS[Each], PCIDLIST, A_PtrSize * (Each - 1), "Ptr")
}
DllCall("Shell32.dll\SHCreateShellItemArrayFromIDLists", "UInt", Files.Count(), "Ptr", &PCIDLIST, "PtrP", pItems, "Int")
for Each, PIDLIST in PIDLISTS
   DllCall("Shell32.dll\ILFree", "Ptr", PIDLIST)

MsgBox % DllCall(DeleteItems, "Ptr", FileOperation, "Ptr", pItems, "UInt") "`n" ErrorLevel "`n" A_LastError
DllCall(PerformOperations, "Ptr", FileOperation, "Int")
ObjRelease(pItems)

but it feels like I am missing something, e.g. the IUnknown part of the parameter description. The above code works but A_LastError yields ERROR_NO_TOKEN - An attempt was made to reference a token that does not exist (1008). Can you help me understand how would I might get the "Pointer to the IUnknown of the IShellItemArray"?

Thank you.

iPhilip
  • 43
  • 6
  • 3
    `pItems` is an `IShellItemArray` object, which is derived from `IUnknown`. Your code works. Your problem is calling `GetLastError()` (which is what `A_LastError` returns) when nothing failed. The error code is erroneous. If `IFileOperation::DeleteItems` fails it returns its own error code. – Jonathan Potter Nov 13 '19 at 03:26
  • Thank you @JonathanPotter. I appreciate the prompt feedback. I read up on the [GetLastError()](https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) function and learned that when you said "The error code is erroneous" (which sounds funny), you were right. Thanks again. – iPhilip 1 hour ago – iPhilip Nov 13 '19 at 21:17

1 Answers1

1

IFileOperation::DeleteItems supports different types of input.

Just pass in the instance pointer you have (IShellItemArray, IDataObject, IEnumShellItems or IPersistIDList) and the DeleteItems method will treat the input as a IUnknown and call QueryInterface until it finds a type it understands.

You can imagine that the implementation looks something like this:

HRESULT DeleteItems(IUnknown*p)
{
    if (!p->QueryInterface(IShellItemArray)) ...
    else if (!p->QueryInterface(IDataObject)) ...
    ...
    else if (!p->QueryInterface(IPersistIDList)) ...
    else return E_NOINTERFACE;
}
Anders
  • 97,548
  • 12
  • 110
  • 164