Had some time, wrote a note
CURRENT IMPLEMENTATION
As for the
memory leak, the reason is this cycle:
Code: Select all
Loop
{
SHParseDisplayName(pidl) ; <-- MAIN REASON: each call creates a new instance of ITEMIDLIST, and only the last remaining instance in the 'pidl' variable is freed
SHBindToParent(pIShellFolder) ; I have not checked if on each loop returns the same ppv pointer (logically should be the same), but if not, you must release every one of them
}
CoTaskMemFree(pidl)
ObjRelease(pIShellFolder)
In fact,
SHBindToParent needs to be called just once to get a pointer to the IShellFolder interface of the parent object (In our case, the parent folder is the same for all passed objects).
The "pure" call chain looks like this:
SHGetDesktopFolder(Desktop.IShellFolder)
Desktop\BindToObject(ParentIShellFolder.IShellFolder) ;(you prefer SHBindToParent)
ParentIShellFolder\GetUIObjectOf(IContextMenu.IContextMenu)
IContextMenu\QueryInterface(IContextMenuX.IContextMenu[2\3])
IContextMenuX\QueryContextMenu()
And
SHParseDisplayName just converter [
path >
ITEMIDLIST] it returns a pointer to the ITEMIDLIST (aka PIDL), which is then stored in an array for
GetUIObjectOf.
POSSIBLE IMPROVEMENTS
- Create the XY64ctxmenu.exe process immediately after starting XYplorer
- This will avoid a delay when calling the context menu for the first time
- Startup argument - XYplorer's window handle (although it will be passed in the wParam of WM_COPYDATA, but why not)
- Use a Job Objects to auto-kill XY64ctxmenu
NOTE: Set JOB_OBJECT_LIMIT_BREAKAWAY_OK (to child processes of XY64ctxmenu are not associated with the job)
otherwise all programs started with XYplorer will also be terminated
Using Job Objects will avoid unnecessary code and spam messages
- For code speed, it is critical to avoid all strings operations wherever possible:
I have never coded in VB6, but surely the list of selected files at some point is in the one of the shell object format i.e., as an ITEMIDLIST
SHParseDisplayName is called at least once for the selected item
[0x0] : shell32!SHParseDisplayName
[0x1] : shell32!SHGetFileInfoW + 0xd5
[0x2] : shell32!SHGetFileInfoWStub + 0x17
[0x3] : XYplorer + 0x3e8de0
[0x4] : XYplorer + 0x2a41de
[0x5] : XYplorer + 0x29fa02
[0x6] : XYplorer + 0x1e2539
[0x7] : XYplorer + 0x1f0a53
so we already have a ready-made ITEMIDLIST somewhere...
Actually it is rather sad that now we first extract paths from ITEMIDLIST, combine them into a string, send it, then split it again and convert it into ITEMIDLIST one more time and then create an array for GetUIObjectOf.
A lot of unnecessary work, if you ask me...
By far the best ways would be to pass an
CFSTR_SHELLIDLIST or
DataObject or
ITEMIDLIST (in a message "body" or as a pointer to shared memory) instead of one big string.
In the latter case you can even go further and read it directly from the XYplorer process memory instead of data transferring (others cases use global memory).
In the worst case if for some reason this is not possible, then send message with following format:
---------------------------------------------------------------------------------------------------------------------------------------
*Reason:BYTE Byte containing the call reason code (menu|open|openwith)
*Flags:BYTE Byte containing extra options (EXTENDEDVERBS)
although it can be eliminated by adding variations of the first position (aka menu|menuex)
*Mouse:POINT Qword with mouse coordinates
*in:USHORT The number of items that are being transferred
*DATA:DtArray Array of paths
---------------------------------------------------------------------------------------------------------------------------------------
DtArray
{
USHORT cb The size of the path string, in bytes, includes the 2 bytes of the size itself, 0 if item is empty (last one)
BYTE path[1] A variable-length path
}
Example with 2 files (Unicode)
Code: Select all
cb path cb path cb
┌──┬────────┬──┬────────┬──┬┐
│18│C:\1.txt│18│C:\2.txt│00││
└──┴────────┴──┴────────┴──┴┘
And instead of slow string operations you'll get fast pointer math
The first pointer
*Ptr is
message\DtArray offset, second one
*Ptr + *Ptr\cb etc until
cb = 0
Working with strings is completely eliminated, just pass pointers to
SHParseDisplayName in the loop.
I'm not entirely sure why you need to pass the mouse coordinates, but if you need them for something, use the appropriate
POINT format (Parsing a string with RegEx for this is some kind of insane

)
And one more thing.
You create a XYplorer's message loop with
PeekMessage but in my opinion
GetMessage is much preferable since at idle it does not waste CPU cycles.
