TypeLib2AHK - Convert COM Type libraries to AHK code

Post your working scripts, libraries and tools for AHK v1.1 and older
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

TypeLib2AHK - Convert COM Type libraries to AHK code

19 Aug 2017, 10:09

TypeLib2AHK

TypeLib2AHK is a free, open source tool to convert information about COM interfaces stored in type libraries to code usable with AutoHotkey 1.1 and AutoHotkey v2 (https://autohotkey.com/).

The aim is to make it more comfortable and seamless to work with COM from AutoHotkey by providing class wrappers for structures and non-dispatch interfaces which are not natively supported by AutoHotkey and also the named constants associated with the interfaces.

Download


How to use

TypeLib2AHK.ahk can either be run directly as a standalone application or be used in an AutoHotkey script to retrieve information about specific COM objects (see examples at the beginning of the code in TypeLib2AHK.ahk). It can be used with AutoHotkey 32bit and 64bit Unicode releases. AutoHotkey ANSI releases are not supported.

TypeLib2AHK.ahk currently only runs under AutoHotkey 1.1 but can create code for AutoHotkey 1.1 and AutoHotkey v2.

The standalone application offers the user a list of type libraries stored on the computer and retrieved from applications currently in memory. Type library information can also be loaded from DLL files.

Type libraries can be viewed or directly converted. The viewing functionality is fairly basic; to get more complete information you can use Oleview.exe which is part of the Windows SDK, which can be downloaded for free from Microsoft.


How to use the created code


The resulting code is structured as follows (all examples refer to the UIAutomationClient type library):
  • Header: Information about the type library.
  • CoClasses: The code in this section allows to instantiate the underlying COM interfaces. Example: UIA:=CUIAutomation()
  • Alias type definitions: For information only as type definitions are usually not relevant in AHK.
  • Constants (enumerations and modules): They can be used directly (Example: myTreeScope:=UIAutomationClientTLConst.TreeScope_Ancestors) or the wrapper class can be instantiated as needed. Reverse lookup of a constant name is also possible (Example ValueName:=UIAutomationClientTLConst.UIA_ControlTypeIds(Value))
  • Structures (records and unions): The structures are wrapped as classes for transparent use in AHK scripts (see Example code below). Wrapped structures can be used directly as parameters in function calls or wrapped interfaces.
  • Interfaces: The interfaces are wrapped as classes for transparent use in AHK scripts (see Example code below).
  • Dispatch interfaces: Dispatch interfaces are handled natively by AHK. Their contents are included for information only.
The code may not contain all the above sections, depending on what is defined in the respective type library.

IMPORTANT:
  • Before using the created code carefully read the definitions and make sure that it doesn't redefine any Autohotkey functions. As an example: mscorlib.dll overrides Object().
  • Many basic structures are defined in several type libraries. You may need to edit the created code to avoid doubles.
  • The converted AutoHotkey code will most likely be different for 32bit and 64bit (most notably: differences in structure offsets and variant handling in DLL-calls), so be sure to use the correct version and take care when manually merging the code for different bit versions.
Occasionally the type libraries make reference to interfaces or structures which are not included in the library. These can usually be found in other type libraries. Many basic structures and interfaces are defined in the type library "mscorlib.dll".


Known issues

  • some memory leaks
If you find errors or think something could be improved:
Please inform me about any errors in the created code that you find. I have (obviously) only tested this with a small number of type libraries and a huge number of things could go wrong.


Changes

  • v 0.95 added output of AHK v2 compatible files
Related

ImportTypeLib by maul.esel (https://github.com/maul-esel/ImportTypeLib) wraps type libraries directly at run-time. It requires a slightly more complex syntax in use and seems to have issues in 64bit.


Example code
Example code
Last edited by Elgin on 01 Sep 2017, 04:34, edited 1 time in total.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

19 Aug 2017, 10:11

Thank you!

(I've had problems with the IDispatch interface of one of the network interfaces in the past. FreePascal was the only language I tried that did what I wanted properly, and I noticed that used the TypeLib to do so.)
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

19 Aug 2017, 17:50

Elgin,
Do you have a link to UIAutomationClient_1_0_64bit.ahk?
I just Googled it and nothing shows up.
Many thanks!
burque505
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

19 Aug 2017, 19:12

burque505 wrote:Elgin,
Do you have a link to UIAutomationClient_1_0_64bit.ahk?
I just Googled it and nothing shows up.
Many thanks!
burque505
No need for a link. That's the job of TypeLib2AHK. It creates that file (and many more libraries) for you:

Download+unzip the TypeLib2AHK repository linked in my post, run TypeLib2AHK.ahk, select UIAutomationClient from the list and press Convert.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

20 Aug 2017, 13:27

Elgin,
That is absolutely awesome. Hats off. Thank you!!
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

23 Sep 2017, 16:54

Elgin, I agree, this is absolutely awesome. I had been struggling with ImportTypeLib and on a whim decided to search the forums for more recent information and found this. This is really great stuff.

I already posted this same exact question here https://autohotkey.com/boards/viewtopic ... nt+handler and got a great (and working answer), but that is using uia.ahk which I won't get into more except that I'd prefer to use your generated code.

Trouble is I need to implement IUIAutomationEventHandler and based on the class that it generated (and what I gleaned from the help of my other post), what is here would not be sufficient (as I understand the situation). Here is what your code produced (v1.1 64 bit):

Code: Select all

; *************************************************************************
; IUIAutomationEventHandler
; GUID: {146C3C17-F12E-4E22-8C27-F894B9B79C69}
; *************************************************************************


class IUIAutomationEventHandler
{ 
	; Generic definitions

	static __IID := "{146C3C17-F12E-4E22-8C27-F894B9B79C69}"

	__New(p="", flag=1)
	{
		this.__Type:="IUIAutomationEventHandler"
		this.__Value:=p
		this.__Flag:=flag
	}

	__Delete()
	{
		this.__Flag? ObjRelease(this.__Value):0
	}

	__Vt(n)
	{
		return NumGet(NumGet(this.__Value+0, "Ptr")+n*A_PtrSize,"Ptr")
	}

	; Interface functions

	; VTable Positon 3: INVOKE_FUNC Vt_Hresult HandleAutomationEvent([FIN] IUIAutomationElement*: sender, [FIN] Int: eventId)
	HandleAutomationEvent(sender, eventId)
	{
		If (IsObject(sender) and (ComObjType(sender)=""))
			refsender:=sender.__Value
		else
			refsender:=sender
		res:=DllCall(this.__Vt(3), "Ptr", this.__Value, "Ptr", refsender, "Int", eventId, "Int")
		return res
	}
}
Transgarbling what I think I know about this, it's expecting a pointer which looks like it expects it to be a vtable, it also implements a HandleAutomationEvent() which seems it would call the 4th function in the vtable, though I'm not sure what would call that function (HandleAutomationEvent()).

In any case, it would seem this is an Interface class and not an implementation of the interface.
  • Is there a way to get TypeLib2AHK to build an implementation for this type of interface?
  • Is there a library out there for building these kinds of implementations?
  • Would you have an idea how to manually build an implementation for this IUIAutomationEventHandler with using your code generation tool?
Thanks!

P.S. I'd be happy to contribute to your project to implement this automated building of an implementation class for these types of things if the type library gives any indication that an implementation would be needed for the interface to be of use (since in this case, I don't think windows would ever give back a pointer to an implementation).
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

24 Sep 2017, 10:07

Hi cpriest,

first off I must say that I have never tried around with UIA event handlers or implemented my own COM objects in AHK, so my knowledge on this is very limited.

The code you quoted is indeed not intended to implement the interface. It merely gives you a wrapper to access an existing object having that interface.

In theory it should be no problem to have these implementations automatically written. After all, at it's core TypeLib2AHK merely patches pre-written strings and formatted data from the type libraries together into a file. The same procedure should work for basic implementation skeletons.

The more interesting point is to distinguish when an interface should be implemented and when it should be wrapped as the two seem mutually exclusive (at least I don't see a way to wrap existing COM objects and implement them at the same time). I've looked at the raw data from the UIA type library and noticed that all the event handlers have a TYPEFLAG_FOLEAUTOMATION flag set which no other interface in the library has. The MSDN documentation on this flag is not all that enlightening ("The types used in the interface are fully compatible with Automation, including VTBL binding support. Setting dual on an interface sets this flag in addition to TYPEFLAG_FDUAL. Not allowed on dispinterfaces.") imho, so I'm not really sure if this is a good indicator that an implementation is needed for every type library out there. But it sounds good enough to try...

If you could provide me with a working implementation code (i.e. what you'd like to see written in the generated code file instead of what's there now) and some sample code to try it out, I can try to patch that into my code. Fair warning: I'm rather busy at the moment, so it might take a while.
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

24 Sep 2017, 17:19

Sure, I'll put something together and post it here when I get it working properly.
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

01 Oct 2017, 15:25

Elgin wrote:If you could provide me with a working implementation code (i.e. what you'd like to see written in the generated code file instead of what's there now) and some sample code to try it out, I can try to patch that into my code. Fair warning: I'm rather busy at the moment, so it might take a while.
Hey Elgin, I have a fully functional example here now. This isn't precisely how I would have it output by TypeLib2AHK since I figured you might not be able to get to this right away. Probably your output would be less DRY than this, but if you smooshed these classes together and Eliminated some of the "don't create a new vTable for each new handler code I have in there, you'd be down to the basics.

LMK if you have any questions about it, the one include at the top is the output from TypeLib2AHK for the UIAutomation Client Type Library

Code: Select all

#Include TL2A\UIAutomationClient-v1_0-x86_64.ahk

; *************************************************************************
; IUIAutomationEventHandler
; GUID: {146C3C17-F12E-4E22-8C27-F894B9B79C69}
; *************************************************************************

; class IUIAutomationEventHandler {
; 	; Generic definitions

; 	static __IID := "{146C3C17-F12E-4E22-8C27-F894B9B79C69}"

; 	__New(p="", flag=1) {
; 		this.__Type:="IUIAutomationEventHandler"
; 		this.__Value:=p
; 		this.__Flag:=flag
; 	}

; 	__Delete() {
; 		this.__Flag? ObjRelease(this.__Value):0
; 	}

; 	__Vt(n) {
; 		return NumGet(NumGet(this.__Value, "Ptr")+n*A_PtrSize,"Ptr")
; 	}

; 	; Interface functions

; 	; VTable Positon 3: INVOKE_FUNC Vt_Hresult HandleAutomationEvent([FIN] IUIAutomationElement*: sender, [FIN] Int: eventId)
; 	HandleAutomationEvent(sender, eventId) {
; 		If (IsObject(sender) and (ComObjType(sender)=""))
; 			refsender:=sender.__Value
; 		else
; 			refsender:=sender
; 		res:=DllCall(this.__Vt(3), "Ptr", this.__Value, "Ptr", refsender, "Int", eventId, "Int")
; 		return res
; 	}
; }


class Heap {
	ProcessHeap {
		get {
			static heap := DllCall("GetProcessHeap", "Ptr")
			return heap
		}
	}
	Allocate(bytes) {
		static HEAP_GENERATE_EXCEPTIONS := 0x00000004, HEAP_ZERO_MEMORY := 0x00000008
		return DllCall("HeapAlloc", "Ptr", Heap.ProcessHeap, "UInt", HEAP_GENERATE_EXCEPTIONS|HEAP_ZERO_MEMORY, "UInt", bytes, "UPtr")
	}
	GetSize(buffer) {
		return DllCall("HeapSize", "Ptr", Heap.ProcessHeap, "UInt", 0, "Ptr", buffer, "Ptr" )
	}
	Release(buffer) {

		return DllCall("HeapFree", "Ptr", Heap.ProcessHeap, "UInt", 0, "Ptr", buffer, "Int")
	}
}

StringFromCLSID(riid) {
	res := DllCall("ole32\StringFromCLSID", "Ptr", riid, "PtrP", pStrCLSID := 0)
	sCLSID := StrGet(pStrCLSID, "UTF-16")
	DllCall("ole32\CoTaskMemFree", "Ptr", pStrCLSID)
	return sCLSID
}

global 	VT 		:= 0
	   ,REFS 	:= 1

/**
 *	Base implementation for all Com Objects
 *		- Handles creation of static vTable
 *		- Registers new class instance with vtoMap for efficient memory management of vTable use
 *		- Creates global pObject pointer for use with COM
 *
 *	 NOTE - Tested that:
 *		- this.vTable and this.vtoMap accesses the static declaration in the top level class declaration
 */
class ComObjImpl {

	; Pointers to vTables by this.IID
	static vTables := { }
	; Map of Interface Pointers to Object by this.IID
	static ObjMap := { }

	; Array of string GUID Com Interfaces that this object implements
	ImplementsInterfaces := []

	; Array of Com Functions that are implemented in the class hierarchy
	ComFunctions := []

	__New() {
		; Using the last Interface in the implementation array, this *should* be the highest IID in the chain
		this.IID := this.ImplementsInterfaces[this.ImplementsInterfaces.Length()]

		this.PopulateVirtualMethodTable()

		this.pInterface := Heap.Allocate(A_PtrSize + 4)
		ComObjImpl.ObjMap[this.pInterface] := this
		ObjRelease(&this)	; Release the reference just created, this reference will be free'd when __Delete is actually called

		NumPut(ComObjImpl.vTables[this.IID][VT], this.pInterface, 0, "Ptr")
		ComObjImpl.vTables[this.IID][REFS] += 1

		Refs := this._AddRef(this.pInterface)	; Adding our own reference, on delete should be 0
	}

	/**
	 *	Populates the static this.vTable with the callback points
	 *		NOTE: vTable is being passed in ByRef because it would seem using this.vTable with
	 *		VarSetCapacity/NumPut does not function correctly, using them on a ByRef of it works
	 *		just fine.
	 */
	PopulateVirtualMethodTable() {
		if (!ComObjImpl.vTables[this.IID]) {
			; Allocate a Ptr record for each method in ComFunctions
			ComObjImpl.vTables[this.IID, VT] := Heap.Allocate(this.ComFunctions.Length() * A_PtrSize)
			ComObjImpl.vTables[this.IID, REFS] := 0

			for i, func in this.ComFunctions {
				callback := RegisterCallback(this["_" func].Name)


				NumPut(callback, ComObjImpl.vTables[this.IID][VT], (i-1) * A_PtrSize)
			}
		}
	}

	__Delete() {
		ComObjImpl.vTables[this.IID][REFS] -= 1

		Heap.Release(this.pInterface)

		if (ComObjImpl.vTables[this.IID][REFS] == 0) {
			Heap.Release(ComObjImpl.vTables[this.IID][VT])
			ComObjImpl.vTables[this.IID] :=
		}
	}
}

class UnknownImpl extends ComObjImpl {
	; Declare the functions our class is implementing (expected to be over-ridden by sub-classes)
	; This array should be complete for the interface and in the correct vTable order per the Type Library
	ComFunctions := ["QueryInterface", "AddRef", "Release"]

	__New() {
		; Add the GUID of the interface this class is implementing
		this.ImplementsInterfaces.InsertAt(1, "{00000000-0000-0000-C000-000000000046}")		; IUnknown
		base.__New()
	}
	/**
	 *	Implementation of IUnknown::QueryInterface
	 *		Each class level that implements some part of the final vTable should have its interface GUID
	 *		declared in this.ImplementsInterface array
	 */
	_QueryInterface(pInterface, riid, ppvObject) {
		ExtCall := 0
		if (!IsObject(this)) {
			; Called from Callback, shift parameters
			ppvObject := riid
			riid := pInterface
			pInterface := this
			this := ComObjImpl.ObjMap[pInterface]
			ExtCall := 1
		}

		sCLSID := StringFromCLSID(riid)

		for i, sIID in this.ImplementsInterfaces {
			if (sCLSID == sIID) {
				NumPut(pInterface, ppvObject+0, "Ptr")

				this._AddRef(pInterface)
				return 0 ; S_OK
			}
		}

		NumPut(0, ppvObject+0, "Ptr")
		return 0x80004002 ; E_NOINTERFACE
	}

	; Implementation of IUnknown::AddRef
	_AddRef(pInterface) {
		ExtCall := 0
		if (!IsObject(this)) {
			pInterface := this
			this := ComObjImpl.ObjMap[pInterface]
			ExtCall := 1
		}
		NumPut((RefCount := NumGet(pInterface+0, A_PtrSize, "UInt") + 1), pInterface+0, A_PtrSize, "UInt")

		return RefCount
	}

	; Implementation of IUnknown::Release
	_Release(pInterface) {
		ExtCall := 0
		if (!IsObject(this)) {
			pInterface := this
			this := ComObjImpl.ObjMap[pInterface]
			ExtCall := 1
		}

		if ((RefCount := this.GetRefs()) > 0) {
			RefCount -= 1
			NumPut(RefCount, pInterface+0, A_PtrSize, "UInt")
		}

		return RefCount
	}

	GetRefs() {
		return NumGet(this.pInterface+0, A_PtrSize, "UInt")
	}

	__Delete() {
		if ( (RefCount := this.GetRefs()) != 1)
			OutputDebug % Format("WARNING: RefCount={} in {}, should be 1 (our own reference)", RefCount, A_ThisFunc "()")
		base.__Delete()
	}
}

/**
 *	UIAutomationEventHandlerImpl
 *		- Top Level Interface Implementation
 *		- Should have vTable and vtoMap declare as static
 *		- Sub-classes are user-level implementations and should just override
 *			HandleAutomationEvent verbatim.
 */
class UIAutomationEventHandlerImpl extends UnknownImpl {

	; Declare the functions our class is implementing, this should not be over-ridden by userland classes
	; 	This array should be complete for the interface and in the correct vTable order per the Type Library
	ComFunctions := ["QueryInterface", "AddRef", "Release", "HandleAutomationEvent"]

	__New() {
		; Add the GUID of the interface this class is implementing
		this.ImplementsInterfaces.InsertAt(1, "{146C3C17-F12E-4E22-8C27-F894B9B79C69}")		; IUIAutomationEventHandler
		base.__New()
	}

	;
	; 	Handles a Microsoft UI Automation event.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee696045(v=vs.85).aspx
	;
	;	IUIAutomationEventHandler 	pInterface	Pointer to our interface address
	;	IUIAutomationElement		pSender		Pointer to Element for which Event happened
	;	EVENTID 					EventID 	The event identifier.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx
	;
	_HandleAutomationEvent(pInterface, pSender, EventID) {

		ExtCall := 0
		if (!IsObject(this)) {
			; Called from Callback, shift parameters
			EventID := pSender
			pSender := pInterface
			pInterface := this
			this := ComObjImpl.ObjMap[pInterface]
			ExtCall := 1
		}

		this.HandleAutomationEvent(pInterface, pSender, EventID)
	}
}

class UIA_SnoopWindowsDestroyed extends UIAutomationEventHandlerImpl {
	;
	; 	Handles a Microsoft UI Automation event, called by the parent class which received the
	;		event call.
	;	@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee696045(v=vs.85).aspx
	;
	;	IUIAutomationEventHandler 	pInterface	Pointer to our interface address
	;	IUIAutomationElement		pSender		Pointer to Element for which Event happened
	;	EVENTID 					EventID 	The event identifier.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx
	;
	HandleAutomationEvent(pInterface, pSender, EventID) {

		Sender := new IUIAutomationElement(pSender)
		hWnd := Sender.CurrentNativeWindowHandle
		WinGet, sProcess, ProcessName, ahk_id %hWnd%
		OutputDebug % Format("Window Destroyed - hwnd={:X}, CurrentName={:20.20s}, Class={:20.20s}, Process={:s}", hWnd, Sender.CurrentName, Sender.CurrentClassName, sProcess)
	}
}

class UIA_SnoopWindowsCreated extends UIAutomationEventHandlerImpl {
	;
	; 	Handles a Microsoft UI Automation event, called by the parent class which received the
	;		event call.
	;	@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee696045(v=vs.85).aspx
	;
	;	IUIAutomationEventHandler 	pInterface	Pointer to our interface address
	;	IUIAutomationElement		pSender		Pointer to Element for which Event happened
	;	EVENTID 					EventID 	The event identifier.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx
	;
	HandleAutomationEvent(pInterface, pSender, EventID) {
		Sender := new IUIAutomationElement(pSender)
		hWnd := Sender.CurrentNativeWindowHandle
		WinGet, sProcess, ProcessName, ahk_id %hWnd%
		OutputDebug % Format("Window Created - hwnd={:X}, CurrentName={:20.20s}, Class={:20.20s}, Process={:s}", hWnd, Sender.CurrentName, Sender.CurrentClassName, sProcess)
	}
}

global OnCreated := new UIA_SnoopWindowsCreated()
global OnDestroyed := new UIA_SnoopWindowsDestroyed()
global UIA := CUIAutomation()

global DesktopElem := UIA.GetRootElement()

OutputDebug % Format("AddAutomationEventHandler, DesktopElem={}, OnCreated.pInterface={:X}", DesktopElem.CurrentName(), OnCreated.pInterface )

hr := UIA.AddAutomationEventHandler( UIAutomationClientTLConst.UIA_Window_WindowOpenedEventId
	, DesktopElem
	, UIAutomationClientTLConst.TreeScope_Children
	, 0
 	, OnCreated.pInterface )

UIA_Exit() {
	OutputDebug % Format(A_ThisFunc "()")
	; Remove our exit function from the list
	OnExit(A_ThisFunc, 0)

	if (IsObject(UIA)) {
		UIA.RemoveAllEventHandlers()
		UIA := ""
	}

	if (DesktopElem) {
		ObjRelease(DesktopElem)
		DesktopElem := 0
	}

	if(OnCreated)
		OnCreated :=
	if(OnDestroyed)
		OnDestroyed :=

	return 0
}

OnExit("UIA_Exit")


F1::Exit, 0
Last edited by cpriest on 02 Oct 2017, 20:54, edited 1 time in total.
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

02 Oct 2017, 01:24

Thanks. I'll look into it.
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

02 Oct 2017, 16:54

You know, after writing this and trying to solve some of the problems I had originally, which eventually became the above, I have some other ideas I'm going to try out. I'll leave the above posted for reference, but I'll post another time in the future once I've refactored it some more.

In essence, it will be the same, but cleaner.
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

02 Oct 2017, 17:47

I gave your code a quick test and it only seems to run in 64bit but not in 32. Any idea what might cause this? I'd like to support both if possible.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

02 Oct 2017, 19:11

Re. x64/x32, the one thing I noticed was this.
HeapAlloc: Ptr,UInt,>UPtr< (UPtr not UInt).
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

02 Oct 2017, 20:30

Hmm yeah, I hadn't tried it in 32 bit, did that fix it?

Edit:
Ok, yeah I see how that should be the case based on it being SIZE_T -> ULONG_PTR -> unsigned int64 (64 bit) or just a regular long (32 bit) in 32 bit.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

02 Oct 2017, 21:47

I had a look through the code, I haven't tested.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

03 Oct 2017, 08:07

I've tested it; still doesn't work in 32bit. I've logged the function calls and found that the call to AddAutomationEventHandler doesn't complete in 32 bit.

Here are the logs:
64bit startup:
Spoiler
A number of interfaces which seem to be mostly about marshaling get queried and most calls fail obviously.

64bit window open event:
Spoiler
32bit:
Spoiler
After the query for INoMarshal there are simply no more queries and the call to AddAutomationEventHandler never returns.

To try around I added INoMarshal to the supported interfaces. The call to AddAutomationEventHandler then returns but no events are received, so this doesn't solve the problem either:
Spoiler
When INoMarshal is added in 64bit, the call to AddAutomationEventHandler fails with 0x80004021 (CO_E_NOT_SUPPORTED) btw.
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

03 Oct 2017, 19:06

I'm not sure what's going on exactly with the 32-bit stuff, but I'll test it out and see what I can find out.

I did run into that issue before I had posted my solution, drove me nuts and I had discovered that _QueryInterface() was incorrectly returning a decimal rather than hex, that is this line:

Code: Select all

		return 0x80004002 ; E_NOINTERFACE      <--- Correct

		return 80004002 ; E_NOINTERFACE      <--- Incorrect		
That was precisely the cause of that exact same effect I was getting. Perhaps the 0x80004002 is being returned as unsigned or signed incorrectly in 32-bit?

On another note, I've got an update to the code. What I was thinking didn't work out but made me realize the parameter juggling I was doing wasn't necessary so this is cleaner. Posting again in case you want to diff them.

Code: Select all

#Include TL2A\UIAutomationClient-v1_0-x86_64.ahk

; *************************************************************************
; IUIAutomationEventHandler
; GUID: {146C3C17-F12E-4E22-8C27-F894B9B79C69}
; *************************************************************************

; class IUIAutomationEventHandler {
; 	; Generic definitions

; 	static __IID := "{146C3C17-F12E-4E22-8C27-F894B9B79C69}"

; 	__New(p="", flag=1) {
; 		this.__Type:="IUIAutomationEventHandler"
; 		this.__Value:=p
; 		this.__Flag:=flag
; 	}

; 	__Delete() {
; 		this.__Flag? ObjRelease(this.__Value):0
; 	}

; 	__Vt(n) {
; 		return NumGet(NumGet(this.__Value, "Ptr")+n*A_PtrSize,"Ptr")
; 	}

; 	; Interface functions

; 	; VTable Positon 3: INVOKE_FUNC Vt_Hresult HandleAutomationEvent([FIN] IUIAutomationElement*: sender, [FIN] Int: eventId)
; 	HandleAutomationEvent(sender, eventId) {
; 		If (IsObject(sender) and (ComObjType(sender)=""))
; 			refsender:=sender.__Value
; 		else
; 			refsender:=sender
; 		res:=DllCall(this.__Vt(3), "Ptr", this.__Value, "Ptr", refsender, "Int", eventId, "Int")
; 		return res
; 	}
; }


class Heap {
	ProcessHeap {
		get {
			static heap := DllCall("GetProcessHeap", "Ptr")
			return heap
		}
	}
	Allocate(bytes) {
		static HEAP_GENERATE_EXCEPTIONS := 0x00000004, HEAP_ZERO_MEMORY := 0x00000008
		return DllCall("HeapAlloc", "Ptr", Heap.ProcessHeap, "UInt", HEAP_GENERATE_EXCEPTIONS|HEAP_ZERO_MEMORY, "UInt", bytes, "UPtr")
	}
	GetSize(buffer) {
		return DllCall("HeapSize", "Ptr", Heap.ProcessHeap, "UInt", 0, "Ptr", buffer, "Ptr" )
	}
	Release(buffer) {

		return DllCall("HeapFree", "Ptr", Heap.ProcessHeap, "UInt", 0, "Ptr", buffer, "Int")
	}
}

StringFromCLSID(riid) {
	res := DllCall("ole32\StringFromCLSID", "Ptr", riid, "PtrP", pStrCLSID := 0)
	sCLSID := StrGet(pStrCLSID, "UTF-16")
	DllCall("ole32\CoTaskMemFree", "Ptr", pStrCLSID)
	return sCLSID
}

global 	VT 		:= 0
	   ,REFS 	:= 1

/**
 *	Base implementation for all Com Objects
 *		- Handles creation of static vTable
 *		- Registers new class instance with vtoMap for efficient memory management of vTable use
 *		- Creates global pObject pointer for use with COM
 *
 *	 NOTE - Tested that:
 *		- this.vTable and this.vtoMap accesses the static declaration in the top level class declaration
 */
class ComObjImpl {

	; Pointers to vTables by this.IID
	static vTables := { }
	; Map of Interface Pointers to Object by this.IID
	static ObjMap := { }

	; Array of string GUID Com Interfaces that this object implements
	ImplementsInterfaces := []

	; Array of Com Functions that are implemented in the class hierarchy
	ComFunctions := []

	__New() {
		; Using the last Interface in the implementation array, this *should* be the highest IID in the chain
		this.IID := this.ImplementsInterfaces[this.ImplementsInterfaces.Length()]

		this.PopulateVirtualMethodTable()

		this.pInterface := Heap.Allocate(A_PtrSize + 4)
		ComObjImpl.ObjMap[this.pInterface] := this
		ObjRelease(&this)	; Release the reference just created, this reference will be free'd when __Delete is actually called

		NumPut(ComObjImpl.vTables[this.IID][VT], this.pInterface, 0, "Ptr")
		ComObjImpl.vTables[this.IID][REFS] += 1

		Refs := this._AddRef(this.pInterface)	; Adding our own reference, on delete should be 0
	}

	/**
	 *	Populates the static this.vTable with the callback points
	 *		NOTE: vTable is being passed in ByRef because it would seem using this.vTable with
	 *		VarSetCapacity/NumPut does not function correctly, using them on a ByRef of it works
	 *		just fine.
	 */
	PopulateVirtualMethodTable() {
		if (!ComObjImpl.vTables[this.IID]) {
			; Allocate a Ptr record for each method in ComFunctions
			ComObjImpl.vTables[this.IID, VT] := Heap.Allocate(this.ComFunctions.Length() * A_PtrSize)
			ComObjImpl.vTables[this.IID, REFS] := 0

			for i, func in this.ComFunctions {
				callback := RegisterCallback(this["_" func].Name)

				NumPut(callback, ComObjImpl.vTables[this.IID][VT], (i-1) * A_PtrSize)
			}
		}
	}

	__Delete() {
		ComObjImpl.ObjMap[this.pInterface] :=
		ComObjImpl.vTables[this.IID][REFS] -= 1

		Heap.Release(this.pInterface)

		if (ComObjImpl.vTables[this.IID][REFS] == 0) {
			Heap.Release(ComObjImpl.vTables[this.IID][VT])
			ComObjImpl.vTables[this.IID] :=
		}
	}
}

class UnknownImpl extends ComObjImpl {
	; Declare the functions our class is implementing (expected to be over-ridden by sub-classes)
	; This array should be complete for the interface and in the correct vTable order per the Type Library
	ComFunctions := ["QueryInterface", "AddRef", "Release"]

	__New() {
		; Add the GUID of the interface this class is implementing
		this.ImplementsInterfaces.InsertAt(1, "{00000000-0000-0000-C000-000000000046}")		; IUnknown
		base.__New()
	}
	/**
	 *	Implementation of IUnknown::QueryInterface
	 *		Each class level that implements some part of the final vTable should have its interface GUID
	 *		declared in this.ImplementsInterface array
	 */
	_QueryInterface(pInterface, riid, ppvObject) {
		if (!IsObject(this))
			return ComObjImpl.ObjMap[this]._QueryInterface(this, pInterface, riid)

		sCLSID := StringFromCLSID(riid)

		for i, sIID in this.ImplementsInterfaces {
			if (sCLSID == sIID) {
				NumPut(pInterface, ppvObject+0, "Ptr")

				this._AddRef(pInterface)
				return 0 ; S_OK
			}
		}

		NumPut(0, ppvObject+0, "Ptr")
		return 0x80004002 ; E_NOINTERFACE
	}

	; Implementation of IUnknown::AddRef
	_AddRef(pInterface) {
		if (!IsObject(this))
			return ComObjImpl.ObjMap[this]._AddRef(this)

		NumPut((RefCount := NumGet(pInterface+0, A_PtrSize, "UInt") + 1), pInterface+0, A_PtrSize, "UInt")

		return RefCount
	}

	; Implementation of IUnknown::Release
	_Release(pInterface) {
		if (!IsObject(this))
			return ComObjImpl.ObjMap[this]._Release(this)

		if ((RefCount := this.GetRefs()) > 0) {
			RefCount -= 1
			NumPut(RefCount, pInterface+0, A_PtrSize, "UInt")
		}

		return RefCount
	}

	GetRefs() {
		return NumGet(this.pInterface+0, A_PtrSize, "UInt")
	}

	__Delete() {
		if ( (RefCount := this.GetRefs()) != 1)
			OutputDebug % Format("WARNING: RefCount={} in {}, should be 1 (our own reference)", RefCount, A_ThisFunc "()")
		base.__Delete()
	}
}

/**
 *	UIAutomationEventHandlerImpl
 *		- Top Level Interface Implementation
 *		- Should have vTable and vtoMap declare as static
 *		- Sub-classes are user-level implementations and should just override
 *			HandleAutomationEvent verbatim.
 */
class UIAutomationEventHandlerImpl extends UnknownImpl {

	; Declare the functions our class is implementing, this should not be over-ridden by userland classes
	; 	This array should be complete for the interface and in the correct vTable order per the Type Library
	ComFunctions := ["QueryInterface", "AddRef", "Release", "HandleAutomationEvent"]

	__New() {
		; Add the GUID of the interface this class is implementing
		this.ImplementsInterfaces.InsertAt(1, "{146C3C17-F12E-4E22-8C27-F894B9B79C69}")		; IUIAutomationEventHandler
		base.__New()
	}

	;
	; 	Handles a Microsoft UI Automation event.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee696045(v=vs.85).aspx
	;
	;	IUIAutomationEventHandler 	pInterface	Pointer to our interface address
	;	IUIAutomationElement		pSender		Pointer to Element for which Event happened
	;	EVENTID 					EventID 	The event identifier.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx
	;
	_HandleAutomationEvent(pInterface, pSender, EventID) {
		if (!IsObject(this))
			return ComObjImpl.ObjMap[this]._HandleAutomationEvent(this, pInterface, pSender)

		this.HandleAutomationEvent(pInterface, pSender, EventID)
	}
}

class UIA_SnoopWindowsDestroyed extends UIAutomationEventHandlerImpl {
	;
	; 	Handles a Microsoft UI Automation event, called by the parent class which received the
	;		event call.
	;	@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee696045(v=vs.85).aspx
	;
	;	IUIAutomationEventHandler 	pInterface	Pointer to our interface address
	;	IUIAutomationElement		pSender		Pointer to Element for which Event happened
	;	EVENTID 					EventID 	The event identifier.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx
	;
	HandleAutomationEvent(pInterface, pSender, EventID) {

		Sender := new IUIAutomationElement(pSender)
		hWnd := Sender.CurrentNativeWindowHandle
		WinGet, sProcess, ProcessName, ahk_id %hWnd%
		OutputDebug % Format("Window Destroyed - hwnd={:X}, CurrentName={:20.20s}, Class={:20.20s}, Process={:s}", hWnd, Sender.CurrentName, Sender.CurrentClassName, sProcess)
	}
}

class UIA_SnoopWindowsCreated extends UIAutomationEventHandlerImpl {
	;
	; 	Handles a Microsoft UI Automation event, called by the parent class which received the
	;		event call.
	;	@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee696045(v=vs.85).aspx
	;
	;	IUIAutomationEventHandler 	pInterface	Pointer to our interface address
	;	IUIAutomationElement		pSender		Pointer to Element for which Event happened
	;	EVENTID 					EventID 	The event identifier.
	;		@see: https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx
	;
	HandleAutomationEvent(pInterface, pSender, EventID) {
		Sender := new IUIAutomationElement(pSender)
		hWnd := Sender.CurrentNativeWindowHandle
		WinGet, sProcess, ProcessName, ahk_id %hWnd%
		OutputDebug % Format("Window Created - hwnd={:X}, CurrentName={:20.20s}, Class={:20.20s}, Process={:s}", hWnd, Sender.CurrentName, Sender.CurrentClassName, sProcess)
	}
}

global OnCreated := new UIA_SnoopWindowsCreated()
global OnDestroyed := new UIA_SnoopWindowsDestroyed()
global UIA := CUIAutomation()

global DesktopElem := UIA.GetRootElement()

OutputDebug % Format("AddAutomationEventHandler, DesktopElem={}, OnCreated.pInterface={:X}", DesktopElem.CurrentName(), OnCreated.pInterface )

hr := UIA.AddAutomationEventHandler( UIAutomationClientTLConst.UIA_Window_WindowOpenedEventId
	, DesktopElem
	, UIAutomationClientTLConst.TreeScope_Children
	, 0
 	, OnCreated.pInterface )

UIA_Exit() {
	OutputDebug % Format(A_ThisFunc "()")
	; Remove our exit function from the list
	OnExit(A_ThisFunc, 0)

	if (IsObject(UIA)) {
		UIA.RemoveAllEventHandlers()
		UIA := ""
	}

	if (DesktopElem) {
		ObjRelease(DesktopElem)
		DesktopElem := 0
	}

	if(OnCreated)
		OnCreated :=
	if(OnDestroyed)
		OnDestroyed :=

	return 0
}

OnExit("UIA_Exit")


F1::Exit, 0
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

03 Oct 2017, 20:53

The 0x80004002 does not seem to be the issue. Returning -2147467262, 0x80004002 or 0xFFFFFFFF80004002 all produces identical results. But when any other value is returned, the final AddRef does not get called anymore which I assume means that QueryInterface went wrong. So it looks like things are going right from our side.
Strange.

Btw: did you come across any example of this successfully being done in 32bit in another non-.net programming language? Maybe the fault is not even with us...

Another thing:
Some interfaces contain properties in addition to functions e. g. IRawElementProviderSimple. How should those be implemented? Like other functions (after all type libraries have separate vtable entries for property get and put) or in a different way?
cpriest
Posts: 20
Joined: 17 Sep 2017, 08:06

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

03 Oct 2017, 21:00

Hmm, I don't know enough about com with that, do you know of an article that discusses the memory structure of a com pointer?
Elgin
Posts: 124
Joined: 30 Sep 2013, 09:19

Re: TypeLib2AHK - Convert COM Type libraries to AHK code

06 Oct 2017, 09:17

Hi,

I've uploaded the changes so you can give them a try:
https://github.com/Elgin1/TypeLib2AHK/tree/Test

Was there any particular reason for shifting the function parameters and inserting pInterface? I noticed it creates big problems with byref parameters and removed it. Seems to have no ill effect...

For some reason your original code and my adaptation of your example only seems to receive a single event and then nothing more. Do you have any idea why that could be?

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: tiska and 175 guests