FUNCTION ... END FUNCTION procedure

FUNCTION ... END FUNCTION encapsulates a series of statements to form a procedure which returns a value to the FUNCTION call expression.


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

Syntax 2:
 
 FUNCTION TheName([Parameters][AS data type])[,AS data type]
 DIM ReturnValue
     [Statements]
 RETURN 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
    
    

    or the equivalent

    
     RETURN 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 same data type as the data type declared in the declaration of the FUNCTION.

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

     
     FUNCTION = ""
    
    

    or

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

    or else use

     
     FUNCTION = NUL$
    
    
  • ReturnValue The return value of the function. Like all variables ReturnValue must be declared 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:

An argument in the FUNCTION calling statement cannot be modified. For example, the attempt to modify "This can not be changed!" will fail in,


 DIM literal$

 literal$ = FunOne$("This can not be changed!")
 PRINT literal$

 FUNCTION FunOne$(idiom$)
   idiom$ = "You should keep your eye out for this!"
   FUNCTION = idiom$
 END FUNCTION

Result:


  The result is undefined behaviour which is dependent on the usage context.

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()

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 as a Foo_TYPE declaration as a member of a user defined TYPE.

Example 1:

 
 DIM FUNCTION Foo$(a as LPCTSTR)
  
 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

Subclassing Windows Controls

If you want a control to do something that is not already built in to the control, subclassing allows you to add that new functionality at runtime.

Subclassing involves tapping into the windows message stream for a custom control. Text labels, editboxes, listboxes, etc all have their own windows procedure. You usually don't need to concern yourself with these.

CALLBACK FUNCTION ... END FUNCTION procedure

Purpose:

CALLBACK indicates that a procedure is to be subclassed.


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); // CallBackFlag
    
    
  • 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); // CallBackFlag
    
    

Example 1 : Below is a tiny example of subclassing a control, in this case a BCX_BUTTON. By subclassing controls in this way, you can organize your GUI apps such that your BEGIN EVENTS ... END EVENTS loop is not overloaded with code. Instead, you direct each control to do your bidding, in this case, the BCX_BUTTON control detects the left mouse click and displays a MSGBOX when clicked.

 
 GUI "Subclassing Example"
 
 SUB FormLoad
  DIM AS CONTROL Form1   = BCX_FORM ("Subclassing Example")
  DIM AS CONTROL Button1 = BCX_BUTTON("Click Me!", Form1, WM_USER+10, 100, 40, 40, 20)
  '------------------------------------------------------------------------------------- 
  GLOBAL Button_Before_Subclass AS WNDPROC
  Button_Before_Subclass = SubclassWindow (Button1, Button1_Events)
  '------------------------------------------------------------------------------------- 
  CENTER Form1
  SHOW   Form1
 END SUB
 
 BEGIN EVENTS
  SELECT CASE CBMSG
  CASE WM_CLOSE
   LOCAL id
   id = MessageBox(hWnd, "Are you sure?", "Quit Program!", MB_YESNO | MB_ICONQUESTION)
   IF id = IDYES THEN DestroyWindow (hWnd)
  END SELECT
 END EVENTS
 
 CALLBACK FUNCTION Button1_Events
  SELECT CASE CBMSG
  CASE WM_LBUTTONUP
   MSGBOX "Hello From Button1_Events"
   EXIT FUNCTION
  END SELECT
  FUNCTION = CallWindowProc (Button_Before_Subclass, CBHWND, CBMSG, CBWPARAM, CBLPARAM)
 END FUNCTION

