PROPERTY / END PROPERTY statement

Purpose | Define a PROPERTY procedure within a class.
Syntax | [OVERRIDE] PROPERTY GET|SET name [<DispID>] [ALIAS "altname"] (var AS type...) [THREADSAFE] [AS type] [statements] PROPERTY = expression END PROPERTY

Remarks

PROPERTY/END PROPERTY is used to define a PROPERTY procedure within a class. Properties can only be called through a virtual function table on an active object. A PROPERTY is a special type of METHOD, which is only used to set or retrieve data in an object. While the work of a PROPERTY could readily be accomplished with a standard METHOD, this distinction is convenient to emphasize the concept of encapsulation of instance data within an object. There are two forms of PROPERTY procedures: PROPERTY GET and PROPERTY SET. As implied by the names, the first form is used to retrieve a data value from the object, while the second form is used to assign a value. Properties must be defined within a CLASS Block, and may only be declared within a DECLARE CLASS Block. Properties are defined:

PROPERTY GET name [ALIAS "altname"] (BYVAL var AS type...) [THREADSAFE] AS type

[statements]

PROPERTY = expression

END PROPERTY

PROPERTY SET name [ALIAS "altname"] (BYVAL var AS type...) [THREADSAFE]

[statements]

variable = value

END PROPERTY

When you use PROPERTY SET, the value to be assigned is passed to the right of an equal sign, just like a normal assignment to a variable:

Properties can only be called through a virtual function table on an active object. Property parameters may be of any variable type.

ObjVar.Prop1 = NewValue

A PROPERTY may be considered "Read-Only" or "Write-Only" by simply omitting one of the two definitions. However, if both GET and SET forms are defined for a particular property, all parameters and the property data type must be identical in both forms, and they must be paired. That is, the PROPERTY SET must immediately follow the PROPERTY GET.

Property parameters may be of any variable type.

You can access a PROPERTY GET with:

DIM ObjVar AS MyInterface

LET ObjVar = NEWCOM Prgid$

1. ObjVar.Prop1(param) TO var

2. CALL ObjVar.Prop1(param) TO var

3. var = ObjVar.Prop1(param)

You can access a PROPERTY SET with:

DIM ObjVar AS MyInterface

LET ObjVar = NEWCOM Prgid$

1. ObjVar.Prop1(param) = expr

2. CALL ObjVar.Prop1(param) = expr

Note that the choice of Property procedure is syntax directed. In other words, depending upon the way you use the name, PowerBASIC will automatically decide whether the GET or SET PROPERTY should be called.

In every Method and Property, PowerBASIC automatically defines a pseudo-variable named ME, which is treated as a reference to the current object. Using ME, it's possible to call any other Method or Property which is a member of the class: var = ME.Method1(param)

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.

Type Libraries only support the following data types: BYTE, WORD, DWORD, INTEGER, LONG, QUAD, SINGLE, DOUBLE, CURRENCY, OBJECT, STRING, and VARIANT. If any Methods or Properties use data types not supported by Type Libraries, you will receive a Error 581 - Type Library creation error, when using the #COM TLIB ON metastatement.

In addition to the explicit return value which you declare, all COM Methods and Properties 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 in the returned 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.

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

Override Properties

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.

BYREF and BYVAL parameters

BYVAL | A copy of the data value is placed on the stack as a parameter. The copy is destroyed when the PROPERTY ends. BYVAL parameters default to an IN attribute, if no explicit direction is specified.
BYREF | A pointer to the data is placed on the stack as a parameter. This option may not be used with an internal PROPERTY parameter.

Direction attributes

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

IN | Data is passed from the caller to the PROPERTY. 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 PROPERTY back to the caller. All OUT parameters must be passed BYREF.
INOUT | Data is passed from the caller to the PROPERTY, 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

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

THREADSAFE Option Descriptor

If you include the option THREADSAFE, PowerBASIC automatically establishes a semaphore which allows only one thread to execute it at a time. Others must wait until the first thread exits the THREADSAFE procedure before they are allowed to begin.

See also | #COM, CLASS, INSTANCE, INTERFACE (Direct), INTERFACE (IDBind), ISINTERFACE, ISNOTHING, ISMISSING, ISOBJECT, Just what is COM?, LET (with Objects), ME, METHOD, OBJACTIVE, OBJPTR, OBJRESULT, What is an object, anyway?

Example

CLASS cMyClass

INSTANCE Value AS LONG

INTERFACE iMyClass

INHERIT IDISPATCH

PROPERTY GET Value <1> AS LONG

PROPERTY = Value

END PROPERTY

PROPERTY SET Value <1> (BYVAL NewValue AS LONG)

Value = NewValue

END PROPERTY

END INTERFACE

END CLASS