drag-and-drop programmatically by using OleDropTargetInterface

Post your working scripts, libraries and tools
User avatar
jeeswg
Posts: 6722
Joined: 19 Dec 2016, 01:58
Location: UK

drag-and-drop programmatically by using OleDropTargetInterface

30 Sep 2018, 16:53

- This is a script to drag-and-drop programmatically onto Spek. It is intended as a prototype script that can programmatically drag-and-drop onto programs that use an OleDropTargetInterface.
- I do not have any plans to further develop this, although I welcome any efforts by others to potentially extend this.
- I was able to get this to work on Spek and LibreOffice Writer. Warning: it could potentially crash those programs, and it did crash other programs.
- The idea is to be able to bypass the Open dialog, to avoid having to use it.
- Credits to qwerty12 for collaborating with me, and to HotKeyIt for his InjectAhkDll function and AutoHotkeyMini dlls. Note: this script works with AutoHotkey_L v1.1, and doesn't require AutoHotkey_H.

Code: Select all

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

;drag-and-drop programmatically
;(by using OleDropTargetInterface)
;(and not WM_DROPFILES)
;(tested on Spek)

;how to use:
;you need AutoHotkey v1.1 U32 (note: Spek is 32-bit)
;you need the AutoHotkeyMini w32 dll (see the link below)
;(you do not need AutoHotkey_H)
;you need an mp3
;you need Spek (around 8MB)
;Spek – Free Acoustic Spectrum Analyzer / Spectrogram Viewer
;http://spek.cc/

;this injects the AutoHotkey dll into the program,
;warning: dll injection can potentially crash a program,
;warning: various programs crashed when I tried this,
;note: to drag-and-drop onto an x64/x32 process,
;the bitnesses of the AutoHotkey exe and AutoHotkeyMini dll must match the process

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

;[get x64 and x32 versions of AutoHotkeyMini.dll]
;GitHub - HotKeyIt/ahkdll-v1-release: AutoHotkey_H v1 release
;https://github.com/HotKeyIt/ahkdll-v1-release

vDir := "C:\Program Files\AutoHotkey"
if (A_PtrSize = 8)
	vPathDll := vDir "\AutoHotkeyMiniU64.dll"
else
	vPathDll := vDir "\AutoHotkeyMiniU32.dll"

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

;tested on Windows 7, AHK v1.1.30.00 U32, Spek (x86) and LibreOffice Writer (x86)
q:: ;drag-and-drop file onto program (if the bitnesses match, and if a drop target interface is available)
WinGet, hWnd, ID, A
WinGet, vPID, PID, % "ahk_id " hWnd
WinGet, vPName, ProcessName, % "ahk_id " hWnd

;if !(vPName = "spek.exe")
;	return
if !(JEE_ProcessIs64Bit(vPID) = (A_PtrSize=8))
{
	MsgBox, % "error: process bitness mismatch"
	return
}
pDropTarget := DllCall("user32\GetProp", Ptr,hWnd, Str,"OleDropTargetInterface", Ptr)
MsgBox, % pDropTarget
if !pDropTarget
	return

;pipe-separated list of paths
filenames := A_Desktop "\MyMp3.mp3"
filenames := A_ScriptDir "\MyMp3.mp3"

vScript1 := "hWnd := " hWnd
vScript1 .= "`r`n" "filenames := " Chr(34) filenames Chr(34)
Gosub SubGetScript

;MsgBox, % vScript1 "`r`n" vScript2
rThread := InjectAhkDll(vPID, vPathDll)
rThread.Exec(vScript1 "`r`n" vScript2)
rThread := ""
return

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

;based on code by qwerty12:
;SHMultiFileProperties - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=30483&p=145200#p145200

