IVirtualDesktopManager dllCall

Get help with using AutoHotkey and its commands and hotkeys
jpginc
Posts: 123
Joined: 29 Sep 2013, 22:35

IVirtualDesktopManager dllCall

10 Apr 2016, 18:02

Hi guys,

I'm trying to get the GUID of the current desktop using the IVirtualDesktopManager's GetWindowDesktopId method (https://msdn.microsoft.com/en-us/librar ... s.85).aspx) but I can't get it to work.

I'm using Flipeador's IsWindowOnCurrentVirtualDesktop code as an example (https://autohotkey.com/boards/viewtopic.php?t=12388) but I keep getting an error code 2 returned from the dll call. I'm guessing that either I have the NumGet offset incorrect (how can i tell what offset i need to use?) OR i need to do something with the type of the output variable as the output to the GetWindowDesktopId is a GUID.

this is the code i'm trying:

Code: Select all

gui, show
gui +LastFound

hWnd := WinExist("A")
MsgBox % "getCurrentDesktopID: " getCurrentDesktopID(hWnd)
ExitApp
 
;returns the GUID of the virtual desktop that contains the given hwnd (you might have to own the process to get the guid)
getCurrentDesktopID(hWnd) {
	;IVirtualDesktopManager interface
	;Exposes methods that enable an application to interact with groups of windows that form virtual workspaces.
	;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186440(v=vs.85).aspx
	CLSID := "{aa509086-5ca9-4c25-8f95-589d3c07b48a}" ;search VirtualDesktopManager clsid
	IID := "{a5cd92ff-29be-454c-8d04-d82879fb3f1b}" ;search IID_IVirtualDesktopManager
	IVirtualDesktopManager := ComObjCreate(CLSID, IID)
 
	;IVirtualDesktopManager::GetWindowDesktopId  method
	;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186441(v=vs.85).aspx
	Error := DllCall(NumGet(NumGet(IVirtualDesktopManager+0), 1*A_PtrSize), "Ptr", IVirtualDesktopManager, "Ptr", hWnd, "IntP", desktopID)
 
	;free IVirtualDesktopManager
	ObjRelease(IVirtualDesktopManager)
 
	;return
	if !(Error=0) ;S_OK
		MsgBox an error: %Error%
	return desktopID
}
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: IVirtualDesktopManager dllCall

10 Apr 2016, 19:53

EDIT: I removed my previous post and replaced it with this one to rectify something large that I overlooked. If the code you downloaded from the post previously doesn't contain a call to VarSetCapacity, redownload it from this post.

Hi,

Try this:

Code: Select all

gui, show
gui +LastFound

hWnd := WinExist("A")
Error := getCurrentDesktopID(hWnd, desktopID)
if !(Error=0) ;S_OK
	MsgBox an error: %Error%
else
	MsgBox % "getCurrentDesktopID: " Guid_ToStr(desktopID)
ExitApp
 
;returns the GUID of the virtual desktop that contains the given hwnd (you might have to own the process to get the guid)
getCurrentDesktopID(hWnd, ByRef desktopID) {
	;IVirtualDesktopManager interface
	;Exposes methods that enable an application to interact with groups of windows that form virtual workspaces.
	;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186440(v=vs.85).aspx
	VarSetCapacity(desktopID, 16, 0) ; a GUID structure occupies 16 bytes in memory; allocate the space for one for ::GetWindowDesktopId to fill in

	CLSID := "{aa509086-5ca9-4c25-8f95-589d3c07b48a}" ;search VirtualDesktopManager clsid
	IID := "{a5cd92ff-29be-454c-8d04-d82879fb3f1b}" ;search IID_IVirtualDesktopManager
	IVirtualDesktopManager := ComObjCreate(CLSID, IID)
 
	;IVirtualDesktopManager::GetWindowDesktopId  method
	;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186441(v=vs.85).aspx
	Error := DllCall(NumGet(NumGet(IVirtualDesktopManager+0), 4*A_PtrSize), "Ptr", IVirtualDesktopManager, "Ptr", hWnd, "Ptr", &desktopID)
 
	;free IVirtualDesktopManager
	ObjRelease(IVirtualDesktopManager)
 
	return Error
}

; https://github.com/cocobelgica/AutoHotkey-Util/blob/master/Guid.ahk#L36
Guid_ToStr(ByRef VarOrAddress)
{
	pGuid := IsByRef(VarOrAddress) ? &VarOrAddress : VarOrAddress
	VarSetCapacity(sGuid, 78) ; (38 + 1) * 2
	if !DllCall("ole32\StringFromGUID2", "Ptr", pGuid, "Ptr", &sGuid, "Int", 39)
		throw Exception("Invalid GUID", -1, Format("<at {1:p}>", pGuid))
	return StrGet(&sGuid, "UTF-16")
}
I changed the following:
  • The offset - the AddRef function of the IUnknown interface that IVirtualDesktopManager inherits from is located at position 1 (counting from zero as the first function pointed to is located right at the beginning of the vtable structure). GetWindowDesktopId is at position 4. For public COM interfaces, the header files included with the Windows 10 SDK (comes with the latest version of Visual Studio, but can be installed standalone, I believe) will contain the vtables, from which you can find the method's position. MSDN usually lists the header file that contains an interface's definition, but not in this case. I Googled and saw that the definitions for IVirtualDesktopManager are in the ShObjIdl.h file. Here's the relevant part from the file:

    Code: Select all

        typedef struct IVirtualDesktopManagerVtbl
        {
            BEGIN_INTERFACE
            
            HRESULT ( STDMETHODCALLTYPE *QueryInterface )( 
                __RPC__in IVirtualDesktopManager * This,
                /* [in] */ __RPC__in REFIID riid,
                /* [annotation][iid_is][out] */ 
                _COM_Outptr_  void **ppvObject);
            
            ULONG ( STDMETHODCALLTYPE *AddRef )( 
                __RPC__in IVirtualDesktopManager * This);
            
            ULONG ( STDMETHODCALLTYPE *Release )( 
                __RPC__in IVirtualDesktopManager * This);
            
            HRESULT ( STDMETHODCALLTYPE *IsWindowOnCurrentVirtualDesktop )( 
                __RPC__in IVirtualDesktopManager * This,
                /* [in] */ __RPC__in HWND topLevelWindow,
                /* [out] */ __RPC__out BOOL *onCurrentDesktop);
            
            HRESULT ( STDMETHODCALLTYPE *GetWindowDesktopId )( 
                __RPC__in IVirtualDesktopManager * This,
                /* [in] */ __RPC__in HWND topLevelWindow,
                /* [out] */ __RPC__out GUID *desktopId);
            
            HRESULT ( STDMETHODCALLTYPE *MoveWindowToDesktop )( 
                __RPC__in IVirtualDesktopManager * This,
                /* [in] */ __RPC__in HWND topLevelWindow,
                /* [in] */ __RPC__in REFGUID desktopId);
            
            END_INTERFACE
        } IVirtualDesktopManagerVtbl;
        
    
    I recommend reading https://maul-esel.github.io/tutorials/C ... faces.html. It's how I learnt to call COM methods using the vtable. It's a better resource than I ever will be at this sort of stuff. Although, if I can somehow clarify something, please let me know.
  • For demonstration purposes, I copy-and-pasted a method to produce a string from the GUID struct ::GetWindowDesktopId fills in so that the MessageBox can actually show its contents in the form you were expecting
  • More importantly, ::GetWindowDesktopId expects to have space for a GUID already allocated. VarSetCapacity is called in getCurrentDesktopID() to do this and I've also added a new ByRef parameter. The function has been reworked slightly to handle that the error code is returned instead, and that whatever was passed as the second parameter to getCurrentDesktopID() will now be a pointer to the GUID structure filled in by GetWindowDesktopId if the call returned S_OK. The ByRef is to avoid the situation Lexikos explained to me here. Sure, you could return a pointer to the GUID structure inside the function, but there's no guarantee of the lifetime of the memory allocated to hold the structure. You could make desktopId static inside the function, but subsequent calls to the function then will essentially invalidate the other pointers.
User avatar
nnnik
Posts: 3981
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: IVirtualDesktopManager dllCall

11 Apr 2016, 01:24

Pointers and the vTable

Now we need to analyse what we got from this operation: we did not get an AHK object that we can use like any other AHK_L object. If that was the case, there would be no point in this tutorial :) Instead, we got a pointer: an integer pointing to some place in memory. You would get the equivalent running the code below:

Code: Select all

ptr := ComObjUnwrap(ComObjCreate("Scripting.Dictionary")) ; requires AHK_L or v2. AHK classic users will ALWAYS get a pointer using COM_CreateObject()
In your code you use:

Code: Select all

IVirtualDesktopManager := ComObjCreate(CLSID, IID)
Recommends AHK Studio
jpginc
Posts: 123
Joined: 29 Sep 2013, 22:35

Re: IVirtualDesktopManager dllCall

11 Apr 2016, 17:15

That was the missing piece thanks :clap:

What I'm trying to do is enumerate the virtual deskotps. My idea is that if i can create a gui on each desktop then I can track which desktop the user is currently on using IsWindowOnCurrentVirtualDesktop. But i'm getting stuck when creating a class. I think it has something to do with how the classes memory is allocated?

This does what I expect it to, i create a GUI on 2 different virtual dekstops and I get 2 different GUIDs:

Code: Select all

CLSID := "{aa509086-5ca9-4c25-8f95-589d3c07b48a}" ;search VirtualDesktopManager clsid
IID := "{a5cd92ff-29be-454c-8d04-d82879fb3f1b}" ;search IID_IVirtualDesktopManager
iVirtualDesktopManager := ComObjCreate(CLSID, IID)

isWindowOnCurrentVirtualDesktopAddress := NumGet(NumGet(iVirtualDesktopManager+0), 3*A_PtrSize)
getWindowDesktopIdAddress := NumGet(NumGet(iVirtualDesktopManager+0), 4*A_PtrSize)
moveWindowToDesktopAddress := NumGet(NumGet(iVirtualDesktopManager+0), 5*A_PtrSize)

Gui, new
Gui, Show, x55 y66 w300 h200, New Title
Gui, +HwndMyGuiHwnd
hwnd := myGuiHwnd

Send ^#{right}

Gui, new
Gui, Show, x55 y66 w300 h200, New Title
Gui, +HwndMyGuiHwnd2
hwnd2 := myGuiHwnd2

desktopId := ""
VarSetCapacity(desktopID, 16, 0)
;IVirtualDesktopManager::GetWindowDesktopId  method
;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186441(v=vs.85).aspx
Error := DllCall(getWindowDesktopIdAddress, "Ptr", iVirtualDesktopManager, "Ptr", hWnd, "Ptr", &desktopID)	
if(Error != 0) {
	msgbox % "error in _loadVirtualDesktopId " Error "`n" this.hwnd
}

desktopId2 := ""
VarSetCapacity(desktopID2, 16, 0)
;IVirtualDesktopManager::GetWindowDesktopId  method
;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186441(v=vs.85).aspx
Error := DllCall(getWindowDesktopIdAddress, "Ptr", iVirtualDesktopManager, "Ptr", hWnd2, "Ptr", &desktopID2)	
if(Error != 0) {
	msgbox % "error in _loadVirtualDesktopId " Error "`n" this.hwnd2
}

MsgBox % _guidToStr(desktopId) "`n" _guidToStr(desktopId2)
ExitApp
; https://github.com/cocobelgica/AutoHotkey-Util/blob/master/Guid.ahk#L36
_guidToStr(ByRef VarOrAddress)
{
	pGuid := IsByRef(VarOrAddress) ? &VarOrAddress : VarOrAddress
	VarSetCapacity(sGuid, 78) ; (38 + 1) * 2
	if !DllCall("ole32\StringFromGUID2", "Ptr", pGuid, "Ptr", &sGuid, "Int", 39)
		throw Exception("Invalid GUID", -1, Format("<at {1:p}>", pGuid))
	return StrGet(&sGuid, "UTF-16")
}
This however gives me the same GUID on every desktop.

Code: Select all

x := new DesktopMapperClass()
x.mapVirtualDesktops()
ExitApp
#c::ExitApp
class DesktopMapperClass
{
	DesktopMarkerClass := Object()
	
	getWindowDesktopIdAddress := ""
	isWindowOnCurrentVirtualDesktopAddress := ""
	moveWindowToDesktop  := ""
	iVirtualDesktopManager := ""
	
	__new()
	{
		MsgBox creating class...
		;IVirtualDesktopManager interface
		;Exposes methods that enable an application to interact with groups of windows that form virtual workspaces.
		;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186440(v=vs.85).aspx
		CLSID := "{aa509086-5ca9-4c25-8f95-589d3c07b48a}" ;search VirtualDesktopManager clsid
		IID := "{a5cd92ff-29be-454c-8d04-d82879fb3f1b}" ;search IID_IVirtualDesktopManager
		this.iVirtualDesktopManager := ComObjCreate(CLSID, IID)
		
		this.isWindowOnCurrentVirtualDesktopAddress := NumGet(NumGet(this.iVirtualDesktopManager+0), 3*A_PtrSize)
		this.getWindowDesktopIdAddress := NumGet(NumGet(this.iVirtualDesktopManager+0), 4*A_PtrSize)
		this.moveWindowToDesktopAddress := NumGet(NumGet(this.iVirtualDesktopManager+0), 5*A_PtrSize)

		return this
	}
	
	mapVirtualDesktops() 
	{
		currentDesktop := new DesktopMarkerClass(this.getWindowDesktopIdAddress, this.isWindowOnCurrentVirtualDesktopAddress, this.iVirtualDesktopManager)
		send ^#{Left 10}
		this.DesktopMarkers := Object()
		this._createMarkers()

		this._returnToDesktop(currentDesktop)
		MsgBox markers created
		loop, % this.DesktopMarkers.MaxIndex()
		{
			MsgBox % this.DesktopMarkers[A_Index].virtualDesktopId
		}
		return this
	}
	
	/*
	 * returns the desktop number or -1 if there was an error
	 *
	 * if tryRemapping is true then if the current desktop isn't mapped we will try mapping it again
	 */
	getCurrentDesktopNumber(tryRemapping := true) 
	{
		this._verifyDesktopMapping()

		loop, % this.DesktopMarkers.MaxIndex()
		{
			if(this.DesktopMarkers[A_Index].virtualDesktopId == otherDesktop.virtualDesktopId)
			{
				return A_Index
			}
		}
		;debug
		
		if(tryRemapping) 
		{
			this.mapVirtualDesktops()
			return this.getCurrentDesktopNumber(false)
		}		
		
		return -1
	}
	
	/*
	 * fixes up mapping by removing any markers that have moved desktop 
	 * (this happens when the user closes a virtual desktop)
	 *
	 * does not map new desktops
	 */
	_verifyDesktopMapping() 
	{
		verifiedDesktopMarkerClass := Object()
		
		loop, % this.DesktopMarkers.MaxIndex()
		{
			if(this.DesktopMarkers[A_Index].isOnSameDesktop())
			{
				verifiedDesktopMarkerClass.Insert(this.DesktopMarkers[A_Index])
			}
		}
		
		this.DesktopMarkers := verifiedDesktopMarkerClass
		return this
	}
	
	_createMarkers() 
	{
		MsgBox creating markers
		while(true)
		{
			ToolTip, about to create a gui %A_index%
			sleep 1000
			nextMarker := new DesktopMarkerClass(this.getWindowDesktopIdAddress, this.isWindowOnCurrentVirtualDesktopAddress, this.iVirtualDesktopManager)
			MsgBox, %A_index% just created it
			sleep 1000
			if(this._desktopAlreadyMapped(nextMarker)) 
			{
				ToolTip, the thing is mapped!
				return this
			}
			this.DesktopMarkers.Insert(nextMarker)
			send ^#{right}
			sleep 1000
		}
		return
	}
	
	_desktopAlreadyMapped(otherDesktop) 
	{
		loop, % this.DesktopMarkers.MaxIndex()
		{
			ToolTip % this.DesktopMarkers[A_Index].virtualDesktopId "`n" otherDesktop.virtualDesktopId
			sleep 1000
			if(this.DesktopMarkers[A_Index].virtualDesktopId == otherDesktop.virtualDesktopId)
			{
				return true
			}
		}
		;debug
		return false
	}
	
	_returnToDesktop(returnTo)
	{
		currentDesktop := this.getCurrentDesktopNumber()
		returnToDesktopNumber := 1
		
		loop, % this.DesktopMarkers.MaxIndex()
		{
			if(this.DesktopMarkers[A_Index].virtualDesktopId == returnTo.virtualDesktopId)
			{
				returnToDesktopNumber := A_Index
				break
			}
		}
		
		distanceToMove := currentDesktop - returnToDesktopNumber
		absDistanceToMove := Abs(distanceToMove)
		
		if(distanceToMove < 0)
		{
			send ^#{right %absDistanceToMove%}
		} else 
		{
			send ^#{left %absDistanceToMove%}
		}
		
		return this
	}
}
class DesktopMarkerClass
{
	hwnd := ""
	virtualDesktopGuid := ""
	virtualDesktopId := ""
	
	getWindowDesktopIdAddress := ""
	isWindowOnCurrentVirtualDesktopAddress := ""
	iVirtualDesktopManager := ""
	
	
	__new(getWindowDesktopIdAddress, isWindowOnCurrentVirtualDesktopAddress, iVirtualDesktopManager) 
	{
		Gui, new
		Gui, Show, x55 y66 w300 h200, New Title
		Gui, +HwndMyGuiHwnd
		this.hwnd := myGuiHwnd

		this.getWindowDesktopIdAddress := getWindowDesktopIdAddress
		this.isWindowOnCurrentVirtualDesktopAddress := isWindowOnCurrentVirtualDesktopAddress
		this.iVirtualDesktopManager := iVirtualDesktopManager
		
		this._loadVirtualDesktopId(desktopId)
		this.virtualDesktopGuid := desktopId

		this.virtualDesktopId := this._guidToStr(desktopId)
		ToolTip, % "the guid is " this.virtualDesktopId "`n" this.hwnd
		sleep 1000
		;~ MsgBox % "is desktop active? " this.isDesktopCurrentlyActive()
		return this
	}
	
	destroy()
	{
		
		hwnd := this.hwnd
		MsgBox destroying! %hwnd%
		if(WinExist("ahk_id " hwnd))
		{
			Gui %hwnd%:Destroy 
		}
		
		return this
	}
	
	/* 
	 * checks if this marker is still on the same virtual desktop it was when it was created
	 *
	 * if it isn't then it's virtual desktop was probably closed
	 */
	isOnSameDesktop() 
	{
		this._loadVirtualDesktopId(desktopId)
		return this.virtualDesktopId == this._guidToStr(desktopId)
	}

	isDesktopCurrentlyActive() 
	{
		;IVirtualDesktopManager::IsWindowOnCurrentVirtualDesktop method
		;Indicates whether the provided window is on the currently active virtual desktop.
		;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186442(v=vs.85).aspx
		Error := DllCall(this.isWindowOnCurrentVirtualDesktopAddress, "Ptr", this.iVirtualDesktopManager, "Ptr", this.hWnd, "IntP", onCurrentDesktop)
		if(Error != 0) {
			msgbox error in isDesktopCurrentlyActive
		}
		return onCurrentDesktop
	}
	
	_loadVirtualDesktopId(desktopId) 
	{
		VarSetCapacity(desktopID, 16, 0)
		;IVirtualDesktopManager::GetWindowDesktopId  method
		;https://msdn.microsoft.com/en-us/library/windows/desktop/mt186441(v=vs.85).aspx
		Error := DllCall(this.getWindowDesktopIdAddress, "Ptr", this.iVirtualDesktopManager, "Ptr", this.hWnd, "Ptr", &desktopID)	
		if(Error != 0) {
			msgbox % "error in _loadVirtualDesktopId " Error "`n" this.hwnd
		}
		return desktopID
	}
	
	
	; https://github.com/cocobelgica/AutoHotkey-Util/blob/master/Guid.ahk#L36
	_guidToStr(ByRef VarOrAddress)
	{
		ToolTip, % &VarOrAddress " address"
		sleep 2000
		pGuid := IsByRef(VarOrAddress) ? &VarOrAddress : VarOrAddress
		VarSetCapacity(sGuid, 78) ; (38 + 1) * 2
		if !DllCall("ole32\StringFromGUID2", "Ptr", pGuid, "Ptr", &sGuid, "Int", 39)
			throw Exception("Invalid GUID", -1, Format("<at {1:p}>", pGuid))
		return StrGet(&sGuid, "UTF-16")
	}
	
	__Delete()
	{
		MsgBox getting deleted
		this.destroy()
		return
	}
}
It looks like the call to _guidToStr is access the same memory address every time. Any ideas?
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: IVirtualDesktopManager dllCall

11 Apr 2016, 18:04

nnnik wrote: In your code you use:

Code: Select all

IVirtualDesktopManager := ComObjCreate(CLSID, IID)
If this is for me (apologies if it is not): I kept the ComObjCreate call as it was because unwrapping the Scripting.Dictionary object that is used there to get the raw COM object only makes sense in that context as it implements IDispatch, but IVirtualDesktopManager which does not, so ComObjCreate will just return the raw COM object anyway.
jpginc wrote: This however gives me the same GUID on every desktop.
[...]

It looks like the call to _guidToStr is access the same memory address every time. Any ideas?
Ah, see, I'm really inexperienced at both AHK classes and AHK GUIs, but this did work for me: change _loadVirtualDesktopId(desktopId) to _loadVirtualDesktopId(ByRef desktopId).

That said, if you're not averse to trying something less future-proof, you can use the not-for-public-use IVirtualDesktopManagerInternal interface (as reversed by NickoTin here: http://www.cyberforum.ru/blogs/105416/blog3671.html). It has a GetCurrentDesktop method which makes all this a lot simpler. I have a small example of using the internal API to switch virtual desktops here without sending keypresses, but - more relevant for your purposes - I also wrote a program that uses it to show the current virtual desktop in the taskbar (the code gets the currently activated desktop object and then the GUID of it from that and then quickly compares GUIDs without the string conversion): https://www.reddit.com/r/Windows10/comm ... he/d1ep4y9
User avatar
nnnik
Posts: 3981
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: IVirtualDesktopManager dllCall

12 Apr 2016, 03:21

qwerty12 wrote:
nnnik wrote: In your code you use:

Code: Select all

IVirtualDesktopManager := ComObjCreate(CLSID, IID)
If this is for me (apologies if it is not): I kept the ComObjCreate call as it was because unwrapping the Scripting.Dictionary object that is used there to get the raw COM object only makes sense in that context as it implements IDispatch, but IVirtualDesktopManager which does not, so ComObjCreate will just return the raw COM object anyway.
I was just curious. I simply did not find any specific information about it.
Recommends AHK Studio

Return to “Ask For Help”

Who is online

Users browsing this forum: Albireo, electrified, eqv, euras, Flipeador, Google [Bot], MannyKSoSo, sinkfaze and 64 guests