Hi guys

I'm glad to see some interest in my other thread, so I thought I'd create this thread to keep everyone up to date.

I'm currently testing some changes I've made to the framework, but it feels quite solid, so it shouldn't be too much
longer before it's released.

The framework uses macros like the one by NaN and Thomas and I've actually surprised myself with
what I've been able to achieve using macros, especially since I didn't know that much about them when
I started :grin:

DEFINING OUR OBJECT

This is the basic structure of our class



.CLASS name, base

m_data dd ?

.OBJECT name, type, [PTR]

.STATIC name, argCount

.VIRTUAL name, argCount, [PURE]

.INLINE name, argList
; inline code here
ENDM

.ENDC


OK, lets get into it.

.CLASS name, base

This macro allows you to specify your class name and it's ( optional ) base class.
Only single inheritance is supported.

Any interest or use for multiple inheritance?

.OBJECT name, type, PTR

This macro allows you to add an object, or an object reference to the class. If you specify the 3rd
argument ( PTR ) an object reference ( pointer ) will be added, otherwise the object will be
embedded.



.OBJECT m_file, CFILE ; embedded object
.OBJECT m_file, CFILE, PTR ; object reference


.STATIC name, argCount

This macro defines a static or direct call method. The argCount are the number of arguments the method
expects. You don't need to include the 'THIS' argument in the count, the macro will automatically
add it for you.

You can have multiple methods with the same name as long as they have different argument counts.
This works with virtual and inline method also.



.STATIC Blah, 1
.STATIC Blah, 3
.STATIC Blah, 256 ; ok a little excessive :grin: but you get the idea


.VIRTUAL name, argCount, PURE

This macro defines a virtual or indirect call method. The virtual macro also allows you to define abstract
or pure methods by specifying the 3rd argument ( PURE ). Pure virtuals are mainly for base or interface
classes and must be overloaded by a derived class.



.VIRTUAL Draw, 0 ; normal virtual
.VIRTUAL Draw, 0, PURE ; pure virtual


.INLINE name, argList

This macro defines an inline or macro method. Since inline methods are simply macros, define arguments
as you would in a macro. Inline methods gain access to the 'THIS' pointer that was passed to the method
by using the 'this' argument.



.INLINE ImAMacroInDisguise, src, dest:=<eax>
mov dest, 5
mov [this].m_classData, src
ENDM


.ENDC

This macro ends your class definition.

Qweerdy

Good idea about converting old OOP files :alright:

NEXT : Defining our methods

:alright:
Maelstrom
Posted on 2002-10-05 23:50:13 by Maelstrom
DEFINING OUR METHODS

OK, there are 3 things we need to do in the source ...

1. Define the class template
2. Define the methods
3. Pray it works :grin:

.TEMPLATE name

This macro generates a template for the class. This template is used to initialize the class
when it's created, so it must exist. There is one exception to the rule, any class that contains
PURE virtuals doesn't need a template since you won't be able to create an object of that type.
If the macro comes across an abstract method it'll let you know.



.TEMPLATE CFILE


.METHOD class, name, argList
.ENDM

The method macro requires the name of the class that the method belongs to, the method name,
and the method arguments. The macro automatically saves/restores the following registers
( EBX ECX EDX EDI ESI ). This behaviour can be modified the changing an equate.

.ENTER reg

The enter macro initializes the method ready for use. The macro can also take a single
argument which determines the register used to hold the 'THIS' pointer. The default is ESI.
You can access the 'THIS' pointer by using the 'this' equate

.RET retVal

The ret macro returns an optional value to the caller.



.METHOD CFILE, Open, _pfileName

; locals go here

.ENTER

; code goes here

.RET

.ENDM


NEXT - Accessing our methods

:alright:
Maelstrom
Posted on 2002-10-06 03:18:51 by Maelstrom
ACCESSING OUR METHODS

.ASSUME reg, class, ref

The assume macro works much like MASMs keyword.



.ASSUME ebx, CFILE, edx
.ASSUME ebx, CFILE, g_pFile
.ASSUME ebx, CFILE, struct.m_pFile ; struct

.ASSUME ebx, CFILE, [this].m_pFile ; object ref


The 1st, 2nd, and 3rd examples read : move the value ( last arg ) into EBX and treat
EBX as a CFILE ptr. The values must be pointers.

