Sign in to follow this  
Followers 0

Hooking into the IDispatch interface.

71 posts in this topic

Posted (edited)

So, today my mind has been 100 % focused on something that on the surface looks really boring. The IDispatch interface.

A lot of you probably heard the name IDispatch before. And you may even know that it's required by COM-objects used in autoit with the ObjCreate() function. So we're dealing with COM.

Before reading any further I would recommend that you read the following wikipedia articles since that's what this article really is about. Link 1 Link 2.

So what is IDispatch good for? It's a way for applications to resolve methods, properties and arguments during runtime instead when compiled. This is an extremely powerful feature that means that applications like AutoIt, VBScript and others that don't come shipped with thousands of lines of code from microsoft can utilize powerful code.

We're going to take advantage of IDispatch's method of work. IDispatch exposes 4 methods for outside use. In our hooking application we will hook two of them. GetIdsFromNames and Invoke. These two are used to resolve member names in objects and finally use them.

Why would we want to hook them? Because it provides some interesting opportunities, we can for example modify objects during runtime to bend over to our will.

In the example below we're going to select one innocent victim. The only requirement for this victim is that it's an IDispatch interface. In my almighty wisdom I chose Shell.Application.

So what will we do to poor Shell.Application? We will explore his vtable (you did read the wiki links?) and simply change the pointers that points to the real methods to custom autoit made callbacks.

Now we have a problem. If we try to use ObjCreate function we will only get an abstract autoit object, this is no good since we wanna edit his inner workings, we need a pointer.

So we simply create the object manually. Is this a free deal? No, because now we have a pointer, not the autoit object. Fortunately we exploit the "idispatch*" dllcall data type to overcome this problem.

What we will do next is to examine the data coming form autoit and tell autoit that certain new names exists in the object - but in reality they really aren't. When autoit then tries to call this new method we're there too to execute some custom code.

Enough chit chat. Here's the code. It's completely stand alone, no third party tools whatsoever, no assembly or nothing. Just Dll* functions.

Global $hOle32=0

Global Const $tagGUID 	= "ulong;ushort;ushort;byte[8]"
Global Const $tagCLSID = $tagGUID
Global Const $tagUUID = $tagGUID
Global Const $CLSCTX_INPROC_SERVER	= 0x1
Global Const $S_OK = 0
Global Const $DISP_E_UNKNOWNNAME = 2147614726
Global Const $DISPID_UNKNOWN=4294967295
Global Const $DISP_E_MEMBERNOTFOUND=2147614723

; Could be anything really.
Global Const $HELLO_THERE_ID = 1337

; Starts COM
Func CoInitialize()
	$hOle32=DllOpen("Ole32.dll")
	DllCall($hOle32,"ulong","CoInitialize","ptr",0)
EndFunc

; This will make a registry lookup to find out what the passed
; string really means. That is a GUID
Func CLSIDFromProgID($ProgID)
	$clsid = DllStructCreate($tagCLSID)
	DllCall($hOle32,"long","CLSIDFromProgID","wstr",$ProgID,"ptr",DllStructGetPtr($clsid))
	Return $clsid
EndFunc

; Returns the UUID of IDispatch
Func IDispatch_UUID()
	; Autoit sets it all to null
	$uuid = DllStructCreate($tagUUID)
	DllStructSetData($uuid,1,132096)
	DllStructSetData($uuid,4,192,1)
	DllStructSetData($uuid,4,70,8)
	Return $uuid
EndFunc

; Creates a instance of a COM object from a clsid and iid
; Usually done internally by autoit but we need the pointer.
Func CoCreateInstance($clsid,$pUnkOuter,$ClsContext,$iid,$pOutObj)
	Return DllCall($hOle32,"long","CoCreateInstance","ptr",DllStructGetPtr($clsid),"ptr",$pUnkOuter,"dword",$ClsContext,"ptr",DllStructGetPtr($iid),"ptr",$pOutObj)
EndFunc

; WRAPPER FOR IDISPATCH INTERFACES.
; This is like ObjCreate except this returns the raw pointer to the IDispatch interface.
; This is essential for hooking the methods of the IDispatch object.
Func CreateIDispatchFromProgID($ProgID)
	; Ptr to IDispatch object
	$pObj = DllStructCreate("ptr")
	$aCall = CoCreateInstance(CLSIDFromProgID($ProgID),0,$CLSCTX_INPROC_SERVER,IDispatch_UUID(),DllStructGetPtr($pObj))
	Return DllStructGetData($pObj,1)
EndFunc

; In the end we still want the autoit object. This function converts a raw pointer to an autoit object
Func ConvertPtrToIDispatch($IDispatch_Ptr)
	; This would have been 10000x easier if autoit had supported the idispatch* type in dllstructs...
	; Fortunetely memcpy can copy the pointer into a idispatch*, lucky us.
	$ptr_struct=DllStructCreate("ptr")
	DllStructSetData($ptr_struct,1,$IDispatch_Ptr)
	$aCall = DllCall("ntdll.dll","ptr:cdecl","memcpy","idispatch*","","ptr",DllStructGetPtr($ptr_struct),"long",4)
	return $aCall[1]
EndFunc

; Sets the MEM_EXECUTE_READWRITE flag on the specified memory. Use with care, I use because I'm lazy.
Func UnprotectMemory($pointer,$size)
	DllCall("Kernel32.dll","int","VirtualProtect","ptr",$pointer,"long",$size,"dword",0x40,"dword*",0)
EndFunc

; Returns the pointer the passed pointer points to ;)
Func DereferencePointer($ptr)
	$tempstruct = DllStructCreate("ptr",$ptr)
	Return DllStructGetData($tempstruct,1)
Endfunc


; Takes a pointer to the v-table in a class and replaces specified pointer in it to a new one.
Func ReplaceVTableEntry($vtable_ptr,$offset,$new_ptr)
	; Dereference the pointer
	$first_entry_ptr=DereferencePointer($vtable_ptr)

	$entry_pointer = $first_entry_ptr+$offset
	; Make the memory free for all. Yay!
	UnprotectMemory($entry_pointer,4)
	$entry_struct = DllStructCreate("ptr",$entry_pointer)
	DllStructSetData($entry_struct,1,$new_ptr)

EndFunc

; Everytime autoit wants to call a method, get or set a property in a object it needs to go to
; IDispatch::GetIDsFromNames. This is our version of that function, note that by defining this ourselves
; we can fool autoit to believe that the object supports a lot of different properties/methods.
;
Func IDispatch_GetIDsFromNames($self,$refiid,$str_array,$array_size,$context,$out_array)
	; It's self explainable that autoit only asks for one member
	$str = DllStructCreate("wchar[256]",DereferencePointer($str_array))
	ConsoleWrite("AutoIt wants to look up: "&DllStructGetData($str,1)&@CRLF)

	; Autoit gave us an array with one element ready to accept the id of the member it requested.
	$ids = DllStructCreate("long",$out_array)

	; If autoit tried to call method "HelloThere" then supply a valid ID and indicate success
	if DllStructGetData($str,1)=="HelloThere" Then
		DllStructSetData($ids,1,$HELLO_THERE_ID)
		return $S_OK
	; ....else say it was an unknown name.
	; We really should redirect execution to the original function here, but meh!
	Else
		DllStructSetData($ids,1,$DISPID_UNKNOWN)
		return $DISP_E_UNKNOWNNAME
	EndIf



