Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[AHK_L + AHK v2] COM Classes Framework (CCF)


  • Please log in to reply
46 replies to this topic

Poll: What do you think about this idea? (6 member(s) have cast votes)

What do you think about this idea?

  1. good idea, I will participate! (4 votes [66.67%])

    Percentage of vote: 66.67%

  2. good idea, but I won't participate. (0 votes [0.00%])

    Percentage of vote: 0.00%

  3. unfortunately not practicable! (0 votes [0.00%])

    Percentage of vote: 0.00%

  4. not sure if this is a good idea... (2 votes [33.33%])

    Percentage of vote: 33.33%

  5. this is a bad idea! (0 votes [0.00%])

    Percentage of vote: 0.00%

  6. other (please explain) (0 votes [0.00%])

    Percentage of vote: 0.00%

Vote Guests cannot vote
maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011
This framework collects AHK_L / AHK v2 classes that wrap COM interfaces.

Introduction:
To make it easy for AutoHotkey scripters to access the power of COM, this COM Classes Framework uses AHK v2 / AHK_L classes that wrap COM interfaces and their implementations. It also offers a lot of helper classes to make your code both easy to write and easy to understand, and gives it a clean look.

Contributing:
Everyone who is advanced enough in AHK (and maybe COM, too) is welcome to wrap the interfaces he needs or wants to share. It looks difficult, yet it isn't. Read the tutorial and have a look at the classes already wrapped. Once you have wrapped one (be sure to obey the guidelines), you can either release them here or you can fork the project on github and add your stuff.

Coding & other guidelines

Resources:[*:1rwcg7sc]githup repo
[*:1rwcg7sc]wiki tutorial
[*:1rwcg7sc]interface documentation including methods in vTable-Order[*:1rwcg7sc]Win32 Programmer's Reference
[*:1rwcg7sc]OLE Programmer's Reference[/list]Wrapped & requested Interfaces

Downloads:
[*:1rwcg7sc]for AHK v2
[*:1rwcg7sc]AHK_L v1.1+
[*:1rwcg7sc]Docs (for both)Regards
maul.esel
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
Nitpicking: you don't seem to know the fact that in COM, the interface is separate from the implementation. In C++, the ISomething classes are abstract, that means, you cannot just create an instance of it, because there's no implementation. In other words, new ISomething alone makes no sense. What you actually do in COM is create an object (the implementation) that supports an interface.

So, I would suggest something like this:
; Base wrapper class for all COM interface wrappers
class IUnknown
{
	__New(ptr, own=1)
	{
		if ptr
			this.ptr := ptr, this.vt := NumGet(ptr+0), this.own := own
		else return ; return empty string as an error
	}
	
	__Delete()
	{
		if own
			this.Release()
	}
	
	; the following are rarely used
	
	QueryInterface(iface)
	{
		return ComObjQuery(this.ptr, iface)
	}
	
	AddRef()
	{
		return ObjAddRef(this.ptr)
	}
	
	Release()
	{
		return ObjRelease(this.ptr)
	}
}

; ISomething wrapper
class ISomething extends IUnknown
{
	var IID := "{...IID goes here...}"
	MyMethod(params)
	{
		return (ErrorLevel := DllCall(this.vt + n*A_PtrSize, ...)) >= 0
	}
}

CreateSomething()
{
	global ISomething
	return new ISomething(ComObjCreate("{...CLSID goes here...}", ISomething.IID))
}

With a little bit of effort, interface wrapper classes could maybe be generated automatically by a program.

maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011
IsNull mentioned something similar some days ago.
But what's the advantage of this (except a lot more of code :lol:) :?:

The idea of a automatical creation is awful! You're right, that would be a good idea as it is a lot less of effort. I will look if I can script a simple version.

Regards
maul.esel
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
A lot more of code? Actually not, the code in the IUnknown wrapper would be shared by all the interfaces, so there's less code. A typical wrapper would look like this:
class ISomething extends IUnknown
{
	var IID := "{...IID here...}"
	
	; Methods
	
	Method1(...)
	{
		return DllCall(this.ptr + n*A_PtrSize, ...)
	}
	
	; ...
}


maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011

A lot more of code? Actually not, the code in the IUnknown wrapper would be shared by all the interfaces, so there's less code

Of course, but I meant the additonal implementations: if the interface class would contain the implementation,
it's less code than including something like CreateInterface() (not a lot, though).

Regards
maul.esel
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
Nope, it's still not more code. Either way, you need a function to create the object, let it be ISomething.Create() (which does not make sense as I pointed out) or CreateSomething(). Mind you that the interface name doesn't necessarily have to match the COM class name.

IsNull
  • Moderators
  • 990 posts
  • Last active: May 15 2014 11:56 AM
  • Joined: 10 May 2007
maul.esel, I think we are mixing here some things together :)

An Interface is just a description which public Methods & Properties a class must have. The idea beyond this is, that you can have multiple, differing Implementations of an interface.

