get full paths of selected files on Desktop and Common File Dialogs

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

get full paths of selected files on Desktop and Common File Dialogs

28 Apr 2017, 23:58

[UPDATE:]

Common Item Dialog (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
Starting with Windows Vista, the Common Item Dialog supersedes the older Common File Dialog when used to open or save a file.
For the Common File Dialog:
CDM_FIRST := 0x464
CDM_GETSPEC := 0x464 ;focused item get name
CDM_GETFILEPATH := 0x465 ;focused item get path
CDM_GETFOLDERPATH := 0x466 ;get folder
CDM_GETFOLDERIDLIST := 0x467 ;get folder ID for special folders e.g. 'Computer'

For the Common Item Dialog:
[script by qwerty12:]
get full paths of selected files on Desktop and Common File Dialogs - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 77#p145577
[notes by jeeswg:]
get full paths of selected files on Desktop and Common File Dialogs - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 83#p145883

[a simple approach:]
get full paths of selected files on Desktop and Common File Dialogs - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 40#p165840

To get files from desktop:
Explorer interaction (folder windows/Desktop, file/folder enumeration/selection/navigation/creation) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=35041
- On older systems (Windows XP and older I believe), you may be able to use window messages to retrieve information from the Desktop listview control, including PIDLs, and convert those PIDLs to file paths. This PIDL method may also to apply to listview controls in Common File Dialogs on older systems.

==================================================

It appears that PIDLs may provide a way to obtain the full paths of selected files on Desktop and Common File Dialogs. Any help with this would be most appreciated. It involves WM_USER+7 (0x407) and IShellView.

What I have found out so far:

==================================================

c++ - Obtain the true name of the currently select file in the common file dialog? - Stack Overflow
http://stackoverflow.com/questions/1757 ... ile-dialog
We used to rely upon the fact that the Windows 9x, 2000, and XP version of the common file dialog stored each item's PIDL in the LVITEM data (original credit to Paul DiLascia):
Send WM_USER+7 to get the browser, and then get its active shell view's IShellView interface.
==================================================

[the page above has a link to this:]
How to change a current directory from a CFileDialog-based class after the call to DoModal()?
https://social.msdn.microsoft.com/Forum ... =vcgeneral

Code: Select all

/*
#define WM_UNDOCUMENTED (WM_USER + 7)
*/
IShellBrowser *pSB = (IShellBrowser *)GetParent()->SendMessage( WM_UNDOCUMENTED, 0, 0 );
if( pSB != 0 )
{
        CStringW sw( strNewDirectory );
	PIDLIST_ABSOLUTE pidl;
	DWORD flags = SFGAO_FOLDER;
	HRESULT hr = SHILCreateFromPath( sw, &pidl, &flags );
	ASSERT( hr == S_OK );
	flags = SBSP_SAMEBROWSER | SBSP_DEFMODE | SBSP_ABSOLUTE;
	hr = pSB->BrowseObject( pidl, flags );
		                                // The call works fine, but returns
		                                // ... S_FALSE
//	ASSERT( hr == S_OK );
}
The only minor problem - like one can see from comments - is that the call to BrowseObject() returns S_FALSE, despite that works fine.
Why? Any misunderstanding on my side regarding flags definition, or something else?
use SUCCEEDED(hr) or FAILED(hr) to determine success or failure. Error codes generally begins with E_.
==================================================

[a similar question]
c# - Get PIDL's of items in GetOpenFileName dialog - Stack Overflow
http://stackoverflow.com/questions/5369 ... ame-dialog

Thanks for reading.

[EDIT:] Btw I was also having difficulty trying to navigate to a PIDL e.g. via Navigate2. Which I thought may be a possible solution to this problem, relating to navigating to folders that contain hashes:
4 options to change the current folder in Windows Explorer - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 6&start=40

Note: there is code here to convert a path to a PIDL:
SHMultiFileProperties - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 00#p145200

==================================================

[EDIT:]
[This link mentions 'WM_GETISHELLBROWSER', which may be an unofficial name for the message.]
How to change location of Open Save Dialog (COMDLG32) cwith DllCall? - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/9109 ... h-dllcall/
There is a way to control the file dialog without sending keystrokes, messages or mouse clicks - it involves sending an undocumented message (WM_GETISHELLBROWSER = 0x407) to the dialog and using the IShellBrowser interface pointer which it returns. This will completely fail with dialogs which were created by some other process. That can be worked around by injecting code into the process which owns the dialog, but that cannot be done with AutoHotkey alone.
[It turns out I'd found out some of this information before, regarding a related but separate issue. Although this time around I found out some additional information, and everything makes much more sense.]
get/set path of external Common Item Dialog possible? - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=28603
Last edited by jeeswg on 22 Aug 2017, 22:21, edited 6 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

30 Apr 2017, 20:30

jeeswg wrote:It appears that PIDLs may provide a way to obtain the full paths of selected files on Desktop and Common File Dialogs. Any help with this would be most appreciated. It involves WM_USER+7 (0x407) and IShellView.
For file selection common file dialogs, you can try this:

Code: Select all

#NoEnv
#NoTrayIcon
ListLines, Off
SetBatchLines, -1
#KeyHistory 0
#SingleInstance ignore
#Persistent

main(WinExist("Select File(s) ahk_exe AutoHotkey.exe")), return

main(fileDialogHwnd)
{
	try {
		if (!fileDialogHwnd)
			throw

		fileDialogTid := DllCall("GetWindowThreadProcessId", "Ptr", fileDialogHwnd, "UInt*", fileDialogPid, "UInt")

		; open process for writing into
		if (!(hProcess := DllCall("OpenProcess", "UInt", PROCESS_CREATE_THREAD := 0x0002 | PROCESS_QUERY_INFORMATION := 0x0400 | PROCESS_VM_OPERATION := 0x0008 | PROCESS_VM_READ := 0x0010 | PROCESS_VM_WRITE := 0x0020, "Int", False, "UInt", fileDialogPid, "Ptr")))
			throw

		; get addresses of all modules loaded in remote process
		INFINITE := 0xffffffff
		LIST_MODULES_DEFAULT := 0x00
		Loop {
			if (!DllCall("psapi\EnumProcessModulesEx", "Ptr", hProcess, "Ptr", 0, "UInt", 0, "UInt*", cbNeeded, "UInt", LIST_MODULES_DEFAULT))
				throw
			VarSetCapacity(hModules, cbNeeded, 0)
		} until (DllCall("psapi\EnumProcessModulesEx", "Ptr", hProcess, "Ptr", &hModules, "UInt", cbNeeded, "UInt*", cbNeeded, "UInt", LIST_MODULES_DEFAULT))			

		VarSetCapacity(modName, 524) ; (MAX_PATH + 2) * sizeof(WCHAR)
		Loop % cbNeeded / A_PtrSize {
			if (DllCall("psapi\GetModuleBaseName", "Ptr", hProcess, "Ptr", NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr"), "Str", modName, "UInt", 260)) {
				if (!user32 && modName = "user32.dll") {
					user32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr") ; store address of user32.dll present in remote process
				} else if (!kernel32 && modName = "kernel32.dll") {
					kernel32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				} else if (!shell32 && modName = "shell32.dll") {
					shell32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				} else if (!ntdll && modName = "ntdll.dll") {
					ntdll := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				} else if (!ole32 && modName = "ole32.dll") {
					ole32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				}
			}
		}

		if (!user32 || !kernel32 || !shell32 || !ntdll || !ole32)
			throw

		; prepare ThreadData struct
		VarSetCapacity(threadData, (A_PtrSize * 2) + (16 * 2) + (A_PtrSize * 3)) ; keep in alignment with ThreadData struct definition inside the C code

		; allocate space for a *separate* copy of ThreadData inside the process - we need to get its address before the code is compiled
		if (!(pRemoteThreadData := DllCall("VirtualAllocEx", "Ptr", hProcess, "Ptr", 0, "Ptr", VarSetCapacity(threadData), "UInt", MEM_COMMIT := 0x00001000, "UInt", PAGE_READWRITE := 0x04, "Ptr")))
			throw

		; Since the injected code doesn't have the benefit of a linker to tell it where library functions can be found, we do the resolving for it here
		fpWcslen := ProcAddressFromRemoteProcess(hProcess, ntdll, "wcslen")
		fpCloseHandle := ProcAddressFromRemoteProcess(hProcess, kernel32, "CloseHandle")
		fpWaitForSingleObject := ProcAddressFromRemoteProcess(hProcess, kernel32, "WaitForSingleObject")
		fpSetEvent := ProcAddressFromRemoteProcess(hProcess, kernel32, "SetEvent")
		fpVirtualAllocEx := ProcAddressFromRemoteProcess(hProcess, kernel32, "VirtualAllocEx")
		fpCreateEventW := ProcAddressFromRemoteProcess(hProcess, kernel32, "CreateEventW")
		fpSetWindowsHookExW := ProcAddressFromRemoteProcess(hProcess, user32, "SetWindowsHookExW")
		fpPostMessageW := ProcAddressFromRemoteProcess(hProcess, user32, "PostMessageW")
		fpSendMessageW := ProcAddressFromRemoteProcess(hProcess, user32, "SendMessageW")
		fpCallNextHookEx := ProcAddressFromRemoteProcess(hProcess, user32, "CallNextHookEx")
		fpUnhookWindowsHookEx := ProcAddressFromRemoteProcess(hProcess, user32, "UnhookWindowsHookEx")
		fpSHGetPathFromIDListW := ProcAddressFromRemoteProcess(hProcess, shell32, "SHGetPathFromIDListW")
		fpCoTaskMemFree := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandleW", "WStr", "ole32.dll", "Ptr"), "AStr", "CoTaskMemFree", "Ptr") ; ProcAddressFromRemoteProcess bug - it cannot get these functions properly...
		fpCoTaskMemAlloc := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandleW", "WStr", "ole32.dll", "Ptr"), "AStr", "CoTaskMemAlloc", "Ptr")

		injected_func = 
		(
		/* Do the normal thing, instead. */
		#define NULL ((void *)0)

		#define __declspec(x) __attribute__((x))
		#define __stdcall __attribute__((__stdcall__))
		typedef unsigned short wchar_t;

		#define DECLSPEC_IMPORT __declspec(dllimport)
		#define WINUSERAPI DECLSPEC_IMPORT
		#define WINAPI __stdcall
		#define STDMETHODCALLTYPE WINAPI

		typedef wchar_t WCHAR;
		#define CONST const
		typedef CONST WCHAR *LPCWSTR,*PCWSTR;
		typedef WCHAR *NWPSTR,*LPWSTR,*PWSTR;

		typedef unsigned int UINT, DWORD;

		typedef unsigned char BYTE;
		typedef BYTE* PBYTE;

		typedef void* LPVOID;
		typedef void *HANDLE;

		#define DECLARE_HANDLE(name) struct name##__ { int q12; }; typedef struct name##__ *name
		DECLARE_HANDLE(HWND);
		DECLARE_HANDLE(HHOOK);
		DECLARE_HANDLE(HINSTANCE);

		#ifdef _WIN64
			#define __int64 long long
			typedef __int64 LONG_PTR,*PLONG_PTR;
			typedef unsigned __int64 UINT_PTR,*PUINT_PTR;
			typedef unsigned __int64 ULONG_PTR, *PULONG_PTR;
		#else
			typedef long LONG_PTR,*PLONG_PTR;
			typedef unsigned int UINT_PTR,*PUINT_PTR;
			typedef unsigned long ULONG_PTR, *PULONG_PTR;
		#endif
		typedef long HRESULT;
		typedef unsigned long ULONG;
		typedef UINT_PTR WPARAM;
		typedef LONG_PTR LPARAM;
		typedef LONG_PTR LRESULT;

		typedef ULONG_PTR SIZE_T, *PSIZE_T;

		typedef struct tagPOINT {
			long x;
			long y;
		} POINT,*PPOINT,*NPPOINT,*LPPOINT;

		typedef struct tagMSG {
			HWND hwnd;
			UINT message;
			WPARAM wParam;
			LPARAM lParam;
			DWORD time;
			POINT pt;
		} MSG,*PMSG,*NPMSG,*LPMSG;

		#define WM_NULL                         0x0000
		
		#define INFINITE %INFINITE%

		typedef unsigned short USHORT;

		#pragma pack(push, 1)
		typedef struct _SHITEMID
		{
		USHORT cb;
		BYTE abID[ 1 ];
		} 	SHITEMID;

		typedef struct _ITEMIDLIST
		{
			SHITEMID mkid;
		} ITEMIDLIST;
		#pragma pack(pop)

		typedef ITEMIDLIST *LPITEMIDLIST;

		typedef struct {
			HRESULT (STDMETHODCALLTYPE *QueryInterface)(void * This, void* riid, void **ppvObject);
			ULONG (STDMETHODCALLTYPE *AddRef)(void * This);
			ULONG (STDMETHODCALLTYPE *Release)(void * This);
		} IUnknownVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			LPVOID dummyq[12];
			HRESULT (STDMETHODCALLTYPE *QueryActiveShellView)(void * This, void **ppshv);
		} partIShellBrowserVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			LPVOID dummy[4];
			HRESULT (STDMETHODCALLTYPE *ItemCount)(void * This, UINT uFlags, int* pcItems);
			HRESULT (STDMETHODCALLTYPE *Items)(void * This, UINT uFlags, void* riid, void **ppshv);
		} partIFolderViewVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			HRESULT (STDMETHODCALLTYPE *Next)(void * This, ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
		} partIEnumIDListVtbl;

		typedef struct {
			IUnknownVtbl *lpVtbl;
		} IUnknown;

		// Badly defined function type definitions
		typedef LPVOID (WINAPI *VIRTUALALLOCEX)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
		typedef SIZE_T (__attribute__((__cdecl__)) *WCSLEN)(LPWSTR);
		typedef HHOOK (WINAPI *SETWINDOWSHOOKEXW)(int, LPVOID, HINSTANCE, DWORD);
		typedef LRESULT (WINAPI *CALLNEXTHOOKEX)(HHOOK, int, WPARAM, LPARAM);
		typedef int (WINAPI *UNHOOKWINDOWSHOOKEX)(HHOOK);
		typedef LRESULT (WINAPI *SENDMESSAGEW)(HWND, UINT, WPARAM, LPARAM);
		typedef int (WINAPI *POSTMESSAGEW)(HWND, UINT, WPARAM, LPARAM);
		typedef HANDLE (WINAPI *CREATEEVENTW)(LPVOID, int, int, LPVOID);
		typedef int (WINAPI *SETEVENT)(HANDLE);
		typedef DWORD (WINAPI *WAITFORSINGLEOBJECT)(HANDLE,DWORD);
		typedef int (WINAPI *CLOSEHANDLE)(HANDLE);
		typedef int (WINAPI *SHGETPATHFROMIDLISTW)(LPVOID,LPWSTR);
		typedef void (WINAPI *COTASKMEMFREE)(LPVOID);
		typedef LPVOID (WINAPI *COTASKMEMALLOC)(SIZE_T);

		/* --------------------------------------------------------------------------------------
		   -----------------------------------Real code follows----------------------------------
		   -------------------------------------------------------------------------------------- */

		#pragma pack(push, 1) // disable struct padding to make things a bit easier for me when populating the struct in AutoHotkey below
		typedef struct {
			LPVOID lpHookFunc;
			HHOOK msgHook;
			char IID_IFolderView[16]; // CBA to include the proper definition for GUID
			char IID_IEnumIDList[16];
			HANDLE hThreadWaitEvent;
			SIZE_T allocatedSize;
			LPWSTR selectedFilenames;
		} ThreadData, *PThreadData;
		#pragma pack(pop)

		LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
		{
			CALLNEXTHOOKEX fpCallNextHookEx = (CALLNEXTHOOKEX)%fpCallNextHookEx%;
			if (code == 0) { // HC_ACTION
				PMSG msg = (PMSG)lParam;
				if (msg->hwnd == (HWND)%fileDialogHwnd% && msg->message == WM_NULL) { // only work if activated through the message we posted
					PThreadData threadData = (PThreadData)%pRemoteThreadData%;

					// We're on the main thread, no need for the hook to be called again after this
					UNHOOKWINDOWSHOOKEX fpUnhookWindowsHookEx = (UNHOOKWINDOWSHOOKEX)%fpUnhookWindowsHookEx%;
					fpUnhookWindowsHookEx(threadData->msgHook); 
					threadData->msgHook = 0;

					SENDMESSAGEW fpSendMessageW = (SENDMESSAGEW)%fpSendMessageW%;
					IUnknown *iShellBrowser = (IUnknown*)fpSendMessageW((HWND)%fileDialogHwnd%, 0x407, 0, 0); // WM_GETISHELLBROWSER
					if (iShellBrowser) {
						iShellBrowser->lpVtbl->AddRef(iShellBrowser); // make sure it doesn't disappear on us while working with it
						IUnknown *iShellView = NULL;
						((partIShellBrowserVtbl*)(iShellBrowser->lpVtbl))->QueryActiveShellView(iShellBrowser, (LPVOID*)&iShellView);
						if (iShellView) {
							IUnknown *iFolderView = NULL;
							iShellView->lpVtbl->QueryInterface(iShellView, &(threadData->IID_IFolderView), (LPVOID*)&iFolderView);
							if (iFolderView) {
								int cItems = 0;
								if (((partIFolderViewVtbl*)(iFolderView->lpVtbl))->ItemCount(iFolderView, 0x1, &cItems) >= 0 && cItems) { // find out how many selected items there are
									IUnknown *iEnumIdList = NULL;
									((partIFolderViewVtbl*)(iFolderView->lpVtbl))->Items(iFolderView, 0x1, &(threadData->IID_IEnumIDList), (LPVOID*)&iEnumIdList);
									if (iEnumIdList) {
										VIRTUALALLOCEX fpVirtualAllocEx = (VIRTUALALLOCEX)%fpVirtualAllocEx%;
										threadData->allocatedSize = ((261 * sizeof(WCHAR)) * cItems) + (2 * sizeof(WCHAR)); // set aside enough space to hold the filenames, with null terminators of course, and extra space for the double-null terminator at the end
										if ((threadData->selectedFilenames = fpVirtualAllocEx((HANDLE)-1, NULL, threadData->allocatedSize, %MEM_COMMIT%, %PAGE_READWRITE%))) { // use VirtualAllocEx because we can then free the memory from the AutoHotkey script
											WCSLEN fpWcslen = (WCSLEN)%fpWcslen%;
											SHGETPATHFROMIDLISTW fpSHGetPathFromIDListW = (SHGETPATHFROMIDLISTW)%fpSHGetPathFromIDListW%;
											COTASKMEMFREE fpCoTaskMemFree = (COTASKMEMFREE)%fpCoTaskMemFree%;

											LPITEMIDLIST pidlItem;
											ULONG celtFetched = 0;
											int written = 0;
											LPWSTR target = threadData->selectedFilenames;
											while ((((partIEnumIDListVtbl*)iEnumIdList->lpVtbl)->Next(iEnumIdList, 1, &pidlItem, &celtFetched) >= 0) && celtFetched == 1 && written < cItems) { // while we keep getting a PIDL:
												if (fpSHGetPathFromIDListW(pidlItem, target)) { // convert the PIDL into a fully fledged path
													target += fpWcslen(target) + 1; // shift the pointer to now point just after the written string + the null terminator
													written++; // make sure not to include any new paths that are included between us getting the count and actually iterating, just in case - CBA to realloc
												}
												fpCoTaskMemFree(pidlItem);
											}
											*target = '\0'; // make sure string is double null-terminated
											*target-- = '\0'; // make sure string has its normal null terminator
										} else {
											threadData->allocatedSize = 0;
										}
										iEnumIdList->lpVtbl->Release(iEnumIdList);
									}
								}
								iFolderView->lpVtbl->Release(iFolderView);
							}
							iShellView->lpVtbl->Release(iShellView);
						}
						iShellBrowser->lpVtbl->Release(iShellBrowser);
					}
					
					SETEVENT fpSetEvent = (SETEVENT)%fpSetEvent%;
					fpSetEvent(threadData->hThreadWaitEvent); // signal the event to get the ThreadFunc to exit

					return 0;
				}
			}
			return fpCallNextHookEx(NULL, code, wParam, lParam);
		}

		UINT GetMsgProcSize(void) { return (PBYTE) GetMsgProcSize - (PBYTE) GetMsgProc; }

		DWORD WINAPI ThreadProc(PThreadData threadData)
		{
			if (threadData) {
				CREATEEVENTW fpCreateEventW = (CREATEEVENTW)%fpCreateEventW%;
				if ((threadData->hThreadWaitEvent = fpCreateEventW(NULL, 0, 0, NULL))) {
					CLOSEHANDLE fpCloseHandle = (CLOSEHANDLE)%fpCloseHandle%;
					SETWINDOWSHOOKEXW fpSetWindowsHookExW = (SETWINDOWSHOOKEXW)%fpSetWindowsHookExW%; // set up a hook so that we can run code on the same thread the open folder dialog box is displaying itself on - COM is usually touchy about multithread use
					if ((threadData->msgHook = fpSetWindowsHookExW(3, threadData->lpHookFunc, 0, %fileDialogTid%))) { // WH_GETMESSAGE
						POSTMESSAGEW fpPostMessageW = (POSTMESSAGEW)%fpPostMessageW%;
						fpPostMessageW((HWND)%fileDialogHwnd%, WM_NULL, 0, 0); // fire up the hook function ASAP

						WAITFORSINGLEOBJECT fpWaitForSingleObject = (WAITFORSINGLEOBJECT)%fpWaitForSingleObject%;
						fpWaitForSingleObject(threadData->hThreadWaitEvent, INFINITE);

						fpCloseHandle(threadData->hThreadWaitEvent);

						return 1;
					}
					fpCloseHandle(threadData->hThreadWaitEvent);
				}
			}
			return 0;
		}

		UINT noCompetitionMandemFaster(void) { return (PBYTE) noCompetitionMandemFaster - (PBYTE) ThreadProc; }
		)

		if (!(ctx := CompileC(injected_func,, False)))
			throw

		if (!(size := DllCall("libtcc\tcc_relocate", "Ptr", ctx.tccState, "Ptr", 0, "CDecl"))) ; get size of compiled code
			throw

		if (!(pCompiled := DllCall("GlobalAlloc", "UInt", GMEM_FIXED := 0x0000, "Ptr", size, "Ptr"))) ; allocate memory for said code
			throw

		if (DllCall("libtcc\tcc_relocate", "Ptr", ctx.tccState, "Ptr", pCompiled, "CDecl") == -1) ; get tcc to write the compiled code into the allocated memory
			throw

		if (!DllCall("VirtualProtect", "Ptr", pCompiled, "Ptr", size, "UInt", PAGE_EXECUTE_READWRITE := 0x40, "UInt*", 0)) ; Make sure we can execute from memory chunk
			throw

		if (!(cbThreadRoutine := DllCall(DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "noCompetitionMandemFaster", "CDecl Ptr"), "CDecl UInt"))) ; get the size the function takes up
			throw

		if (!(pThreadRoutine := DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "_ThreadProc@" . (1 * A_PtrSize), "CDecl Ptr"))) ; fuck me... get pointer to thread function copied into remote process
			throw

		if (!(cbGetMsgProc := DllCall(DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "GetMsgProcSize", "CDecl Ptr"), "CDecl UInt")))
			throw

		if (!(pGetMsgProc := DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "_GetMsgProc@" . (3 * A_PtrSize), "CDecl Ptr")))
			throw

		; allocate memory for the process' copy of the message hook function
		if (!(pGetMsgFunc := DllCall("VirtualAllocEx", "Ptr", hProcess, "Ptr", 0, "Ptr", cbGetMsgProc, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")))
			throw

		if (!DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pGetMsgFunc, "Ptr", pGetMsgProc, "Ptr", cbGetMsgProc, "Ptr", 0)) ; write the message hook function into the process' memory
			throw

		NumPut(pGetMsgFunc, threadData,, "Ptr") ; threadData->lpHookFunc - memory for this is allocated after this is compiled as I need to know the function's size, which I only get after compiling. So put the pointer to the function into the ThreadData struct 
		DllCall("ole32\CLSIDFromString", "WStr", "{cde725b0-ccc9-4519-917e-325d72fab4ce}", "Ptr", &threadData+(A_PtrSize * 2)) ; threadData->IID_IFolderView - it's easier to get these from AutoHotkey beforehand
		DllCall("ole32\CLSIDFromString", "WStr", "{000214F2-0000-0000-C000-000000000046}", "Ptr", &threadData+(A_PtrSize * 2)+16) ; threadData->IID_IEnumIDList
		
		if (!DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData, "Ptr", &threadData, "Ptr", VarSetCapacity(threadData), "Ptr", 0)) ; now copy the ThreadData over now that we've populated it
			throw

		; do what we did for the message hook function for the ThreadFunc
		if (!(pRemoteThreadFunc := DllCall("VirtualAllocEx", "Ptr", hProcess, "Ptr", 0, "Ptr", cbThreadRoutine, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")))
			throw

		if (!DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadFunc, "Ptr", pThreadRoutine, "Ptr", cbThreadRoutine, "Ptr", 0)) 
			throw

		; start the thread in the remote process
		hRemoteThread := DllCall("CreateRemoteThread", "Ptr", hProcess, "Ptr", 0, "Ptr", 0, "Ptr", pRemoteThreadFunc, "Ptr", pRemoteThreadData, "UInt", 0, "Ptr", 0, "Ptr")
		if (hRemoteThread) {
			DllCall("WaitForSingleObject", "Ptr", hRemoteThread, "UInt", INFINITE) ; block AutoHotkey until the thread has finished
			if (DllCall("GetExitCodeThread", "Ptr", hRemoteThread, "UInt*", exitCode)) { ; get the number the ThreadFunc returned
				if (exitCode == 1) { ; if our thread exited normally
					if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData+(A_PtrSize * 2)+(16*2)+A_PtrSize, "Ptr*", allocatedSize, "Ptr", A_PtrSize, "Ptr*", br) && br == A_PtrSize && allocatedSize) { ; get the size of the filename string
						if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData+(A_PtrSize * 2)+(16*2)+(A_PtrSize * 2), "Ptr*", lpSelectedFilenames, "Ptr", A_PtrSize, "Ptr*", br) && br == A_PtrSize && lpSelectedFilenames) {	; get the address of where the filename string is stored						
							VarSetCapacity(filenamesBuf, allocatedSize) ; allocate space for our copy of it
							if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", lpSelectedFilenames, "Ptr", &filenamesBuf, "Ptr", allocatedSize, "Ptr*", br) && br == allocatedSize) { ; read the filename string from the remote process and copy it over
								fileNames := &filenamesBuf
								while (*fileNames) { ; keep reading the filenames until we reach a double-null terminated one: https://blogs.msdn.microsoft.com/oldnewthing/20091008-00?p=16443
									MsgBox % (filename := StrGet(fileNames,, "UTF-16"))
									fileNames += (DllCall("ntdll\wcslen", "Ptr", fileNames, "CDecl Ptr") * 2) + 2
								}
							}
						}
					}
				}
			}
		}
	} ;catch {}
	finally {
		if (True) {
			if (hProcess) {
				if (lpSelectedFilenames) ; free the memory the injected function allocated to store the strings
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", lpSelectedFilenames, "Ptr", 0, "UInt", MEM_RELEASE := 0x8000)

				if (hRemoteThread)
					DllCall("CloseHandle", "Ptr", hRemoteThread)

				if (pRemoteThreadFunc)
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", pRemoteThreadFunc, "Ptr", 0, "UInt", MEM_RELEASE)

				if (pGetMsgFunc)
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", pGetMsgFunc, "Ptr", 0, "UInt", MEM_RELEASE)

				if (pRemoteThreadData)
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", pRemoteThreadData, "Ptr", 0, "UInt", MEM_RELEASE)

				; close our handle to the process
				DllCall("CloseHandle", "Ptr", hProcess)
			}

			if (pCompiled)
				DllCall("GlobalFree", "Ptr", pCompiled, "Ptr") ; free memory for compiled code

			if (IsObject(ctx))
				TccCleanup(ctx) ; free libtcc context and unload libtcc.dll
			
			ExitApp
		}
	}
}