EndFunc
; Create the callback so we have a pointer to this function.
$IDispatch_GetIDsFromNames_Callback = DllCallbackRegister("IDispatch_GetIDsFromNames","long","idispatch;ptr;ptr;int;int;ptr")
$IDispatch_GetIDsFromNames_Callback_Ptr = DllCallbackGetPtr($IDispatch_GetIDsFromNames_Callback)


; This is called when a method is called, a property is set or get.
; This call also contains arguments returns and a lot other stuff.
; However in this trivial example we don't return anything and we don't take any arguments. Puh, could get messy
Func IDispatch_Invoke($self,$dispID,$riid,$lcid,$wFlags,$pDispParams,$pVarResult,$pExceptInfo,$puArgErr)
	;; Dump all parameters to console.
	ConsoleWrite("DispID: "&$dispID&@CRLF&"RIID: "&$riid&@CRLF&"LCID: "&$lcid&@CRLF&"wFlags: "&$wFlags&@CRLF& _
					"pDispParams: "&$pDispParams&@CRLF&"pVarResult: "&$pVarResult&@CRLF&"pExceptInfo: "&$pExceptInfo&@CRLF&"puArgError: "&$puArgErr&@CRLF)

	; Oh, autoit tries to use a macro we know! Lets do something
	If $dispID=$HELLO_THERE_ID Then
		MsgBox(0,"Hi","Hello There!")
		; Give autoit the message that everything went according to plan
		return $S_OK
	Else
		; Here we should Redirect execution to the original IDispatch::Invoke.
		; But I'm lazy and we have avoid any assembly so for so lets make it stay that way
		Return $DISP_E_MEMBERNOTFOUND
	EndIf

EndFunc
; Create callback
$IDispatch_Invoke_Callback = DllCallbackRegister("IDispatch_Invoke","long","idispatch;long;dword;ushort;ptr;ptr;ptr;ptr;ptr")
$IDispatch_Invoke_Callback_Ptr = DllCallbackGetPtr($IDispatch_Invoke_Callback)


; Initalize COM
CoInitialize()

; Create a victim. Could be any COM object that inherits from IDispatch
$obj_ptr = CreateIDispatchFromProgID("shell.application")
; Create autoit object as well.
$obj = ConvertPtrToIDispatch($obj_ptr)

; Hook into the object
; Offset 20 & 24 is fifth entry in vtable. Look at IDispatch and IUnknown interfaces to see why
ReplaceVTableEntry($obj_ptr,20,$IDispatch_GetIDsFromNames_Callback_Ptr)
ReplaceVTableEntry($obj_ptr,24,$IDispatch_Invoke_Callback_Ptr)

; Perform magic. All this work has been so this little line would work.
; Computer science is truly amazing.
$obj.HelloThere()

What can we do with this newly found power?

First off one can quite easily overcome the no ByRef in Mehtod calls limitation autoit have had forever.

But most important: With this, AutoIt3 can, and will be an object oriented language, yes you read it right. Without even touching the core of autoit we can extend it into new heights. Inheritance, polymorphism and instances, just with some clever thinking.

If anyone feel like they can contribute to the OO project and preferable knows at least basic COM feel free to contact me.

Lastly a would like to thanks trancexx a bunch for ideas, code and clever thinking!

Still reading? You're probably kinda alone ;)

Edited by monoceres

Share this post


Link to post
Share on other sites



Posted

This sounds awesome!

11 months ago I started on a UDF only to crash inte the ByRef limit, will be fun to see if I can get longer now ;)

Share this post


Link to post
Share on other sites

Posted

This sounds awesome!

11 months ago I started on a UDF only to crash inte the ByRef limit, will be fun to see if I can get longer now :evil:

Well it's possible now to build around it. Not saying I'm going to do it though ;)

I'm more into the other thing. Doing some incredible progress right now.

Share this post


Link to post
Share on other sites

Posted

Well it's possible now to build around it. Not saying I'm going to do it though :evil:

Can't you do both? ;)

How hard would it be anyway? Should I even bother?

Share this post


Link to post
Share on other sites

Posted

Can't you do both? ;)

How hard would it be anyway? Should I even bother?

I don't think you want to until my OO work is done.

Stuff like this need to be implemented, and that's not too easy :evil:

struct tagVARIANT
	{
	union 
 	{
 	struct __tagVARIANT
 	{
 	VARTYPE vt;
 	WORD wReserved1;
 	WORD wReserved2;
 	WORD wReserved3;
 	union 
 	{
 	LONGLONG llVal;
 	LONG lVal;
 	BYTE bVal;
 	SHORT iVal;
 	FLOAT fltVal;
 	DOUBLE dblVal;
 	VARIANT_BOOL boolVal;
 	_VARIANT_BOOL bool;
 	SCODE scode;
 	CY cyVal;
 	DATE date;
 	BSTR bstrVal;
 	IUnknown *punkVal;
 	IDispatch *pdispVal;
 	SAFEARRAY *parray;
 	BYTE *pbVal;
 	SHORT *piVal;
 	LONG *plVal;
 	LONGLONG *pllVal;
 	FLOAT *pfltVal;
 	DOUBLE *pdblVal;
 	VARIANT_BOOL *pboolVal;
 	_VARIANT_BOOL *pbool;
 	SCODE *pscode;
 	CY *pcyVal;
 	DATE *pdate;
 	BSTR *pbstrVal;
 	IUnknown **ppunkVal;
 	IDispatch **ppdispVal;
 	SAFEARRAY **pparray;
 	VARIANT *pvarVal;
 	PVOID byref;
 	CHAR cVal;
 	USHORT uiVal;
 	ULONG ulVal;
 	ULONGLONG ullVal;
 	INT intVal;
 	UINT uintVal;
 	DECIMAL *pdecVal;
 	CHAR *pcVal;
 	USHORT *puiVal;
 	ULONG *pulVal;
 	ULONGLONG *pullVal;
 	INT *pintVal;
 	UINT *puintVal;
 	struct __tagBRECORD
 	{
 	PVOID pvRecord;
 	IRecordInfo *pRecInfo;
 	} 	__VARIANT_NAME_4;
 	} 	__VARIANT_NAME_3;
 	} 	__VARIANT_NAME_2;
 	DECIMAL decVal;
 	} 	__VARIANT_NAME_1;
	} ;

Share this post


Link to post
Share on other sites

