Purpose |
Define a Function block. | |
Syntax |
[CALLBACK | THREAD] FUNCTION FuncName
[BDECL | CDECL | SDECL] [ALIAS "AliasName"]
[([arguments])] [EXPORT | PRIVATE] [AS type] {statements} [{FuncName | FUNCTION} = ReturnValue] {statements} [EXIT FUNCTION] {statements} END FUNCTION | |
Remarks |
All executable code must reside in a Sub, Function, Method, or Property block. Functions may not be nested. That is, you cannot define a code block (Sub, Function, Method, Property) inside another code block.
| |
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 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: FUNCTION THREAD MyThreadFunction(BYVAL x AS LONG) AS
LONG The THREAD CREATE statement creates and begins execution of a new thread Function. | |
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 may 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. | |
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. | |
CDECL |
Specifies that the declared Function uses the C calling convention. When a CDECL Function 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 the called procedure is imported or exported, PowerBASIC will automatically create a C style ALIAS for the function 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: FUNCTION C_Function CDECL () EXPORT AS LONG FUNCTION C_Function CDECL ALIAS "_c_function" () EXPORT AS LONG | |
SDECL |
This is the default if neither BDECL nor CDECL are specified. SDECL (and its synonym STDCALL) specifies that the declared Function uses the "Standard Calling Convention" as defined by Microsoft. When calling an SDECL Function, parameters are passed on the stack from right to left. PowerBASIC procedures that use the SDECL/STDCALL convention automatically clean up the stack before execution returns to the calling code. | |
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. | |
|
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 copy (BYCOPY). Either way, the address of the 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 parameters (true in many cases), you can speed up your calls by passing the parameters by value using the BYVAL keyword. You can also 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, ASCIIZ 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%) ... 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) 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. 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 [..]: FUNCTION KerPlunk CDECL (x%, y% [, z%]) AS LONG 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: x& = KerPlunk(x%, y%) x& = KerPlunk(x%, y%, z%) Optional parameters must be the last parameters designated in the list. The following is invalid: FUNCTION KerPlunk CDECL ([x%,] y%, z%) AS LONG Because the FUNCTION (SUB, 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. 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. | |
EXPORT |
Functions are private by default. The EXPORT keyword can be used to make a Function accessible from another module. | |
PRIVATE |
Functions are private by default. The PRIVATE keyword is not required, but may be used for clarity. | |
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 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 ... 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 ... 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&()) ... FUNCTION CheckTheData(lArr() AS LONG) AS LONG ... 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.
| |
|
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 ASCIIZ 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, 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 |