METHOD/END METHOD statements  

Purpose

Define a METHOD procedure within a class.

Syntax

[CLASS|OVERRIDE] METHOD name [<DispID>] [ALIAS "altname"] (var AS type...) [AS type]
  statements...
  METHOD = expression
END METHOD

Remarks

METHOD/END METHOD is used to define a METHOD procedure within a class.  Standard methods can only be called through a virtual function table on a valid object.

[CLASS] METHOD name [ALIAS "altname"] (var AS type...) [AS type]
  statements...
  METHOD = expression
END METHOD

A METHOD is a block of code, very similar to a user-defined function. Optionally, it can return a value, like a FUNCTION, or merely act as a subroutine, like a SUB.  If the optional "AS type" is included, the method returns a value set by "Method=expr", or defaults to a return value of zero (0) or nul string, depending upon the type.  METHOD parameters may be any variable type, including VARIANT variables. Methods may be called using any of the five following forms:

DIM ObjVar AS MyInterface
LET ObjVar  = NEW MyInterface ON MyClass
1. ObjVar.Method1(param)
2. CALL ObjVar.Method1(param)
3. ObjVar.Method1(param) TO var
4. CALL ObjVar.Method1(param) TO var
5. var = ObjVar.Method1(param)

Forms 1 and 2 assume that the Method does not return a value, or you simply wish to discard it.  Forms 3, 4, and 5 require that the Method return a value compatible with the type of variable specified as var. Parentheses enclosing parameters are optional in forms 1 and 3.

Methods may be declared (using AS type...) to return a string, any of the numeric types, a specific class of object variable (AS MyClass), a Variant, or a user defined Type.

In addition to the explicit return value which you declare, all Methods and Properties on an IAutomation or IDispatch interface have another "Hidden Return Value", which is cryptically named hResult.  While the name would imply a handle for a result, it's really not a handle at all, but just a long integer value, used to indicate success or failure of the Method.  After calling a Method or Property, you can retrieve the hResult value with the PowerBASIC function OBJRESULT.  The most significant bit of the value is known as the severity bit.  That bit is 0 (value is positive) for success, or 1 (value is negative) for failure. The remaining bits are used to convey error codes and additional status information.  If you call any object Method/Property (either Dispatch or Direct), and the severity bit of hResult is set, PowerBASIC generates Run-Time error 99: Object error.  When you create a Method or Property, PowerBASIC automatically returns an hResult of zero, which implies success.  You can return a non-zero hResult value by executing a METHOD OBJRESULT = expr within a Method, or PROPERTY OBJRESULT = expr within a Property.

Class Methods

A CLASS METHOD is one which is private to the class in which it is located.  That is, it may only be called from a METHOD or PROPERTY in the same class.  The CLASS METHOD must be located within a CLASS block, but outside of any INTERFACE blocks.  This shows it is a direct member of the class, rather than a member of an interface.

CLASS MyClass
  INSTANCE MyVar AS LONG

  CLASS METHOD MyClassMethod(BYVAL param AS LONG) AS STRING
    METHOD = "My" + STR$(param + MyVar)
  END METHOD

  INTERFACE MyInterface
    INHERIT IUNKNOWN
    METHOD MyMethod()
      Result$ = ME.MyClassMethod(66)
    END METHOD
  END INTERFACE
END CLASS

In the above example, MyClassMethod() is a CLASS METHOD, and is always accessed using the pseudo-object ME (in this case ME.MyClassMethod). Class methods are never accessible from outside a class, nor are they ever described or published in a type library.  By definition, there is no reason to have a private PROPERTY, so PowerBASIC does not offer a CLASS PROPERTY structure.

Constructors and Destructors

There are two special class methods which you may optionally add to a class.  They meet a very specific need: automatic initialization when an object is created, and cleanup when an object is destroyed.  Technically, they are known as constructor and destructor methods, and can perform almost any functionality needed by your object: initialization of variables, reading/writing data to/from disk, etc.  You do not call these methods directly from your code.  If they are present in your class, PowerBASIC automatically calls them each time an object of that class is created or destroyed.  If you choose to use them, these special class methods must be named CREATE and DESTROY.  They may take no parameters, and may not return a result.  They are defined at the class level, so they may never appear within an INTERFACE definition.                         

CLASS MyClass
  INSTANCE MyVar AS LONG

  CLASS METHOD CREATE()
    ' Do initialization
  END METHOD

  CLASS METHOD Destroy()
    ' Do cleanup
  END METHOD

  INTERFACE MyInterface
    INHERIT IUNKNOWN
    METHOD MyMethod()
      ' Do things
    END METHOD
  END INTERFACE
END CLASS

As displayed above, CREATE and DESTROY must be placed at the class level, but outside of any interface block.  You should note that it's not possible to name any standard method (one that's accessible through an interface) as CREATE or DESTROY.  That's just to help you remember the rules for a constructor or destructor.  However, you may use these names as needed to describe a method external to your program.