Posted

That thing is ugly :evil:

It's even worse than SDL_RWops.... I think, I never understood that ;)

Share this post


Link to post
Share on other sites

Posted

monoceres made a great breakthrough with this. He opened a new door for AutoIt.

And since I saw a bit of what followed the code from the first post I would say this - AutoIt will have classes built-in in the near future if there would be enough wisdom by the devs.

Brilliant.

Share this post


Link to post
Share on other sites

Posted

I'm not surprised, monoceres and trancexx at it again. You sure know how to deliver quality code. ;)

Share this post


Link to post
Share on other sites

Posted

After monoceres explain why this could would be a useful asset to AutoIt, I sat there looking like this Posted Image for a good few minutes.

If what he told me he is doing at the moment, works... Then... Wow, AutoIt will be an even better language.

Share this post


Link to post
Share on other sites

Posted

I just got arguments to work.

At the moment the following syntax is correct.

$obj.Awesome("This is a MessageBox with automatic title")
$obj.MessageBox("Regular","MessageBox")

Func Awesome($self, $arg)
	Return $self.MessageBox("Awesome",$arg)
EndFunc ;==>Awesome

Func MessageBox($self,$arg,$arg2)
	MsgBox(0, $arg, $arg2)
EndFunc ;==>MessageBox

Properties should be next. They will be tricky.

Share this post


Link to post
Share on other sites

Posted

I just got arguments to work.

At the moment the following syntax is correct.

$obj.Awesome("This is a MessageBox with automatic title")
$obj.MessageBox("Regular","MessageBox")

Func Awesome($self, $arg)
	Return $self.MessageBox("Awesome",$arg)
EndFunc ;==>Awesome

Func MessageBox($self,$arg,$arg2)
	MsgBox(0, $arg, $arg2)
EndFunc ;==>MessageBox

Properties should be next. They will be tricky.

Awesome!

This is also a place where it would be necessary to introduce new syntax elements in AutoIt.

Share this post


Link to post
Share on other sites

Posted (edited)

monoceres, could you give some practical appliances of this code other than introducing OO?

I did a Lua implementation of OO once. It allows you to take no particular order of which classes you include (or load) first.

Edit: You will probably not understand this if you don't know about metatables and metamethods: http://www.lua.org/pil/13.html

Library (ignore annoying double white lines):

// Manadar OO (to not conflict with any other OO systems)

// it allows you to create classes with inheritance without knowing ahead of time which classes are loaded first

// an important thing to know is that everything is built from tables. There is one table for each class (static) and one table for each class instance.

// when calling a function/accessing a variable, this is the order of checking which table contains the function/variable: class instance -> class -> baseclass ( -> baseclass -> baseclass , etc. )

// Each class can access their base object with the self.base "keyword"

//

// some rules:

//	Be as strict as possible

//	Inheritance from Object class is enforced when no other class is defined. If you write your own Object class then no guarantees are given.

//	Properties and method that start with _ are private, and should be not be called outside of another object, unless the object calling should never be decoupled from the other

//	You should be much more careful about when you are dealing with a static class or when with an instance

//

// General OOP rules apply!!





// Object class, the most simplest of classes, all other classes inherit from this

Object = {}

Object_instance_mt = {__index = Object}

Object.Classname = "Object"



function Object.Create()

	local instance = {}

	setmetatable(instance, Object_instance_mt) // This makes sure that if an instance of Object  does not have method/var it checks for the statics (these are ToString() for example)

	return instance

end



function Object:ToString()

	return self.Classname

end

// Object class end







Moo = {}



local loadedClasses = {} // Contains a list of classes that are loaded/registered, these classes can be inherit from

loadedClasses["Object"] = Object // Add the object class to the classes that are loaded



local requireLoadClasses = {} // This will contain a list of classes that want to be loaded, but can't right now because their baseclass is not yet loaded/registered



function Moo.getRegisteredClass(className) // Returns a loaded/registered class based on the classname. Search for "Object" this returns a static reference to Object.

	for k, v in pairs(loadedClasses) do

		if (k == className) then return v end

	end

end



function Moo.registerClass(class) // Registers a class as being loaded, removes this class from classes that needed loading, and checks again if any other classes need loading

	loadedClasses[class.Classname] = class

	

	// Remove this class from the list of classes that still need to be loaded

	for key, requireLoad in pairs(requireLoadClasses) do

		if (requireLoad.Class.Classname == class.Classname) then

			requireLoadClasses[key] = nil

			break

		end

	end

	

	// Maybe there are other classes waiting for this class to load

	Moo.checkClassesToLoad()

end



function Moo.checkClassesToLoad() // Re evaluates all the classes that are still waiting for their base object, and loads them if possible

	for loadMeKey, loadMe in pairs(requireLoadClasses) do // Loop through the classes we want to load

		for loadedClassName, loadedClass in pairs(loadedClasses) do // Loop through the classes that are loaded

			

			if (loadMe.baseClassName == loadedClassName) then // If we want to load a class which base class is already loaded

				Moo.inheritClassFrom(loadMe.Class, loadedClass) // Load this class

			end

			

		end

	end

end



function Moo.CreateClass(className, inheritFrom)

	if (!inheritFrom) then inheritFrom = "Object" end // you must always inherit from Object, so I am enforcing that

	

	print("Moo loading class: " .. tostring(className) .. " extends " .. tostring(inheritFrom))

	

	local newClass = {} // Create a table for the new class (static member)

	newClass.Classname = className // Set the classname (static member)

	

	// Our class is now pretty much complete, but we still have to know about our base object before we initialize it

	

	baseClass = Moo.getRegisteredClass(inheritFrom) // Gets our base class, if it is loaded

	if (baseClass) then

		Moo.inheritClassFrom(newClass, baseClass) // Our base class is loaded, we inherit from it

		// The class is now fully loaded, with inheritance included

	else

		// The base object did not exist, let's wait for it and hope it arrives here shortly

		table.insert(requireLoadClasses, {Class = newClass, baseClassName = inheritFrom})

	end

	

	return newClass

end



function Moo.inheritClassFrom(newClass, baseClass)

	print("Moo loaded class: " .. tostring(newClass.Classname) .. " extends " .. tostring(baseClass.Classname))

	// A utility function to create the link between the static class and the static base class

	local newClass_instance_mt = { __index = newClass }

	

	local newClass_mt = { __index = baseClass } // This class inherits from the base class

	setmetatable(newClass, newClass_mt)

	

	function newClass.Create(param1, param2, param3, param4, param5) // This is the static function to create a new object (similar to the new keyword)

		local newClass_instance = baseClass.Create(param1, param2, param3, param4, param5) // Create our base class before we create ourselves (common practice)

		

		newClass_instance.base = baseClass // Set up the self.base keyword to be used in any class instances

        setmetatable( newClass_instance, newClass_instance_mt ) // Our class instances references our class object if it must

		

        return newClass_instance

	end

	

	// The class is now fully loaded, with inheritance included

	Moo.registerClass(newClass)