The 4th example needs more explaining. The .m_pFile is a reference to
either, an embedded object, or an object reference inside a CFILE class. Depending on
what type of object m_pFile is, the value ( reference ) or address ( embedded ) of m_pFile
is moved into EBX and EBX is treated like whatever type m_pFile was defined as.

.CINVOKE class, this, method, argList

This macro is used to call ALL methods, no matter what type they are. So if you change
a static method to a virtual, simply recompile and the macro will automatically adjust.
There is also a shorthand version, simply prefix the class name with a period.



.CINVOKE CFILE, this, Open, _pFileName

; same as

.CFILE this, Open, _pFileName


I imposed one restriction on what can be passed to the macro as the 'THIS' ptr.
My primary reasons were to clarify what was going on and to simplify the invoke macro.
The 'THIS' pointer can be any of the following as long as they are pointers.



.CFILE ebx, Open, _pFileName
.CFILE this, Open, _pFileName

.CFILE g_file, Open, _pFileName
.CFILE g_file.m_obj, Open, _pFileName ; struct

.CFILE [ebx], Open, pFileName ; need to test this one


If you need to call a method from an embedded or referenced object, then use the
assume macro above to load a register.



; lets assume [this].m_image was a CIMAGE class

.ASSUME ebx, CFILE, [this].m_image

.CIMAGE ebx, DoSomethingUseful, blah


.SUPERCLASS class, this, method, argList

This macro will allow you to call the base class version of the specified method.
The 'THIS ptr must be a register at present, but that shouldn't be much of a
problem since you will probably only try to superclass a method from inside its
overloaded equivalent.



.SUPERCLASS CFILE, this, Constructor


NEXT - Creating our object

:alright:
Maelstrom
Posted on 2002-10-06 03:39:22 by Maelstrom
CREATING OUR OBJECT

We have 2 options when creating objects.

1. Dynamic creation
2. Static creation

Would local creation be a useful addition?

.NEW reg, class, args
.DELETE this

These macros will create and destroy dynamic objects.

.CREATE reg, class, args
.DESTROY this

These macros will create and destroy static objects.

:eek: I hope I haven't scared everyone away ... anyone there ... it's not as complicated as it looks :grin:

I had some more ideas while typing this and I want to I'll try them out. I will post the framework and an example ASAP.

Comments, questions, and suggestions welcome.

:alright:
Maelstrom
Posted on 2002-10-06 04:09:37 by Maelstrom
Come on guys, where did everyone go, Nan, Maverick, anyone ...

Oh well, suppose I'll talk to myself some more :grin:

CHANGES

I've tweeked the .TEMPLATE macro a little bit, simply include it in the source and let the macro determine if it's actually needed.

I've also added local stack objects ( .LOCAL name, class, argList )
Simply specify them with the rest of your methods locals and they'll be automatically constructed and destroyed.

The example below shows how to define a local object. The m_bob vararible will end up containing a pointer to the object.



.METHOD CCLASS, SomeMethod, args

.LOCAL m_bob, CBOB, bobsArgs

.ENTER

.RET

.ENDM


I've also changed the way objects are destroyed. The .DELETE and .DESTROY macros have been
tossed and all objects now inherit a Release method. The Release method will destroy the object no matter
how it was created ( dynamically or statically ) which saves you from having to remember what you did.

Also attached is a simple example.
You can't compile the code but it shows the framework layout, the EXE is included.

The example is based on the simple OOP example by Nan - hope you don't mind Nan :grin:
Would you mind if I converted your other object examples ??

Now come on guys, feedback

:alright:
Maelstrom
Posted on 2002-10-08 21:01:58 by Maelstrom
I like the syntax, but the example doesn't tell much about the code generation. I think it is important to retain the flexiblity that ASM is, while still providing OOP syntax to speed development. Without rewriting the parser in MASM macro syntax or bloating the code, I think this is getting close to the limits of what is possible in MASM.

For another level of granularity you will want to ensure object files are encapsulated by making all the PROCs private and only exposing public PROCs with an interface definition (ie include file).