CompileC(codeToCompile, outputToMemory := True, performRelocation := True, tcc_dir := "")
{
	static ptrErrCb := 0, TCC_RELOCATE_AUTO := 1, orrrr_clic := 270

	; if we're called as a result of libtcc's error callback, then:
	if ((ptrErrCb) && ((x := A_EventInfo == ptrErrCb) || codeToCompile == orrrr_clic)) {
		if (codeToCompile == orrrr_clic && x && outputToMemory)
			MsgBox % StrGet(outputToMemory, "CP0") ; show the error message
		return
	}

	if (!ptrErrCb)
		ptrErrCb := RegisterCallback(A_ThisFunc, "Fast CDecl", 2)

	if (!tcc_dir)
		tcc_dir := A_ScriptDir

	if ((libtcc := DllCall("LoadLibrary", "Str", tcc_dir . "\libtcc.dll"))) {
		ctx := {hModuleTcc: libtcc}
		if ((s := DllCall("libtcc\tcc_new", "CDecl Ptr"))) { 
			success := False

			DllCall("libtcc\tcc_set_error_func", "Ptr", s, "Ptr", orrrr_clic, "Ptr", ptrErrCb, "CDecl")
			DllCall("libtcc\tcc_set_output_type", "Ptr", s, "Int", outputToMemory ? 0 : 3, "CDecl")
			DllCall("libtcc\tcc_set_options", "Ptr", s, "AStr", "-nostdinc -nostdlib", "CDecl") ; since all we have is libtcc.dll, don't look for include files we don't have or libtcc1.a

			if (DllCall("libtcc\tcc_compile_string", "Ptr", s, "AStr", codeToCompile, "CDecl") == 0) {
				if (outputToMemory) {
					success := performRelocation ? DllCall("libtcc\tcc_relocate", "Ptr", s, "Ptr", TCC_RELOCATE_AUTO, "CDecl") == 0 : True
				} else {
					success := DllCall("libtcc\tcc_output_file", "Ptr", s, "AStr", "test.obj", "CDecl") == 0
				}
			}

			ctx := {hModuleTcc: libtcc, tccState: s}
			if (success && outputToMemory)
				return ctx
		}
		TccCleanup(ctx)
	}
	
	return 0
}