end

Example:

include('moo.lua')



-- Syntax for CreateClass is:

-- variable to contain the class (static) = CreateClass("Class name")

-- You can use another name for the variable that holds the class, but I don't recommend it (for clarity)



Animal = Moo.CreateClass("Animal")

Animal.Legs = 6 // Static variable



function Animal:Walk()

	print("I am now walking.")

end



function Animal:Stop()

	print("I have now stopped walking.")

end



function Animal:ToString()

	return "An animal usually has " .. tostring(self.Legs) .. " legs"

end



// Dog inherits from Animal

Dog = Moo.CreateClass("Dog", "Animal")

Dog.Legs = 4



function Dog:ToString() // override ToString

	print("A dog has " .. self.Legs .. " legs")

end



function Dog:Stop() // override Stop

	print("Dogs don't stop!! :D")

end





// Let's try out the animal class

anml = Animal.Create() // anml = new Animal()

anml:Walk() // Tell the animal to walk

anml:Stop() // Tell the animal to stop

print(anml:ToString()) // ToString test



//Output:

//	I am now walking.

//	I have now stopped walking.

//	An animal usually has 6 legs





// Let's try out the dog class :D

doggy = Dog.Create()

doggy:Walk()

doggy:Stop()

print(doggy:ToString())



// Output:

//	I am now walking.

//	Dogs don't stop!! :D

//	A dog has 4 legs



doggy.Legs = 2

print(doggy:ToString())



// Output:

//	A dog has 2 legs

Edited by Manadar

Share this post


Link to post
Share on other sites

Posted

Awesome!

This is also a place where it would be necessary to introduce new syntax elements in AutoIt.

Yeah, trhe syntax for defining objects will not be very good. But it will work nevertheless

monoceres, could you give some practical appliances of this code other than introducing OO?

I did a Lua implementation of OO once. It allows you to take no particular order of which classes you include (or load) first.

Edit: You will probably not understand this if you don't know about metatables and metamethods: http://www.lua.org/pil/13.html

Library (ignore annoying double white lines):

// Manadar OO (to not conflict with any other OO systems)

// it allows you to create classes with inheritance without knowing ahead of time which classes are loaded first

// an important thing to know is that everything is built from tables. There is one table for each class (static) and one table for each class instance.

// when calling a function/accessing a variable, this is the order of checking which table contains the function/variable: class instance -> class -> baseclass ( -> baseclass -> baseclass , etc. )

// Each class can access their base object with the self.base "keyword"

//

// some rules:

//	Be as strict as possible

//	Inheritance from Object class is enforced when no other class is defined. If you write your own Object class then no guarantees are given.

//	Properties and method that start with _ are private, and should be not be called outside of another object, unless the object calling should never be decoupled from the other

//	You should be much more careful about when you are dealing with a static class or when with an instance

//

// General OOP rules apply!!





// Object class, the most simplest of classes, all other classes inherit from this

Object = {}

Object_instance_mt = {__index = Object}

Object.Classname = "Object"



function Object.Create()

	local instance = {}

	setmetatable(instance, Object_instance_mt) // This makes sure that if an instance of Object does not have method/var it checks for the statics (these are ToString() for example)

	return instance

end



function Object:ToString()

	return self.Classname

end

// Object class end







Moo = {}



local loadedClasses = {} // Contains a list of classes that are loaded/registered, these classes can be inherit from

loadedClasses["Object"] = Object // Add the object class to the classes that are loaded



local requireLoadClasses = {} // This will contain a list of classes that want to be loaded, but can't right now because their baseclass is not yet loaded/registered



function Moo.getRegisteredClass(className) // Returns a loaded/registered class based on the classname. Search for "Object" this returns a static reference to Object.

	for k, v in pairs(loadedClasses) do

		if (k == className) then return v end

	end

end



function Moo.registerClass(class) // Registers a class as being loaded, removes this class from classes that needed loading, and checks again if any other classes need loading

	loadedClasses[class.Classname] = class

	

	// Remove this class from the list of classes that still need to be loaded

	for key, requireLoad in pairs(requireLoadClasses) do

		if (requireLoad.Class.Classname == class.Classname) then

			requireLoadClasses[key] = nil

			break

		end

	end

	

	// Maybe there are other classes waiting for this class to load

	Moo.checkClassesToLoad()

end



function Moo.checkClassesToLoad() // Re evaluates all the classes that are still waiting for their base object, and loads them if possible

	for loadMeKey, loadMe in pairs(requireLoadClasses) do // Loop through the classes we want to load

		for loadedClassName, loadedClass in pairs(loadedClasses) do // Loop through the classes that are loaded

			

			if (loadMe.baseClassName == loadedClassName) then // If we want to load a class which base class is already loaded

				Moo.inheritClassFrom(loadMe.Class, loadedClass) // Load this class

			end

			

		end

	end

end



function Moo.CreateClass(className, inheritFrom)

	if (!inheritFrom) then inheritFrom = "Object" end // you must always inherit from Object, so I am enforcing that

	

	print("Moo loading class: " .. tostring(className) .. " extends " .. tostring(inheritFrom))

	

	local newClass = {} // Create a table for the new class (static member)

	newClass.Classname = className // Set the classname (static member)

	

	// Our class is now pretty much complete, but we still have to know about our base object before we initialize it

	

	baseClass = Moo.getRegisteredClass(inheritFrom) // Gets our base class, if it is loaded

	if (baseClass) then

		Moo.inheritClassFrom(newClass, baseClass) // Our base class is loaded, we inherit from it

		// The class is now fully loaded, with inheritance included

	else

		// The base object did not exist, let's wait for it and hope it arrives here shortly

		table.insert(requireLoadClasses, {Class = newClass, baseClassName = inheritFrom})

	end

	

	return newClass

end



function Moo.inheritClassFrom(newClass, baseClass)

	print("Moo loaded class: " .. tostring(newClass.Classname) .. " extends " .. tostring(baseClass.Classname))

	// A utility function to create the link between the static class and the static base class

	local newClass_instance_mt = { __index = newClass }

	

	local newClass_mt = { __index = baseClass } // This class inherits from the base class

	setmetatable(newClass, newClass_mt)

	

	function newClass.Create(param1, param2, param3, param4, param5) // This is the static function to create a new object (similar to the new keyword)

		local newClass_instance = baseClass.Create(param1, param2, param3, param4, param5) // Create our base class before we create ourselves (common practice)

		

		newClass_instance.base = baseClass // Set up the self.base keyword to be used in any class instances

 setmetatable( newClass_instance, newClass_instance_mt ) // Our class instances references our class object if it must

		

 return newClass_instance

	end

	

	// The class is now fully loaded, with inheritance included

	Moo.registerClass(newClass)

end

Example:

include('moo.lua')



