SUB/END SUB statements

Purpose | Define a Sub code section.
Syntax | SUB ProcName [ALIAS "AliasName"] [(arguments)] <Descriptors> [statements] END SUB

Remarks

All executable code must reside in a Sub, Function. Method, Property, or FastProc block. Subs may not be nested. That is, you cannot define a code block (Sub, Function, Method, FastProc, Property) inside another code block.

SUB and END SUB define a subroutine-like block of statements called a procedure (or subprogram), which is invoked with the CALL statement, and may be passed parameters by value or by reference. A Sub may also be invoked without the use of the CALL statement. If the CALL word is omitted, the parentheses around the arguments list must also be omitted.

Previous versions of PowerBASIC required that you create an explicit DECLARE statement if you wished to execute a SUB or FUNCTION which did not physically precede the reference to it. This extra work is no longer required, as PowerBASIC resolves all forward references to internal procedures automatically.

DECLARE statements for a Sub/Function imported from a DLL must still precede any reference to the procedure.

ProcName | The name of the Sub. ProcName must be unique: no other variable, Function, Sub, Method, Property, FastProc or label can share it.

ALIAS

String literal that identifies a case-sensitive alternative name for the sub. This lets you export a Sub by a different unique name. This can be useful if you want to abbreviate a long name, provide a more descriptive name, or if the exported name needs to contain characters that are illegal in PowerBASIC. AliasName is the routines actual name as it appears in the export table, and ProcName is the title that you can use in PowerBASIC. For example:

SUB ShortName ALIAS "LongProcName" () EXPORT STATIC

The ALIAS clause is very important when exporting procedures. Omitting the ALIAS clause or incorrectly capitalizing the alias name are common causes of "Missing Export" errors. Please refer to the DECLARE topic for more information.

| Descriptors
| You may optionally add one or more descriptor words (Export, Common, Private, ThreadSafe, Local, Static, BDecl, CDecl, SDecl) to provide specific functionality. They may be added to the SUB as a comma delimited list. You should note that some of them are mutually exclusive.
EXPORT | This descriptor identifies a Sub or Function which may be accessed between Dynamic Link Libraries (DLLs), and/or the main executable which links them. If a procedure is not marked EXPORT, it is hidden from these other modules. The EXPORT attribute may be added to a Sub/Function defined elsewhere, by specifying EXPORT in a DECLARE statement. EXPORT can even be added to a Sub/Function in an SLL with a DECLARE in the host module.
COMMON | A COMMON Sub/Function is one which may be referenced by and between linked unit modules (Host or SLL). If you DECLARE a Common Sub or Function which is not present in this module, it is presumed to be found in a separate linked module (Host or SLL).
PRIVATE | A PRIVATE Sub/Function is one which may only be accessed from within the current PowerBASIC program or library. Even if not specified, this is the default mode of operation.
THREADSAFE | With the THREADSAFE option, PowerBASIC automatically establishes a semaphore which allows only one thread to execute the Sub/Function at a time. Other callers must wait until the first thread exits the THREADSAFE procedure before they are allowed to begin.
LOCAL | This descriptor specifies that all undeclared variables in a sub are LOCAL. This is the default condition if neither LOCAL nor STATIC is specified. Local variables and arrays variables are automatically deallocated when the procedure terminates. LOCAL scalar variables (except dynamic strings) are stored on the stack, and visible only within the sub.
STATIC | This descriptor specifies that all undeclared variables in a sub are STATIC. Static variables retain their values as long as the program is running. They are visible only within the sub.
BDECL | Specifies that the declared procedure uses the legacy BASIC/Pascal calling convention. Parameters are pushed on the stack from left to right, and the called procedure is responsible for removing them. BDECL should only be used when necessary to match outside modules.
CDECL | Specifies that the declared procedure uses the C calling convention. Parameters are pushed on the stack from right to left, and the calling code is responsible for removing them. CDECL should only be used when necessary to match outside modules.
SDECL | This is the default convention, and should be used whenever possible. SDECL (and its synonym STDCALL), specifies the "Standard Calling Convention" for Windows. Parameters are pushed on the stack from right to left, and the called procedure is responsible for removing them.
| Passing Parameters

Arguments