TccCleanup(tccContext)
{
	if (tccContext) {
;		if (tccContext.ptrErrCb) {
;			DllCall("GlobalFree", "Ptr", tccContext.ptrErrCb, "Ptr")
;			tccContext.ptrErrCb := 0
;		}
		if (tccContext.tccState) {
			DllCall("libtcc\tcc_delete", "Ptr", tccContext.tccState, "CDecl")
			tccContext.tccState := 0
		}
		if (tccContext.hModuleTcc) {
			DllCall("FreeLibrary", "Ptr", tccContext.hModuleTcc)
			tccContext.hModuleTcc := 0
		}
	}
}

; Very little error checking. TBH, I'd be surprised if someone actually uses this, so...
ProcAddressFromRemoteProcess(hProcess, hModule, targetFuncName, ByRef Magic := 0)
{
	; MarkHC: https://www.unknowncheats.me/forum/1457119-post3.html
	IMAGE_DOS_SIGNATURE := 0x5A4D, IMAGE_NT_SIGNATURE := 0x4550
	if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule, "UShort*", header, "Ptr", 2, "Ptr*", br) && br == 2 && header == IMAGE_DOS_SIGNATURE) {
		if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+60, "Int*", e_lfanew, "Ptr", 4, "Ptr*", br) && DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew, "UInt*", Signature, "Ptr", 4, "Ptr*", br)) {
			if (Signature == IMAGE_NT_SIGNATURE) {
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew+24, "UShort*", Magic, "Ptr", 2, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew+24+(Magic == (IMAGE_NT_OPTIONAL_HDR64_MAGIC := 0x20b) ? 112 : 96), "UInt*", exportTableRVA, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+20, "UInt*", NumberOfFunctions, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+24, "UInt*", NumberOfNames, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+28, "UInt*", AddressOfFunctions, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+32, "UInt*", AddressOfNames, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+36, "UInt*", AddressOfNameOrdinals, "Ptr", 4, "Ptr*", br)
				
				VarSetCapacity(functions, NumberOfFunctions * 4)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfFunctions, "Ptr", &functions, "Ptr", NumberOfFunctions * 4, "Ptr*", br)
				VarSetCapacity(exports, NumberOfNames * 4)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfNames, "Ptr", &exports, "Ptr", NumberOfNames * 4, "Ptr*", br)
				VarSetCapacity(ordinals, NumberOfNames * 2)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfNameOrdinals, "Ptr", &ordinals, "Ptr", NumberOfNames * 2, "Ptr*", br)
				
				Loop % NumberOfNames {
					addr := NumGet(exports, 4 * (A_Index - 1), "UInt")
					i := 0, funcName := ""
					while (true) {
						DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+addr+i, "Int*", letter, "Ptr", 1, "Ptr*", br)
						if (!letter)
							break
						funcName .= Chr(letter)
						i += 1
					}
					if (funcName == targetFuncName) {
						ordinal := NumGet(ordinals, 2 * (A_Index - 1), "UShort")
						return NumGet(functions, 4 * ordinal, "UInt") + hModule
					}
				}
			}
		}
	}
	return 0
}
It works from a 64-bit AutoHotkey process to a 64-bit process and from a 32-bit AutoHotkey process to a 32-bit one.Mixing the two isn't possible (at least with this, and I don't care to try anything different to make it possible). I'm not interested in other options personally, but using TCC isn't the only option; you could use InjectAhkDll to load AutoHotkey_H into the remote process and do kinda the same thing. If you need to get results from a 32-bit process from a 64-bit AutoHotkey, then do something like spawning AutoHotkeyU32 and finding some suitable from of IPC to get the filenames out of there back to your first script.

I tested with what you linked: https://gist.github.com/BiggerDigger/69 ... 4d2263867e

Drop tcc.dll from tcc-0.9.26-win32-bin.zip/tcc-0.9.26-win64-bin.zip into the same folder as the script. The C code is shittily written (something I'll mostly blame on me being silly enough to redefine the WinAPI... :/) and there might be memory leaks in the remote process (although, AFAICT, I do clean everything up).

It works by using TCC to compile a function that does the SendMessage in the context of the process to get an IShellView that can actually be worked on. From there, the filenames are obtained and stored. Once the function returns, the AutoHotkey script reads the memory of remote process to get the filenames.
Last edited by qwerty12 on 01 May 2017, 11:33, edited 1 time in total.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

30 Apr 2017, 20:39

Cheers qwerty12, I thought this might be your area of expertise, thanks so much for this, I'll test it quite soon. I was under the impression that this would fail for external programs, unless you used dll injection, or does TCC use dll injection?

It could also be useful for dialogs created in AutoHotkey. One thing though, I don't know if this method will help with getting the full paths of selected files on Desktop.

==================================================

[EDIT:] Here is a link to some related areas of code, that may be of interest to you.
get a process's GDI handles (e.g. get/set title bar font and apply WM_SETFONT to a control) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=31228
c++ - How to get list of GDI handles - Stack Overflow
http://stackoverflow.com/questions/1390 ... di-handles
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

30 Apr 2017, 21:03

jeeswg wrote:Cheers qwerty12, I thought this might be your area of expertise, thanks so much for this, I'll test it quite soon. I was under the impression that this would fail for external programs, unless you used dll injection, or does TCC use dll injection?
Yes, you're right, this would fail for external programs - sure, you can SendMessage WM_USER + 7 to an external file dialog window fine, but the address returned is not going to make any sense in the context of your program...

So this uses TCC to compile a C function and then using WriteProcessMemory to write the resulting assembled function into the external process. DLL injection would be far, far easier - I'm copying raw functions that do not know where anything else is, hence me using AutoHotkey to get the library function address beforehand and shoving them into the C code TCC compiles and allocating space for a struct that contains data I need to share between the two compiled functions.

If you were to inject a DLL to do it, I'd look at HotKeyIt's AutoHotkey_H dll - the syntax is the same, which helps immensely... I used TCC because I don't fully understand how to use AHK.dll and I'm too lazy to look at the documentation, and because with TCC, you don't actually need a full blown compiler setup to change what the compiled code does. Think of it as MCode but the mcode is compiled on the fly.
One thing though, I don't know if this method will help with getting the full paths of selected files on Desktop.
Probably not. I was careful enough to only say "for file selection common file dialogs". ;)
Here is a link to some related areas of code, that may be of interest to you
Interesting links, thanks. Your conversion would be good to see :thumbup:. Incidentally enough, Chrome keeps crashing here because I think it's approached the desktop heap limit... :\

EDIT: I knew there was already code out there to get the names of selected desktop items: https://autohotkey.com/board/topic/6098 ... er-window/
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

01 May 2017, 23:04

That script to get selected Desktop items has several drawbacks, comments lower down. Btw LOL at big text.

- My PC is x64, I created a folder with AHK x64 (latest version, renamed it to AutoHotkey.exe to match the script), CommonItemDialog.ahk, your script, libtcc.dll (x64 version).
- I had CommonItemDialog.ahk open at the Select File(s) dialog.
- I added a line to confirm WinExist had found an hWnd.
- The code looks amazing, but unfortunately it wasn't working, I put a MsgBox just before the ExitApp line, which was triggered.
- I presume that when it is working it lists each file individually in a MsgBox.
[EDIT: I made sure to open both ahk files with the AutoHotkey.exe that was in the same folder.]
[EDIT: also, select some files.]
[EDIT: at the time of writing, the script only works if the Common Item Dialog is displaying the contents of the Desktop folder, but this may change if qwerty12 updates the script in future.]