-- Syntax for CreateClass is:

-- variable to contain the class (static) = CreateClass("Class name")

-- You can use another name for the variable that holds the class, but I don't recommend it (for clarity)



Animal = Moo.CreateClass("Animal")

Animal.Legs = 6 // Static variable



function Animal:Walk()

	print("I am now walking.")

end



function Animal:Stop()

	print("I have now stopped walking.")

end



function Animal:ToString()

	return "An animal usually has " .. tostring(self.Legs) .. " legs"

end



// Dog inherits from Animal

Dog = Moo.CreateClass("Dog", "Animal")

Dog.Legs = 4



function Dog:ToString() // override ToString

	print("A dog has " .. self.Legs .. " legs")

end



function Dog:Stop() // override Stop

	print("Dogs don't stop!! :D")

end





// Let's try out the animal class

anml = Animal.Create() // anml = new Animal()

anml:Walk() // Tell the animal to walk

anml:Stop() // Tell the animal to stop

print(anml:ToString()) // ToString test



//Output:

//	I am now walking.

//	I have now stopped walking.

//	An animal usually has 6 legs





// Let's try out the dog class :D

doggy = Dog.Create()

doggy:Walk()

doggy:Stop()

print(doggy:ToString())



// Output:

//	I am now walking.

//	Dogs don't stop!! :D

//	A dog has 4 legs



doggy.Legs = 2

print(doggy:ToString())



// Output:

//	A dog has 2 legs

Interesting. You wouldn't be interesting in helping my a little with this? I'm not sure that current lookup table is as flexible as I want it to.

Another use for the code is as mentioned before, adding ByRef support fr parameters. I'm sure more uses can be found.

Share this post


Link to post
Share on other sites

Posted

I have full faith in your abilities as a programmer, you will do this just fine. Probably even better than me..

Share this post


Link to post
Share on other sites

Posted (edited)

Read and wheep. Here's the first example of Object orientation in pure autoit without any preprocessor or anything.

The code is unstable (I'm getting a random crash somewhere, annoying, freed memory is probably the error), not functional for x64 and the namespace is raped beyond recognition. But it works god damn it!

For those not interested in the technical implementation just look at the last 20 lines. There's magic going on right there.

#include <Array.au3>

Global $hOle32 = 0

Global Const $VT_EMPTY = 0
Global Const $VT_NULL = 1
Global Const $VT_I2 = 2
Global Const $VT_I4 = 3
Global Const $VT_R4 = 4
Global Const $VT_R8 = 5
Global Const $VT_CY = 6
Global Const $VT_DATE = 7
Global Const $VT_BSTR = 8
Global Const $VT_DISPATCH = 9
Global Const $VT_ERROR = 10
Global Const $VT_BOOL = 11
Global Const $VT_VARIANT = 12
Global Const $VT_UNKNOWN = 13
Global Const $VT_DECIMAL = 14
Global Const $VT_I1 = 16
Global Const $VT_UI1 = 17
Global Const $VT_UI2 = 18
Global Const $VT_UI4 = 19
Global Const $VT_I8 = 20
Global Const $VT_UI8 = 21
Global Const $VT_INT = 22
Global Const $VT_UINT = 23
Global Const $VT_VOID = 24
Global Const $VT_HRESULT = 25
Global Const $VT_PTR = 26
Global Const $VT_SAFEARRAY = 27
Global Const $VT_CARRAY = 28
Global Const $VT_USERDEFINED = 29
Global Const $VT_LPSTR = 30
Global Const $VT_LPWSTR = 31
Global Const $VT_RECORD = 36
Global Const $VT_INT_PTR = 37
Global Const $VT_UINT_PTR = 38
Global Const $VT_FILETIME = 64
Global Const $VT_BLOB = 65
Global Const $VT_STREAM = 66
Global Const $VT_STORAGE = 67
Global Const $VT_STREAMED_OBJECT = 68
Global Const $VT_STORED_OBJECT = 69
Global Const $VT_BLOB_OBJECT = 70
Global Const $VT_CF = 71
Global Const $VT_CLSID = 72
Global Const $VT_VERSIONED_STREAM = 73
Global Const $VT_BSTR_BLOB = 0xfff
Global Const $VT_VECTOR = 0x1000
Global Const $VT_ARRAY = 0x2000
Global Const $VT_BYREF = 0x4000
Global Const $VT_RESERVED = 0x8000
Global Const $VT_ILLEGAL = 0xffff
Global Const $VT_ILLEGALMASKED = 0xfff
Global Const $VT_TYPEMASK = 0xfff

Global Const $tagGUID = "ulong;ushort;ushort;byte[8]"
Global Const $tagCLSID = $tagGUID
Global Const $tagUUID = $tagGUID
Global Const $CLSCTX_INPROC_SERVER = 0x1
Global Const $S_OK = 0
Global Const $DISP_E_UNKNOWNNAME = 2147614726
Global Const $DISPID_UNKNOWN = 4294967295
Global Const $DISP_E_MEMBERNOTFOUND = 2147614723
Global Const $tagVARIANT = "ushort vt;ushort r1;ushort r2;ushort r3;uint64 data"
Global Const $tagDISPPARAMS = "ptr rgvargs;ptr rgdispidNamedArgs;dword cArgs;dword cNamedArgs;"


Global Const $DISPATCH_METHOD = 0x1
Global Const $DISPATCH_PROPERTYGET = 0x2
Global Const $DISPATCH_PROPERTYPUT = 0x4
Global Const $DISPATCH_PROPERTYPUTREF = 0x8



; Starts COM
Func CoInitialize()
	$hOle32 = DllOpen("Ole32.dll")
	DllCall($hOle32, "ulong", "CoInitialize", "ptr", 0)
EndFunc ;==>CoInitialize

; This will make a registry lookup to find out what the passed
; string really means. That is a GUID
Func CLSIDFromProgID($ProgID)
	$clsid = DllStructCreate($tagCLSID)
	DllCall($hOle32, "long", "CLSIDFromProgID", "wstr", $ProgID, "ptr", DllStructGetPtr($clsid))
	Return $clsid
EndFunc ;==>CLSIDFromProgID

; Returns the UUID of IDispatch
Func IDispatch_UUID()
	; Autoit sets it all to null
	$uuid = DllStructCreate($tagUUID)
	DllStructSetData($uuid, 1, 132096)
	DllStructSetData($uuid, 4, 192, 1)
	DllStructSetData($uuid, 4, 70, 8)
	Return $uuid
EndFunc ;==>IDispatch_UUID

; Creates a instance of a COM object from a clsid and iid
; Usually done internally by autoit but we need the pointer.
Func CoCreateInstance($clsid, $pUnkOuter, $ClsContext, $iid, $pOutObj)
	Return DllCall($hOle32, "long", "CoCreateInstance", "ptr", DllStructGetPtr($clsid), "ptr", $pUnkOuter, "dword", $ClsContext, "ptr", DllStructGetPtr($iid), "ptr", $pOutObj)
EndFunc ;==>CoCreateInstance

; WRAPPER FOR IDISPATCH INTERFACES.
; This is like ObjCreate except this returns the raw pointer to the IDispatch interface.
; This is essential for hooking the methods of the IDispatch object.
Func CreateIDispatchFromProgID($ProgID)
	; Ptr to IDispatch object
	$pObj = DllStructCreate("ptr")
	$aCall = CoCreateInstance(CLSIDFromProgID($ProgID), 0, $CLSCTX_INPROC_SERVER, IDispatch_UUID(), DllStructGetPtr($pObj))
	Return DllStructGetData($pObj, 1)
EndFunc ;==>CreateIDispatchFromProgID

; In the end we still want the autoit object. This function converts a raw pointer to an autoit object
Func ConvertPtrToIDispatch($pIDispatch)
	; This would have been 10000x easier if autoit had supported the idispatch* type in dllstructs...
	; Fortunetely memcpy can copy the pointer into a idispatch*, lucky us.
	Local $aCall = DllCall("kernel32.dll", "none", "RtlMoveMemory", _
			"idispatch*", 0, _
			"ptr*", $pIDispatch, _
			"dword", 4)

	If @error Then
		Return SetError(1, 0, 0)
	EndIf

	Return $aCall[1]

EndFunc ;==>ConvertPtrToIDispatch

; Sets the MEM_EXECUTE_READWRITE flag on the specified memory. Use with care, I use because I'm lazy.
Func UnprotectMemory($pointer, $size)
	DllCall("Kernel32.dll", "int", "VirtualProtect", "ptr", $pointer, "long", $size, "dword", 0x40, "dword*", 0)
EndFunc ;==>UnprotectMemory

; Returns the pointer the passed pointer points to ;)
Func DereferencePointer($ptr)
	$tempstruct = DllStructCreate("ptr", $ptr)
	Return DllStructGetData($tempstruct, 1)
