CUSTOM OBJECTS
Updated: Apr 16, 2005

Custom Objects may be created (please see newobj.inc below), used
in any program (please see newobj.bas below) and "extend" existing
objects.

Generally, (1) include in your OBJECT/TYPE definition items "As SUB"
or "As FUNCTION" and (2) declare and define corresponding SUBs or
FUNCTIONs with the same name.

OBJECT is an alias for TYPE.  It is not necessary to edit existing
code if "TYPE" is used.

For other native HotBasic Objects, you just

  DIM MyName As qualified_type  'e.g., As STRING, As FORM

For Custom Objects you name and create an OBJECT/TYPE structure as
shown below and use that as a qualified_type in DIM statements in the
main program (as described in "Dimension").

Custom Objects may be packaged in .inc files for use in one or more
HotBasic applications (examples in HotInclude download).

When writing your OBJECT/TYPE structure,

  OBJECT OpenGL
  '...
  END OpenGL

or

  TYPE OpenGL
  '...
  END OpenGL

both create a new "qualified type" which can be dimensioned in source
code (please see "Dimension").

For example, if the OBJECT is named "OpenGL", then source code can
"DIM GL as OpenGL" (please see hotudt.bas).


==OBJECT PROPERTIES and CONSTRUCTOR Syntax

As in any UDT OBJECT, the "properties" are all RW items.

The ordinary UDT OBJECT may be defined with default values with simple
CONSTRUCTOR syntax.  Both numeric and string OBJECT/TYPE items may be
assigned default values, as shown in newobj.inc below.

This CONSTRUCTOR syntax is similar to that used in DEF... dimensioning
statements for variables (DEFINT, DEFSTR, etc).  construc.bas in
HotTrial shows sample code.

One simply appends " = value" to the OBJECT/TYPE item line.  The value
must be an "immediate" or "literal" value -- that is, a number for
numeric items or a quoted string for string items in your OBJECT/TYPE
definition code block.  The value cannot be a variable or expression.

For numeric items, the value should match the item type (floating vs
non-floating).  "MyFloat as double = 1.5" and "MyInt as integer = 10"
meet this requirement.  A hex value such as &HA000 may be used.

For string items, consider the optional "* n" syntax where n is a
literal integer for maximum item length as shown in newobj.inc.
If "* n" syntax is omitted, n defaults to 256.

The quoted string is truncated if longer than the item size.  With

  MyText As STRING*12 = "HotBabe Hello"

"HotBabe Hello" is truncated and the default .MyText item will be
"HotBabe Hell" -- something nobody wants!

At run-time all string assignments to OBJECT/TYPE string items are
similarly truncated if necessary to avoid buffer overflows.

  MyObj.MyText = s$

is always logically the same as

  MyObj.MyText = left$(s$,n) 'where n is maximum item size


==OBJECT METHODS +

Custom Objects add two new qualifed types: SUB and FUNCTION.  You
might think of the SUB item type as a "method" in your OBJECT.

To create a method, include an item in your OBJECT like this:

  CallHome as SUB

To "activate" this item as a method, we then Declare "CallHome"
as a SUB with whatever arguments it may have in the ordinary manner.

  Declare SUB CallHome (phone_number As STRING, CH_wait as LONG)
  'code
  SUB CallHome (phone_number As STRING, CH_wait as LONG)
  'your method code
  END SUB

Now this new method is used in the application as if it were an
integral part of HotBasic!

  MyObj.CallHome "555-1212", 10 'or phone$, sec_wait variables


==OBJECT FUNCTIONS +

The "As FUNCTION" qualified type in a Custom Object structure is used
in the same way as "As SUB" -- just the ordinary code you would write
in any HotBasic application -- (1) declare it, then (2) define it.

newobj.inc below shows an example.


==OBJECT EVENTS +

Custom Object "events" are already covered with methods (As SUB) and
functions (As FUNCTION).  That is, any of the events in any HotBasic
Object, or that you create, can be treated as an "event".

The bottom-line is that an "event" is anything you say it is.
It could merely be a check of any values you choose with any logic
you choose when you choose to flag the "event" you define.
[This is how Windows itself creates most event messages.]

