DECLARE statement  

Purpose

Explicitly declare a Sub or Function.

Syntax

DECLARE SUB ProcName [ALIAS "AliasName"] [(arguments)] <Descriptors>

DECLARE FUNCTION ProcName [ALIAS "AliasName"] [(arguments)] <Descriptors> AS RetType

DECLARE CALLBACK FUNCTION ProcName [[()] AS LONG]
DECLARE THREAD FUNCTION ProcName (BYVAL var AS (LONG | DWORD}) AS {LONG | DWORD}

Remarks

The DECLARE statement has the following parts:

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 RetType] clause.

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

ALIAS

An alias clause may be used to specify an alternate name to be used for imported or exported procedures.  This allows you to interact with outside modules (DLL or EXE) using a different procedure name.  The alias name must be specified by a string literal which provides the name and capitalization of the procedure in the external DLL.

This option is particularly 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 alias name is the actual name used in the other module, while the ProcName is the word you use in your PowerBASIC program.  For example:

DECLARE SUB ShortName ALIAS "VeryLongProcName"()

DECLARE FUNCTION LegalName ALIAS "Illegal$Name"()

Although a ProcName 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 STRINGZ) 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.

 

Descriptors

 

You may optionally add one or more descriptor words (Import, Export, Common, Private, ThreadSafe, BDecl, CDecl, SDecl) to provide specific functionality.  They may be added to the DECLARE as a comma delimited list.  You should note that some of them are mutually exclusive.

IMPORT

A string literal or equate that specifies the name of the module in which an imported procedure is located.  This allows you to call Subs or Functions that reside in DLLs.  The legacy word LIB may be substituted for IMPORT.

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 called between linked unit modules (Host or SLL).  If the Common Sub/Function is not present in this module, it is presumed to be found in a separate linked module (Host or SLL).  It is not necessary to DECLARE a COMMON Sub or Function in the Host Module.  If you choose to do so, it is generally advisable to omit the COMMON descriptor, as its presence will force the SLL to be linked, whether needed or not.

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.

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.

When a procedure is imported or exported, PowerBASIC automatically creates a lowercase ALIAS, prefixed with an underscore.  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 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

Callback Functions are reserved for use with Dynamic Dialog Tools (DDT) functions.  No parameters should be specified, as data is retrieved with the CALLBACK (CB) functions.  Parentheses and the AS LONG return type may be added for clarity.

THREAD

Thread functions are reserved for use with the THREAD CREATE statement.  It must take exactly one Long Integer parameter by value (BYVAL LONG), and must return a Long Integer value (AS LONG).  It is permissible to substitute DWORD for both of these items.

 

Passing parameters

Arguments

Contains the name(s) 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.

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, STRINGZ, WORD, WSTRING, WSTRINGZ 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 address of the variable passed to the routine is placed on the stack.  When they are passed by value (BYVAL), the actual data is placed on the stack.  You can use the BYVAL or BYREF keywords to specify that a parameter should always be passed in a known format.

Using ANY disables type checking for a particular parameter, and passes the address of the variable on the stack.  Since the internal format of variables differ greatly by type, you must use caution to be 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.

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.

 

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.

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

#EXPORT, #LINK, CALL, CALL DWORD, FASTPROC, FUNCTION/END FUNCTION, IMPORT, ISMISSING, SUB/END SUB, THREAD CREATE

Example

' Main program

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

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