FUNCTION/END FUNCTION statements  

Purpose

Define a Function block.

Syntax

FUNCTION ProcName [ALIAS "AliasName"] [(arguments)] <Descriptors> AS Type
  [statements]

  [{FuncName | FUNCTION} = ReturnValue]

END FUNCTION

CALLBACK FUNCTION ProcName [AS LONG]...

THREAD FUNCTION ProcName (BYVAL var AS LONG) AS LONG...

Remarks

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

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.

FuncName

The name of the Function.  A type-specifier may be appended (just like an ordinary variable name) to specify the data type of the Function's return value, in place of the [AS type] clause. FuncName must be unique: no other variable, Function, Sub, Method, Property, or label can share it.  Also see ALIAS below.

Future versions of PowerBASIC will not support type-specifier symbols for the Function return type, so specify the return data type with an explicit AS type clause in all DECLARE and FUNCTION definitions, to ensure future compatibility.

ALIAS

String literal that identifies an case-sensitive alternative name for the function. This lets you export a Function 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 routine's actual name as it appears in the export table, and FuncName is the title that you can use in PowerBASIC.  For example:

FUNCTION ShortName ALIAS "LongFuncName"() EXPORT STATIC AS LONG

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

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

STATIC

This descriptor specifies that all undeclared variables in a function are STATIC.  Static variables retain their values as long as the program is running.  They are visible only within the function.

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.

CALLBACK

Specifies that this is a callback function, which is used only to receive messages from the operating system.  It may never be called directly from your code. Details about the message sent to the callback are retrieved using the CB group of PowerBASIC functions. Callback functions may not include parameters, and always return a long integer result.  For example:

CALLBACK FUNCTION DlgProc AS LONG
  ' Callback code goes here
END FUNCTION

Callback functions have the unique ability to optionally return two distinct values when necessary for certain Windows messages.  This allows them to return the value zero (0) as a function result, while still specifying that the message has been processed.  See the section CALLBACK RETURN VALUE (below) and the CALLBACKS page for more details.

THREAD

Specifies that this is a thread function, which is the point where execution of a new thread begins.  It may never be called directly from your code.  Thread functions must take exactly one long-integer or double-word parameter by value (BYVAL), and must return either a long-integer or double-word result.  For example:

THREAD FUNCTION MyThreadFunction(BYVAL x AS LONG) AS LONG
  ' Thread code goes here
END FUNCTION

The THREAD CREATE statement creates and begins execution of a new thread Function.

 

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 Function 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:

FUNCTION Test&(A AS INTEGER) 'integer passed by ref

FUNCTION Test&(A%)           'integer passed by ref

FUNCTION Test&(BYREF A%)     'integer passed by ref

FUNCTION Test&(BYVAL A%)     'integer passed by val

 

Parameter restrictions

 

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

Fixed-length strings, Nul-Terminated Strings, and User-Defined Types/Unions may also be passed as BYVAL or OPTIONAL parameters. 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.

PowerBASIC Functions cannot return an array or Variant variable as a Function return value.  Pass these variable types as BYREF parameters instead.  For example:

lResult& = ProcessData(TheArray&(), iSize%)

[statements]

FUNCTION ProcessData(lArr() AS LONG, iSize%) AS LONG

  REDIM lArr(iSize%) AS LONG

  lArr(iSize%) = 1&

  FUNCTION = -1&

END FUNCTION

 

Pointer parameters

 

When a Function 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)
FUNCTION Test(BYVAL A AS INTEGER PTR) AS LONG
  @A = 56
END FUNCTION

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

 

Optional parameters

 

PowerBASIC 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

 

FUNCTION 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:

FUNCTION sABC(a&, OPTIONAL BYVAL b&, OPTIONAL BYVAL c&) AS LONG

FUNCTION sABC(a&, OPT BYVAL b&, BYVAL c&) AS LONG

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

AS type

Function blocks are constructed very much like Subs (see SUB/END SUB statement).  However, Functions differ from Subs in that they always return a result, so they can be used in assignments and expressions. Therefore, there are two ways to specify the return type of a Function:

You may specify the type of data returned by a Function to the calling code.  If you do not specify a type, PowerBASIC assumes that the Function returns the data type specified by a DEFtype statement. However, if no DEFtype or AS type has been specified, a compile-time error is generated.

Therefore, there are two ways to specify the return type of a Function:

  • Include a type-specifier character at the end of FuncName

  • Include the AS type clause as the last part of the FUNCTION statement (this is the recommended syntax to ensure future compatibility).

For example, the following statements are equivalent:

FUNCTION aFunction?()

FUNCTION aFunction() AS BYTE