Example.  If x <> 0 is your "event" signal, then anything you do to set
the value of x might trigger your event when you call the "x event"
handler ".Event_x".  Thus, we may have a ".Detect_x" Sub.
To make this concrete, say the x event is "negative net worth" and
TellCEO is "send memo to CEO".  In brief, code might:

  MyObj.Detect_x: MyObj.Event_x

where .Event_x is merely

  IF x THEN call TellCEO 'send memo to CEO

Example.  Along the same lines, as a .Detect_x result, say x <> 0 is:
any monster is closer to hero than y units of distance.  Among other
things, .Event_x may contain: IF x THEN call AddSound "Oh-no".

To help make your .inc and .bas files more readable, you might
create OBJECT/TYPE items using "event-oriented" symbols like this:

Event1 As SUB
FPU_error As SUB
InKey As FUNCTION 
TimeOut As FUNCTION
MouseMoved As FUNCTION
'etc


==CUSTOM OBJECT INTERNAL VARIABLES

===hbObj +

Within your OBJECT SUB and FUNCTION code, "hbObj" is a pre-defined,
pre-dimensioned DWORD value containing the address of the calling 
object dimensioned in application code.

As you write your Custom Object, the symbol name for the OBJECT
instance in application code is unknown.  Indeed, if appropriate,
an application may dimension multiple instances of your OBJECT.

If you need to access data in your Custom Object in its SUB/FUNCTION
procedures, hbObj may be used to RW any OBJECT item, as shown in
newobj.inc below.

The address of an object item is hbObj plus the item offset.
The offset is the sum of the foregoing (previous) item lengths.
Let us use this definition to comment in the offsets:

OBJECT XYZ
item1 as long  'offset=0
item2 as long  'offset=4
item3 as string*32  'offset=8
item4 as long  'offset=40
item5 as long  'offset=44
item6 as dword  'offset=48
item7 as string*64  'offset=52
item8 as string*16  'offset=116
method1 as function  'offset=132
method2 as sub  'offset=136
END XYZ

Notice sizeof(X) = 140

In Custom Object SUB/FUNCTION code, @<object>.<member> syntax provides the
address of a specific item.  For XYZ above, we can write

  x = BYREF(@XYZ.item4)  ' = BYREF(hbObj + 40)

and 

  BYREF(@XYZ.item4) = x


===hbWnd +

If a Custom Object EXTENDS a FORM object, "hbWnd" is the object's
handle.  To put or get properties of, or cause actions in, the
extended object, use (1) SENDMESSAGE or POSTMESSAGE functions or
(2) WINDOW(hbWnd).Member syntax.


===As SUB, As FUNCTION +

"As SUB" and "As FUNCTION" items in your OBJECT not only create
methods and functions as described above, but also are 32-bit Custom
Object items which may store any data or flags you like.

How?  One way is to use BYREF as shown in newobj.inc to RW these
internal variables, as might be convenient to develop your OBJECT.
You may be familiar with "user data" items in various programming
environments.  This is it!  One unique 32-bit "user data" item for
each SUB and FUNCTION in each OBJECT instance.

The user of your OBJECT would have RW access to this data only if
you make SUBs and FUNCTIONs specifically to do that.  Maybe
.GetXXXData and .SetXXXData where "XXX" is SUB/FUNCTION identifier
text.

Overall, it may be easier just to add other items to your OBJECT or
DIM global variables in your .inc to implement similar functionality.
However, beware that such "global variables" may not be unique to
either the procedure item or instance, limiting control of RW activity.

On the other hand, the 32-bit "As SUB" and "As FUNCTION" locations
provide the OBJECT writer with unique locations to avoid "mixing up"
data and are there if you want to use them.


==OBJECT SUB/FUNCTION SCOPE

As might be evident in newobj.inc, SUB and FUNCTION procedures in an
OBJECT .inc may be used in the main application code also and also by
other objects.  Your OBJECT manual should indicate where this is 
permissible or desirable.  Routines that alter OBJECT state might best
be "off the table" for general application usage, since they might
impair proper function of your OBJECT.

In the OBJECT XYZ example above, Method2 could be declared as

DECLARE SUB Method2  'Method2 might appear in other objects

or as

DECLARE SUB XYZMethod2  'Method2 is localized to object XYZ

There is nothing to prevent XYZMethod2 from being called elsewhere in 
code.  However, the above syntax would facilitate use of a procedure
called "Method2" in OBJECT XYZ and another OBJECT ABC, which could