Can't wait to try this out on a couple of projects. Can all the code produced be inline and without memory access? For example, the x86 supports bit array's directly with the BT/BTC/BTR/BTS instructions and I'd like to make a bit array class that merely inlines the instructions. Trick is: I'd like to use a register if the bit arrays are <= 32 bits -- rather than using a memory pointer. :) What is the smallest amount of code that will be produced by the class macros?
Posted on 2002-10-08 22:15:11 by bitRAKE
How about allowing:
.CREATE m_circle, CCIRCLE


; .CREATE macro establishes m_circle as being of type CCIRCLE
; so invoking methods of this object only require the instance name
; followed by method name and parameters.

m_circle SetColor, 7
m_circle SetRadius, 2
See other examples:
http://www.asmcommunity.net/board/index.php?topic=1945
http://www.asmcommunity.net/board/index.php?topic=2932.msg18991
Posted on 2002-10-08 22:36:34 by bitRAKE
Thanks for the suggestions bitRAKE

For another level of granularity you will want to ensure object files are encapsulated by making all the PROCs private and only exposing public PROCs with an interface definition (ie include file).


I think NaN posted a suggestion somewhere about using PRIVATE code segments to this end, but I haven't looked into this yet - nice idea tho
Can't you specify PRIVATE when you define your PROC? Would that work, would save having lots of different segments?

Can all the code produced be inline and without memory access?


If you mean can all methods be inline, that should work, you still need to create an object tho - is this what you mean?
If not a small code example of what your after would give me a better idea of what you want.
The only method I can't inline is the destructor since it's virtual.



.CLASS CBITARRAY

.INLINE Constructor
xor eax, eax
ENDM

.INLINE Method1
add eax, ebx
ENDM

.INLINE Method2
add eax, ecx
ENDM

.ENDC


What is the smallest amount of code that will be produced by the class macros?


The smallest amount of code should be from inlines when passing the 'THIS' ptr in a register.
Using the above example ...



.CREATE ebx, CBITARRAY ; create static bit array object

.CBITARRAY ebx, Method1
.CBITARRAY ebx, Method2


The above code should produce the following ...



mov ebx, OFFSET ??0001 ; or whatever the local symbol for the object is
xor eax, eax ; Constructor
add eax, ebx ; Method1
add eax, ecx ; Method2


How about the following .CREATE m_circle, CCIRCLE


Nice idea, definitely worth some investigation, shouldn't be to hard to implement - touch wood ( taps on head ) :grin:

EDIT1

bitRAKE

I've added your suggestion, whats next! hehe :grin:

You can create and invoke methods as shown below and access the object ptr using m_circle



.CREATE m_circle, CCIRCLE, [args]

.m_circle SetColor, 7
.m_circle SetRadius, 2

; same as

.CCIRCLE m_circle, SetColor, 7
.CCIRCLE m_circle, SetRadius, 2

; or

.CREATE ebx, CCIRCLE, [args]

.CCIRCLE ebx, SetColor, 7
.CCIRCLE ebx, SetRadius, 2


The .LOCAL and .NEW macros to will perform the same. You will have to use the second method if you want to use
a register as the 'THIS' ptr however.

When passing the 'THIS' ptr as a memory argument slightly more code is generated when calling inline or virtual
methods since these move the 'THIS' ptr into a register, statics are fine however since since the memory argument
is passed directly to the method.

EDIT2

bitRAKE

I'm investigating replacing the .ENTER macro will a custom PROLOGUE, looks good so far but I was
wondering why the assembler generates an add esp, -# when allocating locals, is add more efficient than sub?
What about the EPILOGUE, should I use leave or mov esp, ebp, pop ebp?

Also looking at the .LOCAL macro, it would be more efficient to create a static object since its initialized at compile time.
If I create an object on the stack I have to initialize it at run time. Either way the constructor ( if one exists ) and
destructor need to be called everytime the method is called. What do you think?

EDIT3

bitRAKE

The prologue was going so well too, I've just found it doesn't like my segment fix ( n1 EQU $ ), it says its too complex :confused:
How does it work with the default prologue then?

:alright:
Maelstrom
Posted on 2002-10-09 02:21:03 by Maelstrom
PRIVATE METHODS

bitRAKE

I think I've worked out how to support private methods. Now I'm makng the assumption that private methods should only
be called from inside public methods of the same class. For example, if we have a class with public methods ( x and y ) and private
methods ( z ), then z can only be called from inside x and y. Does this sound correct?

