Below is some sample code to demonstrate what it can do so far, nothing overly special. 'virtual' methods can be overloaded whereas 'static' methods cannot. That might change as I'm thinking of making a change to the syntax now that the core is working fairly well.

Even though I mention it in the source, I want to go ahead and thank Ultrano and Homer for their help with this as I wouldn't have ever gotten this far without their help.

BITS 32

%define SINGLE_FILE_BUILD
%include "objects.inc"

EXTERN puts
EXTERN printf

CLASS CTest
virtual method, Say
static method, CTest
static method, Hello
static method, GoodBye
static dword, m_who
ENDCLASS

CLASS CHaha, CTest
ENDCLASS

CLASS CBlah, CHaha
virtual method, Say
static method, CBlah
static method, Hi
ENDCLASS

CLASS MYA
static method, Say
ENDCLASS

SECTION .text

MYA_Say:
%push
%stacksize flat
%arg this:DWORD, msg:DWORD

push ebp
mov ebp, esp
push dword
call puts
add esp, 4
leave
ret

%pop

CBlah_CBlah:
%push
%stacksize flat
%arg this:DWORD

push ebp
mov ebp, esp
mov ecx,
mov edx, strAuthor
mov , edx
leave
ret

%pop

CBlah_Hi:
%push
%stacksize flat
%arg this:DWORD

push ebp
mov ebp, esp
mov ecx,
push dword
push dword strHello
call printf
add esp, 8
leave
ret

%pop

CBlah_Say:
%push
%stacksize flat
%arg this:DWORD, msg:DWORD

push ebp
mov ebp, esp
mov ecx,
push dword
push dword strCBS
call printf
add esp, 8
leave
ret

%pop


CTest_CTest:
%push
%stacksize flat
%arg this:DWORD, msg:DWORD

push ebp
mov ebp, esp
mov ecx,
mov edx,
mov , edx
leave
ret

%pop

CTest_Say:
%push
%stacksize flat
%arg this:DWORD, msg:DWORD

push ebp
mov ebp, esp
mov ecx,
push dword
call puts
add esp, 4
leave
ret

%pop

CTest_Hello:
%push
%stacksize flat
%arg this:DWORD

push ebp
mov ebp, esp
mov ecx,
push dword
push dword strHello
call printf
add esp, 8
leave
ret

%pop

CTest_GoodBye:
%push
%stacksize flat
%arg this:DWORD

push ebp
mov ebp, esp
mov ecx,
push dword
push dword strGoodBye
call printf
add esp, 8
leave
ret

%pop

GLOBAL main
main:
push ebp
mov ebp, esp

new CTest, dword strAuthor
mov , eax

vcall , CTest,Say, strTitle
mcall , CTest,Hello
mcall , CTest,GoodBye

delete

new CBlah
mov , eax

mcall , CBlah,Hi
vcall , CBlah,Say, strTitle

delete

new MYA
mov , eax
mcall , MYA,Say, strTitle
delete

xor eax, eax
leave
ret

SECTION .data
pObject DD 0 ;; Our Object Pointer

strHello DB "Hello, %s", 10, 0
strGoodBye DB "GoodBye, %s", 10, 0
strTitle DB "NASM OOP Demo", 0
strAuthor DB "Bryant Keller", 0
strCBS DB "CBS: %s", 10, 0
Attachments:
Posted on 2010-07-12 00:10:38 by Synfire
Update:

Change 1 - BugFix(001)
Okay, so I'm not perfect. lol It's not a really major bug (no security problem or anything) it's just something that creeped up because of my personal coding style. When I do OOP development, I put classes in a file by themselves and link the resulting .OBJ files together. Because of this tendency, when I tested my objects.inc I didn't check to see if two classes with methods/members of the same name might cause a conflict, they did. That has been fixed in my first official bugfix. :)

Change 2 - Ctor/Dtor Generator
Having the user be forced to write a Ctor/Dtor for every single class just seems a bit excessive.. So I've come up with a much simpler solution which mimics the way that C++ does. Unless the user declares a Ctor/Dtor, then you don't have to worry about defining one. This will reduce the overall executable size dramatically from the first version of objects.inc and I'm really happy about how this feature has turned out.
Posted on 2010-07-12 17:55:23 by Synfire
Update:

Change 3 - Optimizations!!!
A Lot! of optimizations have taken place within the objects.inc file. I haven't really kept this thread as up-to-date on the conversation as I have the Nasm-Forum thread, but too keep a long story short -- almost everything is compile-time now except the new/delete and the internal constructor (and your code of course). So much of the code has been turned into just identifier manipulations and state change monitoring that there is very little actual code being generated now. :)