DECLARE SUB ABCMethod2  'Method2 name shared but not the procedure.


==CUSTOM OBJECT EXTENDS NATIVE OBJECT +

A Custom Object may extend a native HotBasic Object (see "Dimension")
shown in extends.bas (FILE object) and printdev.bas (PRINTER object).

With EXTENDS syntax, e.g., "OBJECT TurboList EXTENDS LIST", TurboList
may add new properties, methods and functions not defined for the
native LIST object.  When code is compiled, TurboList items are used
if defined, else the ordinary LIST items are used.  If TurboList
duplicates an item used in LIST, then that item over-rides the native
property, method or function.


==CAN A CUSTOM OBJECT USE SUB and FUNCTION CODE ELSEWHERE IN THE APP?

Yes.  Any declared SUB or FUNCTION in a program can be used as an
OBJECT method (as SUB) or function (as FUNCTION).  Thus, different
OBJECTS can "share" some of the same SUBS and FUNCTIONS.


==HOW TO USE OTHER OBJECTS IN A CUSTOM OBJECT

At least two approaches apply:

(1) Put an OBJECT (any qualified type) as an item in your OBJECT/TYPE
definition.  For example, NewObject in newobj.inc does this.  This
includes the ordinary item types such as STRING, WORD, LONG, etc.
There are all the other HotBasic qualified types to pick and choose
from, or even other Custom Objects.

There may be coding issues with this approach, depending on what you
dimension *within* your Custom Object structure.

Qualified type items in your OBJECT/TYPE definition most often would
have the "status" of "properties", but in this abstract world of 
Custom Objects, who knows what you will devise and how you wish to
describe it.

(2) DIM an OBJECT in your Custom Object .inc file and use it in the
Custom Object code.  E.g., iObj in newobj.inc is a trivial example.

Generally, any other OBJECT can be used in Custom Object code as
convenient.  Thus, Custom Objects can create "multi-object" procedures
for more complicated tasks.  For example, your Custom Object may need
FILE, LIST, REGISTRY, etc objects, along with various ordinary
string and numeric variables to do its work.

These may be viewed as "internal variables and objects" in your Custom
Object.  That is, user access with OBJECT syntax is limited to your
properties, methods and functions which may use these "internal" items.

For wide usage of your .inc for a Custom Object, use symbol names for
these "internal" variables and objects which are weird, off-the-hook,
unusual, etc, to avoid the possibility that applications using your
.inc duplicate these names and cause compilation conflicts.  [Advice
on creating secure passwords may apply here.]

E.g., if you DIM i As LONG in your .inc, lots of math and matrix
people will also try to DIM i As LONG, and bingo, you have a conflict.
In that case, try DIM i_am_light As LONG or DIM i9990 As LONG, betting
that no sane person would use such a variable name in their application
code.  Likewise, not "DIM form as form", but something wacky like
"DIM 6DfbC As FORM".

Of course, DIM within Custom Object SUB/FUNCTION procedures creates
"local" variables, as usual, which are probably safe, regarding
duplicate names in application code.


==OBJECT MANUAL

For distributed usage of your OBJECT, a manual or help file is probably
necessary in most cases.  Generally, the OBJECT is written because you
have specialized knowledge of a specific coding area which you want to
share with users who understand the functionality, but not how to do
it themselves in source code.


==MULTIPLE INSTANCES OF A CUSTOM OBJECT

Example.  You have an "account analysis" OBJECT.  Maybe your application
will DIM multiple instances to work with more than one account or
the same account in different time periods.

Example.  You make a SPRITE OBJECT.  Your GAME OBJECT may then use
multiple instances of SPRITE:

DIM hero as SPRITE, bullet as SPRITE, villian as SPRITE


==CUSTOM OBJECTS WITHIN A CUSTOM OBJECT

Example.  A general SENSOR OBJECT and MOTOR OBJECT may be dimensioned
multiple times in a ROBOT OBJECT.  Each SENSOR and MOTOR OBJECT
properties can be set to define the "cast of characters" (IO interfaces)
in your ROBOT.


==TRUE OOP (Object-Oriented Programming)