A very important caution:  You must never create an object of the current class in a CREATE method.  To do so will cause CREATE to be executed again and again until all available memory is consumed. This is a fatal error, from which recovery is impossible.

Override Methods

You can add to, or replace, the functionality of a particular method or property of an inherited base class by coding a replacement which is preceded by the word OVERRIDE. The overriding method must have the same name and signature (parameters, return value, etc.) as the one it replaces.

Dispatch ID

Every method and property in a dual interface needs a positive, long integer value to identify it.  That integer value is known as a DispID (Dispatch ID), and it's used internally by COM services to call the correct function on a Dispatch interface.  You can optionally specify a particular DispID by enclosing it in angle brackets immediately following the Method/Property name:

METHOD  MethodOne <76> ()

If you don't specify a DispID, PowerBASIC will assign a random value for you.  This is fine for internal objects, but may cause a failure for published COM objects, as the DispID could change each time you compile your program.  It is particularly important that you specify a DispID for each Method/Property in a COM Event Interface.

BYREF and BYVAL attributes

Just like a SUB or FUNCTION, PowerBASIC uses BYREF parameters as the default form, unless you specify a BYVAL override.  Either key word can be placed before the parameter name, along with IN, OUT, and INOUT, as described later.

BYVAL

A copy of the data value is placed on the stack as a parameter. The copy is destroyed when the METHOD ends.  BYVAL parameters default to an IN parameter, if no explicit direction is specified.

BYREF

A pointer to the data is placed on the stack as a parameter. If the data is a variable, any changes to the parameter are passed back to the caller in the variable.  If the data is an expression, it is destroyed when the METHOD ends.  BYREF parameters default to an INOUT parameter, if no explicit direction is specified.

Direction attributes

METHOD parameters may also specify the direction in which data is passed between the caller and callee:

IN

Data is passed from the caller to the METHOD. Generally speaking, you'll find that almost all IN parameters are passed BYVAL, and that is highly recommended. However, it is possible to pass them BYREF if necessary.

OUT

Data is passed from the METHOD back to the caller. All OUT parameters must be passed BYREF.

INOUT

Data is passed from the caller to the METHOD, and results are returned to the caller in the same parameter. All INOUT parameters must be passed BYREF.

In many cases, the direction of a parameter can be inferred directly from the BYVAL/BYREF attribute (BYVAL=IN, BYREF=OUT).  However, we recommend that you include the direction attribute as an added means of self-documentation.  Each METHOD parameter name may be preceded by one of BYVAL/BYREF, and one of IN/OUT/INOUT, in any sequence.

You should note an interesting rule of COM objects: IN parameters are read-only.  They may not be altered.

IN parameters are considered by COM rules to be "constant" which may not be altered, because they are values which are not returned to the caller.  However, since this is not a rule normally applied to a standard SUB or FUNCTION, it can allow programming bugs which are most difficult to find and correct.  For this reason, PowerBASIC automatically protects you from this issue with no action needed on your part.  When writing METHOD or PROPERTY code in PowerBASIC, you may freely assign new values to BYVAL/IN parameters.  They will simply be discarded when the METHOD exits.  Of course, not every programming language protects you in this way, so you must use caution if you create a COM METHOD in another compiler.

Using OPTIONAL/OPT

METHOD statements may specify one or more parameters as optional by preceding the parameter with either the keyword OPTIONAL (or the abbreviation OPT).  When a parameter is declared optional, all subsequent parameters in the declaration are optional as well, whether or not they specify an explicit OPTIONAL or OPT directive.

VARIANT variables are particularly well suited for use as an optional parameter.  If the calling code omits an optional VARIANT parameter, (BYVAL or BYREF), PowerBASIC (and most other compilers) substitute a variant of type %VT_ERROR which contains an error value of %DISP_E_PARAMNOTFOUND (&H80020004).  In this case, you can check for this value directly, or use the ISMISSING function to determine whether the parameter was physically passed or not.

When optional parameters (other than VARIANT) are omitted from the calling code, the stack area normally reserved for those parameters is zero-filled.

If the parameter is defined as a BYVAL parameter, it will have the value zero.  For TYPE or UNION variables passed BYVAL, the compiler will pass a string of binary zeroes of length SIZEOF(Type_or_union_var).

If the parameter is defined as a BYREF parameter, VARPTR(Varname) will equal zero; when this is true, any attempt to use Varname in your code will result in a General Protection Fault or memory corruption.  You should use the ISMISSING() function first to determine whether it is safe to access the parameter.

See also

CLASS, INSTANCE, INTERFACE (Direct), ISMISSING, Just what is COM?, ME, PROPERTY, What is an object, anyway?