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:
|
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()
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 _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
CALLBACK FUNCTION...END FUNCTION procedure
Syntax 1: CALLBACK FUNCTION CBFuncName() [Statements] END FUNCTION Parameters:
|
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:
|
☞ 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:
|
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:
|
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:
|
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)
{
}