Interfaces

Homepage

Interfaces can be best described as an abstract class without fields.

Table of Contents

Introduction

Before reading this section on interfaces, you might want to first check out the included examples.

For each of the these, there is an example script (found in the "Class examples" folder) demonstrating the interface / class.


The below list contains practical interfaces that can also be used as a reference and used throughout your OOP scripts.


Additionally, if you wrote any interfaces that you would like me to link to in the above list, please PM me.
In the message, please include:

  1. A link to your Interface - the AHK thread
  2. A link that you want me to include - to give you credit for your Interface.

    This can be a homepage, your AHK profile, or whatever (within reason). If you don't specify a link, I will still give you credit for your work.

Creating an interface

Interfaces are very easy to create and use. The many rough drafts that I thought about before this version weren't so pretty, but the final result is nice.

When creating an interface, you define functions that an implementing class will implement. When calling an interface function, the function with the same name in the passed object's class will be (dynamically) called. For example, List_size when passed an Array object will call Array_size; when passed a Vector object, Vector_size will be called instead.

There are two functions that facilitate this process: Class_call and Class_Scall

Class_call

In most cases, the interface function will call a member function - use Class_call to facilitate this process.

The first parameter passed to Class_call is a class object that implements the interface. For the second parameter, pass the A_ThisFunc value.

If the class implements the interface, the respective function will be called. Otherwise, no call will be done and ErrorLevel will be set. See the notes on ErrorLevel below for details.


Example:

List_indexOf(ListObject, searchValue, startIndex = "C1@5s", useFunction = "C1@5s", CaseSensitive = "C1@5s")
{
    return Class_call(ListObject, A_ThisFunc, searchValue, startIndex, useFunction, CaseSensitive)
}

The above function is part of the List interface. It will call the indexOf function in the appropriate class. For example, pass an Array and Array_indexOf will be called; pass a Vector and it will call Vector_indexOf.

Class_Scall

There may be a case when you want to dynamically call a static class function. To facilitate this, use Class_Scall (static call).

The first parameter passed to Class_Scall is a class object that implements the interface or the name of a class which implements the interface. For the second parameter, pass the A_ThisFunc value.


For the object / class name

  1. If a number is passed, it is assumed to be a class object. The object's class is the class name for the call.
  2. If the argument is not a number, it is assumed to be the Class' name.

In both cases, if the class implements the interface, the respective function will be called. Otherwise, no call will be done and ErrorLevel will be set. See the notes on ErrorLevel below for details.


Example:

Coin_getWorth(CoinObjectOrClass)
{
    return Class_Scall(CoinObjectOrClass, A_ThisFunc)
}

The above function is part of the Coin interface. It will call the static function, getWorth.

For a static function, you can pass either an object that implements the interface, or the name of a class which implements the interface. For example, passing a Dime object, or passing the string "Dime" will call Dime_getWorth, whereas passing a Nickel object or the string "Nickel" would call Nickel_getWorth.

Note: although an object / class name is required in the interface function, it will not be passed to the called function, since the interface function makes a static call. The object / class name is only used to determine the class name to use when calling the respective function.

Implementing an interface

If a class implements an interface you must perform two steps:

  1. Specify that the class implements the interface
  2. Implement all interface functions

Specify that the class implements the interface

To specify that a class implements an interface, put the comma-delimited list of implemented interfaces in square brackets "[" as part of the ClassName value located in the Class' initClass function.

Example:

Vector_initClass()
{
    ;Vector implements List
    static ClassName := "Vector(Cloneable)[List]"

    return &ClassName
}

Implement all interface functions

If a class implements an interface, that class must implement all interface functions. Interface functions call Class_call / Class_Scall.

Example:

List_add(ListObject, index, object = "C1@5s")
{
    return Class_call(ListObject, A_ThisFunc, index, object)
}

Note: a function inside an interface may not be an "interface function". For example, List_copy is not an interface function because the function doesn't call Class_call or Class_Scall and instead is implemented directly.

List_copy(Destination, Source, length)
{
    srcAddr := Source
    destAddr:= Destination

    Loop, %length%
    {
        addr := NumGet(srcAddr+0)
        NumPut(addr, destAddr+0)

        if (addr)
            Class_increaseLockCount(addr)

        srcAddr += 4
        destAddr += 4
    }
}

