SUB/END SUB statements

Purpose

Define a Sub block.

Syntax

[STATIC] SUB ProcName [BDECL | CDECL | SDECL] [ALIAS "AliasName"] [([arguments])] [EXPORT | PRIVATE] [STATIC]

  [LOCAL variable_list]

  [STATIC variable_list]

  {statements}

  [EXIT SUB]

  {statements}

END SUB

Remarks

All executable code must reside in a Sub (Function. Method, or Property) block.  You cannot define a procedure inside another procedure.

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 statement is omitted, the parentheses around the arguments list must also be omitted.

STATIC

Specifies the default storage class for variables declared inside the Sub.  If not specified, the default storage class is LOCAL.  The STATIC keyword may appear before the SUB keyword, or after the list of arguments.

ProcName

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

BDECL

Specifies that the declared procedure uses the BASIC/Pascal calling convention.  When a procedure calls a BDECL procedure, it passes its parameters on the stack from left to right.

It is the responsibility of the called procedure to clean up the stack before returning to the calling procedure.  Therefore, all PowerBASIC procedures that specify the BDECL convention automatically clean up the stack before execution returns to the calling code.

In the event the called procedure is imported or exported, PowerBASIC will automatically capitalize the  procedure name unless an explicit ALIAS clause is specified.  See ALIAS below.

CDECL

Specifies that the declared procedure uses the C calling convention.  When a CDECL procedure is called, it passes its parameters on the stack from right to left.

The calling procedure removes any passed parameters from the stack as part of the return process.  When PowerBASIC code calls procedures using the CDECL convention, the stack is cleaned automatically after execution returns from the called code.

In the event that the called procedure is imported or exported, PowerBASIC will automatically create a C style ALIAS for the procedure name.  This alias will be prefixed with an underscore, followed by the original function name converted to lowercase.

The following two declarations are equivalent, indicating how the default ALIAS name would be created by PowerBASIC:

DECLARE SUB C_Function CDECL ()

DECLARE SUB C_Function CDECL ALIAS "_c_function" ()

SDECL

This is the default if neither BDECL nor CDECL are specified.  SDECL (and its synonym STDCALL) specifies that the declared procedure uses the "Standard Calling Convention" as defined by Microsoft.  When calling an SDECL procedure, parameters are passed on the stack right to left.

PowerBASIC procedures that use the SDECL/STDCALL convention automatically clean up the stack before execution returns to the calling code.

In the event the called procedure is imported or exported, PowerBASIC will automatically capitalize the  procedure name unless an explicit ALIAS clause is specified.

ALIAS

String literal that identifies an case-sensitive alternative name for the procedure.  This lets you export a  procedure by a name other than what it is called and referenced within the source code.

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 routine's 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, FUNCTION, METHOD, and PROPERTY sections for more information.

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.

 

Parameters

arguments

An optional, comma-delimited sequence of formal parameters.  The parameters used in the arguments list serve only to define the procedure; they have no relationship to other variables in the program (outside of the procedure) with the same name.  Including a type class keyword (STATIC, or LOCAL) at the end of the SUB header can specify the default variable type within the SUB body.  If no keyword is included, the default is LOCAL.

Normally, PowerBASIC passes parameters to a procedure either by reference or by copy.  Either way, the address of a variable is passed, and the procedure has to look at that address to get the value of the parameter.  If you do not need to modify the parameter (true in many cases), you can speed up your procedures by passing the parameter by value using the BYVAL keyword with your parameter name.

The type of the parameter is specified either by including a type-declaration character at the end of the name or 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

PowerBASIC compilers have a limit of 32 parameters per SUB.  To pass more than 32 parameters to a SUB, construct a User-Defined Type (UDT) and pass the address of the UDT by reference (BYREF) instead.

Fixed-length strings, ASCIIZ strings, and User-Defined Types/Unions may also be passed as BYVAL or OPTIONAL parameters, now.  Try to avoid passing large items BYVAL, as it's terribly inefficient, and there is a maximum size limit of 64 Kb for a given parameter list.

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.  While the use of the explicit BYVAL override in the calling code is optional, it is recommended for clarity.  It is necessary to explicitly declare all pointer parameters as BYVAL.  Failure to do so will generate a compile-time Error 549 ("BYVAL required with pointers").  For example:

SUB DoPtrMath(BYVAL x AS BYTE PTR)

A Sub may be imported and exported within the same module.  That is, a  procedure in the module may be stated as EXPORT, while a DECLARE in the same module specifies it as an imported SUB by the option LIB "filename.dll", provided FILENAME.DLL is the name of the module.  This may be particularly valuable when you wish to build an #INCLUDE file with all of the DECLARE statements for a project.

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

 

Optional parameters

 

PowerBASIC now supports two syntax formats for optional parameters: the classic optional parameter syntax using brackets "[..]", and the new syntax using the OPTIONAL (or OPT) keyword.  We'll discuss each one in turn.

 

Using OPTIONAL/OPT

 

SUB statements may specify one or more parameters as optional by preceding the parameter with either the keyword OPTIONAL (or the abbreviation 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_Ee_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 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 Var_name in your code will result in General Protection Fault or memory corruption.  You should use the ISMISSING() function first to determine whether it is safe to access the parameter.

The OPTIONAL directive provides the same functionality as the older syntax using square brackets "[..]".  See below.

 

Using classic optional parameters

 

When declaring a CDECL procedure, you can specify trailing parameters as optional, using a set of brackets [..]:

SUB KerPlunk CDECL (x%, y% [, z%])

Note that the comma separating the y% parameter from the optional z% parameter is inside the brackets.  The following calling sequences would then be valid:

CALL KerPlunk (x%, y%)

CALL KerPlunk (x%, y%, z%)

Optional parameters must be the last parameters designated in the list.  The following is invalid:

SUB KerPlunk CDECL ([x%,] y%, z%)

Because the SUB (or procedure) 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.

PowerBASIC continues to  support the use of classic optional parameter syntax using brackets ([..]) but this will not be the case in future versions of PowerBASIC.  Existing code should be changed to the new OPTIONAL syntax as soon as possible to ensure compatibility with future versions of PowerBASIC.

 

Local variables

 

By default, all undeclared variables in a procedure are LOCAL (unless you have specified STATIC in the procedure header).  You can also use the LOCAL statement before any executable statements in the Sub definition, to explicitly declare local variables.  Since this default behavior is subject to change, you should make an effort to declare every variable used in a procedure.  For example:

SUB MySub()

  LOCAL a%, b#, BigArray%()

  ...

END SUB

creates three local variables: scalar variables a% and b# (Integer and Double-precision, respectively), and the Integer array BigArray%.  The array must then be dimensioned appropriately:

DIM BigArray%(1000)

Local variables and arrays variables are automatically deallocated when the procedure terminates.  LOCAL scalar variables (except dynamic strings) are stored on the stack, so creating large ASCIIZ or fixed-length strings (say, approaching 1 MB) may cause a stack overflow.  In this case, use STATIC or GLOBAL ASCIIZ or fixed-length strings, or change the code to use LOCAL dynamic (variable-length) strings.

 

Static variables

 

Static variables retain their values as long as the program is running.  To declare static variables within a procedure, use the STATIC statement before any executable statements in the definition.

Use the GLOBAL statement to declare variables that are global to the rest of the program.

You must terminate a procedure definition with END SUB, which returns control to the statement directly after the invoking CALL.  Use the EXIT SUB statement to return from a procedure definition before reaching the END SUB statement.

 

Exported procedures

 

The EXPORT attribute makes a procedure available from another program (DLL or EXE).  This is similar to the "PUBLIC" keyword used by some other programming languages.

EXPORT

Subs are private by default.  The EXPORT keyword can be used to make a Sub accessible from another module (a DLL).  Exported procedures can only be imported from a DLL, not from another .EXE.

PRIVATE

Subs are private by default.  The PRIVATE keyword is not necessary, but may be used to help make your code more readable.

See also

CALL, DECLARE, EXIT SUB, 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())