An optional, comma-delimited sequence of formal parameters. The parameters used in the arguments list serve only to define the Function; they have no relationship to other variables in the calling code with the same name.

Normally, PowerBASIC passes parameters to a Sub either by reference (BYREF) or by value (BYVAL). If you do not need to modify the parameters (true in many cases), you can speed up your calls by passing the parameters by value using the BYVAL keyword. You can clarify that a parameter is passed by reference by using the optional BYREF keyword.

The type of the parameter is specified either by appending a type-specifier character to the name or by using an AS clause. For example:

SUB Test(A AS INTEGER) ' integer passed by reference

SUB Test(A%) ' integer passed by reference

SUB Test(BYREF A%) ' integer passed by reference

SUB Test(BYVAL A%) ' integer passed by value

| Parameter Restrictions
| PowerBASIC compilers have a limit of 32 parameters per Sub. To pass more than 32 parameters to a FUNCTION, construct a User-Defined Type (UDT) and pass the UDT by reference (BYREF) instead.
| Pointer Parameters

When a Sub definition specifies either a BYREF parameter or a pointer variable parameter, the calling code may freely pass a BYVAL DWORD or a Pointer instead. Pointer variable parameters must always be declared as BYVAL parameters.

' Integer Pointer (passed by value)

SUB Test(BYVAL A AS INTEGER PTR)

@A = 56

END SUB

Additional information on BYVAL/BYREF/BYCOPY parameter passing can be found in the CALL statement topic.

| Using OPTIONAL/OPT

SUB statements may specify one or more parameters as optional by preceding the parameter with either the keyword OPTIONAL or OPT. Optional parameters are only allowed with CDECL or SDECL calling conventions, not BDECL.

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. The following two lines are equivalent, with both second and third parameters being optional:

SUB sABC(a&, OPTIONAL BYVAL b&, OPTIONAL BYVAL c&)

SUB sABC(a&, OPT BYVAL b&, BYVAL c&)

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 a VARIANT) are omitted in the calling code, the stack area normally reserved for those parameters is zero-filled. This allows you to test if an optional parameter was passed or not.

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 Error #9 (null pointer); failure to detect this error using error-trapping may 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.

Because the FUNCTION, SUB, FASTPROC, METHOD, or PROPERTY being called does not know how many parameters are being passed at the time it is called, you should pass the number of parameters as one of the required parameters in the list.

| Variables within Subs
| LOCAL variables are created within the procedure stack frame. If a LOCAL variable exceeds the amount of stack space available, it may become necessary to use a STATIC or GLOBAL variable instead. For example, creating a LOCAL nul-terminated or LOCAL fixed-length string that is very large (say, approaching 1 MB) can trigger a General Protection Fault (GPF) because it may overrun the stack frame.
| Procedure definitions and program flow

The position of procedure definitions is mostly immaterial. They are usually grouped together in one region of the source code, but you cannot nest procedure definitions. That is, you cannot define a procedure within another procedure (although a procedure definition can contain calls to other procedures). Unlike subroutines (see GOSUB), program execution cannot accidentally "fall into" a procedure, even if it is located before the PBMAIN or WINMAIN Function in your code. For example:

#COMPILE EXE

SUB DisplayInfo(a$)

' Code goes here

END SUB

...

FUNCTION PBMAIN

' Main program code goes here

END FUNCTION

When this program is executed, the code in DisplayInfo is only executed if the procedure is explicitly called, even though it is located earlier in the source code file. Procedure definitions should be treated like isolated islands of code; do not jump in or out of them with GOTO, GOSUB or RETURN. Within a procedure block, such statements are legal.

See also | CALL, DECLARE, EXIT SUB, FASTPROC, FUNCNAME$, FUNCTION/END FUNCTION, GLOBAL, GOSUB, ISMISSING, LOCAL, RETURN, STATIC

Example

SUB TestProcedure(I%, L&, S!, D#, E##, A())

' Code to process parameters

END SUB ' end procedure TestProcedure

DIM MyArray(20) ' declare array of numbers

IntegerVar% = 1

LongInt& = 2

SinglePre! = 3

DoublePre# = 4

MyArray(3) = 5

CALL TestProcedure(IntegerVar%, LongInt&, SinglePre!, DoublePre#, IntegerVar%^2, MyArray())