flat assembler
Message board for the users of flat assembler.

Index > Projects and Ideas > OOP extension - request for comments.

Goto page 1, 2, 3  Next
Author
Thread Post new topic Reply to topic
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
This is the next my attempt to create OOP extension for FreshLib. The full source with an example can be downloaded from: Here

The file that have to be compiled with Fresh IDE is: "freshlib/test_code/test_real_obj.fpr" or with FASM: "freshlib/test_code/test_real_obj.asm".

In the second case, the environment variable %lib% = ".." and %TargetOS% = "Win32" or "Linux" is needed.

The macro library is placed in: "freshlib/gui/realobjects.inc"

Here I will post only part of the test code that illustrates the syntax of the macros:
Code:
object TAnimal
  param .String, .GetStr, NONE

  method .GetStr
  method .Voice, .prefix
endobj


object TCat, TAnimal
  method .GetStr
endobj


object TDog, TAnimal
  method .GetStr
endobj



method TAnimal.Voice, .prefix
begin
        get     ecx, [.self], TAnimal:String
        jecxz   @f

        stdcall FileWriteString, [STDOUT], [.prefix]
        stdcall FileWriteString, [STDOUT], ecx
@@:
        return
endp


method TAnimal.GetStr
begin
        movx    eax, <'nothing...', 13, 10>
        return
endp


method TCat.GetStr
begin
        movx    eax, <'Meow!', 13, 10>
        return
endp


method TDog.GetStr
begin
        movx    eax, <'Woof!', 13, 10>
        return
endp



start:
        InitializeAll

        create TCat
        mov    esi, eax

        create TDog
        mov    edi, eax

        create TAnimal
        mov    edx, eax

        exec    esi, TAnimal:Voice, 'The cat says '
        exec    edi, TAnimal:Voice, 'The dog says '
        exec    edx, TAnimal:Voice, 'Some animal says '

        stdcall FileReadLine, [STDIN]  ; wait for ENTER

        FinalizeAll
        stdcall TerminateAll, 0    


The syntax for "param" is "param name, get_method, set_method". For "method": "method name, [arg]".

In the attached file are the essential files and compiled binaries for Linux and Windows.

Any critics and analysis is highly welcome. What I missed? What it needs more? ...


Description:
Download
Filename: test_real_obj.zip
Filesize: 5.22 KB
Downloaded: 312 Time(s)


_________________
Tox ID: 48C0321ADDB2FE5F644BB5E3D58B0D58C35E5BCBC81D7CD333633FEDF1047914A534256478D9
Post 20 May 2014, 11:57
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1397
Location: Toronto, Canada
AsmGuru62
This is interesting.
I went a different way with OOP.
Not with macros, but with code generation from a script.
But, yeah, it is nice to have OO in assembly.
Post 20 May 2014, 14:45
View user's profile Send private message Send e-mail Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
Well, FASM macro engine is powerful enough to provide such features in native way.

BTW, one additional explanation: the parameters defined by "param" macro allow using of field name instead of method, when there is no need to run code. For example:
Code:
object MyObj
    .FMyParam dd ?
    param .MyParam, .FMyParam, .FMyParam
endobj    


This way, "get" and "set" macros will generate very simple code:
Code:
; get ecx, esi, MyObj:MyParam
    mov  ecx, [esi+MyObj.FMyParam]

; set esi, MyObj:MyParam, eax
   mov  [esi+MyObj:MyParam], eax    
Post 20 May 2014, 15:15
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
sid123



Joined: 30 Jul 2013
Posts: 340
Location: Asia, Singapore
sid123
Hi JohnFound,
Does this library require any OS specific functions? Except I guess malloc() and free()?
Is it possible to use it standalone? It looks like a nice idea though!
Post 20 May 2014, 15:24
View user's profile Send private message Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
@sid123: It uses FreshLib functions GetMem and FreeMem (which are portable), but otherwise it is standalone and can be used freely. All macros are in "realobjects.inc" file.