Consider this example:

IFruit myfruit1 := new Apple()
IFruit myfruit2 := new Banana()
If you work against the interface IFruit, you don't have to care which concrete Implementation (fruit) it is. That way, you can create Methods/Classes which work against interfaces and this allows you to widely reuse this classes/methods with any object who is implementing the given Interface.

Similar to this, if you request a COM Object, you actually request an (any) implementation of an Interface. You don't know which implementation but you know that it at least implements the requested interface. So you know that at least the methods provided by the interface exist.

That being said, you might have a factory class which builds the correct instance (implementation) of the requested interface, the logic which concrete implementation is builded depends on internal logic and must not be exposed to the client.

However - as long as AHK 1.x/2.x doesn't support typing, interfaces are useless now and actually not supported. What you can do to imitating this is to make an "abstract" base class which has the Method definitions but without any interaction logic. But - this is actually also pointless, as without the ability to strictly typing you can pass any object to any method and the Lookup if the method exists is done anyway.


Note:
Interface vs. Abstract classes
An Interface is a description which Methods & Properties an implementing class must have. It contains NO implementation at all.
An abstract class is the same, except that it can implement some methods already while leave others blank (those methods must be implemented by the deriving subclass)

maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011
Hi,

I modified finc's IUnknwon wrapper a little bit, and I would suggest it as base for all other wrappers:
class IUnknown
	{
	__New(ptr = 0){
		if (!ptr)
			this.ptr := ComObjCreate(this.CLSID, this.IID) ; create the pointer
		else
			this.ptr := ptr ; we can use this to create a new instance [color=red]given a pointer[/color] as they are returned by other methods.
		this.vt := NumGet(this.ptr + 0)
		}
	
	__Delete(){
		return this.Release()
		}
	
	QueryInterface(riid){
		return ComObjQuery(this.ptr, riid)
		}
	
	AddRef(){
		return ObjAddRef(this.ptr)
		}
   
	Release(){
		return ObjRelease(this.ptr)
		}
	}
(I will update the coding style suggestion, too.)
But I see, you had something with own. What does this mean?

Either way, you need a function to create the object, let it be ISomething.Create() (which does not make sense as I pointed out) or CreateSomething().

I don't want to argue with you (and maybe I'm terribly ignorant :oops:), but I don't see the advantage for AHK users when there's something like
sth := CreateComething() ; instead of
sth := new ISomething
I just see one more function :lol:

Mind you that the interface name doesn't necessarily have to match the COM class name.

I would suggest to make the classes have the same name as the interface
(except maybe omitting the "I" prefix or adding any other prefix) as this makes it a lot easier to find the class you're looking for.

IsNull: Again, I might be ignorant, but what do you want to say? Is it just we create classes that don't "wrap an interfaces" but "implement an interface"?

What I want to do is extend the possibilities of AHK and make them accessible for not so advanced users as well as for power-users.
OOP coding conventions might be nice & useful, but they are not the most important thing to me.

What do you think about the other questions, e.g. what to return?

I'm no longer sure if automatic creation is possible (maybe because my RegEx is not so good :oops:).

Gruß
maul.esel
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

IsNull
  • Moderators
  • 990 posts
  • Last active: May 15 2014 11:56 AM
  • Joined: 10 May 2007
Nope, that way:
class ISomething
{
	ChickenCurry(){ }
}

class Something extends ISomething
{
	ChickenCurry(){
		msgbox chicken curry!
	}
}

sth := new Something


OOP coding conventions might be nice & useful, but they are not the most important thing to me.

I purely understand but
instance := new IMyInterface
is very missleading ;)

If you dont want to create the Interfaces, just omit the leading I, so its clear that its an implementation (no matter if its just delegating invokes down to the COM Object)

What do you think about the other questions, e.g. what to return?

The idea of an wrapper should be to make the usage of the code as easy and as ahk'isch as possible. So I would suggest to hide any lowlevel return values.

If bool values arn't enougt, you might want to return some kind of an enum; (That way you can use the original errorcodes but they are hidden from the user)



class ErrorCode
{
	var None := 0
	var Ok := 1
	var Fail := 2
	var IDontKnowWTF := 1337
}

;usage: use as static class/enum
huhu := new Uhu
if(huhu.DoSomeThingThatCanFail() == ErrorCode.Fail){
	MsgBox it seems that it failed...
}


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I'd say if the documentation/type library defines a "retval" parameter, use it as the return value and return the HRESULT some other way. Otherwise, you could return either the HRESULT or a boolean value. Returning the HRESULT would be more useful but perhaps less consistent if some methods returned the retval parameter.

The built-in COM functionality uses A_LastError for the last HRESULT. This can be emulated by calling DllCall("SetLastError", "uint", ErrorCode), but ErrorLevel := ErrorCode would perform better. I guess you'd have to choose between consistency and performance.

