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 |
|
JohnFound 20 May 2014, 11:57
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? ...
_________________ Tox ID: 48C0321ADDB2FE5F644BB5E3D58B0D58C35E5BCBC81D7CD333633FEDF1047914A534256478D9 |
|||||||||||
20 May 2014, 11:57 |
|
JohnFound 20 May 2014, 15:15
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 |
|||
20 May 2014, 15:15 |
|
sid123 20 May 2014, 15:24
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! |
|||
20 May 2014, 15:24 |
|
JohnFound 20 May 2014, 15:39
@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. |
|||
20 May 2014, 15:39 |
|
r22 20 May 2014, 16:29
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. |
|||
20 May 2014, 16:29 |
|
JohnFound 20 May 2014, 16:41
@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. |
|||
20 May 2014, 16:41 |
|
AsmGuru62 20 May 2014, 22:00
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. |
|||
20 May 2014, 22:00 |
|
JohnFound 20 May 2014, 22:11
@AsmGuru62, is your post for me? There is no such things in my implementation.
|
|||
20 May 2014, 22:11 |
|
r22 21 May 2014, 11:36
@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 |
|||
21 May 2014, 11:36 |
|
JohnFound 21 May 2014, 12:29
@r22 - "istype" is needed. I will see what can I do. BTW, using CF for the Boolean result seems to be better.
|
|||
21 May 2014, 12:29 |
|
JohnFound 21 May 2014, 13:27
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... |
|||
21 May 2014, 13:27 |
|
r22 21 May 2014, 14:51
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. |
|||
21 May 2014, 14:51 |
|
JohnFound 21 May 2014, 14:59
@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.
|
|||
21 May 2014, 14:59 |
|
r22 21 May 2014, 19:31
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 ' |
|||
21 May 2014, 19:31 |
|
JohnFound 21 May 2014, 19:56
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 |
|||
21 May 2014, 19:56 |
|
m3ntal 22 May 2014, 12:20
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? Quote: The explicit use of the class name in the calls IMHO only makes the code more readable 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? Last edited by m3ntal on 22 May 2014, 12:31; edited 1 time in total |
|||
22 May 2014, 12:20 |
|
JohnFound 22 May 2014, 12:29
"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? |
|||
22 May 2014, 12:29 |
|
m3ntal 22 May 2014, 12:48
Quote: or this? is much more readable and informative Quote: the content of esi is totally obscured. What is it? |
|||
22 May 2014, 12:48 |
|
JohnFound 22 May 2014, 12:56
"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 ' |
|||
22 May 2014, 12:56 |
|
Goto page 1, 2, 3 Next < Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.