Example 2 : 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
  
 MACRO  IDC_FORM1_BUTTON1 = 201
 MACRO  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 3:

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 = SetWindowLongPtr(Label_1,GWLP_WNDPROC,Label_1_WndProc)
 END SUB
  
 SUB SubClassLabel2
   GLOBAL Original_Label_2_WndProc AS WNDPROC
   Original_Label_2_WndProc = SetWindowLongPtr(Label_2,GWLP_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.

An argument in the SUB calling statement cannot be modified. For example, the attempt to modify "This can not be changed!" will fail in,


 CALL FunOne("This can not be changed!")
 
 SUB FunOne(idiom$)
   idiom$ = "You should keep your eye out for this!"
   PRINT idiom$
 END SUB

Result:


 The result is undefined behaviour which is dependent on the usage context.

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

Can BCX handle a recursive function? Yes, a recursive call can be made from a subroutine. What this means is that the SUB can be called from within itself. Here's one popular example.


 ' Example function call, usage 1 
 ' SearchForFiles("C:\", "win.ini") 
  
 ' Example function call, usage 2 
 SearchForFiles("C:\Windows", "Notepad.exe")
   
 SUB SearchForFiles(startPath$, FileToFind$)
   DIM RAW fPath$, wildpath$, fName$, fPathName$
   DIM RAW hfind AS HANDLE
   DIM RAW WFD AS WIN32_FIND_DATA
   DIM RAW found AS BOOL
   DIM RAW filename$
   
   ' Prepare filename$ for comparison with found file names 
   filename$ = LCASE$(FileToFind$)
   
   ' Make a local copy of the search path 
   fPath$ = startPath$
   
   ' Make sure search path ends with a backslash 
   IF RIGHT$(fPath$, 1) <> "\" THEN fPath$ = fPath$ + "\"
   
   ' We want to find all files and directories so we'll 
   ' add a wildcard asterisk to the search path 
   wildpath$ = fPath$ + "*"
   
   ' Ask Windows API for first file or directory 
   ' in our search path 
   hfind = FindFirstFile(wildpath$, &WFD)
   
   ' Did we get one? 
   IF hfind != INVALID_HANDLE_VALUE THEN
     found = TRUE
   ELSE
     found = FALSE
   END IF
  
   DO WHILE found
     ' Get the name of the file or directory 
     ' from the WIN32_FIND_DATA structure 
     fName$ = WFD.cFileName
   
     ' Add the file or directory name to the search path 
     fPathName$ = fPath$ + fName$
   
     IF fName$ != "." THEN ' ignore "current directory" 
       IF fName$ != ".." THEN ' ignore "parent directory" 
  
         ' Did we find a file or a directory? 
         IF (WFD.dwFileAttributes BAND FILE_ATTRIBUTE_DIRECTORY) THEN
   
           ' Found a directory so look for our target 
           ' file in that directory 
           SearchForFiles(fPathName$, filename$)
   
           ' Found a file so does it match our target? 
         ELSEIF LCASE$(fName$) = filename$ THEN
   
           ' Code here could call another routine, 
           ' add the found file to a listbox, 
           ' or whatever. We'll just display it's 
           ' full path and name in a message box 
           PRINT "Found ", fPathName$
         ELSE
   
           ' File searching can be a lengthy process 
           ' so give other processes a chance to do 
           ' whatever they need to do 
           DOEVENTS
   
         END IF
       END IF
     END IF
   
     ' Ask Windows API for the next file or 
     ' directory in our search path 
     found = FindNextFile(hfind, &WFD)
   LOOP
   
   ' Tell Windows API we've finished searching 
   FindClose(hfind)
   
 END SUB

Result:


 Found C:\Windows\notepad.exe
 Found C:\Windows\System32\notepad.exe
 Found C:\Windows\SysWOW64\notepad.exe
 Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.117_none_4d353cf1ceb5d6d2\f\notepad.exe
 Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.117_none_4d353cf1ceb5d6d2\notepad.exe
 Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.117_none_4d353cf1ceb5d6d2\r\notepad.exe
 Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.1_none_250b9aff0f5d41ee\notepad.exe
 Found C:\Windows\WinSxS\wow64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.1_none_2f60455143be03e9\notepad.exe

Here's another recursion 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

Passing Arguments to FUNCTION and SUB procedures

Arguments are passed to procedures through parameters.

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

FUNCTION as Parameter

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

The OPTIONAL keyword is optional since BCX 7.7.2.

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

OPTIONAL parameters must be placed to the right of any normal parameters in the list.


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

Syntax 2:
 
 FUNCTION MyFunc%(Normalparam AS STRING, Optionalparam AS STRING = "BCX")

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

Syntax 4:
 
 FUNCTION MyFunc%(Normalparam AS STRING, Optionalparam AS data type = Value)

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

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

Remarks:

OPTIONAL parameters are not permitted in exported DLL functions.

OPTIONAL parameters are not allowed in FUNCTION or SUB procedures within a user defined TYPE structure.

OVERLOADED procedures cannot use OPTIONAL parameters.

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.

Many BCX functions and subroutines have optional parameter default arguments. For example, all the BCX GUI functions have optional window style and extended window style parameters with defined default arguments that can be replaced with user specified arguments.

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 AS INTEGER, BYREF var2 AS DOUBLE, ...)

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.

Example 1:

In this BYREF example, an ampersand is prepended to the ChangeUp parameter in the CALL Test2(&ChangeUp) statement. to qualify the passed argument.


 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

Example 2:

Here is another example that uses BYREF to access a file scoped RECT structure from within a subroutine. In SUB rectProc2, an alternate syntax is used to accomplish the same access as BYREF by qualifying rct AS RECT PTR.


 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. 
 ? rct.left
 ? rct.top
 ? rct.right
 ? rct.bottom
 ? 
 
 PAUSE
 
 SUB rectProc1(BYREF iAm AS RECT)
   ? (iAm).left
   ? (iAm).top
   ? (iAm).right
   ? (iAm).bottom
   ? 
 
   iAm.left   = 2
   iAm.top    = 4
   iAm.right  = 200
   iAm.bottom = 200
 
   ? rct.left
   ? rct.top
   ? rct.right
   ? rct.bottom
   ? 
 END SUB
 
 SUB rectProc2(uIs AS RECT PTR)
   ? uIs->left
   ? uIs->top
   ? uIs->right
   ? uIs->bottom
   ? 
 END SUB

Result:


  1
  2
  100
  100

  2
  4
  200
  200

  2
  4
  200
  200

  2
  4
  200
  200

Remarks:

Note carefully the use of the parentheses in SUB rectProc1. The BCX translator requires that only parentheses, in this case, (rct), are syntaxed instead of the typical (*rct) used, when dereferencing a pointer to a struct. The BCX translator adds the asterisk in the 'C' code.

$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)
 {
 }

FUNCNAME$ keyword

The FUNCNAME$ keyword is a case-insensitive keyword that equates to __func__ which is a C99/C++11 macro that returns the name of the FUNCTION or SUB within which it is used. FUNCNAME$ is intended as one more debugging tool -- useful while you develop your applications. Below is a simple example.


 $BCXVERSION "7.8.0"

 CALL Test_Funcname
 
 SUB Test_Funcname
  PRINT "Entering ", FUNCNAME$
  PRINT "Leaving  ", FUNCNAME$
 END SUB

Result:


 Entering Test_Funcname
 Leaving  Test_Funcname