Btw are these the recommended safe links for TCC?
TCC : Tiny C Compiler
https://bellard.org/tcc/
Index of /releases/tinycc/
http://download.savannah.gnu.org/releases/tinycc/

Btw, also, it appears that CommonItemDialog.ahk is actually opening the new (Windows Vista and after) Common File Dialog. Also, I had believed that this technique would work only with the old-style Common Item Dialog prompt.

==================================================

Desktop paths.

On Windows XP and 7, Desktop uses a listview, and the old Common Item Dialog's used a listview. Apparently in Windows XP and earlier, in each listview item's LVITEM structure, in the lParam, were PIDLs which could be used to retrieve the full paths for those listview items.

[EDIT:] You would use LVM_GETITEM, you prepare an LVITEM struct, with details of which item to get information for, and what information to get (e.g. LVIF_PARAM := 0x4), send the message, the LVITEM struct is updated, and then you can retrieve the PIDL from the lParam parameter. You'd also need the classic 6 dll functions: OpenProcess,VirtualAllocEx,WriteProcessMemory,ReadProcessMemory,VirtualFreeEx,CloseHandle.

It looks like now, that a method using objects may work for the old Common Item Dialog, but so far, no news on Desktop items. Does the Desktop on Windows 10 still use a listview (where if you press End it selects the wrong icon, because items are added 'by columns', but the last icon is determined 'by rows').

Anyhow, the best solution I have so far is based on listing all the full paths and display names for Desktop files and retrieving the display names for selected Desktop files, and checking in case there is more than one file with the same display name.

Btw the script you linked to, I believe depends on extensions being shown, plus lnk files for example always hide extensions (and dll files always show extensions).

(will post code ...)
[EDIT:] Here's the code:

It uses the display name and type name, that can be retrieved from the listview. The main difficulty is ambiguous filenames, e.g. with extensions hidden, 'MyFile.log' and 'MyFile.txt', have the same display name, 'MyFile', and the same type name, 'Text Document'. If for example, there are exactly 3 files with a particular Name/Type combination, if none or all of those files are selected, the function can proceed, otherwise the function fails, because we can't be sure which files were selected.

Code: Select all

;e.g.
;q::
WinGet, hWnd, ID, A
MsgBox, % JEE_ExpGetSelDesktop(hWnd)
return

JEE_ExpGetSelDesktop(hWnd, vSep="`n")
{
	WinGetClass, vWinClass, % "ahk_id " hWnd
	if !(vWinClass = "Progman") && !(vWinClass = "WorkerW")
		return

	ControlGet, vText, List, Selected, SysListView321, % "ahk_id " hWnd
	oArray := {}
	Loop, Parse, vText, `n
	{
		oTemp := StrSplit(A_LoopField, "`t")
		if (oTemp.3 = "") ;e.g. Recycle Bin
			continue
		vSummary := oTemp.1 "`t" oTemp.3
		oArray[vSummary] := oArray.HasKey(vSummary) ? oArray[vSummary]+1 : 1
	}
	oTemp := ""

	vList := A_Desktop "|" A_DesktopCommon
	vOutput := ""
	Loop, Parse, vList, |
	{
		vDir := A_LoopField
		Loop, Files, % vDir "\*", FD
		{
			vPath := A_LoopFileFullPath
			vDName := JEE_FileGetDisplayName(vPath, vType)
			vSummary := vDName "`t" vType
			if oArray.HasKey(vSummary)
				oArray[vSummary] -= 1, vOutput .= vPath vSep
		}
	}

	for vKey in oArray
		if !(oArray[vKey] = 0)
		{
			oArray := ""
			return
		}
	oArray := ""
	return SubStr(vOutput, 1, -StrLen(vSep))
}

;==================================================

;JEE_FileGetFriendlyName
;JEE_PathGetDisplayName
;get display name and type name e.g. 'New Text Document.txt', 'Text Document'
JEE_FileGetDisplayName(vPath, ByRef vType="")
{
	;SHGFI_TYPENAME := 0x400 ;SHGFI_DISPLAYNAME := 0x200
	vSize := A_PtrSize=8?696:692
	VarSetCapacity(SHFILEINFO, vSize, 0)
	DllCall("shell32\SHGetFileInfo" (A_IsUnicode?"W":"A"), Str,vPath, UInt,0, Ptr,&SHFILEINFO, UInt,vSize, UInt,0x600)

	MAX_PATH := 260
	vSizeDN := MAX_PATH*(1+!!A_IsUnicode)
	VarSetCapacity(vDName, vSizeDN, 0)
	DllCall("kernel32\RtlMoveMemory", Ptr,&vDName, Ptr,&SHFILEINFO+(A_PtrSize=8?16:12), UInt,vSizeDN)
	VarSetCapacity(vDName, -1)
	if IsByRef(vType)
	{
		vSizeTN := 80*(1+!!A_IsUnicode)
		VarSetCapacity(vType, vSizeTN, 0)
		DllCall("kernel32\RtlMoveMemory", Ptr,&vType, Ptr,&SHFILEINFO+(A_PtrSize=8?16:12)+vSizeDN, UInt,vSizeTN)
		VarSetCapacity(vType, -1)
	}
	return vDName
}
Last edited by jeeswg on 02 May 2017, 16:09, edited 7 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

01 May 2017, 23:31

jeeswg wrote:Btw LOL at big text.
Sorry, it's just I was thinking "I'm sure I've seen something like that in the past" and then I saw a post on this forum with what I was thinking of and was surprised...
- The code looks amazing, but unfortunately it wasn't working, I put a MsgBox just before the ExitApp line, which was triggered.
At the risk of sounding like an idiot, especially when it seems like you got everything correct, did you actually highlight a bunch of files? The script isn't very descriptive when it comes to error messages, sorry (the script used to show AutoHotkey exception messages when a throw was hit - but I somehow messed that up). Also, for some reason, it doesn't work when started in some folders - I've had success running from my Desktop and Downloads folder.

I'll set up a Windows 7 x64 VM soon to see if I'm relying on behaviour specific to my Windows 10 system.