(And if you don't want to use FreshLib, you can simply replace these calls with whatever library you want):
Code:
macro create objname {
        stdcall GetMem, sizeof.#objname
        mov     dword [eax], vtables.#objname

   if defined objname#.Create & (objname#.Create and $ffff0000) = cMethodMask
        exec    eax, objname#:Create
   end if
}


macro destroy ptrObj {
   if defined TObject.Destroy & (TObject.Destroy and $ffff0000) = cMethodMask
        exec ptrObj, TObject.Destroy
   end if
       stdcall  FreeMem, ptrObj
}    


But notice, that the whole library is experimental. Code changes are guaranteed. Although, there will be no external dependencies.
Post 20 May 2014, 15:39
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
r22



Joined: 27 Dec 2004
Posts: 805
r22
OOP in ASM is tough because OOP is just syntactic sugar for having methods that take a first (this/self/me) parameter of the object instance's data structure. The data structure can be as simple as data and pointers to methods.
Code:
struc Animal
{
.Me dd ? ;; address of allocated struc
.Name dd ? ;; StringEmpty
.Noise dd ? ;; StringEmpty
.ToString dd ? ;; AnimalToString
.MakeNoise dd ? ;; AnimalMakeNoise
}

struc Cat
{
.Me dd ? ;; address of allocated struc
.Name dd ? ;; <''>
.Noise dd ? ;; Meow
.ToString dd ? ;; AnimalToString
.MakeNoise dd ? ;; AnimalMakeNoise
.Purr dd ? ;; CatPurr
}

StringEmpty db 0
Meow db 'Meow!', 0

AnimalToString: ;; (this)
 MOV eax, [esp+4] ;; instance object data structure
 MOV eax, [eax + Animal.Name] ;; pointer to name
 RET 4

AnimalMakeNoise: ;; (this, prefix)
 MOV eax, [esp+4] ;; instance object data structure
 MOV ecx, [eax + Animal.Noise] ;; pointer to voice/noise
 MOV edx, [esp+8] ;; prefix
 cinvoke printf, <'%s %s'>, ecx, edx
 RET 8

CatPurr: ;; (this)
 MOV eax, [esp+4] ;; instance object data structure
 PUSH [eax + Animal.Name]
 PUSH [eax + Animal.Noise]
 PUSH eax
 CALL Animal.MakeNoise
 RET 4

NewAnimal: ;; ()
 PUSH Animal.size
 CALL malloc
 MOV [eax + Animal.Me], eax
 MOV ecx, StringEmpty
 MOV [eax + Animal.Name], ecx
 MOV [eax + Animal.Noise], ecx
 MOV ecx, AnimalToString
 MOV [eax + Animal.ToString], ecx
 MOV ecx, AnimalMakeNoise
 MOV [eax + Animal.MakeNoise], ecx
 RET 0

NewCat: ;; (name)
 PUSH Cat.size
 CALL malloc
 MOV [eax + Animal.Me], eax
 MOV ecx, [esp+4] ;; Cat's name
 MOV [eax + Animal.Name], ecx
 MOV ecx, Meow
 MOV [eax + Animal.Noise], ecx
 MOV ecx, AnimalToString
 MOV [eax + Animal.ToString], ecx
 MOV ecx, AnimalMakeNoise
 MOV [eax + Animal.MakeNoise], ecx
 MOV ecx, CatPurr
 MOV [eax + Cat.Purr], ecx
 RET 0

Start:
 stdcall NewAnimal
 MOV esi, eax
 stdcall NewCat, <'Mr. Fluffy'>
 MOV edi, eax

 stdcall [esi + Animal.MakeNoise], esi, <'Animal : '>
 stdcall [edi + Animal.MakeNoise], edi, <'Animal : '>
 stdcall [edi + Cat.Purr], edi

 RET 0
    

*Note:* above was written in the browser so it may include many errors.

The above is the most naive / simple OOP implementation in ASM. If there were macros that could make the above easier that would be neat.

* Automatically UNION Animal struc with Cat struc, so you would only define the value of Noise and Purr for Cat.

* Automatically generate the NewAnimal and NewCat methods perhaps naming them New.Animal and New.Cat or Animal.Create and Cat.Create.

@JohnFound

Don't think you should make param/field forced to have a Getter and Setter function, but you should allow for an initialization value. Maybe have Get and Set as implicit methods like Exec or allow users to reference them directly like the struc.

In a lot of OOP the ESI reg is always the instance pointer (this/self/ms) which makes writing the methods easier and have 1 less push/pop, but then you would store your create results into a local/global variable data dd instead of directly to a register since your exec macro would have to get the instance into esi for you.

Having method definitions inside of the object endobj would probably be nice, but this is a preference to Java class style vs CPP class header prototypes/cpp implementation.
Post 20 May 2014, 16:29
View user's profile Send private message AIM Address Yahoo Messenger Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
@r22: get and set methods are optional. You can omit any of them and replace with NONE - in this case, you will have read-only or write-only parameter.
Also, instead of method, you can specify field name (the macro distinct them) and the values will be directly moved to/from the field.

In the method definition, there is a ".self" argument, that is implicitly defined and contains the pointer to the object. I choose to keep all registers for the programmer, so all OOP "instructions" will try to keep all the registers not changed.
Post 20 May 2014, 16:41
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1397
Location: Toronto, Canada
AsmGuru62
It is not that simple.
The pointers to the functions cannot be inside object itself.
It can be only a reference to the structure with the function pointers.
Imagine you have an object with 100 functions.
These 100 function pointers will be bloated as object members,
and you have to set them up at runtime, because usually object instance
is allocated at run time. So, you will have this:
Code:
mov [esi + TObject.func_001]
...
mov [esi + TObject.func_100]
    

The proper way is to declare that 100 members structure once and attach its address to each object. There may be a case where you need 1000s of objects and every one of them will have these 100 members!

Of course, only virtual methods must be done this way. A non-virtual method
must be just a call to a static address as it is done in C++.

I learned that the only way to do OO properly is to generate FASM code
from some pseudo-script. I am working on it for some years now.
Post 20 May 2014, 22:00
View user's profile Send private message Send e-mail Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
@AsmGuru62, is your post for me? There is no such things in my implementation.
Post 20 May 2014, 22:11
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
r22



Joined: 27 Dec 2004
Posts: 805
r22
@JohnFound - no, AsmGuru was responding to my post. I think an "Is" macro would be useful to do type comparisons (polymorphism) like checking if an instance of TCat is a TAnimal.
Code:
        create TCat 
        istype eax, TAnimal ;; return EAX = TRUE 

        create TAnimal 
        istype eax, TCat ;; return EAX = FALSE 
    


@AsmGuru62 - I'm well aware of HLL OOP implementations, but there was no way I could pull a truely proper/robust implementation off the top of my head, so I settled for a
Quote:
most naive / simple
version.
Post 21 May 2014, 11:36
View user's profile Send private message AIM Address Yahoo Messenger Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
@r22 - "istype" is needed. I will see what can I do. BTW, using CF for the Boolean result seems to be better.
Post 21 May 2014, 12:29
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
Implemented "istype". Download it from Here

Test code:
Code:
        create TCat
        mov    esi, eax

        create TDog
        mov    edi, eax

        create TAnimal
        mov    edx, eax

        istype edi, TAnimal
        jne    @f

        stdcall FileWriteString, [STDOUT], <"TDog is TAnimal", 13, 10>
@@:

        istype edi, TCat
        je     @f

        stdcall FileWriteString, [STDOUT], <"TDog is not TCat", 13, 10>
@@:

        exec   esi, TAnimal:Voice, 'The cat says '
        exec   edi, TAnimal:Voice, 'The dog says '
        exec   edx, TAnimal:Voice, 'Some animal says '    


The output:
Code:
TDog is TAnimal
TDog is not TCat
The cat says Meow!
The dog says Woof!
Some animal says nothing...    
Post 21 May 2014, 13:27
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
r22



Joined: 27 Dec 2004
Posts: 805
r22
Could you use the type information to make the exec calls shorter where the object name is implied?

Code:
exec   esi, TAnimal:Voice, 'The cat says '    

Becomes
Code:
exec   esi, :Voice, 'The cat says '    


In an HLL you'd have.
Code:
objCat = new Cat() // or Cat.Create()
objCat.Voice()
    


Not sure if this is even possible with FASM macro/preprocessor but I think it would improve usability.
Post 21 May 2014, 14:51
View user's profile Send private message AIM Address Yahoo Messenger Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
@r22, it is possible, but only if the object is in a memory variable (as in all HLL). The registers are not typed, so the assembler can't know the class of the object during the compile-time, because it is clear only in run-time. So, I chose to specify the class explicitly.
Post 21 May 2014, 14:59
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
r22



Joined: 27 Dec 2004
Posts: 805
r22
Wasn't sure if you could extend the create macro to store the class instance to a parameter and then use some sort of 'virtual at' or other such voodoo to keep the type information for subsequent calls to set, get and exec.

Code:
create TAnimal, edx
exec   edx, :Voice, 'Some animal says '
    
Post 21 May 2014, 19:31
View user's profile Send private message AIM Address Yahoo Messenger Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
r22 wrote:
Wasn't sure if you could extend the create macro to store the class instance to a parameter and then use some sort of 'virtual at' or other such voodoo to keep the type information for subsequent calls to set, get and exec.


Well, it reminds me the "assume" directive of MASM, but IMHO such behavior is also not very good. And why we should use such tricks? The explicit use of the class name in the calls IMHO only makes the code more readable and can prevent some hard to detect mistakes. And in addition, even if such mechanism is implemented, we still need to support explicit type casting, as in my example above - notice, how we are calling not "TCat:Voice" and "TDog:Voice", but only "TAnimal:Voice" method.

_________________
Tox ID: 48C0321ADDB2FE5F644BB5E3D58B0D58C35E5BCBC81D7CD333633FEDF1047914A534256478D9
Post 21 May 2014, 19:56
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
m3ntal



Joined: 08 Dec 2013
Posts: 296
m3ntal
Here, I do not see one good example usage of OOP. Nothing that requires beyond the functionality of a struct with functions and inheritance. Why support HL objects with get/set when you don't have basic arithmetic and comparisons.
Quote:
Could you use the type information to make the exec calls shorter where the object name is implied?
You can match the "(Class) P->Member" syntax: Class is the virtual structure name, P is a register or alias, member is the virtual offset: lea/mov [P+Class.Member]. How to omit the (Class) and only use the instance name P? P equ (Class) esi. From here on, P equals (Class) and esi as the base address. It should be restored before end method. With my alias keyword (easier multi-equ), an "assume" might look like this: alias P=(Class) esi.
Quote:
The explicit use of the class name in the calls IMHO only makes the code more readable
Writing the class name defeats the purpose. Which is clearer?
Code:
StringCopy esi, s          ; this?
exec esi, String:Copy, s   ; or this?
DrawImage esi, x, y        ; this?
exec esi, Draw:Image, x, y ; or this?    
And I32 is so limited on registers.


Last edited by m3ntal on 22 May 2014, 12:31; edited 1 time in total
Post 22 May 2014, 12:20
View user's profile Send private message Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
"or this?" is much more readable and informative. It clearly shows that esi is an object and that some method is to be invoked.

In "this?" examples the content of esi is totally obscured. What is it? Object, some bitmap, screen surface?
Post 22 May 2014, 12:29
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
m3ntal



Joined: 08 Dec 2013
Posts: 296
m3ntal
Quote:
or this? is much more readable and informative
You're just accustomed to seeing it written that way. "this?" requires less # of characters, no weird exec prefix and your syntax is limited to a register (no memory pointers).
Quote:
the content of esi is totally obscured. What is it?
A function will know the type of object. Types should only have to be specified once when created. It clearly says DrawImage. The name tells you what it does in English so that no explanation is needed.
Post 22 May 2014, 12:48
View user's profile Send private message Reply with quote
JohnFound



Joined: 16 Jun 2003
Posts: 3500
Location: Bulgaria
JohnFound
"exec" is not a "prefix" it is an "opcode" and is not weirder than "mov".
The syntax is not limited to a register. You can use memory variables this way:
Code:
Dog1 dd ?

        create TDog
        mov    [Dog1], eax

        exec   [Dog1], TAnimal:Voice, 'Dog1 says '
    
Post 22 May 2014, 12:56
View user's profile Send private message Visit poster's website ICQ Number Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page 1, 2, 3  Next

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2019, Tomasz Grysztar.

Powered by rwasa.