DECLARE statement

Purpose

Explicitly declare a Sub or Function

Syntax

DECLARE {SUB | FUNCTION} ProcName [BDECL | CDECL | SDECL] [LIB

    "LibName"] [ALIAS "AliasName"] [([arguments])] [AS type]
DECLARE THREAD FUNCTION ProcName (BYVAL var AS (LONG | DWORD}) AS {LONG | DWORD}

Remarks

The DECLARE statement has the following parts:

THREAD

When declaring a Thread function, the THREAD keyword follows the DECLARE keyword. It must take exactly one Long-integer or Double-word (DWORD) parameter by value (BYVAL), and must return a Long-integer or Double-word value.

ProcName

The name of the Sub or Function to be declared.  For Functions, 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.

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.  PowerBASIC does so automatically.

CDECL

Specifies that the declared procedure uses the C calling convention.  When a procedure calls a CDECL procedure, it passes its parameters on the stack from right to left. The calling procedure must remove any passed parameters from the stack as part of the return process. PowerBASIC does so automatically.

In the event the called procedure is imported or exported, PowerBASIC will automatically create a lowercase ALIAS, prefixed with an underscore. Any periods in the name are replaced with underscores at the same time.

CDECL may be used for declaring external procedures written in the C language, or another language that follows C calling and parameter-passing conventions.  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 from right to left.  It is the responsibility of the called procedure to clean up the stack before returning to the calling procedure. PowerBASIC does so automatically.

LIB

A string literal or equate that specifies the name of the module in which a procedure or function is located.  This allows you to call Subs or Functions that reside in DLLs.  Unlike 16-bit Windows, you must include the .DLL extension in the name of the DLL you wish to access.

DECLARE SUB MySub LIB "ZUSER32.DLL" ()

ALIAS

A string literal that identifies the name and capitalization of the procedure in the external DLL.  This lets you call a procedure or function by a name other than what it was originally named.  This is useful if you want to abbreviate a long name, or if the original name of a function contains characters that are illegal in PowerBASIC.  The AliasName is the routine's actual name, and Name is the title that you can use in PowerBASIC.  For example:

DECLARE SUB ShortName ALIAS "VeryLongProcName"()

DECLARE FUNCTION LegalName ALIAS "Illegal$Name"()

Although a Name must be unique, you may use the same AliasName in multiple declarations.  This is particularly useful for avoiding AS ANY in cases where a procedure is designed to receive several different types of parameters.

DECLARE FUNCTION AddAtom    LIB "KERNEL32.DLL" ALIAS "AddAtomA"

    (lpString AS ASCIIZ) AS WORD

DECLARE FUNCTION AddIntAtom LIB "KERNEL32.DLL" ALIAS "AddAtomA"

    (BYVAL lpString AS DWORD) AS WORD

The ALIAS clause is very important when importing or exporting Subs and Functions from DLLs.  Omitting the ALIAS clause or incorrectly capitalizing the alias name are common causes of DLL load failure problems.  Please refer to the SUB and FUNCTION sections for more information.

 

Passing parameters

Arguments

Contains the name(s) and/or the type of each parameter, in the order they are passed, for up to 32 parameters. If you wish to call a SUB or FUNCTION in a DLL, you must describe the target SUB or FUNCTION with an explicit DECLARE statement. The DECLARE must physically precede any reference to the target procedure.

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.

The complete arguments list must be specified for each routine.  Each parameter may be defined in one of three ways:

  • List only its type name (INTEGER, DOUBLE, etc.)

  • List a variable name with a type-specifier appended (count%, txt$)

  • Use the AS clause to specify the type (count AS INTEGER, text AS STRING * 100, etc.).

Legal type names for arguments include ANY, ASCIIZ, BYTE, CUR, CUX, DOUBLE, DWORD, EXT, INTEGER, LONG, PTR, QUAD, SINGLE, STRING, WORD and ARRAY.  The ARRAY keyword is used in conjunction with one of the other types to specify an entire array of that type.  For example:

DECLARE SUB KerPlunk(INTEGER ARRAY, DOUBLE)

declares a procedure called KerPlunk, which takes an entire Integer array and a Double-precision variable as parameters.  You can also name the parameters using the AS keyword:

DECLARE SUB KerPlunk(iArray() AS INTEGER, dVar AS DOUBLE)

The following four declare statements are equivalent:

DECLARE SUB KerPlunk(x) ' if DEFINT A-Z is in effect

DECLARE SUB KerPlunk(x%)

DECLARE SUB Plunk(x AS INTEGER)

DECLARE SUB KerPlunk(INTEGER)

When parameters are passed by reference (BYREF), the full 32-bit address of the variable passed to the routine is placed on the stack.

Using ANY disables type checking for a given parameter, and passes the full 32-bit address of the variable passed on the stack.  Since the internal format of variables differ greatly by type, you must use caution to be absolutely certain your code knows the data type in each invocation.  Normally, a second parameter is used to specify the actual type of the ANY parameter.

You can use the BYVAL or BYREF keywords to specify that a given parameter should always be passed by value or by reference, respectively.

When a Sub/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.  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 (BYVAL x AS BYTE PTR).  Failure to do so will generate compile-time Error 549 ("BYVAL required with pointers").

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

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

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

DECLARE SUB 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 VARIANT) are omitted in 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.

Using classic optional parameters

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

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

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

Because the SUB or FUNCTION 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.

AS type

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 ProcName

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

For example, the following statements are equivalent:

DECLARE FUNCTION aFunction?()

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

Restrictions

A Sub/Function may be imported and exported within the same module.  That is, a function in the module may be stated as EXPORT, while a DECLARE in the same module specifies it as an imported function by the option LIB "filename.dll", as long as 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.

See also

CALL, CALL DWORD, FUNCTION/END FUNCTION, ISMISSING, SUB/END SUB

Example

' Main program

DECLARE SUB Calculate LIB "A.DLL" (EXT, CUR, QUAD, INTEGER)

CALL Calculate(w##, x@, y&&, z%)