![Photo](https://secure.gravatar.com/avatar/f832d56069fa544b802178ec889fa237?s=100&d=%2F%2Fwww.autohotkey.com%2Fboard%2Fpublic%2Fstyle_images%2Fortem%2Fprofile%2Fdefault_large.png)
[AHK_L + AHK v2] COM Classes Framework (CCF)
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
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #1](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
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.
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #2](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
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
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #3](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
class ISomething extends IUnknown { var IID := "{...IID here...}" ; Methods Method1(...) { return DllCall(this.ptr + n*A_PtrSize, ...) } ; ... }
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #4](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
Of course, but I meant the additonal implementations: if the interface class would contain the implementation,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
it's less code than including something like CreateInterface() (not a lot, though).
Regards
maul.esel
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #5](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #6](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![:)](http://www.autohotkey.com/board/public/style_emoticons/default/happy.png)
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)
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #7](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
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?
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 likeEither 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().
sth := CreateComething() ; instead of sth := new ISomethingI just see one more function :lol:
I would suggest to make the classes have the same name as the interfaceMind you that the interface name doesn't necessarily have to match the COM class name.
(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
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #8](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
class ISomething { ChickenCurry(){ } } class Something extends ISomething { ChickenCurry(){ msgbox chicken curry! } } sth := new Something
I purely understand butOOP coding conventions might be nice & useful, but they are not the most important thing to me.
instance := new IMyInterfaceis very missleading
![;)](http://www.autohotkey.com/board/public/style_emoticons/default/wink.png)
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)
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.What do you think about the other questions, e.g. what to return?
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... }
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #9](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
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.
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.)__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() }
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. }
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #10](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
Ok 8)If you dont want to create the Interfaces, just omit the leading I...
@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:
What do you mean? If the user changes it?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.
Regards
maul.esel
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #11](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
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
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #12](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
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?For interested users: I wrote a quick tutorial about this.
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #13](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
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
![:)](http://www.autohotkey.com/board/public/style_emoticons/default/happy.png)
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #14](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
![Posted Image](http://img39.imageshack.us/img39/6620/github.png)
Win7 HP SP1 64bit | AHK_L U 64bit
Ok, how would you know what method you are calling for an object that doesn't have an IDispatch interface?Calling a function by name, that's a feature of the 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?
![[AHK_L + AHK v2] COM Classes Framework (CCF): post #15](http://www.autohotkey.com/board/public/style_images/ortem/icon_share.png)