[Windows] Find Program handle/info on window title search.

Share your advanced PureBasic knowledge/code with the community.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

[Windows] Find Program handle/info on window title search.

Post by Rescator »

The following let you search for a program window based on window title or class or exe name or all three or a combination of the three.
The window handle or class name or exe name or processid can be returned, or all the info (for more advanced users).
The comments are a tad sparse, but hopefully they plus the example is enough to make you understand the code.

This was just some code I had laying around, I checked it/tweaked it to make sure it works with PureBasic 5.20.

Code: Select all

;The following is hereby placed in the public domain.
;v1.0 by Rescator on PureBasic forum.

EnableExplicit

Structure Find_Program_Struct ;For the programmer/other code to read
	hwnd.i
	title$
	class$
	exe$
	processid.i
EndStructure

Structure Find_Program_Window_EnumWindowsStructure ;internal use only
	result.Find_Program_Struct
	processid.i
	currentprocessid.i
	findtitle$
	findclass$
	findexe$
	matchlimit.i
	match.i
	*QueryFullProcessImageName
	*GetProcessImageFileName
	casematch.i
	all.i
EndStructure

;internal use only
Procedure.l Find_Program_Window_callback(hwnd.i,*param.Find_Program_Window_EnumWindowsStructure)
	Protected result.l=#True,text$,length.i,processid.i=#Null,modulehandle.i,ok.i
	*param\match=0
	*param\result\processid=0
	If *param\findtitle$ Or *param\all
		*param\result\title$=""
		GetWindowThreadProcessId_(hwnd,@processid)
		If processid<>*param\currentprocessid ;We need to check this, getting the window title for a window in our own program in the same thread could cause a deadlock.
			ok=#False
			text$=Space(#MAX_PATH)
			length=GetWindowText_(hwnd,@text$,Len(text$))
			If length
				*param\result\title$=Left(text$,length)
			EndIf
 		EndIf
		If *param\findtitle$
			ok=#False
			If *param\casematch=#False
				text$=LCase(*param\result\title$)
			EndIf
			If FindString(text$,*param\findtitle$)
				*param\match+1
				ok=#True
			EndIf
	 	EndIf
	Else
		ok=#True
	EndIf
	If (*param\findclass$ And ok) Or *param\all
		*param\result\class$=""
		text$=Space(#MAX_PATH)
		length=GetClassName_(hwnd,@text$,Len(text$))
		If length
			*param\result\class$=Left(text$,length)
		EndIf
		If (*param\findclass$ And ok)
			ok=#False
			If *param\casematch=#False
				text$=LCase(*param\result\class$)
			EndIf
			If FindString(text$,*param\findclass$)
				*param\match+1
				ok=#True
			EndIf
		EndIf
	Else
		ok=#True
	EndIf
	If (*param\findexe$ And ok) Or *param\all
		*param\result\exe$=""
		If *param\GetProcessImageFileName Or *param\QueryFullProcessImageName
			If processid=#Null
				GetWindowThreadProcessId_(hwnd,@processid)
			EndIf
			If processid
				modulehandle=OpenProcess_(#PROCESS_QUERY_INFORMATION,#False,processid)
				If modulehandle
					text$=Space(#MAX_PATH)
					If *param\QueryFullProcessImageName ;Windows 6.x
						length=Len(text$)
						CallFunctionFast(*param\QueryFullProcessImageName,modulehandle,#Null,@text$,@length)
					Else ;Windows 5.x/6.0
						length=CallFunctionFast(*param\GetProcessImageFileName,modulehandle,@text$,Len(text$))
					EndIf
					If length
						*param\result\exe$=Left(text$,length)
					EndIf
					CloseHandle_(modulehandle)
				EndIf
				*param\result\processid=processid
			EndIf
			If (*param\findexe$ And ok)
				ok=#False
				If *param\casematch=#False
					text$=LCase(*param\result\exe$)
				EndIf
				If FindString(text$,*param\findexe$)
					*param\match+1
					ok=#True
				EndIf
			EndIf
		EndIf
	EndIf
	If *param\match=*param\matchlimit
		*param\result\hwnd=hwnd
		result=#False
	EndIf
	ProcedureReturn result
EndProcedure

;Returns pointer to result structure. Returns #Null on no match/failure.
Procedure.i Find_Program(*result.Find_Program_Struct,title$,class$,exe$,casematch.i=#False,all.i=#True)
	Protected processid.l,matchlimit.i,dll_psapi.i,dll_kernel32.i,param.Find_Program_Window_EnumWindowsStructure
	Protected *pos.Character,*GetLogicalDriveStrings,*QueryDosDevice,text$,devices$,length.l,device$
	If title$
		param\findtitle$=LCase(title$)
		matchlimit+1
	EndIf
	If class$
		param\findclass$=LCase(class$)
		matchlimit+1
	EndIf
	If exe$
		param\findexe$=LCase(exe$)
		matchlimit+1
	EndIf
	If matchlimit
		param\all=all
		param\casematch=casematch
 		If param\findexe$ Or param\all
			dll_kernel32=OpenLibrary(#PB_Any,"kernel32.dll")
			If dll_kernel32
				;Windows 6.x
 				CompilerIf #PB_Compiler_Unicode
 					param\QueryFullProcessImageName=GetFunction(dll_kernel32,"QueryFullProcessImageNameW")
 				CompilerElse
 					param\QueryProcessImageFileName=GetFunction(dll_kernel32,"QueryFullProcessImageNameA")
 				CompilerEndIf
				If Not param\QueryFullProcessImageName ;Windows 6.1
					CompilerIf #PB_Compiler_Unicode
						param\GetProcessImageFileName=GetFunction(dll_kernel32,"K32GetProcessImageFileNameW")
					CompilerElse
						param\GetProcessImageFileName=GetFunction(dll_kernel32,"K32GetProcessImageFileNameA")
					CompilerEndIf
				EndIf
			EndIf
			If (param\GetProcessImageFileName|param\QueryFullProcessImageName)=#Null ;Windows 5.x/Windows 6.0
				dll_psapi=OpenLibrary(#PB_Any,"psapi.dll")
				If dll_psapi
					CompilerIf #PB_Compiler_Unicode
						param\GetProcessImageFileName=GetFunction(dll_kernel32,"GetProcessImageFileNameW")
					CompilerElse
						param\GetProcessImageFileName=GetFunction(dll_kernel32,"GetProcessImageFileNameA")
					CompilerEndIf
				EndIf
			EndIf
		EndIf
		param\currentprocessid=GetCurrentProcessId_()
		param\result\hwnd=#Null
		param\matchlimit=matchlimit
		EnumWindows_(@Find_Program_Window_callback(),param)
		If param\match=matchlimit
			*result\hwnd=param\result\hwnd
			*result\class$=param\result\class$
			*result\processid=param\result\processid
			*result\title$=param\result\title$
			If param\QueryFullProcessImageName ;Windows 6.x
				*result\exe$=param\result\exe$
			Else ;Windows 5.x/Windows 6.0
				If LCase(Left(param\result\exe$,8))="\device\"
					CompilerIf #PB_Compiler_Unicode
						*GetLogicalDriveStrings=GetFunction(dll_kernel32,"GetLogicalDriveStringsW")
						*QueryDosDevice=GetFunction(dll_kernel32,"QueryDosDeviceW")
					CompilerElse
						*GetLogicalDriveStrings=GetFunction(dll_kernel32,"GetLogicalDriveStringsA")
						*QueryDosDevice=GetFunction(dll_kernel32,"QueryDosDeviceA")
					CompilerEndIf
					If *GetLogicalDriveStrings And *QueryDosDevice
						length=0
						length=CallFunctionFast(*GetLogicalDriveStrings,length,@devices$)
						devices$=Space(length+1)
						length=CallFunctionFast(*GetLogicalDriveStrings,length,@devices$)
						If length
							*pos=@devices$
							Repeat
								text$=PeekS(*pos,#MAX_PATH)
								If Right(text$,1)="\"
									device$=Left(text$,Len(text$)-1)
								Else
									device$=text$
								EndIf
								text$=Space(#MAX_PATH+1)
								length=CallFunctionFast(*QueryDosDevice,@device$,@text$,length)
								length=Len(text$)
								If Left(param\result\exe$,length)=text$
									*result\exe$=device$+Right(param\result\exe$,Len(param\result\exe$)-length)
									Break
								EndIf
								*pos=*pos+StringByteLength(text$)+SizeOf(Character)
							Until *pos\c=#Null
						EndIf
					EndIf
				EndIf
			EndIf
		Else
			*result=#Null
		EndIf
		If dll_kernel32
			CloseLibrary(dll_kernel32)
		EndIf
		If dll_psapi
			CloseLibrary(dll_psapi)
		EndIf
	Else
		*result=#Null
	EndIf
	ProcedureReturn *result
EndProcedure

;Returns a pointer to window handle. Returns #Null on no match/failure.
Procedure.i Find_Program_Window(title$,class$,exe$,casematch.i=#False)
	Protected result.i,results.Find_Program_Struct
	If Find_Program(results,title$,class$,exe$,casematch,#False) ;#False means to only return the window handle (instead of all info), this saves some CPU.
		result=results\hwnd
	EndIf
	ProcedureReturn result
EndProcedure

;Returns a string with exe name (may also have a incomplete path so using GetrFilePart() may be wise). Returns empty string on no match/failure.
Procedure.s Find_Program_Exe(title$,class$,exe$,casematch.i=#False)
	Protected result$,results.Find_Program_Struct
	If Find_Program(results,title$,class$,exe$,casematch,#True)
		result$=results\exe$
	EndIf
	ProcedureReturn result$
EndProcedure

;Returns string with window title. Returns empty string on no match/failure.
Procedure.s Find_Program_Title(title$,class$,exe$,casematch.i=#False)
	Protected result$,results.Find_Program_Struct
	If Find_Program(results,title$,class$,exe$,casematch,#True)
		result$=results\title$
	EndIf
	ProcedureReturn result$
EndProcedure

;Returns string with window class name. Returns empty string on no match/failure.
Procedure.s Find_Program_Class(title$,class$,exe$,casematch.i=#False)
	Protected result$,results.Find_Program_Struct
	If Find_Program(results,title$,class$,exe$,casematch,#True)
		result$=results\class$
	EndIf
	ProcedureReturn result$
EndProcedure

;Returns process id (as seen in Task Manager), returns 0 if no match/failure.
Procedure.i Find_Program_ProcessID(title$,class$,exe$,casematch.i=#False)
	Protected result.i,results.Find_Program_Struct
	If Find_Program(results,title$,class$,exe$,casematch,#True)
		result=results\processid
	EndIf
	ProcedureReturn result
EndProcedure


; ***** Examples *****

Define result.Find_Program_Struct

Debug "Populate results with first match to exact/partial 'foobar' window title"
If Find_Program(result,"foobar2000","","",#False)
	Debug "Exe: "+result\exe$
	Debug "Title: "+result\title$
	Debug "Class: "+result\class$
	Debug "Process ID: "+Str(result\processid)
	Debug "Window Handle: "+Str(result\hwnd)
Else
	Debug "no match"
EndIf

Define hwnd.i

Debug ""
Debug "Return window handle based on exact/partial match to window title and class and exe."
hwnd=Find_Program_Window("foobar","{97E27FAA-C0B3-4b8e-A693-ED7881E99FC1}","foobar2000.exe")
If hwnd
	Debug "Window Handle: "+Str(hwnd)
Else
	Debug "no match"
EndIf



Keywords: Windows, Win32, PB5.20
Last edited by Rescator on Sat Aug 31, 2013 6:38 am, edited 1 time in total.
jassing
Addict
Addict
Posts: 1775
Joined: Wed Feb 17, 2010 12:00 am

Re: [Windows] Find Program handle/info on window title searc

Post by jassing »

Just ran it -- launched notepad with a file called "foobar.txt"
It found the window; but the "exe" was empty.
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Re: [Windows] Find Program handle/info on window title searc

Post by PB »

I tested it with "Calculator" on Win 7 (64-bit) on a Limited
account (not Admin rights) and it returned this for the exe:

Code: Select all

Exe: \Device\HarddiskVolume2\Windows\System32\calc.exe
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Re: [Windows] Find Program handle/info on window title searc

Post by Rescator »

As to exe being empty on foobar.txt and notepad, it was not so here, (worked as expected) so no idea what caused that.

As to "Exe: \Device\HarddiskVolume2\" and similar, that is what GetProcessImageFileName returns.
One could use QueryFullProcessImageName but that is only for Vista an later.
I was aiming for XP (Windows v5.1) and later.
I'll see what I can do.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Re: [Windows] Find Program handle/info on window title searc

Post by Rescator »

Updated, added version and done by whom comment (for those that like to keep track of that).

Now the full imagepath is retrieved, added fallback for any OS older than Vista, on really old systems there is a triple fallback, and on ancient systems there should be just an empty string. No assumption about the OS made, pure function existence testing (as recommended by MicroSoft guidelines).
I hope folks find this code useful.
There is also a nice example on how to loop through and handle a list of null terminated strings where the list ends with a double null termination, actually I hope this is a nice example for several things, I hope I made the code solid enough to not crash/lockup in case of errors, any further tips on optimizations or code hardening are obviously welcome.
Post Reply