Change 4 - USE macro
Since OOP design is oriented around multi-file builds, I've included a neat little macro (and modified the core a little) to allow you to declare your class in a single include file to be used throughout your project and define your methods in a single assembler source file (.asm) which gets built into an object file for linkage. This style of coding is familiar if you've done any large scale projects in C++. The use macro comes into play in the source (.asm) file. Place the use macro at the top of your source to import your declared class (.inc). When you do this, the internal constructor, virtual method table, and virtual data table will be brought into the source (and saved into your .obj file where it belongs).

All this is grand and cool, but since the USE macro introduced a toggling mechanism within the class generator, people who prefer to define and declare their class in the same file (like the above example does) will need to set a switch (SINGLE_FILE_BUILD) to disable all the toggling that USE relies on. I know this seems like a bad idea, but trust me -- it's for the best. :)

EDIT: Oh yea! I forgot to mention this. There is a multi-file build test at http://assembly.ath.cx/code/mft.tar.gz which shows the USE macro in action, as well as how larger scale projects are laid out. When I get some time I might do a three-tier example, I think objects.inc is probably mature enough for it.
Posted on 2010-07-14 13:36:07 by Synfire

Change 2 - Ctor/Dtor Generator
Having the user be forced to write a Ctor/Dtor for every single class just seems a bit excessive.. So I've come up with a much simpler solution which mimics the way that C++ does. Unless the user declares a Ctor/Dtor, then you don't have to worry about defining one. This will reduce the overall executable size dramatically from the first version of objects.inc and I'm really happy about how this feature has turned out.


What currently happens when a class without a c'tor inherits from a parent class with a c'tor that must be invoked during object creation?
Posted on 2010-07-24 15:45:26 by p1ranha
Good point. Actually I hadn't tested for that. I suppose it could be fixed with a modification to new along the lines of:

%macro argcnt 1-*
%assign %$argCount %0
%endm

%imacro new 1-2+
nasm_alloc %{1}_size
push eax

push eax
call %{1}___%{1}
%push
%define %$parent %{1}
%if %[%{$parent}]_has_parent = 1

%define %$parent %[%{$parent}]_parent

%if %[%{$parent}]_has_ctor = 1
rpush %{2}
push eax
call %[%{$parent}]_%[%{$parent}]
%endif
pop eax
argcnt %{2}
%if %$argCount > 0
sub esp, (%{$argCount} * 4)
%endif
%endif
%pop
%if %{1}_has_ctor = 1
rpush %{2}
push eax
call %{1}_%{1}
%endif
pop eax
argcnt %{2}
%if %$argCount > 0
sub esp, (%{$argCount} * 4)
%endif
%endm


And making the Ctor always use CDECL calling convention rather than the STDCALL convention I was using. This will repeatedly call the user Ctor if it exists for each parent class. I might upload a patch with this fix, I'm hesitant because what I really want to do is a completely rewrite of the underlying subsystem to work on a Primer class based system rather than my current system of creating an initialization for every definition (which I think is suboptimal). This is why I've not done any major revisions to this lately, I'm still working out all the theory behind the rewrite, I really don't want to start such an overhaul of the code only to find out that it can't be done because of some quirk in NASM which is why I tread softly in this field. When it comes to OOP, better men have come before me to get NASM to handle it, and better men have failed. I'm just taking my time and working slowly as to avoid any serious design flaws (not too dissimilar from this one :oops:) from rearing their ugly heads.

Thanks for the heads up though.
Posted on 2010-07-24 17:41:02 by Synfire
It would be unexpected for a parent class's c'tor not to be invoked. The best way to handle this is to provide a default c'tor for any class that does not define one to simply call it's parent object c'tor, and so on and so on. As a matter of fact, you are going to need code which does this regardless, unless part of your syntax is to force a call to super() (Java terminology there) as the first line in a class's c'tor prior to any other code executing. By providing a default c'tor in your CLASS macro you can have any other object c'tor call it to make the parent call. Another option is to make the c'tor declaration mandatory in the class definition even if the user will not define a method to do anything other than call it's parent c'tor. Whichever way you choose, you must provide support for this feature. Otherwise "Bad Things" :shock: will happen...

Along the same line of thinking regarding OO inheritance, you most definately want a root, or primer, class. There is a lot of ground to cover using OOP principles. It is especially important for the low-level "glue" that will be used for future object identification, comparison, etc.. You need to start these types of processes somewhere and having a root parent will make your life much easier!  :)
Posted on 2010-07-24 18:22:03 by p1ranha
I would opt for a root class simply named... Object :)
Posted on 2010-07-25 11:54:10 by SpooK
Ours is called "Primer".
It has almost no functionality, but one vital function is error reporting, and its inherited by Everything except for "pure interfaces".
As an example of a "pure interface", see "ID3DXAllocateHierarchy"
Posted on 2010-07-25 11:59:49 by Homer

I would opt for a root class simply named... Object :)


Ack! kind of like the definition of recursion: see recursion?  :lol:


Ours is called "Primer".
It has almost no functionality, but one vital function is error reporting, and its inherited by Everything except for "pure interfaces".
As an example of a "pure interface", see "ID3DXAllocateHierarchy"


Homer,
When comparing instance identities of two different classes whose initial parents are different but whose grandparents are identical - is this functionality implemented in OA32 as interating (or recursing) from child to grandparent or from Primer to grandparent?

eg:

            Primer
              |
            ClassX
            /      \
      ClassY        ClassZ
    /                   \
ClassA                    ClassB

Such that ClassA and ClassB are both instances of ClassX.
Posted on 2010-07-25 14:00:38 by p1ranha
Each object instance has access to every single class in its own family tree which was declared before it was, ie ,"ancestor aware".
Obviously, a grandparent or even a parent class cannot be aware of its child classes, since their definitions did not exist at the moment the ancestor was declared.

Posted on 2010-07-25 21:37:09 by Homer
Perhaps I wasn't clear. From a declaration point of view, sure. But from the RTTI point of view?
I'm thinking specifically of an isInstanceOf() method.
Posted on 2010-07-25 23:18:30 by p1ranha

.if dword ptr == MyClass1_Somefunc ; isInstanceOf


But seriously, do such things as the Object base need to be pre-packed and enforced? What if you don't need IUnknown::Release? Or a vtable when you don't need one, but Object already specified it?  Sure, it's just 4 bytes and one instruction extra, but you might need it slim some time :) .
Posted on 2010-07-26 13:26:19 by Ultrano


.if dword ptr == MyClass1_Somefunc ; isInstanceOf



For shallow classes maybe, but for deep classes?

(C++ pseudocode):

  ClassZ::Primer {}
  ClassY::ClassZ {}
  ClassX::ClassY {}
  ClassA::ClassX {}

  ClassY Y;
  ClassA A;

  bool ClassY::isInstanceOf(ClassY* argObj) { ... }

  // is A an instance of Y
  if (Y.isInstanceOf(&ClassA) ) { ... }


I just wanted to know how OA32 handled this.


But seriously, do such things as the Object base need to be pre-packed and enforced?


Welcome to the beginning of the great OOP debate :)


What if you don't need IUnknown::Release?


You should if you use IUnknown::QueryInterface or IUnknown::AddRef


Or a vtable when you don't need one, but Object already specified it?  Sure, it's just 4 bytes and one instruction extra, but you might need it slim some time :) .


The static keyword should handle that. The _this pointer is not available to static methods, thus no vtable info available to reach into instantiated object member vars.

Posted on 2010-07-26 15:27:21 by p1ranha
By IUnknown::Release I meant the whole IUnknown parent. " NumReferences dd ?" being the key useful feature.

By vtable, I meant this:


CBase struct
;vTable dd ? ; poof, redundancy purged

..
CBase ends



The kind of deep instanceof support seems completely useless. Functionality can be easily designed and implemented with simple virtuals in the rare cases it happens to make sense, imho. In OA32 it may be supported, but Homer has to chime-in for that :)
Posted on 2010-07-26 15:49:04 by Ultrano
A simple way to implement the deep instanceof is:

CChildXYZ_instanceList:  dd  CChildXYZ_Destructor, CChild1_Destructor, CBase_Destructor,0 ; the instanceof func/macro loops this

CChildXYZ_vTable: dd CChildXYZ_instanceList, CChildXYZ_Destructor, CBase_virtual1,CChildXYZ_virtual2


Posted on 2010-07-26 15:59:51 by Ultrano

By IUnknown::Release I meant the whole IUnknown parent. " NumReferences dd ?" being the key useful feature.

By vtable, I meant this:


CBase struct
;vTable dd ? ; poof, redundancy purged

..
CBase ends



Sure, you absolutely can design a static class like that. You're basically creating a simple struct containing data and function pointers. But at the same time you will not benefit from the features that OOP provides.


The kind of deep instanceof support seems completely useless. Functionality can be easily designed and implemented with simple virtuals in the rare cases it happens to make sense, imho.


That is one way. Another way is to implicitly provide the capability from within the system thus making it available to all objects created using that system. If all objects have this built-in ability then you don't have to worry about previously coded objects blowing up when a reference to the latest and greatest objects are given as a parameter. Also, when designing that new object, you eliminate the need to look into the parent class code to see if it's there or not - reducing time and eliminating duplicate or redundant coding (smaller footprint).

I think you see which side of the fence I'm on :)
Posted on 2010-07-26 16:14:19 by p1ranha
i like this tombstone, bump
Posted on 2012-05-13 06:59:16 by Homer
Tombstone is accurate. I've not even been coding for PC's lately so a lot of these old projects are being neglected. About the only thing I do on computers anymore is VHDL/FPGA simulations. As for this project, I don't even really remember what I was working on when I stopped it. :P
Posted on 2012-05-13 11:20:10 by Synfire