EndFunc ;==>DereferencePointer


; Moves the vtable to a new position. useful if you want to add more entries.
Func RelocateVTable($pObj, $pNew, $VTable_size)
	$vtable = DllStructCreate("ptr", $pObj)
	$vtable_ptr = DllStructGetData($vtable, 1)
	DllCall("kernel32.dll", "none", "RtlMoveMemory", "ptr", $pNew, "ptr", $vtable_ptr, "dword", $VTable_size)
	DllStructSetData($vtable, 1, $pNew)
EndFunc ;==>RelocateVTable

; Allocate memory on the heap
Func DynAlloc($dwSize)
	$hHeap = DllCall("Kernel32.dll", "ptr", "GetProcessHeap")
	$hHeap = $hHeap[0]
	$hMem = DllCall("Kernel32.dll", "ptr", "HeapAlloc", "ptr", $hHeap, "dword", 0x8, "dword", $dwSize)
	DllCall("Kernel32.dll", "none", "CloseHandle", "ptr", $hHeap)
	Return $hMem[0]
EndFunc ;==>DynAlloc



; Takes a pointer to the v-table in a class and replaces specified pointer in it to a new one.
Func ReplaceVTableEntry($vtable_ptr, $offset, $new_ptr)
	; Dereference the pointer
	$first_entry_ptr = DereferencePointer($vtable_ptr)

	$entry_pointer = $first_entry_ptr + $offset
	; Make the memory free for all. Yay!
	UnprotectMemory($entry_pointer, 4)
	$entry_struct = DllStructCreate("ptr", $entry_pointer)
	DllStructSetData($entry_struct, 1, $new_ptr)

EndFunc ;==>ReplaceVTableEntry

Func MakeQWORD($val1, $val2)
	$qword = DllStructCreate("uint64")
	$dwords = DllStructCreate("dword;dword", DllStructGetPtr($qword))
	DllStructSetData($dwords, 1, $val1)
	DllStructSetData($dwords, 2, $val2)
	Return DllStructGetData($qword, 1)
EndFunc ;==>MakeQWORD

Func GetHIDWORD($qword_val)
	$qword = DllStructCreate("uint64")
	DllStructSetData($qword, 1, $qword_val)
	$dword = DllStructCreate("dword", DllStructGetPtr($qword) + 4)
	Return DllStructGetData($dword, 1)
EndFunc ;==>GetHIDWORD
Func GetLODWORD($qword_val)
	$qword = DllStructCreate("uint64")
	DllStructSetData($qword, 1, $qword_val)
	$dword = DllStructCreate("dword", DllStructGetPtr($qword))
	Return DllStructGetData($dword, 1)
EndFunc ;==>GetLODWORD

Func CreateDynamicString($str)
	$dynmem = DynAlloc(StringLen($str) + 1)
	DllStructSetData(DllStructCreate("char[" & StringLen($str) + 1 & "]", $dynmem), 1, $str)
	Return $dynmem
EndFunc ;==>CreateDynamicString



Func ConstructLookupTable($pObj, $aNames)
	; Set point in vtable
	$vtable_entry = DllStructCreate("ptr", DereferencePointer($pObj) + 4 * 7)
	; Create dynamic memory for new lookup table
	$mem = DynAlloc(4 + (UBound($aNames) * 8))
	; Create lookup table, first element is number of element
	; Must be sorted!
	$lookup_table = DllStructCreate("int;uint64[" & UBound($aNames) & "]", $mem)
	; Set size of lookup table
	DllStructSetData($lookup_table, 1, UBound($aNames))
	; Modify vtable to point to the lookup table
	DllStructSetData($vtable_entry, 1, $mem)



	For $i = 0 To UBound($aNames) - 1

		If StringLeft($aNames[$i][0], 1) = "@" Then ; This is a method
			DllStructSetData($lookup_table, 2, MakeQWORD(CreateDynamicString($aNames[$i][0]), CreateDynamicString($aNames[$i][1])), $i + 1)
		Else ; It's a property then.
			$variant_ptr = DynAlloc(16)
			ValueToCOMVariant($variant_ptr, $aNames[$i][1])
			DllStructSetData($lookup_table, 2, MakeQWORD(CreateDynamicString($aNames[$i][0]), $variant_ptr), $i + 1)
		EndIf




	Next
EndFunc ;==>ConstructLookupTable

Func ChrPtrToString($ptr, $maxsize = 4096)
	$str_struct = DllStructCreate("char[" & $maxsize & "]", $ptr)
	Return DllStructGetData($str_struct, 1)
EndFunc ;==>ChrPtrToString