But you're not missing out on much, TBH - see below:
- I presume that when it is working it lists each file individually in a MsgBox.
Yes, but I also messed that up - SHGetPathFromIDListW returns the correct filename indeed, but with %A_Desktop% as the path, even when the files are not on the desktop... I didn't notice before because I was only testing with files on the desktop... :oops: I'll try and fix it to use the proper COM interface methods instead of taking shortcuts :(
Btw are these the recommended safe links for TCC?
Yessir
Btw, also, it appears that CommonItemDialog.ahk is actually opening the new (Windows Vista and after) Common File Dialog. Also, I had believed that this technique would work only with the old-style Common Item Dialog prompt.
Don't quote me on this, but while I don't know how, IIRC it's easier to do for the old dialog box.
Desktop paths.

Does the Desktop on Windows 10 still use a listview (where if you press End it selects the wrong icon, because items are added 'by columns', but the last icon is determined 'by rows'.
Presumably so - the script from the old forum still works here
Anyhow, the best solution I have so far is based on listing all the full paths and display names for Desktop files and retrieving the display names for selected Desktop files, and checking in case there is more than one file with the same display name.
That sounds good - will be very interested to see.
Btw the script you linked to, I believe depends on extensions being shown, plus lnk files for example always hide extensions (and dll files always show extensions).
You raise a good point - as I always keep extensions turned on, it's not something I thought of.

https://blogs.msdn.microsoft.com/oldnew ... 0/?p=93535 might be what you're after.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

01 May 2017, 23:38

You were right about selecting files, I didn't have any selected, although in my defence, I might want this to list all files, not just selected files. Unfortunately it still didn't work after I selected some files. HOWEVER, I navigated to Desktop, and tried it, and your script *did* work, so thanks again qwerty12. (Tested on Windows 7 x64.)

Yes I'm glad I listed all the steps I took to test this script, because it was quite easy to slip up.

[EDIT:] Woah, read your link, does that mean there's an object approach to retrieve the selected files from the Desktop after all?
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

02 May 2017, 00:14

jeeswg wrote:although in my defence, I might want this to list all files, not just selected files. Unfortunately it still didn't work after I selected some files.
You raise a good point. After I fix getting the correct path returned, what I'll do is if there are no files selected, the path of the open folder will be returned instead - if you want to know what files are in there, you can do that from AutoHotkey anyway. As for it still not working, that's not good indeed. I'll definitely give it a whirl under Windows 7.
[EDIT:] Woah, read your link, does that mean there's an object approach to retrieve the selected files from the Desktop after all?
Yes, kinda - I just tried out the program. It doesn't return the selected files, but rather the path of the focused item (you can select multiple files, but the last selected file is likely to be the one with focus). Saying that, now that we have a IShellFolder for the actual desktop, getting the selected items' paths can be done in the same way it's done in my script - just without the messy remote code injection...
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

02 May 2017, 00:41

Just to be clear, when I navigated in the dialog prompt to Desktop, and selected some files, the script did work.

Haha good point, about listing *all* the files, just use an AutoHotkey file loop. Although, it might be a special folder. Btw, being able to get the folder/special folder of a dialog prompt, is as important as getting the focused/selected/all files.

When I read that Raymond Chen link and it said *focused* file, I thought that most likely it would only need a little modification to get the selected files instead. Reliably getting a list of selected files on Desktop, without using the clipboard, has been a key AHK desire for a long time. I'm surprised nobody mentioned the PIDL method (for older OSes), on the older forums, although I may have missed it (and maybe there were some problems I don't know about).

If you do ctrl+arrow keys on Desktop, the focused file is not necessarily selected.

[EDIT:] I added my 'desktop get selected files' function above.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

02 May 2017, 15:16

jeeswg wrote:Btw, being able to get the folder/special folder of a dialog prompt, is as important as getting the focused/selected/all files.
For special folders, you should get the GUID back. I'll leave interpreting it up to you back in the script; the injected function is quite large already.
I added my 'desktop get selected files' function above.
Thanks for the code - I had a similar idea in mind, but no idea how to do it in code.

For selected desktop items, try this: - https://autohotkey.com/boards/viewtopic.php?t=9618 is easier
Spoiler
as for getting the path/selected items from common item dialogs, try this revised version of the script:

Code: Select all

#NoEnv
;#NoTrayIcon
ListLines, Off
SetBatchLines, -1
#KeyHistory 0

cleanup := True ; unsurprisingly, you will almost always want this on
main(WinExist("Select File(s) ahk_exe AutoHotkey.exe")), cleanup ? ExitApp : return

main(fileDialogHwnd)
{
	try {
		if (!fileDialogHwnd)
			throw

		if (!(fileDialogTid := DllCall("GetWindowThreadProcessId", "Ptr", fileDialogHwnd, "UInt*", fileDialogPid, "UInt")) || !fileDialogPid)
			throw
		
		MsgWaitForMultipleObjectsEx := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandleW", "WStr", "user32.dll", "Ptr"), "AStr", "MsgWaitForMultipleObjectsEx", "Ptr")

		; open process for writing into
		if (!(hProcess := DllCall("OpenProcess", "UInt", PROCESS_CREATE_THREAD := 0x0002 | PROCESS_QUERY_INFORMATION := 0x0400 | PROCESS_VM_OPERATION := 0x0008 | PROCESS_VM_READ := 0x0010 | PROCESS_VM_WRITE := 0x0020, "Int", False, "UInt", fileDialogPid, "Ptr")))
			throw

		; get addresses of all modules loaded in remote process
		MAX_PATH := 260
		INFINITE := 0xffffffff
		LIST_MODULES_DEFAULT := 0x00
		Loop {
			if (!DllCall("psapi\EnumProcessModulesEx", "Ptr", hProcess, "Ptr", 0, "UInt", 0, "UInt*", cbNeeded, "UInt", LIST_MODULES_DEFAULT))
				throw
			VarSetCapacity(hModules, cbNeeded, 0)
		} until (DllCall("psapi\EnumProcessModulesEx", "Ptr", hProcess, "Ptr", &hModules, "UInt", cbNeeded, "UInt*", cbNeeded, "UInt", LIST_MODULES_DEFAULT))			

		VarSetCapacity(modName, (MAX_PATH + 2) * 2)
		Loop % cbNeeded / A_PtrSize {
			if (DllCall("psapi\GetModuleBaseName", "Ptr", hProcess, "Ptr", NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr"), "Str", modName, "UInt", MAX_PATH)) {
				if (!user32 && modName = "user32.dll") {
					user32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr") ; store address of user32.dll present in remote process
				} else if (!kernel32 && modName = "kernel32.dll") {
					kernel32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				} else if (!shlwapi && modName = "shlwapi.dll") {
					shlwapi := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				} else if (!ntdll && modName = "ntdll.dll") {
					ntdll := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				} else if (!ole32 && modName = "ole32.dll") {
					ole32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
				}
			}
		}

		if (!user32 || !kernel32 || !shlwapi || !ntdll || !ole32)
			throw

		; prepare ThreadData struct
		VarSetCapacity(threadData, A_PtrSize + (16 * 4) + 4 + (A_PtrSize * 4), 0) ; keep in alignment with ThreadData struct definition inside the C code

		; allocate space for a *separate* copy of ThreadData inside the process - we need to get its address before the code is compiled
		if (!(pRemoteThreadData := DllCall("VirtualAllocEx", "Ptr", hProcess, "Ptr", 0, "Ptr", VarSetCapacity(threadData), "UInt", MEM_COMMIT := 0x00001000, "UInt", PAGE_READWRITE := 0x04, "Ptr")))
			throw

		; Since the injected code doesn't have the benefit of a linker to tell it where library functions can be found, we do the resolving for it here
		wcslen := ProcAddressFromRemoteProcess(hProcess, ntdll, "wcslen")
		CloseHandle := ProcAddressFromRemoteProcess(hProcess, kernel32, "CloseHandle")
		WaitForSingleObject := ProcAddressFromRemoteProcess(hProcess, kernel32, "WaitForSingleObject")
		SetEvent := ProcAddressFromRemoteProcess(hProcess, kernel32, "SetEvent")
		VirtualAllocEx := ProcAddressFromRemoteProcess(hProcess, kernel32, "VirtualAllocEx")
		CreateEventW := ProcAddressFromRemoteProcess(hProcess, kernel32, "CreateEventW")
		SetWindowsHookExW := ProcAddressFromRemoteProcess(hProcess, user32, "SetWindowsHookExW")
		PostMessageW := ProcAddressFromRemoteProcess(hProcess, user32, "PostMessageW")
		SendMessageW := ProcAddressFromRemoteProcess(hProcess, user32, "SendMessageW")
		CallNextHookEx := ProcAddressFromRemoteProcess(hProcess, user32, "CallNextHookEx")
		UnhookWindowsHookEx := ProcAddressFromRemoteProcess(hProcess, user32, "UnhookWindowsHookEx")
		MessageBoxW := ProcAddressFromRemoteProcess(hProcess, user32, "MessageBoxW")
		wsprintfW := ProcAddressFromRemoteProcess(hProcess, user32, "wsprintfW")
		StrRetToBufW := ProcAddressFromRemoteProcess(hProcess, shlwapi, "StrRetToBufW")
		CoTaskMemFree := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandleW", "WStr", "ole32.dll", "Ptr"), "AStr", "CoTaskMemFree", "Ptr") ; ProcAddressFromRemoteProcess bug - it cannot get these functions properly...

		injected_func = 
		(
		/* Do the normal thing, instead. */
		#define NULL ((void *)0)

		#define __declspec(x) __attribute__((x))
		#define __stdcall __attribute__((__stdcall__))
		#define __cdecl __attribute__((__cdecl__))
		typedef unsigned short wchar_t;

		#define DECLSPEC_IMPORT __declspec(dllimport)
		#define WINUSERAPI DECLSPEC_IMPORT
		#define WINAPI __stdcall
		#define STDMETHODCALLTYPE WINAPI

		typedef wchar_t WCHAR;
		#define CONST const
		typedef CONST WCHAR *LPCWSTR,*PCWSTR;
		typedef WCHAR *NWPSTR,*LPWSTR,*PWSTR;

		typedef unsigned int UINT, DWORD;

		typedef unsigned char BYTE;
		typedef BYTE* PBYTE;

		typedef void* LPVOID;
		typedef void *HANDLE;

		#define DECLARE_HANDLE(name) struct name##__ { int q12; }; typedef struct name##__ *name
		DECLARE_HANDLE(HWND);
		DECLARE_HANDLE(HHOOK);
		DECLARE_HANDLE(HINSTANCE);

		#ifdef _WIN64
			#define __int64 long long
			typedef __int64 LONG_PTR,*PLONG_PTR;
			typedef unsigned __int64 UINT_PTR,*PUINT_PTR;
			typedef unsigned __int64 ULONG_PTR, *PULONG_PTR;
		#else
			typedef long LONG_PTR,*PLONG_PTR;
			typedef unsigned int UINT_PTR,*PUINT_PTR;
			typedef unsigned long ULONG_PTR, *PULONG_PTR;
		#endif
		typedef long HRESULT;
		typedef unsigned long ULONG;
		typedef UINT_PTR WPARAM;
		typedef LONG_PTR LPARAM;
		typedef LONG_PTR LRESULT;

		typedef ULONG_PTR SIZE_T, *PSIZE_T;

		typedef struct tagPOINT {
			long x;
			long y;
		} POINT,*PPOINT,*NPPOINT,*LPPOINT;

		typedef struct tagMSG {
			HWND hwnd;
			UINT message;
			WPARAM wParam;
			LPARAM lParam;
			DWORD time;
			POINT pt;
		} MSG,*PMSG,*NPMSG,*LPMSG;

		#define WM_NULL                         0x0000

		typedef unsigned short USHORT;

		#pragma pack(push, 1)
		typedef struct _SHITEMID
		{
		USHORT cb;
		BYTE abID[ 1 ];
		} 	SHITEMID;

		typedef struct _ITEMIDLIST
		{
			SHITEMID mkid;
		} ITEMIDLIST;
		#pragma pack(pop)

		typedef ITEMIDLIST *LPITEMIDLIST;

		#pragma pack(push, 8)
		typedef struct _STRRET
			{
			UINT uType;
			/* [switch_is][switch_type] */ union 
				{
				/* [case()][string] */ LPWSTR pOleStr;
				/* [case()] */ UINT uOffset;
				/* [case()] */ char cStr[ 260 ];
				} 	DUMMYUNIONNAME;
			} 	STRRET;

		#pragma pack(pop)
		typedef STRRET *LPSTRRET;

		typedef struct {
			HRESULT (STDMETHODCALLTYPE *QueryInterface)(void * This, void* riid, void **ppvObject);
			ULONG (STDMETHODCALLTYPE *AddRef)(void * This);
			ULONG (STDMETHODCALLTYPE *Release)(void * This);
		} IUnknownVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			LPVOID dummyq[12];
			HRESULT (STDMETHODCALLTYPE *QueryActiveShellView)(void * This, void **ppshv);
		} partIShellBrowserVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			LPVOID dummy[2];
			HRESULT ( STDMETHODCALLTYPE *GetFolder )( void * This, void* riid, void **ppv);
			LPVOID dummy3;
			HRESULT (STDMETHODCALLTYPE *ItemCount)(void * This, UINT uFlags, int* pcItems);
			HRESULT (STDMETHODCALLTYPE *Items)(void * This, UINT uFlags, void* riid, void **ppshv);
		} partIFolderViewVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			LPVOID dummy[8];
			HRESULT ( STDMETHODCALLTYPE *GetDisplayNameOf )( void * This, LPITEMIDLIST pidl, UINT uFlags, STRRET *pName);
		} partIShellFolderVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			HRESULT (STDMETHODCALLTYPE *Next)(void * This, ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
		} partIEnumIDListVtbl;

		typedef struct {
			IUnknownVtbl unkVtbl;
			LPVOID dummy[2];
			HRESULT (STDMETHODCALLTYPE *GetCurFolder)(void * This, LPITEMIDLIST *ppidl);
		} partIPersistFolder2Vtbl;

		typedef struct {
			IUnknownVtbl *lpVtbl;
		} IUnknown;

		// Badly defined function type definitions
		typedef LPVOID (WINAPI *VIRTUALALLOCEX)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
		typedef SIZE_T (__cdecl *WCSLEN)(LPWSTR);
		typedef HHOOK (WINAPI *SETWINDOWSHOOKEXW)(int, LPVOID, HINSTANCE, DWORD);
		typedef LRESULT (WINAPI *CALLNEXTHOOKEX)(HHOOK, int, WPARAM, LPARAM);
		typedef int (WINAPI *UNHOOKWINDOWSHOOKEX)(HHOOK);
		typedef LRESULT (WINAPI *SENDMESSAGEW)(HWND, UINT, WPARAM, LPARAM);
		typedef int (WINAPI *POSTMESSAGEW)(HWND, UINT, WPARAM, LPARAM);
		typedef HANDLE (WINAPI *CREATEEVENTW)(LPVOID, int, int, LPVOID);
		typedef int (WINAPI *SETEVENT)(HANDLE);
		typedef DWORD (WINAPI *WAITFORSINGLEOBJECT)(HANDLE,DWORD);
		typedef int (WINAPI *CLOSEHANDLE)(HANDLE);
		typedef void (WINAPI *COTASKMEMFREE)(LPVOID);
		typedef int (WINAPI *MESSAGEBOXW)(HWND, LPCWSTR, LPCWSTR, UINT);
		typedef int (__cdecl *WSPRINTFW)(LPWSTR, LPWSTR, ...);
		typedef HRESULT (WINAPI *STRRETTOBUFW)(STRRET*, LPITEMIDLIST, LPWSTR, UINT);

		/* --------------------------------------------------------------------------------------
		   -----------------------------------Real code follows----------------------------------
		   -------------------------------------------------------------------------------------- */

		#pragma pack(push, 1) // disable struct padding to make things a bit easier for me when populating the struct in AutoHotkey below
		typedef struct {
			LPVOID lpHookFunc;
			char IID_IFolderView[16]; // CBA to include the proper definition for GUID
			char IID_IEnumIDList[16];
			char IID_IPersistFolder2[16];
			char IID_IShellFolder[16];
			int openFolderPathInstead;
			SIZE_T allocatedSize;
			LPWSTR selectedFilenames;
			HHOOK msgHook;
			HANDLE hThreadWaitEvent;
		} ThreadData, *PThreadData;
		#pragma pack(pop)

		LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
		{
			CALLNEXTHOOKEX CallNextHookEx = (CALLNEXTHOOKEX)%CallNextHookEx%;
			if (code == 0) { // HC_ACTION
				PMSG msg = (PMSG)lParam;
				if (msg->hwnd == (HWND)%fileDialogHwnd% && msg->message == WM_NULL) { // only work if activated through the message we posted
					PThreadData threadData = (PThreadData)%pRemoteThreadData%;

					// We're on the main thread, no need for the hook to be called again after this
					UNHOOKWINDOWSHOOKEX UnhookWindowsHookEx = (UNHOOKWINDOWSHOOKEX)%UnhookWindowsHookEx%;
					UnhookWindowsHookEx(threadData->msgHook); 
					threadData->msgHook = 0;

					SENDMESSAGEW SendMessageW = (SENDMESSAGEW)%SendMessageW%;
					IUnknown *iShellBrowser = (IUnknown*)SendMessageW((HWND)%fileDialogHwnd%, 0x407, 0, 0); // WM_GETISHELLBROWSER
					if (iShellBrowser) {
						iShellBrowser->lpVtbl->AddRef(iShellBrowser);
						IUnknown *iShellView = NULL;
						((partIShellBrowserVtbl*)iShellBrowser->lpVtbl)->QueryActiveShellView(iShellBrowser, (LPVOID*)&iShellView);
						if (iShellView) {
							// read https://blogs.msdn.microsoft.com/oldnewthing/20040720-00/?p=38393 to understand how this works ('cause I sure as hell don't know)
							IUnknown *iFolderView = NULL;
							iShellView->lpVtbl->QueryInterface(iShellView, &(threadData->IID_IFolderView), (LPVOID*)&iFolderView);
							if (iFolderView) {
								IUnknown *iPersistFolder = NULL;
								((partIFolderViewVtbl*)iFolderView->lpVtbl)->GetFolder(iFolderView, threadData->IID_IPersistFolder2, (LPVOID*)&iPersistFolder);
								if (iPersistFolder) {
									IUnknown *iShellFolder = NULL;
									iPersistFolder->lpVtbl->QueryInterface(iPersistFolder, threadData->IID_IShellFolder, (LPVOID*)&iShellFolder);
									if (iShellFolder) {
										int cItems = 0;
										VIRTUALALLOCEX VirtualAllocEx = (VIRTUALALLOCEX)%VirtualAllocEx%;

										if (!threadData->openFolderPathInstead)
											((partIFolderViewVtbl*)iFolderView->lpVtbl)->ItemCount(iFolderView, 0x1, &cItems); // SVGIO_SELECTION - find out how many selected items there are
										cItems = ((threadData->openFolderPathInstead = cItems < 1)) ? 1 : cItems;
										threadData->allocatedSize = (((%MAX_PATH% + 1) * sizeof(WCHAR)) * cItems) + (2 * sizeof(WCHAR)); // set aside enough space to hold the filenames, with null terminators of course, and extra space for the double-null terminator at the end
										if ((threadData->selectedFilenames = VirtualAllocEx((HANDLE)-1, NULL, threadData->allocatedSize, %MEM_COMMIT%, %PAGE_READWRITE%))) { // use VirtualAllocEx because we can then free the memory from the AutoHotkey script
											//for (;;) {
												LPWSTR target = threadData->selectedFilenames;
												WCSLEN wcslen = (WCSLEN)%wcslen%;
												COTASKMEMFREE CoTaskMemFree = (COTASKMEMFREE)%CoTaskMemFree%;
												STRRETTOBUFW StrRetToBufW = (STRRETTOBUFW)%StrRetToBufW%;

												IUnknown *iEnumIdList = NULL;										
												LPITEMIDLIST pidlItem = NULL;
												STRRET str;
												int written = 0;

												if (threadData->openFolderPathInstead) {
													/*	MESSAGEBOXW MessageBoxW = (MESSAGEBOXW)%MessageBoxW%;
														WSPRINTFW wsprintfW = (WSPRINTFW)%wsprintfW%;
														WCHAR fmt[] = L"`%ld";
														WCHAR err[100];
														wsprintfW(err, fmt, ((partIShellFolderVtbl*)iShellFolder->lpVtbl)->GetDisplayNameOf(iShellFolder, 0, 0x80058000, &str));
														MessageBoxW(NULL, err, err, 0); */

													if (((partIPersistFolder2Vtbl*)iPersistFolder->lpVtbl)->GetCurFolder(iPersistFolder, &pidlItem) >= 0)
														goto writeString;
												} else {
													((partIFolderViewVtbl*)iFolderView->lpVtbl)->Items(iFolderView, 0x1, threadData->IID_IEnumIDList, (LPVOID*)&iEnumIdList);
													if (iEnumIdList) {
														ULONG celtFetched = 0;
														while ((((partIEnumIDListVtbl*)iEnumIdList->lpVtbl)->Next(iEnumIdList, 1, &pidlItem, &celtFetched) >= 0) && celtFetched == 1 && written < cItems) { // while we keep getting a PIDL:
															writeString:
															if ((((partIShellFolderVtbl*)iShellFolder->lpVtbl)->GetDisplayNameOf(iShellFolder, threadData->openFolderPathInstead ? 0 : pidlItem, 0x80058000, &str) >= 0)) { // SIGDN_FILESYSPATH
																if (StrRetToBufW(&str, pidlItem, target, %MAX_PATH%) >= 0) {
																	target += wcslen(target) + 1; // shift the pointer to now point just after the written string + the null terminator
																	written++; // make sure not to include any new paths that are included between us getting the count and actually iterating, just in case - CBA to realloc
																}
															}
															CoTaskMemFree(pidlItem);
															if (threadData->openFolderPathInstead)
																break;
														}
														if (iEnumIdList)
															iEnumIdList->lpVtbl->Release(iEnumIdList);
													}
												}
												if (*target) {
													*target = '\0'; // make sure string is double null-terminated
													*target-- = '\0'; // make sure string has its normal null terminator
												}
												//break;
											//}
										} else {
											threadData->allocatedSize = 0;
										}
										iShellFolder->lpVtbl->Release(iShellFolder);
									}
									iPersistFolder->lpVtbl->Release(iPersistFolder);
								}
								iFolderView->lpVtbl->Release(iFolderView);
							}
							iShellView->lpVtbl->Release(iShellView);
						}
						iShellBrowser->lpVtbl->Release(iShellBrowser);
					}
					
					SETEVENT SetEvent = (SETEVENT)%SetEvent%;
					SetEvent(threadData->hThreadWaitEvent); // signal the event to get the ThreadFunc to exit

					return 0;
				}
			}
			return CallNextHookEx(NULL, code, wParam, lParam);
		}

		UINT GetMsgProcSize(void) { return (PBYTE) GetMsgProcSize - (PBYTE) GetMsgProc; }

		DWORD WINAPI ThreadProc(PThreadData threadData)
		{
			DWORD ret = 0;
			if (threadData) {
				CREATEEVENTW CreateEventW = (CREATEEVENTW)%CreateEventW%;
				if ((threadData->hThreadWaitEvent = CreateEventW(NULL, 0, 0, NULL))) {
					CLOSEHANDLE CloseHandle = (CLOSEHANDLE)%CloseHandle%;
					SETWINDOWSHOOKEXW SetWindowsHookExW = (SETWINDOWSHOOKEXW)%SetWindowsHookExW%; // set up a hook so that we can run code on the same thread the open folder dialog box is displaying itself on - COM is usually touchy about multithread use
					if ((threadData->msgHook = SetWindowsHookExW(3, threadData->lpHookFunc, 0, %fileDialogTid%))) { // WH_GETMESSAGE
						POSTMESSAGEW PostMessageW = (POSTMESSAGEW)%PostMessageW%;
						PostMessageW((HWND)%fileDialogHwnd%, WM_NULL, 0, 0); // fire up the hook function ASAP

						WAITFORSINGLEOBJECT WaitForSingleObject = (WAITFORSINGLEOBJECT)%WaitForSingleObject%;
						if (WaitForSingleObject(threadData->hThreadWaitEvent, %INFINITE%) == 0x00000000L) // WAIT_OBJECT_0
							ret = 1;
					}
					CloseHandle(threadData->hThreadWaitEvent);
				}
			}
			return ret;
		}

		UINT noCompetitionMandemFaster(void) { return (PBYTE) noCompetitionMandemFaster - (PBYTE) ThreadProc; }
		)

		if (!(ctx := CompileC(injected_func,, False)))
			throw

		if (!(size := DllCall("libtcc\tcc_relocate", "Ptr", ctx.tccState, "Ptr", 0, "CDecl"))) ; get size of compiled code
			throw

		if (!(pCompiled := DllCall("GlobalAlloc", "UInt", GMEM_FIXED := 0x0000, "Ptr", size, "Ptr"))) ; allocate memory for said code
			throw

		if (DllCall("libtcc\tcc_relocate", "Ptr", ctx.tccState, "Ptr", pCompiled, "CDecl") == -1) ; get tcc to write the compiled code into the allocated memory
			throw

		if (!DllCall("VirtualProtect", "Ptr", pCompiled, "Ptr", size, "UInt", PAGE_EXECUTE_READWRITE := 0x40, "UInt*", 0)) ; Make sure we can execute from memory chunk
			throw

		if (!(cbThreadRoutine := DllCall(DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "noCompetitionMandemFaster", "CDecl Ptr"), "CDecl UInt"))) ; get the size the function takes up
			throw

		if (!(pThreadRoutine := DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "_ThreadProc@" . (1 * A_PtrSize), "CDecl Ptr"))) ; fuck me... get pointer to thread function copied into remote process
			throw

		if (!(cbGetMsgProc := DllCall(DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "GetMsgProcSize", "CDecl Ptr"), "CDecl UInt")))
			throw

		if (!(pGetMsgProc := DllCall("libtcc\tcc_get_symbol", "Ptr", ctx.tccState, "AStr", "_GetMsgProc@" . (3 * A_PtrSize), "CDecl Ptr")))
			throw

		; allocate memory for the process' copy of the message hook function
		if (!(pGetMsgFunc := DllCall("VirtualAllocEx", "Ptr", hProcess, "Ptr", 0, "Ptr", cbGetMsgProc, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")))
			throw

		if (!DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pGetMsgFunc, "Ptr", pGetMsgProc, "Ptr", cbGetMsgProc, "Ptr", 0)) ; write the message hook function into the process' memory
			throw

		NumPut(pGetMsgFunc, threadData,, "Ptr") ; threadData->lpHookFunc - memory for this is allocated after this is compiled as I need to know the function's size, which I only get after compiling. So put the pointer to the function into the ThreadData struct 
		DllCall("ole32\CLSIDFromString", "WStr", "{cde725b0-ccc9-4519-917e-325d72fab4ce}", "Ptr", &threadData+A_PtrSize) ; threadData->IID_IFolderView - it's easier to get these from AutoHotkey beforehand
		DllCall("ole32\CLSIDFromString", "WStr", "{000214F2-0000-0000-C000-000000000046}", "Ptr", &threadData+A_PtrSize+16) ; threadData->IID_IEnumIDList
		DllCall("ole32\CLSIDFromString", "WStr", "{1AC3D9F0-175C-11d1-95BE-00609797EA4F}", "Ptr", &threadData+A_PtrSize+(16 * 2)) ; threadData->IID_IPersistFolder2
		DllCall("ole32\CLSIDFromString", "WStr", "{000214E6-0000-0000-C000-000000000046}", "Ptr", &threadData+A_PtrSize+(16 * 3)) ; threadData->IID_IShellFolder
		;NumPut(True, threadData, A_PtrSize+(16*4), "Int") ; threadData.openFolderPathInstead - uncomment if you want to get the path of the opened folder regardless of whether files are selected

		if (!DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData, "Ptr", &threadData, "Ptr", VarSetCapacity(threadData), "Ptr", 0)) ; now copy the ThreadData over now that we've populated it
			throw

		; do what we did for the message hook function for the ThreadFunc
		if (!(pRemoteThreadFunc := DllCall("VirtualAllocEx", "Ptr", hProcess, "Ptr", 0, "Ptr", cbThreadRoutine, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")))
			throw

		if (!DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadFunc, "Ptr", pThreadRoutine, "Ptr", cbThreadRoutine, "Ptr", 0)) 
			throw

		; start the thread in the remote process
		hRemoteThread := DllCall("CreateRemoteThread", "Ptr", hProcess, "Ptr", 0, "Ptr", 0, "Ptr", pRemoteThreadFunc, "Ptr", pRemoteThreadData, "UInt", 0, "Ptr", 0, "Ptr")
		if (hRemoteThread) {
			Progress m b, Waiting for remote thread to finish,,, Tahoma
			Progress 99
			loop
				r := DllCall(MsgWaitForMultipleObjectsEx, "uint", 1, "ptr*", hRemoteThread, "uint", INFINITE, "uint", 0x4FF, "uint", 0x6), Sleep -1 ; stolen from Lexikos
			until (r == 0 || r == -1)
			Progress Off
			if (DllCall("GetExitCodeThread", "Ptr", hRemoteThread, "UInt*", exitCode)) { ; get the number the ThreadFunc returned
				if (exitCode == 1) { ; if our thread exited normally
					if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData+A_PtrSize+(16*4)+4, "Ptr*", allocatedSize, "Ptr", A_PtrSize, "Ptr*", br) && br == A_PtrSize && allocatedSize) { ; get the size of the filename string
						if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData+A_PtrSize+(16*4)+4+A_PtrSize, "Ptr*", lpSelectedFilenames, "Ptr", A_PtrSize, "Ptr*", br) && br == A_PtrSize && lpSelectedFilenames) {	; get the address of where the filename string is stored						
							VarSetCapacity(filenamesBuf, allocatedSize) ; allocate space for our copy of it
							if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", lpSelectedFilenames, "Ptr", &filenamesBuf, "Ptr", allocatedSize, "Ptr*", br) && br == allocatedSize) { ; read the filename string from the remote process and copy it over
								DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", pRemoteThreadData+A_PtrSize+(16*4), "Int*", openFolderPathInstead, "Ptr", 4, "Ptr", 0)
								fileNames := &filenamesBuf
								while (*fileNames) { ; keep reading the filenames until we reach a double-null terminated one: https://blogs.msdn.microsoft.com/oldnewthing/20091008-00?p=16443
									MsgBox % (openFolderPathInstead ? "Open folder path:`r`n" : "") . (filename := StrGet(fileNames, MAX_PATH, "UTF-16")) ; and if you see "Open folder path" more than once, something went wrong
									fileNames += (DllCall("ntdll\wcslen", "Ptr", fileNames, "CDecl Ptr") * 2) + 2
								}
							}
						}
					}
				}
			}
		} else throw
	} ;catch {}
	finally {
		if (cleanup) {
			if (hProcess) {
				if (lpSelectedFilenames) ; free the memory the injected function allocated to store the strings
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", lpSelectedFilenames, "Ptr", 0, "UInt", MEM_RELEASE := 0x8000)

				if (hRemoteThread)
					DllCall("CloseHandle", "Ptr", hRemoteThread)

				if (pRemoteThreadFunc)
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", pRemoteThreadFunc, "Ptr", 0, "UInt", MEM_RELEASE)

				if (pGetMsgFunc)
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", pGetMsgFunc, "Ptr", 0, "UInt", MEM_RELEASE)

				if (pRemoteThreadData)
					DllCall("VirtualFreeEx", "Ptr", hProcess, "Ptr", pRemoteThreadData, "Ptr", 0, "UInt", MEM_RELEASE)

				; close our handle to the process
				DllCall("CloseHandle", "Ptr", hProcess)
			}

			if (pCompiled)
				DllCall("GlobalFree", "Ptr", pCompiled, "Ptr") ; free memory for compiled code

			if (IsObject(ctx))
				TccCleanup(ctx) ; free libtcc context and unload libtcc.dll
		}
	}
}

