FUNCTION and SUB Procedures

The main difference between a FUNCTION and a SUB procedure is that a FUNCTION returns a value to the FUNCTION call expression.

FUNCTION ... END FUNCTION procedure

Purpose:

FUNCTION ... END FUNCTION encapsulates a series of statements to form a procedure.


Syntax:
 
 FUNCTION TheName([Parameters][AS data type])[,AS data type]
 DIM ReturnValue
     [Statements]
 FUNCTION = ReturnValue
 END FUNCTION

Parameters:

  • TheName This is the name of the function. The value returned by the function can be indicated by using a data type indicator symbol at the end of TheName. For example, if the value returned is a string, the name of the function would be TheName$. If the function returned a double, its name would be TheName#.

    The data type of the value returned by the function also can be indicated by appending an AS data type phrase. For example, if the value returned is a string, the phrase would be AS STRING. If the function returned a double, the phrase would be AS DOUBLE.

  • ([Parameters][AS data type]) is zero or more comma separated variables used to pass values to the function. The data type of each parameter must be indicated. BCX uses this information to construct prototypes for the functions so the data type must be explicit for any variables except integers. For example, if a string is being passed, Parameters$ or Parameters AS CHAR would be used. If a DOUBLE is being passed, Parameters# or Parameters AS DOUBLE would be used.

    By default, string variables are passed by reference, while numeric variables are passed by value. To pass numeric values by reference instead of by value, see the BYREF qualifier.

  • The FUNCTION = ReturnValue statement causes an immediate exit from the function returning to the function caller the value contained in ReturnValue.

    While other BASIC dialects use TheName of the FUNCTION as the variable to return the value created by the function, BCX can use any user defined variable name as long as the variable is the correct data type.

    If an empty string is to be returned explicitly, use either

     
     FUNCTION = ""
    
    

    or

     
     RAW temp$
     temp$ = ""
     FUNCTION = temp$
    
    

    or else use

     
     FUNCTION = NUL$
    
    
  • ReturnValue is the return value of the function. Like all variables ReturnValue must be dimensioned before use. This can be done within the function or externally as a GLOBAL.

Example:

 
 DIM A!
 DIM X!
 DIM Y!

 X! = 2.2
 Y! = 3.1

 A! = Myfunc!(X!, Y!)
 PRINT A!

 FUNCTION Myfunc!(M!, P!)
  FUNCTION = (M! * P!)
 END FUNCTION

Result:


 6.82

Remarks:

Do not modify a literal value or string used as an argument in the FUNCTION calling statement. For example, the attempt to modify "literal" in,

 
 DIM str1$

 str1$ = FOO$("literal")
 PRINT str1$

 FUNCTION FOO$(a$)
  a$ = "k"
  FUNCTION = a$
 END FUNCTION

will cause a crash when the program is run.

OVERLOADED or OPTIONAL FUNCTION procedures are not allowed in a user defined type structure.

BCX has support for functions in single line declaration/assignments, for example,

 
 DIM RAW RetVal = foo(x, y, z) AS INTEGER

BCX allows static SUB/FUNCTION to be coded as:

 
 PRIVATE FUNCTION MyFunction() AS INTEGER
 
 PRIVATE SUB MySub()

User defined string functions that rely on other string functions usually need to use an intermediate string.

Example:

 
 DIM A$

 A$ = Blurb$(65, 66, 67)
 PRINT A$

 FUNCTION Blurb$(A, B, C)
  DIM Z$
  Z$ = CHR$(A) & CHR$(B) & CHR$(C)
  FUNCTION = Z$
 END FUNCTION

Result:


 ABC

FUNCTION declarations emit a TYPE that may be reused. For example, when a FUNCTION has been declared as

 
 DIM FUNCTION Foo$(a$)

any further declarations can be written as:

 DIM MyFunctionPointer AS Foo_TYPE

The following are examples which use the emitted FUNCTION TYPE declaration as a member of a user defined type.