Remarks

As with any function, you can specify ByRef and / or optional parameters.

For optional parameters, specify the reserved value "C1@5s" to use the default value as specified by the implementing class.

Currently, Class_call and Class_Scall can take up to 6 parameters, in addition to the class object parameter and the A_ThisFunc value. If there is a requirement for more than 6 parameters (7 including the class object, for a member function), I can update Class_call and Class_Scall to allow more parameters.

Reserved Value ("C1@5s")

Specify the reserved value "C1@5s" (case-sensitive, for performance) as the default value for any optional parameters in an interface function. This ensures that the default value used is the one specified by the implementing class.


Although this solution works, the only problem with it is that the value "C1@5s" cannot be used as an actual value, because the function assumes that if an argument has this value, then this parameter (and thus the ones after) were omitted.

If AHK had a way to detect how many parameters were actually passed, there would be no need for the reserved value, and instead, the parameter count would be passed to Class_call and Class_Scall.


Details (you don't need to worry about these, but I will document them)

This step is required because if the parameter wasn't actually passed to the interface function, the default value, as specified by the implementing class, should be used. In order to provide this functionality, if an optional parameter wasn't passed when calling the interface function, it shouldn't be passed during the dynamic call to the target function. Thus, it is necessary to know if a parameter was actually passed.

Use of ErrorLevel

When calling an interface function, if the call to the target function isn't allowed or cannot be performed, ErrorLevel is set to indicate the error. However, since ErrorLevel may be set by the calling function, I use the prefix "Class_call: " for each error - this distinguishes a Class_call error from another error. If the call is successful, ErrorLevel is set to 0, unless the called function modifies it. To test if an error is a Class_call error, call Class_callError(ErrorLevel).

If the error has the prefix "Class_call: ", then it is assumed to be an error set by either Class_call or Class_Scall. This prefix is removed and remaining value of ErrorLevel (the actual error) is returned. If the value doesn't have this prefix, 0 is returned.


This means that the return from Class_callError can be used as a quasi-boolean value. The statement if Class_callError(ErrorLevel) would be true if the previous call to Class_call / Class_Scall resulted in an error, and false otherwise. Of course, you could also store ErrorLevel in a variable and pass this stored value (to allow testing if a prior call succeeded or failed).

Note: the return from Class_callError is non-zero only if the error is a Class_call error. For example, if ErrorLevel is "1", then Class_callError(ErrorLevel) would return false (0), even though ErrorLevel is non-zero.


The return from Class_callError is one of the following values. For a mnemonic aid, these values resemble the values set by DllCall.

0: Success.

-1: The function name is invalid. The function name must have this form: ClassName_FunctionName.

-3: The class doesn't implement the interface.

-4: The specified function wasn't implemented.

An (the letter A followed by an integer n): The function could not be called due to too few parameters - "n" is the number of parameters by which the the argument list was incorrect. Since too few parameters were passed, n is negative. In v1.0.48+, passing too many parameters is tolerated, and thus will not result in an error. Since the library makes use of the function isFunc, which was introduced in v1.0.48, anyone running the latest version of the class library must have v1.0.48+, so this is not a concern. If you are not running v1.0.48+, an error will appear when you trying running your script telling you that isFunc is not a recognized function.

Final thoughts

In addition to making calls to member functions (via Class_call), and static functions (via Class_Scall), you can also add functions directly to an interface.

For example, the List interface provides the List_copy function which is used in both Array_new1 and Vector_new1 to create a new Array or Vector, respectively, from an existing List.


Additionally, since getters and setters are functions, you can define them as well. You cannot, however, specify fields in an interface. You can, though, define a getter and /or setter function, and it is up to the implementing class to implement the function and the necessary field.

For example, the List_getData function calls the getData function for the passed list. The getData function returns the internal data for the List. This function along with List_copy makes it possible to create a new Array, Vector, and any future classes which implement the List interface from an existing list - see Array_new1 and Vector_new1 for details.


You can, optionally, specify an interface function as optional. There is no formal declaration or steps required to do this - rather, it is a documentation note. This informs those that might implement the interface that these functions are not required in the interface. For example, the List_add and List_addAll functions are both optional functions in the List interface - Vector implements them, but Array does not.


Homepage