CompileC(codeToCompile, outputToMemory := True, performRelocation := True, tcc_dir := "")
{
	static ptrErrCb := 0, TCC_RELOCATE_AUTO := 1, orrrr_clic := 270

	; if we're called as a result of libtcc's error callback, then:
	if ((ptrErrCb) && ((x := A_EventInfo == ptrErrCb) || codeToCompile == orrrr_clic)) {
		if (codeToCompile == orrrr_clic && x && outputToMemory)
			MsgBox % StrGet(outputToMemory, "CP0") ; show the error message
		return
	}

	if (!ptrErrCb)
		ptrErrCb := RegisterCallback(A_ThisFunc, "Fast CDecl", 2)

	if (!tcc_dir)
		tcc_dir := A_ScriptDir

	if ((libtcc := DllCall("LoadLibrary", "Str", tcc_dir . "\libtcc.dll", "Ptr"))) {
		ctx := {hModuleTcc: libtcc}
		if ((s := DllCall("libtcc\tcc_new", "CDecl Ptr"))) { 
			success := False

			DllCall("libtcc\tcc_set_error_func", "Ptr", s, "Ptr", orrrr_clic, "Ptr", ptrErrCb, "CDecl")
			DllCall("libtcc\tcc_set_output_type", "Ptr", s, "Int", outputToMemory ? 0 : 3, "CDecl")
			DllCall("libtcc\tcc_set_options", "Ptr", s, "AStr", "-nostdinc -nostdlib", "CDecl") ; since all we have is libtcc.dll, don't look for include files we don't have or libtcc1.a

			if (DllCall("libtcc\tcc_compile_string", "Ptr", s, "AStr", codeToCompile, "CDecl") == 0) {
				if (outputToMemory) {
					success := performRelocation ? DllCall("libtcc\tcc_relocate", "Ptr", s, "Ptr", TCC_RELOCATE_AUTO, "CDecl") == 0 : True
				} else {
					success := DllCall("libtcc\tcc_output_file", "Ptr", s, "AStr", "test.obj", "CDecl") == 0
				}
			}

			ctx := {hModuleTcc: libtcc, tccState: s}
			if (success && outputToMemory)
				return ctx
		}
		TccCleanup(ctx)
	} else {
		dw := A_LastError
		ccherrFmt := DllCall("FormatMessage", "UInt", 0x00000100 | 0x00001000 | 0x00000200, "Ptr", 0, "UInt", dw, "UInt", 1024, "Ptr*", errFmt, "UInt", 0, "Ptr", 0, "UInt")
		err := "libtcc was unable to be loaded: " . (ccherrFmt ? StrGet(errFmt, ccherrFmt) : "") . " (" . dw . ")"
		if (ccherrFmt)
			DllCall("kernel32\LocalFree", "Ptr", errFmt, "Ptr")
		throw err
	}

	return 0
}