__New(ptr, own=1)
   {
      if ptr
         this.ptr := ptr, this.vt := NumGet(ptr+0), this.own := own
      else return ; return empty string as an error
   }

   __Delete()
   {
      if own
         this.Release()
   }

If that implementation is given an interface pointer which it won't "own", there's no guarantee that the pointer will remain valid for the lifetime of the object. I would recommend calling AddRef if own is false and calling Release unconditionally when the wrapper is freed. (That would be consistent with ComObj(), except that own has the opposite default value.)

I think it would be better not to expose AddRef/Release as methods. Generally AddRef should only be called when creating a "copy" of the interface pointer; if that is needed, it can be exposed as a method (ToPointer, CopyPointer, or similar), which would AddRef and return the pointer. When a copy of the interface pointer is released, it's more logical to do ObjRelease(ptr) than originalObject.Release(). It should never be necessary to explicitly release the wrapper object's own pointer.

Although there's probably no reason to change a vtable pointer for a live object, as far as I'm aware, there's nothing to guarantee it won't be done. It's probably "safe" to cache the pointer, but not necessarily correct.

This is something I had written while experimenting, before seeing this thread:
class IUnknown
{
    __New(aPtr) {
        return (this.__ptr := aPtr) ? this : 0
    }
    Query(aParams*) {  ; Allows (SID, IID) and (IID).
        return ComObjQuery(this.__ptr, aParams*)
    }
    VTable(n) {
        return NumGet(NumGet(this.__ptr) + n*A_PtrSize)
    }
    __Delete() {
        ObjRelease(this.__ptr)
    }
}

class ITypeInfo extends IUnknown
{
    GetTypeAttr(ByRef aTypeAttr) {
        return DllCall(this.VTable(3), "ptr", this.__ptr, "ptr*", aTypeAttr)
    }
    GetTypeComp(ByRef aTypeComp) {
        return DllCall(this.VTable(4), "ptr", this.__ptr, "ptr*", aTypeComp)
    }
    GetFuncDesc(aIndex, ByRef aFuncDesc) {
        return DllCall(this.VTable(5), "ptr", this.__ptr, "uint", aIndex, "ptr*", aFuncDesc)
    }
    ;... you get the idea.
}


maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011

If you dont want to create the Interfaces, just omit the leading I...

Ok 8)

@Lexikos:
I'm not sure if I understood all: [*:e7r4txjf]If the function has a HRESULT return value and no retval parameter: return bool := (HRESULT >= 0), ErrorLevel := HRESULT
[*:e7r4txjf]if the function has a HRESULT return value and a retval parameter: return retval, ErrorLevel := HRESULT
[*:e7r4txjf]if the function returns a bool, a number etc. return bool_or_numberAnd I still don't understand what the own parameter is for :oops:

Although there's probably no reason to change a vtable pointer for a live object, as far as I'm aware, there's nothing to guarantee it won't be done. It's probably "safe" to cache the pointer, but not necessarily correct.

What do you mean? If the user changes it?

Regards
maul.esel
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011
Status update:
I don't have a lot of time for this at the moment.
Nevertheless, you can contact me if you wrap a class, or you can use the github repository.
For interested users: I wrote a quick tutorial about this.

Regards
maul.esel
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009

For interested users: I wrote a quick tutorial about this.

I am interested in learning this. Would this be the correct thread to ask questions? For instance, how would you know the name of the method you are calling - or how would you call a method by name?

maul.esel
  • Members
  • 790 posts
  • Last active: Jan 05 2013 09:26 PM
  • Joined: 28 Feb 2011
Yeah, you can ask here - it's General Chat ;-)

Calling a function by name, that's a feature of the IDispatch interface. These objects can be used with native COM and object support, such as obj.NamedFunction().

As pointed out in the tutorial, you call functions by index instead. It's a zero-based index, but don't forget parent interfaces.

For example, you have interface IMyInterface:
; pseudo-code!
interface IMyInterface extends IUnknown
    {
    MyFunction() ; first function in IMyInterface. Index is [color=red]3[/color] as 0, 1 and 2 belong to IUnknown:
    ; QueryInterface()
    ; AddRef()
    ; Release()
    }

interface IMyInterface2 extends IMyInterface
   {
   OtherFunction() ; index is 4, as 0 -2 belong to IUnknown and 3 belongs to IMyInterface
   }
You can define a custom class so your code doesn't look so "ugly", using DllCalls all the time :). That's the point of this thread.
Join the discussion on The future of AutoHotkey
Posted Image Visit me on github Posted Image
Win7 HP SP1 64bit | AHK_L U 64bit

jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009

Calling a function by name, that's a feature of the IDispatch interface.

Ok, how would you know what method you are calling for an object that doesn't have an IDispatch interface?

Also, you stated that functions 0-2 on the vtable are from the IUnknown interface. Shouldn't Dispatch objects also inherit from the IDispatch Interface? If so, how would the GetTypeInfo Method be called?