Example 1:

 
 DIM FUNCTION Foo$(a$)
  
 TYPE MyType                                 
   DYNAMIC A$[]
   DYNAMIC FN[] AS Foo_TYPE
   B AS INTEGER
 END TYPE
  
 DIM mt AS MyType
  
 REDIM mt.A$[2]
 REDIM mt.FN[3]
  
 mt.A$[0] = "Hello " 
 mt.A$[1] = "World"
 mt.FN[0] = LCASE$
 mt.FN[1] = MCASE$
 mt.FN[2] = UCASE$
 
 PRINT mt.FN$[0](mt.A$[0]), mt.FN$[0](mt.A$[1])
 PRINT mt.FN$[1](mt.A$[0]), mt.FN$[1](mt.A$[1])
 PRINT mt.FN$[2](mt.A$[0]), mt.FN$[2](mt.A$[1])

Result:


 hello world
 Hello World
 HELLO WORLD

Example 2:

 
 GLOBAL Func(DUMMY AS INTEGER) AS FUNCTION INTEGER
 
 SetFunc(x0)
 ? Func(2)
 
 SetFunc(x1)
 ? Func(2)
 
 
 SUB SetFunc (f AS Func_TYPE)
   Func = f
 END SUB
 
 '-----------------------------------------------

 FUNCTION x0(x AS INTEGER) AS INTEGER
   FUNCTION = x * x
 END FUNCTION
 
 
 FUNCTION x1(x AS INTEGER) AS INTEGER
   FUNCTION = x * x * 2
 END FUNCTION

Result:


 4
 8

Example 3:

 
 GLOBAL PF_1_ARG(DUMMY AS INTEGER) AS FUNCTION INTEGER
 
 SET Funcs3[] AS PF_1_ARG_TYPE
   n0,
   n1,
   n2,
   n3,
   n4
 END SET
 
 FUNCTION n0(n AS INTEGER)
   FUNCTION = n * 0
 END FUNCTION
 
 FUNCTION n1(n AS INTEGER)
   FUNCTION = n * 1
 END FUNCTION
 
 FUNCTION n2(n AS INTEGER)
   FUNCTION = n * 2
 END FUNCTION
 
 FUNCTION n3(n AS INTEGER)
   FUNCTION = n * 3
 END FUNCTION
 
 FUNCTION n4(n AS INTEGER)
   FUNCTION = n * 4
 END FUNCTION
 
 '------------------------------

 DIM i
 
 FOR i = 0 TO 4
   ? Funcs3[i](i)
 NEXT

Result:


 0
 1
 4
 9
 16

CALLBACK FUNCTION...END FUNCTION procedure

OVERLOADED or OPTIONAL FUNCTION procedures are NOT allowed in a User Defined TYPE structure.


Syntax 1:
 
 CALLBACK FUNCTION CBFuncName()
     [Statements]
 END FUNCTION

