Jump to content

Hooking into the IDispatch interface.


monoceres
 Share

Recommended Posts

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

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

  • Replies 70
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

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.

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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;
    } ;

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

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.

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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
Link to comment
Share on other sites

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.

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

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

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

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?

*GERMAN* [note: you are not allowed to remove author / modified info from my UDFs]My UDFs:[_SetImageBinaryToCtrl] [_TaskDialog] [AutoItObject] [Animated GIF (GDI+)] [ClipPut for Image] [FreeImage] [GDI32 UDFs] [GDIPlus Progressbar] [Hotkey-Selector] [Multiline Inputbox] [MySQL without ODBC] [RichEdit UDFs] [SpeechAPI Example] [WinHTTP]UDFs included in AutoIt: FTP_Ex (as FTPEx), _WinAPI_SetLayeredWindowAttributes

Link to comment
Share on other sites

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.

Broken link? PM me and I'll send you the file!

Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...