This would be useful for private inline methods that set class data that you don't want to be accessed publicly. Of cource all class
data is still publicly accessible so you could easily break the rules.

I can see uses for private static and inline methods, but what about private virtuals?

Revised class format will probably look like this



.CLASS

.PUBLIC

; data

; public methods

.PRIVATE

; private methods

.ENDC


:alright:
Maelstrom
Posted on 2002-10-09 22:20:49 by Maelstrom

EDIT3

bitRAKE

The prologue was going so well too, I've just found it doesn't like my segment fix ( n1 EQU $ ), it says its too complex :confused:
How does it work with the default prologue then?
If you are speaking of using it within the prologue macro itself, then try not using it - I'm crossing my fingers that it won't be required. ;)

Wow, the rest looks good - many layers of abstraction - mucho fun. :alright:
Posted on 2002-10-09 23:06:35 by bitRAKE
This is what I tried



.METHOD CTEST, TestMethod

.CREATE m_test, CTEST

.RET

.ENDM


The compiler says the statement is too complex ( n1 EQU $ ) which is the first thing the .CREATE macro does :rolleyes:

No biggie if I have to keep the .ENTER macro I suppose.

Maelstrom
Posted on 2002-10-10 02:07:22 by Maelstrom
Oh well, no luck getting my prologue macro working with the segment fix so I've decided to forget it, for the moment anyway.

Since the .ENTER macro stays, I decided to make it force prologue generation as the first thing it does. This way any segment
changes after the .ENTER should work properly without having to modify any existing macros. I also created the .PROC macro which
has a similar syntax to the .METHOD macro but creates non-method routines.



.METHOD class, method, args

; locals here

.ENTER

; your code here

.RET [val]

.ENDM

.PROC name, args

; locals here

.ENTER

; your code here

.RET [val]

.ENDP


Private methods seem to be working but I need to do some more testing.

I was thinking of changing how the .LOCAL macro creates its objects, instead of creating them on the stack
I thought I would create them statically. This would use a little more memory but since static objects are
initialized at compile time it would be faster.

:alright:
Maelstrom
Posted on 2002-10-10 21:16:07 by Maelstrom
I was thinking about how to make accessing embedded and referenced objects a little more readable.



.CLASS CENGINE

.OBJECT m_render, CRENDER, PTR

.ENDC


To get the address of an embedded object or the ptr to a referenced object you have to use the .ASSUME macro.



.ASSUME ebx, CENGINE, [this].m_render


This will place the object ptr in EBX and make EBX a CENGINE type.
Using the suggestion above by bitRAKE, I could create a named equate so you could access EBX as .m_render
This would allow the following



; default access

.CRENDER ebx, DrawScene

; or using the named equate

.m_render DrawScene


One problem that could occur would be if you trashed EBX, it would probably crash and the problem wouldn't be obvious
in the code since the register usage is hidden by the equate.

A solution could be to have the .ASSUME macro create a data variable to hold the object ptr. Extra data and code
would be generated but the above problem wouldn't occur. I personally don't like the idea of adding any unnecessary
data or code, but on the other hand I don't like hard to find bugs either :grin:

Another problem I just thought of would be if multiple objects each contained an object with the same name. A
simple solution to this could be to add an optional argument to the .ASSUME macro to specify a custom name for the
named equate.



.ASSUME ebx, CENGINE, [this].m_render, m_d3d
.ASSUME edx, CBLAH, [this].m_render, m_opengl


What do you guys think?

:alright:
Maelstrom
Posted on 2002-10-11 07:50:43 by Maelstrom
Anyone still reading this - hehe :grin:

Private methods are in and working and can only be called from public methods from the same class.
You can make static, virtual, and inline methods private.

WARNING :eek:

Inline methods have proven to be quite the minefield. I initially created inline methods for the simple
purpose of directly accessing object data in a controlled manner. With the improvements to the
framework they can now do almost anything, including calling private methods. I guarentee you
that they will cause a lot of headaches if your not careful using them. Since all inlines recieve there 'THIS'
ptr as a register and it is hidden behind the 'this' keyword, it would be very easy to trash the this register. It gets even
worse if you nest other inline calls inside an inline method since no register saving is done, so your code
could very easily turn to custard. I would recommend using inlines sparingly and VERY carefully, you've been
warned - hehe :grin:

