The latest versions of FreshLib, silently migrated to a new object oriented programming macro implementation. Now it implements real OOP architecture with inheritance, polymorphism, virtual methods and properties.
The implementation uses jump tables, so it is pretty fast, especially compared to the previous implementation.
The syntax is based on pseudo-instructions and is compatible with assembly language programming style.
The main target for this library is the programming of GUI interfaces, because the structure of GUI, usually fit very well in the OOP paradigm. But of course, the library can be used for general purpose OOP programming.
2. Object definition. Encapsulation
The definition of the object is provided by object / endobj macros. The syntax is very similar to struct macros, but the definition can be inherited and the body may contain more elements:
object NAME[, PARENT]
PARENT can be either other object type name or not be specified at all. If PARENT is missing the object has no parent and does not inherits any fields. If PARENT is specified, the object inherits all fields from the parent object.
All field names in the object definition must be local names related to the object name, i.e. must start with dot character.
The body may contain following elements:
👉 General FASM data definition fields:
.length dd ?
.height dd ?
.weight dd ?
The fields are aimed to provide storage of private for the object data. Of course, it is assembly language and the fields are not hidden from the user, but a good practice is to not use them as an interface. (There are other members that are to be used as an interface - the parameters and methods).
👉 object parameter definition through param directive. Will be described in details later.
👉 object method definition through method directive. Will be described in details later.
2.1. Object parameters; "param" directive;
The parameters (aka "properties") are object data fields, that are not necessary memory cells. Instead their values can be read and write by several flexible ways. These ways are transparent for the programmer. The FreshLib implementation allows 3 ways to reading writing parameter values:
By direct read/write to a memory fields.
By calling object method that to read or write parameter value using any software method, for example computing the value, or reading it from a file or even from the network.
Not reading/writing at all - the parameters can be read-only and/or write-only.
The syntax of the definition is:
param PARAM_NAME, GET_TARGET, SET_TARGET
PARAM_NAME is the name of the parameter. It must be local related to the object name (to start with dot).
GET_TARGET defines how the value of the parameter will be get. This parameters can be either NONE, object field name or object method name. In the first case, the parameter can't be read. In the second, the read of the parameter becomes simple structure field read and in the third, the read of the parameter will cause the object method to be call and the return value of the method will be accepted as a value of the parameter.
The get method must have no arguments and must be defined in the object definition as normal method.
SET_TARGET defines how the value will be write to the parameter. Again we can use NONE, field name or method name. The only difference with the GET_TARGET is that the set method has one argument - the new value of the parameter.
2.2. Object methods; "method" directive; "abstract" directive;
This directive defines a method field in the object. The syntax is very similar to the proc macro directive:
method NAME, [ARGUMENTS]
abstract NAME, [ARGUMENTS]
NAME must be local related to the object name. The arguments can be arbitrary count and also must start with dot (local).
Besides the explicitly defined arguments, one hidden argument .self is defined for every method, that contains a pointer to the object instance in the moment when the method is called. The .self argument is the first of all arguments in the method.
The only difference between method and abstract variants is that the methods defined with abstract keyword does not need to be implemented in the given class. The abstract methods are simply placeholders for the children object methods. They allow polymorphic calls of the method in the children objects.
Every non-abstract method, defined in the object need to be implemented later in the code. The implementation looks just like proc procedure definition, with the difference that method keyword is used. In the implementation, there is no arguments, because they are already defined in the object type definition.
The methods can be override in the children classes. The inherited method is still accessible through inherided command. This way the methods chains can be called when needed.
As is seen from the above description, the object type itself contains in the same container (capsule) all needed data and the code, needed to process this data. This property of the objects is called Encapsulation.
Some think that "encapsulation" is something about hiding data or code inside the objects, but this is not the case in the common case.
And of course in assembly language hiding data or code is impossible at all. The user have always access to all data fields and code in the object. There is no private fields or methods. It is simply not the assembly way.
3. Example of object definition with inheritance.
In the following example can be seen all the above syntax in practice. The type TAnimal defines several common data fields, the parameter .Energy that is read-only and gives the current kinetic energy of the animal and the abstract method .Jump that is not implemented for TAnimal, as long as the different animals jumps differently and need special implementation.
The children types TCat and TDog implements their own methods .Jump.
.length dd ?
.height dd ?
.weight dd ?
.speed dd ?
param .Energy, .GetEnergy, NONE
abstract .Jump, .height
object TCat, TAnimal
method .Jump, .height
object TDog, TAnimal
method .Jump, .height
mov ecx, [.self]
mov eax, [ecx+TAnimal.speed]
imul eax, eax
imul eax, [ecx+TAnimal.weight]
sar eax, 1 ; E = m.v^2/2
; code that makes the cat jump not as other animals.
; code that makes the dog jump not as other animals.
4. Object manipulation macros.
4.1. Creating objects
Once the object is defined, the user can create object instances, using the macros create. The syntax is:
create DST, TYPE
The macro allocates memory for the object instance, fills needed fields and executes the method Create if such is defined for the given object type.
DST is the destination where we want the pointer to the object instance to be stored. Can be register or memory location. Same as for mov instruction.
TYPE is an object type, previously defined with object macro.
4.2. Destroy object.
The allocated object instance must be destroyed when not needed with the macro destroy. The syntax is:
SRC is a pointer to the object instance. Can be register or memory content.
The macro will first call the Destroy method of the object if such is defined and then will free the allocated dynamic memory.
4.3. Set/Get object parameters.
The value of object parameter can be get with the macro get. The syntax is:
get DST, PTR_OBJ, TYPE:PARAM
The macro gets the value of TYPE:PARAM parameter of the object PTR_OBJ and store it in DST.
DST and PTR_OBJ can be register or memory location, TYPE must be already defined object type and PARAM parameter, defined in this object or any of its predecessors.
Notice, the TYPE and PARAM must be separated by colon
":", not a dot.
The value of object parameter can be set to new value with the macro set. The syntax is similar to the previous, only in different order:
set PTR_OBJ, TYPE:PARAM, VALUE
PTR_OBJ is the object which parameter have to be set.
TYPE:PARAM specifies the parameter name.
VALUE is the new value of the parameter.
4.4. Executing object methods.
The macro exec executes some object method. The syntax is:
exec PTR_OBJ, TYPE:METHOD, [ARG]
The macros has various count of arguments, depending on the arguments the method accepts.
TYPE:METHOD must be valid combination of defined object type and method name, separated by colon ":".
4.5. Check object type
The user can check whether the object belongs to some type by using "istype" macro:
istype OBJECT, TYPE
This macro returns ZF flag set if the object belongs to the type or ZF cleared if not. It will not change any registers.
For example, see following code:
create eax, TCat ; create object instance.
istype eax, TCat
je .yes_cat ; ZF=1 It is a TCat...
istype eax, TAnimal
je .yes_animal ; ZF=1 ...also, it is TAnimal...
istype eax, TDog
je .yes_dog ; ZF=0 ...but is it NOT TDog.
int3 ; will stop here
Polymorphism is one of the main properties of the OOP. It is the ability of the object to execute different code on the same method call, depending on its type.
In the context of our library, as long as the types inherit all the methods of its parents and can redefine it, it means, that in the above example, TCat and TDog both have method .Jump, inherited from TAnimal. (In this sense, they are both of type TAnimal), but the implementation of TCat.Jump and TDog.Jump differs.
This way, we can use the same code to process both types.
create eax, TCat
create ebx, TDog
exec eax, TAnimal:Jump, 100
exec ebx, TAnimal:Jump, 100
You can see, that the same code will call in the first case TCat.Jump and in the second case TDog.Jump methods.