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.
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
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.
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.
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.
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.
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?
Good point. Actually I hadn't tested for that. I suppose it could be fixed with a modification to new along the lines of:
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.
%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.
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! :)
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! :)
I would opt for a root class simply named... Object :)
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"
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"
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.
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.
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.
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.
I'm thinking specifically of an isInstanceOf() method.
.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 :) .
.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.
By IUnknown::Release I meant the whole IUnknown parent. " NumReferences dd ?" being the key useful feature.
By vtable, I meant this:
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 :)
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 :)
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
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 :)
i like this tombstone, bump
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