Parameters:

  • The following C code is emitted by BCX from the above syntax
    
     LRESULT CALLBACK CBFuncName(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
     {
      [Statements];
    
      return DefWindowProc(hWnd, Msg, wParam, lParam);
    
    
  • CALLBACK FUNCTION syntax does not use any function parameters because BCX automatically emits the following :
    
     (HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
    
    
  • When using the automatically emitted function parameters hWnd, Msg, wParam, and lParam, it is important to remember that they must be written as case sensitive.
  • CALLBACK FUNCTION does not require a
    
     FUNCTION = ReturnValue
    
    
    statement because BCX automatically emits the following function return statement:
     
     RETURN DefWindowProc,(hWnd,Msg,wParam,lParam);
    
    

Example 1 : This example of Syntax 1 shows how to set up two forms subclassing the second with a CALLBACK. The trick involves:


 GUI "Two_Forms_Demo"
  
 GLOBAL Form1 AS HWND
 GLOBAL Form2 AS HWND
  
 GLOBAL Form1_Button1 AS HWND
 GLOBAL Form2_Button1 AS HWND
  
 GLOBAL lpForm2_Proc AS WNDPROC
  
 CONST  IDC_FORM1_BUTTON1 = 201
 CONST  IDC_FORM2_BUTTON1 = 202
  
 SUB FORMLOAD
  
   Form1 = BCX_FORM("Form 1", 100, 100, 160, 100)
   Form1_Button1 = BCX_BUTTON("Show", Form1, IDC_FORM1_BUTTON1, 64, 40, 40, 14)
  
   Form2 = BCX_FORM("Form 2", 270, 110, 200, 140)
   Form2_Button1 = BCX_BUTTON("Hide",Form2, IDC_FORM2_BUTTON1,  80, 70, 40, 14)
  
   lpForm2_Proc = SubclassWindow(Form2, Form2_Proc)
  
   SHOW(Form1)
 END SUB
  
 BEGIN EVENTS
   SELECT CASE CBMSG
  
     '**********************
    CASE WM_COMMAND
     '**********************
    IF CBCTL = IDC_FORM1_BUTTON1 THEN
       SHOW (Form2)
     END IF
     EXIT FUNCTION
  
     '**********************
    CASE WM_CLOSE
     '**********************
    DIM RAW id
     id = MSGBOX("Are you sure?","Close Window!",MB_YESNO OR MB_ICONQUESTION)
     IF id THEN PostQuitMessage(0)
     EXIT FUNCTION
  
   END SELECT
 END EVENTS
  
  
 CALLBACK FUNCTION Form2_Proc()
   SELECT CASE CBMSG
     '**********************
    CASE WM_COMMAND
     '**********************
    IF CBCTL = IDC_FORM2_BUTTON1 THEN HIDE(CBHWND)
     EXIT FUNCTION
  
     '**********************
    CASE WM_CLOSE
     '**********************
    HIDE(CBHWND)   'Don't CLOSE it, HIDE it
    EXIT FUNCTION
  
     '**********************
    CASE WM_DESTROY  'Don't DESTROY it, HIDE it
    '**********************
    HIDE(CBHWND)
   END SELECT
 END FUNCTION


Syntax 2:
 
 FUNCTION CBFuncName(hWnd AS HWND, _
                      Msg AS UINT, _
                 wParam AS WPARAM, _
                 lParam AS LPARAM) _
             AS data type CALLBACK
  [Statements]
 FUNCTION = ReturnValue
 END FUNCTION

Parameters:

  • Any parameter names can be used but the parameter data types are fixed by the CALLBACK Win32API define.
  • The FUNCTION = ReturnValue statement causes an immediate exit from the function, returning to the caller the value contained in ReturnValue.
  • data type Specifies the data type of the value returned by the function. The LRESULT data type would be used when the CallWindowProc, DefWindowProc, DefMDIChildProc or DefFrameProc function is used as a function return. In a CALLBACK function which returns TRUE or FALSE either a BOOL data type or an INT_PTR Dataype would be used.
  • ReturnValue The value returned by the function. This value must be the same data type as specified in the FUNCTION declaration. Most commonly a Win32API CallWindowProc, DefWindowProc, DefMDIChildProc or DefFrameProc function is used as a function return in a BCX CALLBACK function.

The DefDlgProc function or the DefWindowProc function must not be called by a dialog box callback procedure; doing so results in recursive execution.

Example 2:

In the sample below, each label control is subclassed and reflects the WM_CTLCOLORSTATIC message to the subclassed windows procedure. This demo of CALLBACK function Syntax 2 creates a form with a black background and two BCX_LABEL controls that have their foreground and background colors changed by the CALLBACK functions.


 GUI "test"
  
 GLOBAL Form1   AS CONTROL
 GLOBAL Label_1 AS CONTROL
 GLOBAL Label_2 AS CONTROL
  
 SUB FORMLOAD
   Form1   = BCX_FORM("test",67,42,160,130)
   Label_1 = BCX_LABEL("Label 1",Form1,100,5,10)
   Label_2 = BCX_LABEL("Label 2",Form1,101,5,50)
  
   BCX_SET_FORM_COLOR(Form1,RGB(0,0,0))
  
   SubClassLabel1()
   SubClassLabel2()
  
   CENTER(Form1)
   SHOW(Form1)
 END SUB
  
 BEGIN EVENTS
   IF CBMSG = WM_CTLCOLORSTATIC THEN
     FUNCTION = SendMessage((HWND)lParam, Msg, wParam, lParam)
   END IF
 END EVENTS
  
 SUB SubClassLabel1
   GLOBAL Original_Label_1_WndProc AS WNDPROC
   Original_Label_1_WndProc = SetWindowLong(Label_1,GWL_WNDPROC,Label_1_WndProc)
 END SUB
  
 SUB SubClassLabel2
   GLOBAL Original_Label_2_WndProc AS WNDPROC
   Original_Label_2_WndProc = SetWindowLong(Label_2,GWL_WNDPROC,Label_2_WndProc)
 END SUB
  
 FUNCTION Label_1_WndProc(hWnd AS HWND, _
                           Msg AS UINT, _
                      wParam AS WPARAM, _
                      lParam AS LPARAM) _
                    AS LRESULT CALLBACK
   IF CBMSG = WM_CTLCOLORSTATIC THEN
     FUNCTION = SetColor(RGB(0,225,0),RGB(0,0,0),wParam,lParam)
   END IF
   FUNCTION = CallWindowProc(Original_Label_1_WndProc,hWnd,Msg,wParam,lParam)
 END FUNCTION
  
 FUNCTION Label_2_WndProc(hWnd AS HWND, _
                           Msg AS UINT, _
                      wParam AS WPARAM, _
                      lParam AS LPARAM) _
                    AS LRESULT CALLBACK
   IF CBMSG = WM_CTLCOLORSTATIC THEN
     FUNCTION = SetColor(RGB(255,0,0),RGB(0,0,0),wParam,lParam)
   END IF
   FUNCTION = CallWindowProc(Original_Label_2_WndProc,hWnd,Msg,wParam,lParam)
 END FUNCTION
  
 FUNCTION SetColor(TxtColr, BkColr, wParam, lParam) AS LRESULT
   GLOBAL ReUsableBrush AS HBRUSH
   DeleteObject(ReUsableBrush)
   ReUsableBrush = CreateSolidBrush(BkColr)
   SetTextColor((HDC)wParam, TxtColr)
   SetBkColor((HDC)wParam, BkColr)
   FUNCTION =(LRESULT) ReUsableBrush
 END FUNCTION
 

If a return value is not required then the CALLBACK syntax can be expressed as for a SUB using VOID for the CALLBACK data type.


Syntax 3:
 
 FUNCTION CBFuncName(hWnd AS HWND, _
                       Msg AS UINT, _
                  wParam AS WPARAM, _
                  lParam AS LPARAM) _
                   AS VOID CALLBACK
   [Statements]
 END FUNCTION

Parameters:

  • Any parameter names can be used but the parameter data types are fixed by the CALLBACK Win32API define.
  • VOID indicates that a return value is not expected to be made.

SUB ... END SUB procedure

Purpose:

SUB ... END SUB encapsulates a series of statements to form a procedure.


Syntax:
 
 SUB SubName(Parameters)
  [Statements]
 END SUB

Parameters:

  • SubName is the name of the subroutine. Subname does not need a data type because a SUB does not return a value. Do not use a data type with the SubName because an error will occur in translation.
  • ([Parameters] [AS data type]) is zero or more comma separated variables used to pass values to the subroutine. The data type of each parameter must be explicit for any variables except integers. For example, if a string is being passed, Parameters$ or Parameters AS CHAR would be used. If a DOUBLE is being passed, Parameters# or Parameters AS DOUBLE would be used. The use of Parameters is optional.

Remarks:

By default, string variables are passed by reference, while numeric variables are passed by value. To pass numeric values by reference instead of by value, see the BYREF qualifier.

Do not modify a literal value or string used as an argument in the SUB calling statement. For example, the attempt to modify the a$ parameter which is passed the "literal" argument in,

 
 CALL FOO("literal")

 SUB FOO(a$)
  a$ = "k"
 END SUB

will cause a crash when the program is run.

CALL statement

Purpose:

CALL must precede a SUB calling procedure if the SUB does not take arguments and parentheses are omitted.

This the only valid method of calling a SUB with no arguments and no parentheses


CALL TheNameOfTheSUB 

Example:

 
 CALL TheNameOfTheSUB

 SUB TheNameOfTheSUB()
  PRINT "HMCS CC-1"
 END SUB

Values can be passed to subroutines. The data type(declarator) of the parameter passing the argument to the subroutine must be indicated. BCX uses this information to construct prototypes for the subroutines and functions so the data type must be explicit for anything except integers. For example, if a string is being passed SUB Passit(A$) would be used not SUB Passit(A). If a DOUBLE is being passed SUB Passit(A#) would be used.

 
 SetConsoleTitle("BCX Demonstration")
 
 COLOR 15, 1
 frame(15, 10, 65, 20)
 DIM a
 INPUT a
 COLOR 7, 0
 CLS
 
 SUB frame(x1, y1, x2, y2) ' while interesting, this is meant only as a
  DIM x                    ' sample SUB.  The run-time PANEL statement
  DIM y                    ' is much faster and more flexible
  FOR y = y1 TO y2
     FOR x = x1 TO x2
       LOCATE y, x, 0
       PRINT " ";
     NEXT
   NEXT
 END SUB

Recursive calls can be made from a subroutine. What this means is that the subroutine can be called from within itself. Here's an Example:


 DIM C$
 
 C$ = "1,2,3,4,5,666,777,88888,99999,101010101010101010"
 
 Parse(C$)
 
 SUB Parse(A$)
   LOCAL Sep
   LOCAL B$
   Sep = INSTR(A$ , ",")
   IF Sep > 0 THEN
     B$ = LEFT$(A$, Sep - 1)
     PRINT B$
     A$ = MID$(A$, Sep + 1, 256)
     Parse(A$)  ' --- Recursive Call ---
  ELSE
     PRINT A$
   END IF
 END SUB

Result:


 1
 2
 3
 4
 5
 666
 777
 88888
 99999
 101010101010101010

Subroutines can be prematurely exited by using the EXIT SUB statement. If you want to use values created in a subroutine outside of it, then GLOBAL variables must hold the values.

 
 JumpOut()

 PRINT a$

 SUB JumpOut()
 GLOBAL a$
 a$ = "JumpOut"
  IF a$ = "JumpOut" THEN
   EXIT  SUB
  END IF
  PRINT "This line is not executed."
 END SUB

Result:


 JumpOut

Arguments and Parameters

Arguments are passed to procedures through parameters.

Passing Arguments to FUNCTION and SUB Procedures

Example 1: This example demonstrates passing a one-dimensional integer array to a function.

 
 DIM A_RAY[10] AS INTEGER
 
 Foo(&A_RAY[0], 3)
 
 PRINT A_RAY[3]
 
 SUB Foo(BYVAL A[], Index AS INTEGER)
  A [Index] = 12345
 END SUB

Result:


 12345

Example 2: This example demonstrates filling a global two-dimensional array in a function.

 
 GLOBAL DYNAMIC A[10][10] AS INTEGER
 GLOBAL a AS INTEGER
 GLOBAL b AS INTEGER
 
 CALL FILL(A,10,10)
 
 FOR a = 0 TO 9
  FOR b = 0 TO 9
   ? A[a][b];
  NEXT
  ?
 NEXT
 
 ! PAUSE
 
 SUB FILL(B AS INTEGER PTR PTR, Dim1 AS INTEGER, Dim2 AS INTEGER)
  'The data type descriptor for the B parameter,
  'in the line above, requires one "PTR"
  'for EACH DIMENSION of the passed array.
  RAW a1
  RAW b1
  Dim1--
  Dim2--
  FOR a1 = 0 TO Dim1
    FOR b1 = 0 TO Dim2
      B[a1][b1] = a1 + b1
    NEXT
  NEXT
 END SUB

Result:


 0 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 6 7 8 9 10
 2 3 4 5 6 7 8 9 10 11
 3 4 5 6 7 8 9 10 11 12
 4 5 6 7 8 9 10 11 12 13
 5 6 7 8 9 10 11 12 13 14
 6 7 8 9 10 11 12 13 14 15
 7 8 9 10 11 12 13 14 15 16
 8 9 10 11 12 13 14 15 16 17
 9 10 11 12 13 14 15 16 17 18

Example 3: treats the multi-dimensioned array as a one-dimensioned array inside the sub, and does a little simple maths to calculate the position of elements.

 
 DIM i, j
 DIM mat[3][3]
 
 FOR i = 0 TO 2
  FOR j = 0 TO 2
   mat[i][j] = i *10+j
  NEXT
 NEXT
 
 PRINT "Initialized data to: "
 FOR i = 0 TO 2
  FOR j = 0 TO 2
   PRINT mat[i][j]
  NEXT
 NEXT
 
 TRY2(mat[0], 3, 3)
 
 KEYPRESS
 
 SUB TRY2(Mymat[], n1 , n2)
  DIM k, h
 
  PRINT "Using simple maths: "
  FOR k = 0 TO n1-1
    FOR h = 0 TO n2-1
      PRINT Mymat[k * n2 + h]
    NEXT
  NEXT
 END SUB

Result:


Initialized data to:
 0
 1
 2
 10
 11
 12
 20
 21
 22
Using simple maths:
 0
 1
 2
 10
 11
 12
 20
 21
 22

Example 4: stores the array in a TYPE variable, and passes the address of the TYPE to the function.

 
 DIM i, j
  
 TYPE MYARRAY
   a[3][3]
 END TYPE
  
 DIM mat AS MYARRAY
  
 FOR i = 0 TO 2
   FOR j = 0 TO 2
     mat.a[i][j] = i*10+j
   NEXT
 NEXT
  
 PRINT "Initialized data to: "
 FOR i = 0 TO 2
   FOR j = 0 TO 2
     PRINT mat.a[i][j]
   NEXT
 NEXT
  
 TRY1(mat, 3, 3)
 PAUSE
  
 SUB TRY1(Mymat AS MYARRAY, n1, n2)
   DIM k, h
  
   PRINT "Using a TYPE to hold the array: "
   FOR k = 0 TO n1 - 1
     FOR h = 0 TO n2 - 1
       PRINT Mymat.a[k][h]
     NEXT
   NEXT
 END SUB

Result:


Initialized data to:
 0
 1
 2
 10
 11
 12
 20
 21
 22
Using a TYPE to hold the array:
 0
 1
 2
 10
 11
 12
 20
 21
 22

Example 5: Here is an easy way to pass mutidimensional static arrays by enclosing the array in a TYPE.


 TYPE fltarray2D
   CA![100, 2]
 END TYPE
   
 GLOBAL myfloat AS fltarray2D
   
 CALL foo(&myfloat)
 PRINT myfloat.CA![1, 1]
   
 SUB foo(Myfloat AS fltarray2D PTR)
   Myfloat->CA![1, 1] = 5.33
 END SUB

Result:


 5.33

Example 6: Using a DYNAMIC array.


 GLOBAL DYNAMIC CA![100, 2]
   
 CALL foo(CA)
 PRINT CA[1, 1]
   
 SUB foo(Myfloat AS FLOAT PTR PTR)
  'The data type descriptor for the Myfloat parameter,
  'in the line above, requires one "PTR"
  'for EACH DIMENSION of the passed array.
  Myfloat[1, 1] = 5.33
 END SUB

Result:


 5.33

Example 7: The following simple example shows how to pass DYNAMIC multi-dimensional numeric arrays to a procedure by reference

 
 OPTION BASE 1
 GLOBAL DYNAMIC A[2, 2, 2] AS FLOAT
 LOCAL I, J, K
  
 A[1, 1, 1] = 1
 A[1, 1, 2] = 2
 A[1, 2, 1] = 3
 A[1, 2, 2] = 4
 A[2, 1, 1] = 5
 A[2, 1, 2] = 6
 A[2, 2, 1] = 7
 A[2, 2, 2] = 8
  
 FOR I = 1 TO 2
   FOR J = 1 TO 2
     FOR K = 1 TO 2
       PRINT "[", I, ",", J, ",", K, "] = ", XXX!(A, I, J, K)
     NEXT K
   NEXT J
 NEXT I
 KEYPRESS
 END PROGRAM
 
 FUNCTION XXX!(B AS FLOAT PTR PTR PTR, i, j, k)
  'The data type descriptor for the B parameter,
  'in the line above, requires one "PTR"
  'for EACH DIMENSION of the passed array.
  FUNCTION = 3.14 * B[i, j, k]
 END FUNCTION
 

Result:


 [ 1, 1, 1] =  3.14
 [ 1, 1, 2] =  6.28
 [ 1, 2, 1] =  9.42
 [ 1, 2, 2] =  12.56
 [ 2, 1, 1] =  15.7
 [ 2, 1, 2] =  18.84
 [ 2, 2, 1] =  21.98
 [ 2, 2, 2] =  25.12

Example 8: The following example shows how to pass TYPE and UNION structures to to a procedure

 
 TYPE STA
  DIM A$
  DIM B AS INTEGER
 END TYPE

 TYPE STB
  DIM A AS INTEGER
  DIM B AS INTEGER
 END TYPE

 ' union of all the structures
 UNION STUNION
  Atype AS STA
  Btype AS STB
 END UNION

 'the actual structure that is going to be used
 TYPE STALL
  DIM ID AS INTEGER 'tells what structure in the union is going to be used
  DIM UN AS STUNION
 END TYPE

 DIM st AS STALL

 st.ID = 1
 st.UN.Atype.A$ = "How is this?"

 CALL ExeSTX(&st)

 st.ID = 2
 st.UN.Btype.A = 2

 CALL ExeSTX(&st)

 PAUSE

 SUB ExeSTX(s AS STALL PTR)
  SELECT CASE s->ID
    CASE 1
    ? s->UN.Atype.A$
    CASE 2
    ? s->UN.Btype.A
  END SELECT
 END SUB

Result:


 How is this?
  2

BCX is able to correctly translate a parameter formed from a call to a FUNCTION as in this example.


 $TYPEDEF CHAR * (__cdecl *STF_TYPE)(CHAR *)
 
 SET pfStringFunctions[] AS STF_TYPE
   do1,
   do2,
   do3,
   NULL
 END SET
 
 '---------word functions-----------------------------------------
 FUNCTION do1$(whatstring$)
   FUNCTION = REVERSE$(whatstring$)
 END FUNCTION
 
 FUNCTION do2$(whatstring$)
   FUNCTION = UCASE$(whatstring$)
 END FUNCTION
 
 FUNCTION do3$(whatstring$)
   FUNCTION = ENC$(whatstring$)
 END FUNCTION
 
 '------------sentence function--------------------------------------
 'Now I have a function that will loop through a string and do something
 'to each word:
 FUNCTION dotoeachword$(whatsentence$, dowhat AS STF_TYPE)
   DIM tmp$
   'for each word in sentence (etc.)
  tmp$ = dowhat$(whatsentence$)
   'next
  FUNCTION = tmp$
 END FUNCTION
 
 '----------main function----------------------------------------
 DIM sentence$,sentence1$,sentence2$,sentence3$
 sentence$ = "George went to town."
 sentence1$ = dotoeachword$(sentence$, do1)
 sentence2$ = dotoeachword$(sentence$, do2)
 sentence3$ = dotoeachword$(sentence$, do3)
 PRINT sentence1$
 PRINT sentence2$
 PRINT sentence3$

Result:


 .nwot ot tnew egroeG
 GEORGE WENT TO TOWN.
 "George went to town."

OPTIONAL Parameters in Functions and Subroutines

An OPTIONAL parameter used in a FUNCTION or SUB declaration is defined with a default value. This value is used if the OPTIONAL parameter is omitted in the calling function or subroutine procedure. If a value is specified in the OPTIONAL parameter in the calling procedure, then that value is used instead of the default.

OPTIONAL parameters are not permitted in exported DLL functions.

OVERLOADED or OPTIONAL SUB procedures are not allowed in a User Defined TYPE structure.

Many BCX functions and subroutines have optional parameters already built in. For example, all the BCX GUI functions have optional window style and extended window style parameters which provide a default that is replaced when an OPTIONAL value is specified.


Syntax 1:
 
 FUNCTION MyFunc% OPTIONAL(Normalparam$, Optionalparam$ = "BCX")

Syntax 2:
 
 FUNCTION MyFunc% OPTIONAL(Normalparam$, Optionalparam AS data type = Value)

In the function or subroutine call, OPTIONAL parameters can be omitted from the right side of the parameter list.

The 'value' assigned to OPTIONAL parameters must be a scalar value, that is a string literal or number, not a function or some other complex expression.

Example:


 Alert()
 Alert("     Hello BCX Lovers!       ")
 Alert("Announcing OPTIONAL ARGUMENTS", "Very Cool!", 64)
 
 SUB Alert OPTIONAL(_Message$ = "" , _Title$ = "", Style = 0)
   LOCAL Message$, Title$
   Message$ = _Message$ & ""
   Title$   = _Title$   & ""
   IF TRIM$(Message$) = "" THEN
     Message$ = "  Alert ... Alert ...Alert!  "
   END IF
   IF TRIM$(Title$)   = "" THEN
     Title$   = "Generic Default Title"
   END IF
   IF Style = 0 THEN Style = MB_OK
   MSGBOX Message$, Title$, Style
 END SUB

BYREF keyword

Purpose: BYREF indicates that the following parameter is passing the argument by reference in the SUB or FUNCTION declaration.


Syntax 1:

 SUB | FUNCTION MySubOrFunc(BYREF var1%, BYREF var2#, ...)

Syntax 2:

 SUB | FUNCTION MySubOrFunc(BYREF var1 AS INTEGER, BYREF var2 AS DOUBLE, ...)

Parameters:

  • BYREF var1 The integer variable var1 is being passed by reference.
  • BYREF var2 The double variable var2 is being passed by reference.

Anytime you want to be able to modify the contents of an argument, you must pass the argument BYREF. Otherwise, a copy of the value is normally passed to the SUB or FUNCTION.

Strings are automatically passed by reference, so BYREF is not needed when passing string arguments. Do not use BYREF within SUB or FUNCTION parameter lists when referencing arrays.

In the FUNCTION or SUB calling statement, the variable being passed by reference to the procedure must be prepended by an ampersand.

In this example, using BYREF to modify the passed argument, an ampersand is prepended to the ChangeUp parameter in the Test2 calling statement.


 CALL Test1 : PAUSE
 
 SUB Test1
   LOCAL ChangeUp
   ChangeUp = 3
   PRINT "ChangeUp", ChangeUp

   CALL Test2(&ChangeUp)  ' ChangeUp variable will be modified by SUB test2! 

   PRINT "ChangeUp", ChangeUp
 END SUB
 
 SUB Test2(BYREF Slider AS INTEGER)
   Slider = Slider + 2
   PRINT "Slider", Slider
 END SUB

Result:


ChangeUp 3
Slider 5
ChangeUp 5

Here is another example that uses BYREF to access a RECT structure from a subroutine (SUB rectProc1). Note carefully the use of parentheses. Also included is an example of an alternate syntax (SUB rectProc2) to accomplish the same access.


 DIM rct AS RECT

 rct.left = 1
 rct.top = 2
 rct.right = 100
 rct.bottom = 100

 rectProc1(&rct) ' The argument being passed by reference
 rectProc2(&rct) ' must be preceded by an ampersand.
 PAUSE

 SUB rectProc1(BYREF rct AS RECT)
  ?(rct).left
  ?(rct).top
  ?(rct).right
  ?(rct).bottom
 END SUB

 SUB rectProc2(rct AS RECT PTR)
  ? rct->left
  ? rct->top
  ? rct->right
  ? rct->bottom
 END SUB

Result:


 1
 2
 100
 100
 1
 2
 100
 100

$FSSTATIC directive

The $FSSTATIC directive causes the BCX translator to place the storage class specifier 'static' before function and subroutine definitions in the output C code.

 
 $FSSTATIC
 SUB GoForward(iBrowser AS IWebBrowser2 PTR)

 END SUB

translates to


 static void GoForward(IWebBrowser2 *iBrowser)
 {
 }

and without $FSSTATIC, BCX translates

 
 SUB GoForward(iBrowser AS IWebBrowser2 PTR)

 END SUB

to


 void GoForward(IWebBrowser2 *iBrowser)
 {
 }