The Windows SENDMESSAGE system is a major-league example of that and
for new GUI OBJECTs, you can use that.  And you can invent your own
messaging system in your own OBJECTs so "events" in the program flow,
including the state of OBJECT x, can "influence" the behavior of
OBJECT y.  Soon you'll have a new world of OBJECT instances "talking
to each other" and "reacting".


==SUPER-CLASS OBJECTS

Likewise, any other OBJECT -- FILE, FORM, REGISTRY, etc -- can
be dimensioned in a new OBJECT and become part of super-class
activities.


###############

=====newobj.inc
OBJECT NEWOBJ
  left as word = 10  'property constructor!
  top as word = 10   'yes, top defaults to 10
  checked as dword  'property
  CheckIt as sub   'method
  ClearIt as sub   'method
  Diagonal as function 
  player as string*32 = "HotDude"
end NEWOBJ

declare sub CheckIt
declare sub ClearIt
declare function Diagonal (x as double, y as double) as double

SUB CheckIt
BYREF(@NEWOBJ.checked) = true  'set .checked true
END SUB

SUB ClearIt
BYREF(@NEWOBJ.checked) = false  'set .checked false
END SUB

FUNCTION Diagonal (x as double, y as double) as double
result = sqr(x^2 + y^2)
END FUNCTION
=====newobj.inc


=====newobj.bas
$apptype console

$include "newobj.inc"

PRINT "HotBasic(tm) Custom Object Test 1.3": PRINT

dim MyObj as NEWOBJ, diag as integer

PRINT "Let's see if the CONSTRUCTOR syntax worked:"
PRINT "MyObj.top     = "; MyObj.top  'prints default
PRINT "MyObj.left    = "; MyObj.left
PRINT "MyObj.checked = "; MyObj.checked
PRINT "MyObj.player  = "; MyObj.player
PRINT

PRINT "We have a METHOD to set .checked property:"
MyObj.CheckIt
PRINT "MyObj.checked = "; MyObj.checked
PRINT

PRINT "Now our METHOD to clear .checked property:"
MyObj.ClearIt
PRINT "MyObj.checked = "; MyObj.checked
PRINT

PRINT "Next we try a numeric function!"
diag = MyObj.Diagonal(3,4)
PRINT "MyObj.Diagonal(3,4) = "; diag
PRINT
PRINT "Maj. Hog: 'May we have a moment of silence.'"
PRINT
pause
END
=====newobj.bas


==OBJECT UTILITY FUNCTIONS

Code like this might be handy in OBJECT writing:

'for 4-byte values such as DWORD, INTEGER, LONG and SINGLE

value = BYREF(hbObj + offset) 'gets value

BYREF(hbOjb + offset) = value 'puts value

'for other numeric values such as BYTE, WORD, DOUBLE and REAL10
Declare FUNCTION GetObjItem (obj_offset As LONG, nbytes As LONG) As LONG

FUNCTION GetObjItem (obj_offset As LONG, nbytes As LONG) As LONG
DIM i as DWORD 
memcpy @i, hbObj+obj_offset, nbytes: result=i
END FUNCTION

Declare SUB PutObjItem (obj_value As DWORD, obj_offset As LONG, nbytes As LONG)

SUB PutObjItem (obj_value As DWORD, obj_offset As LONG, nbytes As LONG)
memcpy hbObj+obj_offset, @obj_value, nbytes
END SUB

'for string values
Declare FUNCTION GetObjItem$ (obj_offset As LONG, nbytes As LONG) As STRING

FUNCTION GetObjItem$ (obj_offset As LONG, nbytes As LONG) As STRING
DIM s$ as STRING: s$=STRING$(nbytes,chr$(0)) 'be sure we have ram space 
memcpy @s$, hbObj+obj_offset, nbytes: result=LEFT$(s$,LEN(s$))
END FUNCTION

Declare SUB PutObjItem$ (obj_value$ As STRING, obj_offset As LONG, _
  maxbytes As LONG)

SUB PutObjItem$ (obj_value$ As STRING, obj_offset As LONG, maxbytes As LONG)
DIM n As LONG
n = obj_value$.length
IF n >= maxbytes THEN n = maxbytes ELSE INC(n) 'to include null terminator
memcpy hbObj+obj_offset, @obj_value$, n
END SUB


+ Penthouse (registered) version

Copyright 2004-2005 James J Keene PhD
Original Publication: July 22, 2004