SubGetScript:
;verbatim script
vScript2 = ;continuation section
(% ` Join`r`n
	filenames := StrSplit(filenames, "|")
	path := filenames.1
	SplitPath, path,, dir
	for key, path in filenames
	{
		SplitPath, path, name
		filenames[key] := name
	}

	if (!IsObject(filenames) || !filenames.MaxIndex() || !filenames[1])
		return
	if (!(pUnk := DllCall("GetProp", "Ptr", hWnd, "Str", "OleDropTargetInterface", "Ptr")))
		return
	if (!VarSetCapacity(IID_IDataObject))
		VarSetCapacity(IID_IDataObject, 16)
		, DllCall("ole32\CLSIDFromString", "WStr", "{0000010e-0000-0000-C000-000000000046}", "Ptr", &IID_IDataObject)
	if (!VarSetCapacity(IID_IShellFolder))
		VarSetCapacity(IID_IShellFolder, 16)
		, DllCall("ole32\CLSIDFromString", "WStr", "{000214E6-0000-0000-C000-000000000046}", "Ptr", &IID_IShellFolder)

	DllCall("shell32\SHGetDesktopFolder", "Ptr*", pDesktop)
	if (dir = A_Desktop)
		pISF := pDesktop
	else
		DllCall("shell32\SHParseDisplayName", "WStr", dir, "Ptr", 0, "Ptr*", pidl_dir, UInt, 0, Ptr, 0)
		, DllCall(NumGet(NumGet(pDesktop+0)+5*A_PtrSize), "Ptr", pDesktop, "Ptr", pidl_dir, "UInt", 0, "Ptr", &IID_IShellFolder, "Ptr*", pISF) ; BindToObject
	if (pISF)
	{
		pDropTarget := ComObjQuery(pUnk, "{00000122-0000-0000-C000-000000000046}")
		VarSetCapacity(pidl_list, filenames.MaxIndex() * A_PtrSize)
		filenameCount := 0, ParseDisplayName := NumGet(NumGet(pISF+0)+3*A_PtrSize)
		for _, filename in filenames
			filenameCount += DllCall(ParseDisplayName, "Ptr", pISF, "Ptr", 0, Ptr, 0, "WStr", filename, "Ptr", 0, "Ptr", &pidl_list+(filenameCount * A_PtrSize), "Ptr", 0) >= 0x00
		if (filenameCount)
		{
			DllCall(NumGet(NumGet(pISF+0)+10*A_PtrSize), "Ptr", pISF, "Ptr", 0, "UInt", filenameCount, "Ptr", &pidl_list, "Ptr", &IID_IDataObject, "Ptr", 0, "Ptr*", pDataObject) ; GetUIObjectOf
			if (pDataObject)
				DllCall(NumGet(NumGet(pDropTarget+0)+3*A_PtrSize), "Ptr", pDropTarget, "Ptr", pDataObject, "UInt", 0, "Int64", 0, "UInt*", 1) ; DragEnter
				, ret := DllCall(NumGet(NumGet(pDropTarget+0)+6*A_PtrSize), "Ptr", pDropTarget, "Ptr", pDataObject, "UInt", 0, "Int64", 0, "UInt*", 1) >= 0x00 ; Drop
				, ObjRelease(pDataObject)
			Loop, % filenameCount
				if ((pidl := NumGet(pidl_list, (A_Index - 1) * A_PtrSize, "Ptr")))
					DllCall("ole32\CoTaskMemFree", "Ptr", pidl)
		}
		DllCall("ole32\CoTaskMemFree", "Ptr", pidl_dir)
		for _, obj in [pDropTarget, pISF]
			ObjRelease(obj)
		if !(dir = A_Desktop)
			ObjRelease(pDesktop)
	}
)
return

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

JEE_ProcessIs64Bit(vPIDOrName:="")
{
	if !(vPID := ProcessExist(vPIDOrName))
		return
	if !A_Is64bitOS
		return 0
	;PROCESS_QUERY_INFORMATION := 0x400
	hProc := DllCall("kernel32\OpenProcess", UInt,0x400, Int,0, UInt,vPID, Ptr)
	DllCall("kernel32\IsWow64Process", Ptr,hProc, IntP,vIsWow64Process)
	DllCall("kernel32\CloseHandle", Ptr,hProc)
	return !vIsWow64Process
}

;commands as functions (AHK v2 functions for AHK v1) - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=37&t=29689
ProcessExist(PIDorName:="")
{
    Process Exist, %PIDorName%
    return ErrorLevel
}

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

;[original version of InjectAhkDll]
;[SOLVED]get other process's working dir - Page 3 - Ask for Help - AutoHotkey Community
;https://autohotkey.com/board/topic/85304-solvedget-other-processs-working-dir/page-3#entry544650

;[get x64 and x32 versions of AutoHotkeyMini.dll]
;GitHub - HotKeyIt/ahkdll-v1-release: AutoHotkey_H v1 release
;https://github.com/HotKeyIt/ahkdll-v1-release

;by HotKeyIt (modified version by jeeswg to not require _Struct.ahk)
InjectAhkDll(PID,dll:="AutoHotkey.dll",script:=0)
{
	static PROCESS_ALL_ACCESS:=0x1F0FFF,MEM_COMMIT := 0x1000,MEM_RELEASE:=0x8000,PAGE_EXECUTE_READWRITE:=64
	,hKernel32:=DllCall("LoadLibrary","Str","kernel32.dll","PTR"),LoadLibraryA:=DllCall("GetProcAddress","PTR",hKernel32,"AStr","LoadLibraryA","PTR")
	,base:={__Call:"InjectAhkDll",__Delete:"InjectAhkDll"},FreeLibrary:=DllCall("GetProcAddress","PTR",hKernel32,"AStr","FreeLibrary","PTR")
	static TH32CS_SNAPMODULE:=0x00000008,INVALID_HANDLE_VALUE:=-1
	,MAX_PATH:=260,MAX_MODULE_NAME32:=255,ModuleName:="",init:=VarSetCapacity(ModuleName,MAX_PATH*(A_IsUnicode?2:1))

	if IsObject(PID)
	{
		if (dll!="Exec" && script)
			return DllCall("MessageBox","PTR",0,"Str","Only Exec method can be used here!","STR","Error","UInt",0)

		hProc := DllCall("OpenProcess", "UInt", PROCESS_ALL_ACCESS, "Int",0, "UInt", PID.PID,"PTR")
		if !hProc
			return DllCall("MessageBox","PTR",0,"Str","Could not open process for PID: " PID.PID,"STR","Error","UInt",0)

		if (!script) ; Free Library in remote process (object is being deleted)
		{
			; Terminate the thread in ahkdll
			hThread := DllCall("CreateRemoteThread", "PTR", hProc, "PTR", 0, "PTR", 0, "PTR", PID.ahkTerminate, "PTR", 0, "UInt", 0, "PTR", 0,"PTR")
			DllCall("WaitForSingleObject", "PTR", hThread, "UInt", 0xFFFFFFFF)
			,DllCall("CloseHandle", "PTR", hThread)

			; Free library in remote process
			hThread := DllCall("CreateRemoteThread", "PTR", hProc, "UInt", 0, "UInt", 0, "PTR", FreeLibrary, "PTR", PID.hModule, "UInt", 0, "UInt", 0,"PTR")
			DllCall("WaitForSingleObject", "PTR", hThread, "UInt", 0xFFFFFFFF)
			,DllCall("CloseHandle", "PTR", hThread),DllCall("CloseHandle", "PTR", hProc)
			return
		}

		nScriptLength := VarSetCapacity(nScript, (StrLen(script)+1)*(A_IsUnicode?2:1), 0)
		,StrPut(script,&nScript)

		; Reserve memory in remote process where our script will be saved
		if !pBufferRemote := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "PTR", nScriptLength, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
			return DllCall("MessageBox","PTR",0,"Str","Could not reseve memory for process.","STR","Error","UInt",0)
		,DllCall("CloseHandle", "PTR", hProc)

		; Write script to remote process memory
		DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pBufferRemote, "Ptr", &nScript, "PTR", nScriptLength, "Ptr", 0)

		; Start execution of code
		hThread := DllCall("CreateRemoteThread", "PTR", hProc, "PTR", 0, "PTR", 0, "PTR", PID.ahkExec, "PTR", pBufferRemote, "UInt", 0, "PTR", 0,"PTR")
		if !hThread
		{
			DllCall("VirtualFreeEx","PTR",hProc,"PTR",pBufferRemote,"PTR",nScriptLength,MEM_RELEASE)
			,DllCall("CloseHandle", "PTR", hProc)
			return DllCall("MessageBox","PTR",0,"Str","Could not execute script in remote process.","STR","Error","UInt",0)
		}

		; Wait for thread to finish
		DllCall("WaitForSingleObject", "PTR", hThread, "UInt", 0xFFFFFFFF)

		; Get Exit code returned by ahkExec (1 = script could be executed / 0 = script could not be executed)
		DllCall("GetExitCodeThread", "PTR", hThread, "UIntP", lpExitCode)
		if !lpExitCode
			return DllCall("MessageBox","PTR",0,"Str","Could not execute script in remote process.","STR","Error","UInt",0)

		DllCall("CloseHandle", "PTR", hThread)
		,DllCall("VirtualFreeEx","PTR",hProc,"PTR",pBufferRemote,"PTR",nScriptLength,MEM_RELEASE)
		,DllCall("CloseHandle", "PTR", hProc)
		return
	}
	else if !hDll:=DllCall("LoadLibrary","Str",dll,"PTR")
		return DllCall("MessageBox","PTR",0,"Str","Could not find " dll " library.","STR","Error","UInt",0),DllCall("CloseHandle", "PTR", hProc)
	else
	{
		hProc := DllCall("OpenProcess","UInt", PROCESS_ALL_ACCESS, "Int",0,"UInt", DllCall("GetCurrentProcessId"),"PTR")
		DllCall("GetModuleFileName","PTR",hDll,"PTR",&ModuleName,"UInt",MAX_PATH)
		DllCall("CloseHandle","PTR",hProc)
	}
	; Open Process to PID
	hProc := DllCall("OpenProcess", "UInt", PROCESS_ALL_ACCESS, "Int",0, "UInt", PID,"PTR")
	if !hProc
		return DllCall("MessageBox","PTR",0,"Str","Could not open process for PID: " PID,"STR","Error","UInt",0)

	; Reserve some memory and write dll path (ANSI)
	nDirLength := VarSetCapacity(nDir, StrLen(dll)+1, 0)
	,StrPut(dll,&nDir,"CP0")

	; Reserve memory in remote process
	if !pBufferRemote := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "PTR", nDirLength, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
		return DllCall("MessageBox","PTR",0,"Str","Could not reseve memory for process.","STR","Error","UInt",0),DllCall("CloseHandle", "PTR", hProc)

	; Write dll path to remote process memory
	DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pBufferRemote, "Ptr", &nDir, "PTR", nDirLength, "Ptr", 0)

	; Start new thread loading our dll

	hThread:=DllCall("CreateRemoteThread","PTR",hProc,"PTR",0,"PTR",0,"PTR",LoadLibraryA,"PTR",pBufferRemote,"UInt",0,"PTR",0,"PTR")
	if !hThread
	{
		DllCall("VirtualFreeEx","PTR",hProc,"PTR",pBufferRemote,"PTR",nDirLength,"Uint",MEM_RELEASE)
		,DllCall("CloseHandle", "PTR", hProc)
		return DllCall("MessageBox","PTR",0,"Str","Could not load " dll " in remote process.","STR","Error","UInt",0)
	}
	; Wait for thread to finish
	DllCall("WaitForSingleObject", "PTR", hThread, "UInt", 0xFFFFFFFF)

	; Get Exit code returned by thread (HMODULE for our dll)
	DllCall("GetExitCodeThread", "PTR", hThread, "UInt*", hModule)

	; Close Thread
	DllCall("CloseHandle", "PTR", hThread)

	if (A_PtrSize=8)
	{ ; use different method to retrieve base address because GetExitCodeThread returns DWORD only
		hModule:=0,VarSetCapacity(me32, (A_PtrSize=8?48:32)+(A_IsUnicode?1032:516), 0) ;W:1080:1064, A:564:548
		;  Take a snapshot of all modules in the specified process.
		hModuleSnap := DllCall("CreateToolhelp32Snapshot","UInt", TH32CS_SNAPMODULE,"UInt", PID, "PTR" )
		if ( hModuleSnap != INVALID_HANDLE_VALUE )
		{
			; reset hModule and set the size of the structure before using it.
			NumPut((A_PtrSize=8?48:32)+(A_IsUnicode?1032:516), &me32, 0, "UInt") ;dwSize  ;W:1080:1064, A:564:548
			;  Retrieve information about the first module,
			;  and exit if unsuccessful
			if ( !DllCall("Module32First" (A_IsUnicode?"W":""),"PTR", hModuleSnap,"PTR", &me32 ) )
			{
				; Free memory used for passing dll path to remote thread
				DllCall("VirtualFreeEx","PTR",hProc,"PTR",pBufferRemote,"PTR",nDirLength,MEM_RELEASE)
				,DllCall("CloseHandle","PTR", hModuleSnap ) ; Must clean up the snapshot object!
				return false
			}
			;  Now walk the module list of the process,and display information about each module
			while(A_Index=1 || DllCall("Module32Next" (A_IsUnicode?"W":""),"PTR",hModuleSnap,"PTR", &me32 ) )
				if (StrGet(&me32+(A_PtrSize=8?48:32)+(A_IsUnicode?512:256))=dll) ;szExePath ;W:560:544, A:304:288
				{
					hModule := NumGet(me32, A_PtrSize=8?40:28, "Ptr") ;hModule
					break
				}
			DllCall("CloseHandle","PTR",hModuleSnap) ; clean up
		}
	}

	hDll:=DllCall("LoadLibrary","Str",dll,"PTR")

	; Calculate pointer to ahkdll and ahkExec functions
	ahktextdll:=hModule+DllCall("GetProcAddress","PTR",hDll,"AStr","ahktextdll","PTR")-hDll
	ahkExec:=hModule+DllCall("GetProcAddress","PTR",hDll,"AStr","ahkExec","PTR")-hDll
	ahkTerminate:=hModule+DllCall("GetProcAddress","PTR",hDll,"AStr","ahkTerminate","PTR")-hDll

	if script
	{
		nScriptLength := VarSetCapacity(nScript, (StrLen(script)+1)*(A_IsUnicode?2:1), 0)
		,StrPut(script,&nScript)
		; Reserve memory in remote process where our script will be saved
		if !pBufferScript := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "PTR", nScriptLength, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
			return DllCall("MessageBox","PTR",0,"Str","Could not reseve memory for process.","STR","Error","UInt",0)
		,DllCall("CloseHandle", "PTR", hProc)

		; Write script to remote process memory
		DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pBufferScript, "Ptr", &nScript, "PTR", nScriptLength, "Ptr", 0)
	}
	else
		pBufferScript:=0

	; Run ahkdll function in remote thread
	hThread := DllCall("CreateRemoteThread","PTR",hProc,"PTR",0,"PTR",0,"PTR",ahktextdll,"PTR",pBufferScript,"PTR",0,"UInt",0,"PTR")
	if !hThread
	{ ; could not start ahkdll in remote process
		; Free memory used for passing dll path to remote thread
		DllCall("VirtualFreeEx","PTR",hProc,"PTR",pBufferRemote,"PTR",nDirLength,MEM_RELEASE)
		DllCall("CloseHandle", "PTR", hProc)
		return DllCall("MessageBox","PTR",0,"Str","Could not start ahkdll in remote process","STR","Error","UInt",0)
	}
	DllCall("WaitForSingleObject", "PTR", hThread, "UInt", 0xFFFFFFFF)
	DllCall("GetExitCodeThread", "PTR", hThread, "UIntP", lpExitCode)

	; Release memory and handles
	DllCall("VirtualFreeEx","PTR",hProc,"PTR",pBufferRemote,"PTR",nDirLength,MEM_RELEASE)
	DllCall("CloseHandle", "PTR", hThread)
	DllCall("CloseHandle", "PTR", hProc)

	if !lpExitCode ; thread could not be created.
		return DllCall("MessageBox","PTR",0,"Str","Could not create a thread in remote process","STR","Error","UInt",0)

	return {PID:PID,hModule:hModule,ahkExec:ahkExec,ahkTerminate:ahkTerminate,base:base}
}

;==================================================
- Many programs accept WM_DROPFILES to invoke a drag-and-drop. See JEE_NotepadSetPath at this link:
notepad get/set path (get/set text file path) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=30050

- Using Spek as the test case was inspired by this thread:
Open selected file in SPEK - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=34754

Queries:
- Does it work on x64 processes?
- Does it work to drag-and-drop multiple files?
- Why doesn't this script, that uses a different approach, work?
[COM] Help with the IDropSource and IDropTarget interfaces - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=8700&p=187406#p187406
- I had to use BindToObject to get a pointer to an IShellFolder interface, and specify file names without dirs to make this script work in Windows 7. According to qwerty12 this was not necessary on Windows 10, you could just specify Desktop as the folder and use full paths.

Links:
[some utilities that return a value for OleDropTargetInterface]
[COM] Help with the IDropSource and IDropTarget interfaces - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=8700&p=165994#p165994

[clue: GetProp and OleDropTargetInterface]
Sending WM_DROPFILES with C++ and WinAPI to third-party application - Stack Overflow
https://stackoverflow.com/questions/22271857/sending-wm-dropfiles-with-c-and-winapi-to-third-party-application

[clue: 'When you call SomeFolder.GetUIObjectOf items MUST be childs of SomeFolder', which led me to using BindToObject to get a pointer to a folder interface]
delphi - How to make my file DropSouce be accepted by all targets that works with files? - Stack Overflow
https://stackoverflow.com/questions/31356071/how-to-make-my-file-dropsouce-be-accepted-by-all-targets-that-works-with-files
IShellFolder::BindToObject | Microsoft Docs
https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nf-shobjidl_core-ishellfolder-bindtoobject
shell - BindToObject fails when binding to the desktop - Stack Overflow
https://stackoverflow.com/questions/25351407/bindtoobject-fails-when-binding-to-the-desktop
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Scripts and Functions”

Who is online

Users browsing this forum: robodesign and 26 guests