bitRAKE

You wanted to know about code generation so I've included a text file that shows the code generated when
defining your methods, calling methods, and creating static objects. I use the /EP compiler switch and a crappy
filter proggy I wrote to generate the file. I use this text file to check the framework output during testing.
There's room for optimization but I think I'll leave that until I look at a writing a preprocessor.

I've also created a named equate for the 'THIS' ptr using your suggestion above.

Tell me what you guys think :alright:

Maelstrom
Posted on 2002-10-14 22:36:03 by Maelstrom
Hi guys, I want your opinion

What format would you prefer when calling methods?



.CTEST ebx, TestMethod

.m_test TestMethod

or

.CTEST( ebx, TestMethod )

.m_test( TestMethod )


I'm currently testing the second format to see how it feels, but I really want other opinions.

:alright:
Maelstrom
Posted on 2002-10-15 19:58:28 by Maelstrom
Lose the parenthesis. Really - I mean it. :grin:

It is bad enough we will have to use them on the parameters where macros are involved - no more.
Posted on 2002-10-15 20:50:13 by bitRAKE
Okay, bitRAKE has spoken so the parentheses have been taken out back and shot! :grin: I didn't like the extra typing anyway.

Anyway I'm currently testing the framework on my old object code ( upgrading to the new format ) and all seems to be
going well. The interface suggestion by bitRAKE makes for some nice clean code. So all going well I'll probably
release the framework some time this weekend, don't tell Murphy :rolleyes:

I've added yet another macro, this time to export object ptrs ( I've got more macros than you can throw your cat at :grin: )



.EXTERNDEF g_console, CCONSOLE


This simply defines g_console as a DWORD and creates the named equate .g_console in the include file so that you can
access it globally.

:alright:
Maelstrom
Posted on 2002-10-17 02:03:22 by Maelstrom
Okay, there are still a couple of things that I'm not happy with ...

Firstly inline volatility ( big word eh :grin: )

The problem with inlines as they stand, is that they recieve the 'THIS' ptr in a register, any register. Passing the 'THIS' pointer in EAX would give some consistancy and would place the responsibility of register preservation in the hands of the coder. There would be a minor overhead increase as EAX would need to be set for both register and memory references ( memory only at the moment ). EAX is also a scratch register, so if other methods need to be invoked the 'THIS' register must be preserved.

Sound okay?

Secondly named equates for .CREATE and .NEW

I'm mainly looking for opinions on this one. When you create an object you can specify a data name or a register to store the object pointer. If you specify a data name the macro generates a named equate so you can access the object using its name, but if you specify a register no equate is generated.

Do I need to generate named equates for registers or not?

If so how, I mean I can't add anything after the argList since it's VARARG. I could create another macro ( don't really like this idea, got enough already :o ) or I could create an equate like .ebx

To put this into code ...



; this is fine

.CREATE m_test, CTEST, [argList]

.m_test Method, args

; but if you specify a register, no equate is generated and
; methods must be called using the original syntax

.CREATE ebx, CTEST, [argList]

.CTEST ebx, Method, [argList]

; would this be okay ( easy solution )

.ebx Method, [argList]


Feedback please!

:alright:
Maelstrom
Posted on 2002-10-17 06:37:38 by Maelstrom
About the inline macro problem, perhaps the register that the THIS pointer is passed in could be defined separately for each inline method? I mean, sometimes it's a good thing to have it in eax, other times it's better when it's in a less-used register like edi.

I have no idea if it's possible, but perhaps something like this:



.INLINE Method1
.THIS ebx

; etc...

ENDM


I'd really like that...
Posted on 2002-10-17 08:26:40 by Qweerdy
.ebx Method, [argList]
The problem I see here is what .ebx is this? Object1, Object2, ... Just for programmer sanity we need to say what .ebx is, imho. It is like the whole ASSUME MASM keyword idea - I don't like it - we like to ASSUME, but imho it is much better to know. :) Maybe something like:
.ebx CTEST, Method, [argList]
This puts the object first and has the type in there - only need one macro for each register. :)
Posted on 2002-10-17 19:37:56 by bitRAKE