While most FUNCTION calling conventions are fairly well defined throughout the industry, there are a few exceptions.  In the case of functions which return a Quad Integer value, some programming languages (including PowerBASIC) return the quad value in the FPU, while others return it in EDX:EAX.  PowerBASIC automatically detects the method used by imported functions and adjusts accordingly for you, but that's not a feature found in other compilers.  Therefore, we recommend that you do not EXPORT QUAD FUNCTIONS unless they will only be accessed by PowerBASIC programs.  A simple equivalent functionality would be to return the quad-integer value to the caller in a BYREF QUAD parameter.

 

Assigning a return value

 

You can specify the return value of the Function by explicitly setting the value, either by assigning a value to the FUNCTION keyword, or by assigning a value to the function name.  For example, the two lines within the following Function block are equivalent:

FUNCTION AddData() AS LONG

  [statements]

  AddData  = 123& ' Assign value to function name

  FUNCTION = 123& ' Assign value to the function

END FUNCTION

 

Default return value

 

If the code within the Function does not explicitly set a return value, the default return value will be zero if the function returns a numeric data type, or an empty string if the function returns a string.  For example:

FUNCTION AddData() AS LONG

  [statements]

  IF condition THEN

    EXIT FUNCTION  ' No assignment, will return 0&

  ELSE

    FUNCTION = -1& ' An explicit return value

  END IF

END FUNCTION

PowerBASIC Functions cannot return an array as a Function return value.  Pass the array as a parameter instead.  For example:

lResult& = CheckTheData(InTheArray&())

[statements]

FUNCTION CheckTheData(lArr() AS LONG) AS LONG

  [statements]

END FUNCTION

 

CALLBACK Return Value

 

Callback functions always return a long integer result.  The primary purpose of this return value is to tell the PowerBASIC DDT engine and the Windows operating system whether your Callback Function has processed this particular message.  If you return the value TRUE (any non-zero value), you are asserting that the message was processed and no further handling is needed.  If you return the value FALSE (zero), the PowerBASIC DDT engine will manage the message for you, using the default message procedures in Windows.  If you do not specify a return value in the function, PowerBASIC chooses the value FALSE (zero) for you.

The term "process a message" may have many meanings.  If it's a simple notification of a change in focus or style, which has no impact on your program, you may decide to consider it processed, yet do nothing.  In other cases, your reaction could be quite complex and involved.  As the programmer, that's your decision to make.  But, regardless of your reaction, you should consider a message "processed" (returning a true value) whenever no further handling of the message (by DDT or Windows) is needed.

In some cases, especially when dealing with Common Controls and custom controls, you may be required to return a second result value through a special Windows data area named DWL_MSGRESULT.  When you complete a Callback Function, PowerBASIC automatically copies any non-zero return value to DWL_MSGRESULT, if you haven't done so already.  Therefore, it's generally safe to ignore this requirement in your code.

In most cases, when you process a message, you'll return a generic value for TRUE, such as:  FUNCTION = 1.  However, some messages require that you return a special value for TRUE, such as a graphical brush handle.  As long as the value is non-zero, you can return it in the normal manner (with FUNCTION = n), since any non-zero value automatically implies that the message was processed.

That said, there are a few unique messages which may require special handling.  Luckily, they're rare, but some just "break all the rules" listed above.  For example, you might find one which requires a zero result, even when you have processed the message.  You may find another which requires the return value be different from DWL_MSGRESULT.  For these very special cases, you can simply specify two return values:

FUNCTION = 1, BrushHandle&

In this form, the first numeric expression specifies the value to be returned from the Callback Function.  The second numeric expression tells the value to be assigned to DWL_MSGRESULT.  When you use this double parameter assignment, the results are absolute.  PowerBASIC assumes you have processed the message, regardless of the values given. PowerBASIC makes no other assumptions of any kind about these values. A double parameter function assignment is only allowed in a Callback Function.

Previous versions of PowerBASIC did not offer a double parameter form of function return.  This caused some difficulty with a few Windows messages which required a special return value of zero. If you return a value of zero (0) with the single parameter form, it implies the message was not processed at all by the Callback. This issue is totally circumvented by the double parameter form.

 

Variables within functions

 

LOCAL variables are created within the procedures 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 string 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.

See also

DECLARE, EXIT, FASTPROC, FUNCNAME$, GLOBAL, INSTANCE, ISMISSING, LOCAL, METHOD, PROPERTY, STATIC, SUB, THREAD CREATE, THREADID

Example

FUNCTION HalfOf ALIAS "HalfOf" (X!) EXPORT AS SINGLE

  FUNCTION = X! / 2

END FUNCTION