TccCleanup(tccContext)
{
	if (tccContext) {
;		if (tccContext.ptrErrCb) {
;			DllCall("GlobalFree", "Ptr", tccContext.ptrErrCb, "Ptr")
;			tccContext.ptrErrCb := 0
;		}
		if (tccContext.tccState) {
			DllCall("libtcc\tcc_delete", "Ptr", tccContext.tccState, "CDecl")
			tccContext.tccState := 0
		}
		if (tccContext.hModuleTcc) {
			DllCall("FreeLibrary", "Ptr", tccContext.hModuleTcc)
			tccContext.hModuleTcc := 0
		}
	}
}

; Very little error checking. TBH, I'd be surprised if someone actually uses this, so...
ProcAddressFromRemoteProcess(hProcess, hModule, targetFuncName, ByRef Magic := 0)
{
	; MarkHC: https://www.unknowncheats.me/forum/1457119-post3.html
	IMAGE_DOS_SIGNATURE := 0x5A4D, IMAGE_NT_SIGNATURE := 0x4550
	if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule, "UShort*", header, "Ptr", 2, "Ptr*", br) && br == 2 && header == IMAGE_DOS_SIGNATURE) {
		if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+60, "Int*", e_lfanew, "Ptr", 4, "Ptr*", br) && DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew, "UInt*", Signature, "Ptr", 4, "Ptr*", br)) {
			if (Signature == IMAGE_NT_SIGNATURE) {
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew+24, "UShort*", Magic, "Ptr", 2, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew+24+(Magic == (IMAGE_NT_OPTIONAL_HDR64_MAGIC := 0x20b) ? 112 : 96), "UInt*", exportTableRVA, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+20, "UInt*", NumberOfFunctions, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+24, "UInt*", NumberOfNames, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+28, "UInt*", AddressOfFunctions, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+32, "UInt*", AddressOfNames, "Ptr", 4, "Ptr*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+36, "UInt*", AddressOfNameOrdinals, "Ptr", 4, "Ptr*", br)
				
				VarSetCapacity(functions, NumberOfFunctions * 4)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfFunctions, "Ptr", &functions, "Ptr", NumberOfFunctions * 4, "Ptr*", br)
				VarSetCapacity(exports, NumberOfNames * 4)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfNames, "Ptr", &exports, "Ptr", NumberOfNames * 4, "Ptr*", br)
				VarSetCapacity(ordinals, NumberOfNames * 2)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfNameOrdinals, "Ptr", &ordinals, "Ptr", NumberOfNames * 2, "Ptr*", br)
				
				Loop % NumberOfNames {
					addr := NumGet(exports, 4 * (A_Index - 1), "UInt")
					i := 0, funcName := ""
					while (true) {
						DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+addr+i, "Int*", letter, "Ptr", 1, "Ptr*", br)
						if (!letter)
							break
						funcName .= Chr(letter)
						i += 1
					}
					if (funcName == targetFuncName) {
						ordinal := NumGet(ordinals, 2 * (A_Index - 1), "UShort")
						return NumGet(functions, 4 * ordinal, "UInt") + hModule
					}
				}
			}
		}
	}
	return 0
}
If nothing's selected, the path of the folder is returned instead; else, you should hopefully get back the full paths of selected items. You can force the retrieval of the folder path regardless of whether anything is selected by uncommenting the "NumPut(True" line.

I tried in WIndows 7 64-bit SP1 on a CID open in Chrome and I was successful on my first attempt: Image
Last edited by qwerty12 on 09 Jun 2017, 07:25, edited 3 times in total.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

02 May 2017, 15:59

qwerty12, this 'desktop get selected files' script of yours is absolutely amazing, cheers.

In summary, I'm interested in get/set, selected 'listview' items/navigated-to folder or 'folder'/file in Edit control, for Common File Dialogs/Common Item Dialogs, that are internal/external to the AHK script. Which it seems you have collected most/all of the techniques for.

Other issues I plan to return to:
- My Notepad replacement 'notify of file selection' issue, for both Common File Dialogs and Common Item Dialogs.
- If a script can be significantly simplified for internal dialogs only, to make a separate simplified script for it.
- The get GDI handles script.
- Explorer windows: set which columns are displayed, change the left/right column order, and set columns to ascending/descending order. Although one workaround is that the Acc library can be used to click on a column header to trigger a sort.

I'm going to have to spend a good deal of time going over your scripts. Possibly in a month's time, because I've got a lot I need to finish.

Btw how did you come across the Desktop link, and have you got any other object-and-Explorer or object scripts in mind to do? Thanks again.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

02 May 2017, 17:47

jeeswg wrote:- If a script can be significantly simplified for internal dialogs only, to make a separate simplified script for it.
For internal dialogs, it's probably easier to just use the actual file dialog APIs.
Btw how did you come across the Desktop link
Just a Google search
and have you got any other object-and-Explorer or object scripts in mind to do? Thanks again.
No, I don't really know what I'm doing here - these are just (mostly) AutoHotkey versions of the code found on Raymond Chen's blog...
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

20 Jun 2017, 00:32

@qwerty12: I've made a few small edits and wrapped up your code for getting the paths of files on Desktop as a function:

I've:
- brought out the curly brackets from the ends of lines
- replaced 'path' with 'vPath' so that the function will work without #NoEnv being set
- made it add all the paths to a variable
- prepared a variable with sufficient capacity in advance, by using ControlGet on the Desktop listview
- given the user the option of what separator string to use

Code: Select all

q:: ;desktop get paths of selected files (tested on Windows 7)
MsgBox, % JEE_ExpGetSelDesktop()
return

JEE_ExpGetSelDesktop(vSep="`n")
{
	;slight edit of code by qwerty12
	;get full paths of selected files on Desktop and Common File Dialogs - AutoHotkey Community
	;https://autohotkey.com/boards/viewtopic.php?f=5&t=31135&p=146043#p146043

	ControlGet, vCount, List, Count Selected, SysListView321, ahk_class Progman
	if (vCount = 0)
		return ""
	vOutput := ""
	VarSetCapacity(vOutput, (260+StrLen(vSep))*vCount*2)

	; Conversion of https://blogs.msdn.microsoft.com/oldnewthing/20160530-00?p=93535
	; FindDesktopFolderView adapted from Lexikos' ShellRun code

	shellWindows := ComObjCreate("Shell.Application").Windows
	VarSetCapacity(_hwnd, 4, 0)
	desktop := shellWindows.FindWindowSW(0, "", 8, ComObj(0x4003, &_hwnd), 1)
	if ptlb := ComObjQuery(desktop
		, "{4C96BE40-915C-11CF-99D3-00AA004AE837}"  ; SID_STopLevelBrowser
		, "{000214E2-0000-0000-C000-000000000046}") ; IID_IShellBrowser
	{
		if DllCall(NumGet(NumGet(ptlb+0)+15*A_PtrSize), "ptr", ptlb, "ptr*", psv:=0) = 0
		{
			VarSetCapacity(IID_IShellFolder, 16)
			DllCall("ole32\CLSIDFromString", "WStr", "{000214E6-0000-0000-C000-000000000046}", "Ptr", &IID_IShellFolder)
			if ((spView := ComObjQuery(psv, "{cde725b0-ccc9-4519-917e-325d72fab4ce}")))
			{
				if (DllCall(NumGet(NumGet(spView+0)+5*A_PtrSize), "ptr", spView, "ptr", &IID_IShellFolder, "ptr*", spFolder:=0) = 0)
				{
					if (DllCall(NumGet(NumGet(spView+0)+7*A_PtrSize), "ptr", spView, "uint", SVGIO_SELECTION := 0x1, "int*", selItems) == 0 && selItems)
					{
						VarSetCapacity(IID_IEnumIDList, 16)
						DllCall("ole32\CLSIDFromString", "WStr", "{000214F2-0000-0000-C000-000000000046}", "Ptr", &IID_IEnumIDList)
						if (DllCall(NumGet(NumGet(spView+0)+8*A_PtrSize), "ptr", spView, "uint", SVGIO_SELECTION, "Ptr", &IID_IEnumIDList, "Ptr*", iEnumIdList) == 0)
						{
							VarSetCapacity(STRRET, 272), VarSetCapacity(vPath, 261 * (!!A_IsUnicode + 1))
							while (DllCall(NumGet(NumGet(iEnumIdList+0)+3*A_PtrSize), "ptr", iEnumIdList, "uint", 1, "Ptr*", pidlItem, "UInt*", celtFetched) == 0 && celtFetched == 1)
							{
								if (DllCall(NumGet(NumGet(spFolder+0)+11*A_PtrSize), "ptr", spFolder, "Ptr", pidlItem, "UInt", SIGDN_FILESYSPATH := 0x80058000, "Ptr", &STRRET) == 0)
								{
									if (DllCall("shlwapi\StrRetToBuf", "Ptr", &STRRET, "Ptr", pidlItem, "Str", vPath, "UInt", 260) == 0)
										vOutput .= vPath vSep
								}
								DllCall("ole32\CoTaskMemFree", "Ptr", pidlItem)
							}
							ObjRelease(iEnumIdList)
						}
					}
					ObjRelease(spFolder)
				}
				ObjRelease(spView)
			}
			ObjRelease(psv)
		}
		ObjRelease(ptlb)
	}
	return SubStr(vOutput, 1, -StrLen(vSep))
}
[EDIT:] Btw re. libtcc.dll, you might be interested in creating machine code functions instead, see:
InBuf function currently 32-bit only (machine code binary buffer searching) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=28393
Last edited by jeeswg on 20 Jun 2017, 05:54, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