Func FindNameInLookupTable($pObj, $name)
	$vtable_entry = DllStructCreate("ptr", DereferencePointer($pObj) + 4 * 7) ; sizeof(ptr)*(num entries in idispatch)
	$header = DllStructCreate("int", DllStructGetData($vtable_entry, 1))
	$lookup_table_size = DllStructGetData($header, 1)
	$lookup_table = DllStructCreate("int;uint64[" & $lookup_table_size & "]", DllStructGetData($vtable_entry, 1))



	For $i = 1 To $lookup_table_size
		If $name = ChrPtrToString(GetLODWORD(DllStructGetData($lookup_table, 2, $i))) Then Return $i
	Next


	Return -1

EndFunc ;==>FindNameInLookupTable

Func IDToValue($pObj, $id)
	$vtable_entry = DllStructCreate("ptr", DereferencePointer($pObj) + 4 * 7)
	$header = DllStructCreate("int", DllStructGetData($vtable_entry, 1))
	$lookup_table_size = DllStructGetData($header, 1)
	$lookup_table = DllStructCreate("int;uint64[" & $lookup_table_size & "]", DllStructGetData($vtable_entry, 1))

	If $id <= 0 Or $id > $lookup_table_size Then Return ""

	$member_name = ChrPtrToString(GetLODWORD(DllStructGetData($lookup_table, 2, $id)))
	If StringLeft($member_name, 1) = "@" Then
		Return ChrPtrToString(GetHIDWORD(DllStructGetData($lookup_table, 2, $id)))
	Else
		Return GetHIDWORD(DllStructGetData($lookup_table, 2, $id))
	EndIf
EndFunc ;==>IDToValue




Func VTType2AutoitType($vt_type)
	Switch $vt_type
		Case $VT_I1
			Return "byte"
		Case $VT_I2
			Return "short"
		Case $VT_I4
			Return "int"
		Case $VT_BSTR
			Return "wstr"
	EndSwitch
EndFunc ;==>VTType2AutoitType

Func AutoitType2VTType($autoit_type)
	Switch $autoit_type
		Case "byte"
			Return $VT_I1
		Case "short"
			Return $VT_I2
		Case "int"
			Return $VT_I4
		Case "wstr"
			Return $VT_BSTR
	EndSwitch

EndFunc ;==>AutoitType2VTType


; Find out length of string. I do not trust autoit to do this.
Func wcslen($pwchar)
	$aCall = DllCall("ntdll.dll", "dword:cdecl", "wcslen", "ptr", $pwchar)
	Return $aCall[0]
EndFunc ;==>wcslen

Func SysAllocString($str)
	$aCall = DllCall("oleaut32.dll", "ptr", "SysAllocString", "wstr", $str)
	Return $aCall[0]
EndFunc ;==>SysAllocString



