CUSTOM OBJECTS +
Updated: Nov 4, 2008

What if existing native HotBasic Objects are not what you need?  You can write
your own Custom Objects and/or optionally "extend" a HotBasic Object to add
more features or change its existing features.

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 HotBasic qualified types, you just

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

When you write an OBJECT/TYPE structure, you have created a new qualified type,
as with ordinary UDT usage.  Then you use DIM/CREATE to create one or more
instances of your Custom Object.

Custom Objects may be packaged in .inc files for use in one or more HotBasic
applications (.eg., RichEd2.inc, ShellEx.inc, Splitter.inc in HotInclude).

Example:  An OBJECT/TYPE structure,

  OBJECT xyz
  'code for XYZ members
  END xyz

or

  TYPE xyz
  'code for XYZ members
  END xyz

both define case-insensitive XYZ as a new qualified type which can be
dimensioned in source code (please see "Dimension").

Our OBJECT above is named "xyz" and source code creates an instance with

  DIM MyObj as xyz  'please see hotudt.bas in HotTrial

A simple SUB or FUNCTION naming convention implements PROPERTY SET and PROPERTY
GET as described in "Variables and Types".


==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
  '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 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.

One might view an "As FUNCTION" member as a "read-only property."


==OBJECT MEMBER PROCEDURE KEYWORDS

HotBasic recognizes Custom Object procedures named as follows:

Invoke as SUB
Method as SUB  'alias for Invoke
GetNum as FUNCTION  'declared as numeric type (LONG, DOUBLE, etc)
GetStr as FUNCTION  'declared as STRING
GetVariant as FUNCTION  'declared as LONG to return variant pointer

and parses a variable number of arguments -- string, numeric or void -- into a
variant array.  Upon entry into a your Custom Object's Invoke, Method, GetNum,
GetStr or GetVariant procedure, internal variable hbArgs points to the variant
array where variant 0 is the number of arguments n and variants 1 to n, in
reverse order, are string or numeric values parsed from the calling source code
(n = 0 to 31).

The code of a GetVariant function should return a pointer,

RESULT = @v  'where v is your result variant

which HotBasic uses to assign the variant result with syntax:

MyVariant = MyObject.GetVariant(...optional arguments...)

"void" used as a parameter is posted as a variant with type 10 and value
&H80020004. 

Thus, DECLARE of Invoke, Method, GetNum, GetStr or GetVariant have no argument
list.  The number and type of parameters may vary among statements calling
these procedures.  The Custom Object procedure code interprets the argument
values presented.  For example,

  nArgs = byref(hbArgs + 8)     'get number of arguments
  lpVariantArray = hbArgs + 16  'pointer to variant 1 in array (last argument)
  lpVariant = hbArgs + 16*nArgs 'pointer to first argument

If you need a variable number of arguments and have the compiler detect string
or number, and pass the arguments to your procedure code via a pointer to a
variant array (hbArgs), this is your baby.

COinvoke.bas shows a coding example (in the HotThing download/directory).


==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.

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(XYZ) = 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

For string items, we have

  s$ = BYREF$(@XYZ.item7)

and

  BYREF$(@XYZ.item7,64) = s$

SELF syntax further simplifies the above statements, which become

  x = self.item4  ' = BYREF(hbObj + 40)
  self.item4 = x

  s$ = self.item7
  self.item7 = s$

The SELF keyword is similar to THIS, except that SELF is automatically
defined on entry to, and only defined in, a Custom Object SUB/FUNCTION.


hbHnd
~~~~~
If a Custom Object EXTENDS a FORM object, "hbHnd" is the extended 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(hbHnd).Member syntax.

hbHnd contains the address of an extended non-FORM object. 


hbArgs
~~~~~~
Pointer to variant array defined on entry to procedures named Invoke, Method,
GetNum, GetStr or GetVariant, as described above.


==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 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 (see newobj.inc), SUB and FUNCTION procedures in an OBJECT
may be used in the main application code also and also by other Objects.
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

  DECLARE SUB XYZMethod2  'Method2 is localized to object XYZ

where SUB name prefix is the Object name in upper-case.

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

so that "Method2" may be associated with different procedures.  Commonly used
method names, such as "Init", "Open", can be used in different Custom Objects
in the same application, but be associated with different SUBs. 


==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" or "replaces" 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

DIM an Object in your Custom Object .inc file and use it in the Custom Object
code.

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 compile 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 D6fbC 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.  During development, duplicate variable names are not an
issue -- HotBasic will not allow them; you get compile error; and thus are
easily changed, if found at compile-time.


==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 reference
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.


+ Penthouse (registered) version

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