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, @. 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