Func COMVariantToValue($pVariant)
	Local $var = DllStructCreate($tagVARIANT, $pVariant)
	; Translate the vt id to a autoit dllcall type
	$type = VTType2AutoitType(DllStructGetData($var, "vt"))

	If $type = "wstr" Then
		$str_ptr = DllStructCreate("ptr", DllStructGetPtr($var, "data"))
		; Getting random crashes when trusting autoit to automatically use right size.
		; doing it myself instead (also, it should be a BSTR, but it's not. Is autoit not obeying the rules!?
		$str_size = wcslen(DllStructGetData($str_ptr, 1))
		$data = DllStructCreate("wchar[" & $str_size + 1 & "]", DllStructGetData($str_ptr, 1))
	Else
		$data = DllStructCreate($type, DllStructGetPtr($var, "data"))
	EndIf


	Return DllStructGetData($data, 1)
EndFunc ;==>COMVariantToValue

Func ValueToCOMVariant($pVariant, $vValue)
	$var = DllStructCreate($tagVARIANT, $pVariant)

	If IsInt($vValue) Then
		$vt_type = AutoitType2VTType("int")
		$var_data = $vValue
	ElseIf IsString($vValue) Then
		$vt_type = AutoitType2VTType("wstr")
		$var_data = SysAllocString($vValue)
	EndIf
	DllStructSetData($var, "vt", $vt_type)
	DllStructSetData(DllStructCreate("int", DllStructGetPtr($var, "data")), 1, $var_data)

EndFunc ;==>ValueToCOMVariant






; Everytime autoit wants to call a method, get or set a property in a object it needs to go to
; IDispatch::GetIDsFromNames. This is our version of that function, note that by defining this ourselves
; we can fool autoit to believe that the object supports a lot of different properties/methods.
Func IDispatch_GetIDsFromNames($self, $refiid, $str_array, $array_size, $context, $out_array)
	; It's self explainable that autoit only asks for one member
	$str = DllStructCreate("wchar[256]", DereferencePointer($str_array))
	ConsoleWrite("AutoIt wants to look up: " & DllStructGetData($str, 1) & @CRLF)

	; Autoit gave us an array with one element ready to accept the id of the member it requested.
	$ids = DllStructCreate("long", $out_array)

;~ 	ConsoleWrite("ZERO"&@CRLF)
	$id = FindNameInLookupTable($self, "@" & DllStructGetData($str, 1))
	ConsoleWrite("ONE" & @CRLF)
	If $id = -1 Then $id = FindNameInLookupTable($self, DllStructGetData($str, 1))
	ConsoleWrite("TWO" & @CRLF)
	If $id <> -1 Then
		DllStructSetData($ids, 1, $id)
		Return $S_OK
	Else
		DllStructSetData($ids, 1, $DISPID_UNKNOWN)
		Return $DISP_E_UNKNOWNNAME
	EndIf



EndFunc ;==>IDispatch_GetIDsFromNames
; Create the callback so we have a pointer to this function.
$IDispatch_GetIDsFromNames_Callback = DllCallbackRegister("IDispatch_GetIDsFromNames", "long", "ptr;ptr;ptr;int;int;ptr")
$IDispatch_GetIDsFromNames_Callback_Ptr = DllCallbackGetPtr($IDispatch_GetIDsFromNames_Callback)


; This is called when a method is called, a property is set or get.
; This call also contains arguments returns and a lot other stuff.
; However in this trivial example we don't return anything and we don't take any arguments. Puh, could get messy
Func IDispatch_Invoke($self, $dispID, $riid, $lcid, $wFlags, $pDispParams, $pVarResult, $pExceptInfo, $puArgErr)
	;; Dump all parameters to console.
	ConsoleWrite("DispID: " & $dispID & @CRLF & "RIID: " & $riid & @CRLF & "LCID: " & $lcid & @CRLF & "wFlags: " & $wFlags & @CRLF & _
			"pDispParams: " & $pDispParams & @CRLF & "pVarResult: " & $pVarResult & @CRLF & "pExceptInfo: " & $pExceptInfo & @CRLF & "puArgError: " & $puArgErr & @CRLF)


	$memberval = IDToValue($self, $dispID)

	If $memberval = "" Then Return $DISP_E_MEMBERNOTFOUND
	$dispparams = DllStructCreate($tagDISPPARAMS, $pDispParams)
	If $wFlags = BitOR($DISPATCH_METHOD, $DISPATCH_PROPERTYGET) Then
		If IsString($memberval) Then



			$self_obj = ConvertPtrToIDispatch($self)
			$callstring = $memberval & "($self_obj"

			If $pDispParams <> 0 And DllStructGetData($dispparams, "cArgs") > 0 Then
				Local $params[DllStructGetData($dispparams, "cArgs")]
				; Fetch all arguments
				For $i = 0 To UBound($params) - 1
					; Save the values backwards (that's how autoit do it)
					$params[(UBound($params) - 1) - $i] = COMVariantToValue(DllStructGetData($dispparams, "rgvargs") + ($i * 16)) ; i*sizeof(VARIANT)
					$callstring &= ",$params[" & $i & "]"
				Next
			EndIf
			$callstring &= ")"
			ConsoleWrite("Calling function: " & $callstring & @CRLF)
			$ret = Execute($callstring)
			; Set return value.
			ValueToCOMVariant($pVarResult, $ret)
			; Give autoit the message that everything went according to plan
			Return $S_OK
		Else
			ValueToCOMVariant($pVarResult, COMVariantToValue($memberval))
			Return $S_OK
		EndIf

	ElseIf $wFlags = $DISPATCH_PROPERTYPUT Then
;~ 		MsgBox(0,"","")
;~ 		ConsoleWrite(DereferencePointer($pDispParams+8)&@CRLF)
;~ 		ConsoleWrite(DllStructGetData($dispparams,"rgvargs")&@CRLF)
		ValueToCOMVariant($memberval, COMVariantToValue(DllStructGetData($dispparams, "rgvargs")))
		Return $S_OK
	EndIf




EndFunc ;==>IDispatch_Invoke
; Create callback
$IDispatch_Invoke_Callback = DllCallbackRegister("IDispatch_Invoke", "long", "ptr;dword;ptr;dword;ushort;ptr;ptr;ptr;ptr")
$IDispatch_Invoke_Callback_Ptr = DllCallbackGetPtr($IDispatch_Invoke_Callback)


Func CreateObject($aMembers)
	; Create a victim. Could be any COM object that inherits from IDispatch
	$obj_ptr = CreateIDispatchFromProgID("ScriptControl")
	; Create autoit object as well.


	; Hook into the object
	; Offset 20 & 24 is fifth entry in vtable. Look at IDispatch and IUnknown interfaces to see why
	ReplaceVTableEntry($obj_ptr, 20, $IDispatch_GetIDsFromNames_Callback_Ptr)
	ReplaceVTableEntry($obj_ptr, 24, $IDispatch_Invoke_Callback_Ptr)

	; Create space for a new bigger vtable
	$p = DynAlloc(4 * 7 + 4) ; sizeof(ptr)*(num entirs in dispatch)+sizeof(ptr)
	RelocateVTable($obj_ptr, $p, 4 * 7)


	ConstructLookupTable($obj_ptr, $aMembers)

	Return ConvertPtrToIDispatch($obj_ptr)
EndFunc




; Initalize COM
CoInitialize()


$msgbox = MessageBox()

$msgbox.flag = 64
$msgbox.title = "Important Announcement"
$msgbox.text = "Object Orientation + AutoIt = True"
$msgbox.Display()



Func MessageBox()
	; Construct a lookup table with properties and methods (methods are marked with @
	Local $members[4][2] = [["@Display", "MessageBox_Display"],["flag", 0],["title", ""],["text", ""]]
	Return CreateObject($members)
EndFunc ;==>MessageBox


Func MessageBox_Display($self)
	Return MsgBox($self.flag, $self.title, $self.text)
EndFunc ;==>MessageBox_Display

So what's next? Inheritance, stability, polymorphism (which the current implementation almost gives away for free. Nice!), more types (right now only ints and strings are supported) and general clean up.

Stay tuned!

Edit: If crashes, run again!

Edit2: Replaced shell.application with ScriptControl, since shell.application would just return same old instance.

Edited by monoceres

Share this post


Link to post
Share on other sites

Posted

I was following you with the x64 compatibility (on blind ;) ).

Wanna see?

Share this post


Link to post
Share on other sites

Posted

I was following you with the x64 compatibility (on blind ;) ).

Wanna see?

Yes! :evil:

Seeing any problems with the current implementation? I'm pretty fucking happy with it.

Share this post


Link to post
Share on other sites

Posted

Do you really need to create a ScriptControl-Object wouldn't it be enough to implement all funcs and then memcpy the vtable-pointer to idispatch?

Share this post


Link to post
Share on other sites

Posted

Do you really need to create a ScriptControl-Object wouldn't it be enough to implement all funcs and then memcpy the vtable-pointer to idispatch?

You're right, I didn't even think of that. However it was way easier to use a ready to use object when developing.

As long as autoit doesn't need important information from one of the methods it will work. Needing the call the real interfaces will make stuff more complicated than it needs to with function pointer call and stuff.

Share this post


Link to post
Share on other sites

Posted

YAYAYAYAY I'm going to have some fun with this ;)

Share this post


Link to post
Share on other sites

Posted

I tried to get rid of the ObjCreate, but I encountered a few problems:

You MUST specify an object error handler or AutoIt won't exit when there's a COM_error

You MUST free all object-Variables ($oObj = 0) or AutoIt won't exit.

I think this results in the DLLCallbacks freed before the Obj-Variables are destroyed and then the Release-Function can't be called anymore. It seems that this is the same error as #1319. Should I add a feature request that DLLCallbacks are freed after Objects and Windows?

Share this post


Link to post
Share on other sites

Posted

Yes, however it's tricky.

Let's say we define a callback that acts like IUnknown::Release(). What will happen if this gets called when everything else is freed? I don't know.

Share this post


Link to post
Share on other sites

Posted

this is perfect, again a great big thing done by the mono man.

if it gets stable and easier to use i will rewrite all my udfs with it.

is it possible for VBS/VBA for example to ulilize the internal functions of that exe to or are they limited to the current process?

Share this post


Link to post
Share on other sites

Posted

this is perfect, again a great big thing done by the mono man.

if it gets stable and easier to use i will rewrite all my udfs with it.

is it possible for VBS/VBA for example to ulilize the internal functions of that exe to or are they limited to the current process?

Nice to hear! I'm doing my best to make this as simple as possible. The last example (the last lines) is probably how it will be done. Not very hard according to me, but if you have another idea, then I'm all ears. Just remember that I cannot create new syntax. We're stuck with that.

It will not be visible outside, the manipulation is done on per-instance basis so it's for autoit's eyes only.

Share this post


Link to post
Share on other sites

Posted

With this code it seems we can get MSAA (accessibility) also working within AutoIT

Will play around with this article and an MSAA example as in topic below

http://www.autoitscript.com/forum/index.php?showtopic=95176&st=0&p=683997&hl=msaa&fromsearch=1&#entry683997

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0

  • Recently Browsing   0 members

    No registered users viewing this page.