20 Jun 2017, 05:51

jeeswg wrote:@qwerty12: I've made a few small edits and wrapped up your code for getting the paths of files on Desktop as a function:
Very useful edits, thank you, but while writing this, I came across this post of Lexikos'. I found out then that you could get the selected names on the desktop through an IDispatch interface, which is far easier... I didn't bump this thread (not my style), but I did edit my previous post in this thread with a link to Lexikos' post: "Last edited by qwerty12 on 09 Jun 2017, 13:25, edited 3 times in total."

[quote]- prepared a variable with sufficient capacity in advance, by using ControlGet on the Desktop listview[/quote]

I see what you're doing and I like it, but I think you're missing the use of the vCount variable in your VarSetCapacity line.


I see you edited your post before I had a chance to post this :-)
[EDIT:] Btw re. libtcc.dll, you might be interested in creating machine code functions instead, see:
InBuf function currently 32-bit only (machine code binary buffer searching) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=28393
MCode wouldn't be the right approach here: it's the same as what I'm already doing, except that currently the C code is in the AHK script itself (bastardised as it is) as opposed to a bunch of base64-encoded opcodes (and I know what I far prefer seeing). I also don't get the benefit of being able to use AHK as a preprocessor, which is extremely important because SetWindowsHookEx doesn't provide a way of passing an arbitrary pointer to the callback function; this is why I allocate the memory for the threadData struct in the remote process first - I then have an address I can hardcode into the compiled code. It's also nicer to be able to hardcode the address for functions instead of saving them into the threadData struct and then getting the C code to look them up out of the struct.

If you want to supply code that's already compiled (it is true that MSVC or GCC will produce far more optimised code than TCC), it would be far, far easier to just compile it into a DLL, with its fancy features like dedicated memory for global variables, and just share that, using AutoHotkey to load that into the remote process.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

20 Jun 2017, 06:12

That Lexikos script looks great, I will try and adapt it for listing files on Desktop. I had wondered if there was a more direct way to get access to the interface, I don't really know much about what starting point object to use, and then the quickest route to get the desired interface, i.e. 'hopping' between interfaces.

I've added vCount to the VarSetCapacity line now, cheers for pointing it out, I hadn't noticed. (In my defence, the script works without it!)

Re. the Common File Dialog script. I'd be tempted to create two versions: a machine code version (no additional files needed) and a dll required version if it is superior in some way. Although from what you're saying it sounds like you do need the dll in this case. You're right that it's better to have the C/C++ code, although, you could just use machine code *and* include it in the script as comments, and/or add a link to it, possibly with some details, e.g. 1/2 lines, of how you converted the C/C++ code to the machine code.

[EDIT:] It's always a major advantage for scripts, whenever they: don't require external files, don't require Admin mode, don't require dll injection (or multithreading?) (i.e. require AutoHotkey_H), and are written to be as compatible as possible, requiring minimal or no changes to work in other older/newer AHK versions.

Cheers, I think at least twice I've had it reported in my feed that you made updates to posts in threads I've been in. I checked the posts, and I couldn't tell what had changed. So maybe if you use '[EDIT] / [EDIT:] / EDIT / EDIT: / edit / edit:' or something like that, I could tell. Btw is there some special way to 'bump' a post without changing it!?

I'll be going over all posts that feature both me and you at some point ... it will be interesting.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

20 Jun 2017, 06:45

FindWindowSW line based on code by Lexikos here:
Create new file in current explorer window? - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 361#p53361

Code: Select all

q:: ;desktop get paths of selected files (tested on Windows 7)
;MsgBox, % JEE_ExpGetSelDesktop()
MsgBox, % Clipboard := JEE_ExpGetSelDesktop("`r`n")
return

JEE_ExpGetSelDesktop(vSep="`n")
{
	oWindows := ComObjCreate("Shell.Application").Windows
	VarSetCapacity(hWnd, 4, 0)
	;SWC_DESKTOP := 0x8 ;VT_BYREF := 0x4000 ;VT_I4 := 0x3 ;SWFO_NEEDDISPATCH := 0x1
	oWin := oWindows.FindWindowSW(0, "", 8, ComObject(0x4003, &hWnd), 1)
	vCount := oWin.Document.SelectedItems.Count
	vOutput := ""
	VarSetCapacity(vOutput, (260+StrLen(vSep))*vCount*2)
	for oItem in oWin.Document.SelectedItems
		if !(SubStr(oItem.path, 1, 3) = "::{")
			vOutput .= oItem.path vSep
	oWindows := oWin := oItem := ""
	return SubStr(vOutput, 1, -StrLen(vSep))
}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: get full paths of selected files on Desktop and Common File Dialogs

20 Jun 2017, 07:01

jeeswg wrote:That Lexikos script looks great, I will try and adapt it for listing files on Desktop. I had wondered if there was a more direct way to get access to the interface, I don't really know much about what starting point object to use, and then the quickest route to get the desired interface, i.e. 'hopping' between interfaces.


Yes, neither do I. I wish I had known that was possible before I wrote my non-IDispatch one as it's so much easier :(
Re. the Common File Dialog script. I'd be tempted to create two versions: a machine code version (no additional files needed) and a dll required version if it is superior in some way. Although from what you're saying it sounds like you do need the dll in this case.
It wouldn't be impossible to do it in MCode that's injected into the remote process like I do my TCC-compiled code (off the top of my head, you'd need to make the C code look for the function pointers from the threadData struct and, additionally, you'd need to numput the address of whereever the threadData struct has been allocated in the rmeote process into the MCode buffer directly. Again, blame SetWindowsHookEx :)), but providing DLLs with the compiled code would be far easier as there'd be no creating multiple buffers to store the two functions inside the remote process, no need to look up the function addresses for WinAPI functions yourself etc. Though you would need to distribute two DLLs with your script. OTOH, it's not like it's any different with TCC now and I think you would also get the benefit of being able to target x86 process from an x64 script too.

Of course, who says that writing C code has to be the way? Like you mention, you could just as easily use AutoHotkey_H and do it with AutoHotkey code running inside the remote process :-)
You're right that it's better to have the C/C++ code, although, you could just use machine code *and* include it in the script as comments, and/or add a link to it, possibly with some details, e.g. 1/2 lines, of how you converted the C/C++ code to the machine code.
For me, while I'm aware half of the code I post looks dodgy as hell, I find MCode downright arcane. I'm sure the MCode for these functions would come out as pretty large, making me especially mistrustful of it, and I don't have the knowledge to use objdump or IDA to verify that, indeed, the MCode roughly matches the commented code given. I mean, I'm not saying my current script looks great, but assuming the TCC DLL is OK, you know what's being executed in the remote process because the C code is compiled then and there from what's in the script.
[EDIT:] It's always a major advantage for scripts, whenever they: don't require external files, don't require Admin mode, don't require dll injection (or multithreading?) (i.e. require AutoHotkey_H), and are written to be as compatible as possible, requiring minimal or no changes to work in other older/newer AHK versions.
I agree, but sometimes ya gotta do what ya gotta do. I mean this script's raison d'être is that WM_GETISHELLBROWSER returns an address that's inside the remote process itself...
Cheers, I think at least twice I've had it reported in my feed that you made updates to posts in threads I've been in. I checked the posts, and I couldn't tell what had changed. So maybe if you use '[EDIT] / [EDIT:] / EDIT / EDIT: / edit / edit:' or something like that, I could tell. Btw is there some special way to 'bump' a post without changing it!?
You're right, sorry, I usually do add an "EDIT: " to the start/end of my posts but in this case, I didn't bother, opting to hide my old script inside a spoiler block and using instead. I think two consecutive posts by the same person causes this forum software to join the two together, but I think that's only done if the two posts are made on the same day or something.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

15 Aug 2017, 21:39

These are links for getting the folder/selected file from a Common Item Dialog (new since Windows Vista).
[script by qwerty12:]
get full paths of selected files on Desktop and Common File Dialogs - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 77#p145577
[notes by jeeswg:]
get full paths of selected files on Desktop and Common File Dialogs - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 83#p145883

I'm mentioning here in case anyone has found any ways to simplify the script, and/or, if anyone feels capable of converting it, so that AutoHotkey (i.e. AutoHotkey_L v1.1/v2.0) and AutoHotkeyMini.dll can be used instead.

I will be returning to the script at some point, but it may be many months, and I would have to acquire some new skills. I might even consider creating an exe that could return a path for a given hWnd. Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: get full paths of selected files on Desktop and Common File Dialogs

19 Aug 2017, 11:37

Here is a fairly basic script for getting the folder and selected file from a Common Item Dialog. The new style dialog that appears since Windows Vista e.g. for Notepad open/save as.

There is a control that seems to consistently contain the folder name.

Also, by grabbing the text from the Edit control, and checking for files in the folder, you can try to retrieve the path of the selected file. The script lists any files whose display name matches the text in the Edit control.

Note: if extensions are hidden, and more than one file with the same name (but different extensions) exists in the folder, then there are multiple possibilities for the selected file. E.g. 'MyImage.gif' and 'MyImage.jpg' would both have display name 'MyImage' if file extensions are set to hidden.

Code: Select all

q:: ;Common Item Dialog - get folder + selected file
WinGet, hWnd, ID, A
ControlGetText, vDir, ToolbarWindow322, % "ahk_id " hWnd
if !InStr(vDir, ": ")
	ControlGetText, vDir, ToolbarWindow323, % "ahk_id " hWnd
ControlGetText, vName, Edit1, % "ahk_id " hWnd
vDir := SubStr(vDir, InStr(vDir, ": ")+2)
vOutput := vDir "`r`n" vName "`r`n`r`n"

vDir := RTrim(vDir, "\")
if !(SubStr(vDir, 2, 1) = ":")
	if (vDir = "Libraries\Documents")
		vDir := A_MyDocuments
	else if (vDir = "Libraries\Pictures")
		vDir := "C:\Users\" A_UserName "\Pictures"
	else if (vDir = "Libraries\Videos")
		vDir := "C:\Users\" A_UserName "\Videos"
	else if (vDir = A_UserName)
		vDir := "C:\Users\" A_UserName

if (vDir = "Desktop")
{
	Loop, Files, % A_Desktop "\*", F
	{
		VarSetCapacity(vDName, 260*2, 0)
		DllCall("comdlg32\GetFileTitle", Str,A_LoopFileFullPath, Str,vDName, UShort,260, Short) ;get display name
		if (vDName = vName)
			vOutput .= A_LoopFileName "`r`n"
	}
	vDir := A_DesktopCommon
}

if InStr(FileExist(vDir), "D") ;if dir exists
{
	Loop, Files, % vDir "\*", F
	{
		VarSetCapacity(vDName, 260*2, 0)
		DllCall("comdlg32\GetFileTitle", Str,A_LoopFileFullPath, Str,vDName, UShort,260, Short) ;get display name
		if (vDName = vName)
			vOutput .= A_LoopFileName "`r`n"
	}
}

MsgBox, % vOutput
return
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: get full paths of selected files on Desktop and Common File Dialogs

16 Dec 2018, 10:22

There were just those running everywhere as it should be. Why don't you use this? What is the disadvantage of this?

https://autohotkey.com/board/topic/6072 ... /?p=382642

Code: Select all

q::MsgBox, % gst()  ; example
gst() {   ; GetSelectedText or FilePath in Windows Explorer  by Learning one 
	IsClipEmpty := (Clipboard = "") ? 1 : 0
	if !IsClipEmpty {
		ClipboardBackup := ClipboardAll
		While !(Clipboard = "") {
			Clipboard =
			Sleep, 10
		}
	}
	Send, ^c
	ClipWait, 0.1
	ToReturn := Clipboard, Clipboard := ClipboardBackup
	if !IsClipEmpty
	ClipWait, 0.5, 1
	Return ToReturn
}

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: inseption86, peter_ahk and 187 guests