Passing Fox arrays by ref or value

This forum is meant for questions about the Visual FoxPro Language support in X#.

Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Passing Fox arrays by ref or value

Post by Karl-Heinz »

Guys,

i´m currently looking at the options how to pass arrays to a function.

Code: Select all

clear

set talk off
set udfParms to value

dime a[2]

=Test ( a  , 4 ) 
? "Alen() after first Test() call" , alen ( a ) 
?
=Test ( @a , 6 )
? "Alen() after second Test() call" , alen ( a ) 
?
set udfParms to reference
=Test ( a , 8 )
? "Alen() after third Test() call" , alen ( a ) 
?
? "APrinters() test"
set udfParms to value
dime apr[1]
? APrinters ( apr)
&& lists all installed printers
disp memo like apr

function Test
parameters arr , n

? "Alen() inside Test() before redim" , alen ( arr )

dime arr [ n ]

return
when you run the code the first Test() call shows the msgbox "'Arr' is no array". Simply press the [ok] button to continue. I also noticed that there are functions like APrinters(a) that change the passed array no matter if the array is passed by ref or if Set UdfParms is set to value.

1. are there any other options to pass arrays ?
2. I wonder how the various array scenarios described will be implemented in X# ?

Karl-Heinz
User avatar
robert
Posts: 4261
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Passing Fox arrays by ref or value

Post by robert »

Karl-Heinz,
(Fox) arrays are a reference type. The variable (either a local or a memory variable) contains a pointer to memory on the heap where the actual array is stored.
If you pass an array variable to a function then the normal behavior is that you pass a copy of the reference to the function. This copy of the reference points to the same memory location. The same is true for objects.
Resizing the array with a DIM command will therefore resize the original array.
Allocating a new array and assigning it to the copy of the reference would update the pointer in the copy of the reference and the original variable would still point to the old copy.

If you would pass an array variable by reference then you would be passing a reference to the location where the reference to the array in memory is located. Resizing the array would do the same as when passing the variable by reference.
Allocating a new array would create a new pointer and this pointer would be stored in the original variable, so the 'old array' would no longer be referenced by this variable.

The behavior of functions like APrinters() in FoxPro and their counterpart in X# would of course depend on how these functions are implemented.
If you look at the FoxPro help file it says that the first parameter of Aprinters() is an array name. The example looks like this:

Code: Select all

IF APRINTERS(gaPrinters) > 0  
   CLEAR  && clear the current output window
   DISPLAY MEMORY LIKE gaPrinters && show the contents of the array
ELSE  
   WAIT WINDOW 'No printers found.'
ENDIF
To me this looks like the first parameter is not "a name" (because that would mean that gaPrinters would be passed as a string) but a variable.
This variable is passed without @ sign, so it is passed by value.
If gaPrinters is already an array then it will be resized to match the number of printers found.

The potential problem lies in this line inside the FoxPro help file:
If the array you include does not exist, Visual FoxPro automatically creates the array

If you are compiling with the option to allow "undeclared variables" then it would indeed be possible to pass gaPrinters without declaring it first. But in that case the only thing the APrinters() function could do is allocate a memory variable with the name "gaPrinters" and assign the array to that.
But to do so, the function needs to know the name of the variable(), and normally a compiler would not pass the name of the variable to the function that is called.

The solution that I see for this is now something like this:
1) Declare a special attribute in the X# runtime that will be used to mark functions that need to be told about the parameter names passed to the. For the sake of this message we could call this the PassParameterNamesAttribute
2) Mark the function (in this case APrinters()) with this attribute. That would look something like this:

Code: Select all

[PassParameterNames];
FUNCTION APrinters(aTarget as __FoxArray, nValue := 0 AS LONG, __xsVariableNames__ as STRING[])
RETURN ...
3) Tell the compiler that when a method / function has this attribute then it needs to add an extra argument to the method call that contains the names (if any) of the parameters passed. When an argument is not a variable but an expression (like the literal 0 or 1 for the second parameter of APrinters) then the variable name passed should be an empty string.

A call to Aprinters() like in the example code should then be translated to this by the compiler

Code: Select all

IF APrinters(gaPrinters, 0, <STRING>{"gaPrinters",""}) > 0
Please note that I have not discussed this with the rest of the team, but something like this would work.
Maybe we could change the attribute to contain the name of the parameter that needs his name passed, so the code could look like this, since there is usually only one parameter that needs to pass its name.

Code: Select all

[PassParameterName("aTarget")];
FUNCTION APrinters(aTarget as __FoxArray, nValue := 0 AS LONG, __xsVariableName__ as STRING)
RETURN ...
Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
User avatar
robert
Posts: 4261
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Passing Fox arrays by ref or value

Post by robert »

Karl Heinz,
One more remark

Code: Select all

set udfParms to value
..
set udfParms to reference
We cannot support that like this. In a compiled language the way that parameters are passed is decided at compile time. We cannot let that be dependent at a runtime setting, except when we would generate all UDF calls twice inside an IIF:

Code: Select all

lResult := IIF(UdfParamsByReference, MyUdf(@var1, @var2), MyUdf(var1, var2))
I really do not want to go there. That would more than double the size of the generated code for each UDF call.

What we could do is add a compiler option (and a matching compiler pragma) to control this, but I am not looking forward to that either.

Also I think it is very dangerous to control this at runtime.
If I write a function and I think I am passing a variable by value, I do not want that to change if someone else uses my function and changes the setting to pass variables by reference. I think that is "asking for trouble" and does not belong to a professional development language.
I am sure FoxPro 3rd party authors have encountered this problem and have then searched for (and found) a solution to safeguard their code against this, for example by passing the variable as an expression (by putting it between parentheses).

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Passing Fox arrays by ref or value

Post by Karl-Heinz »

Robert,

Code: Select all

[PassParameterName("aTarget")];
FUNCTION APrinters(aTarget as __FoxArray, nValue := 0 AS LONG, __xsVariableName__ as STRING)
RETURN ...
yes, so __xsVariableName__ holds the "real" varname of aTarget. But wouldn´t it be better to not type the function ? - see my VFP code below.

i tried several possibilities to feed the APrinters() function. As you can see i declare the privates 'var1' and 'var2'. The assignment Var2 := "text" causes later on the expected Fox error: "'Var2' is no array", while 'var1' becomes an array.

To my surprise it´s even possible to pass undeclared vars to VFP functions !

APrinters ( undeclaredvar ) creates the array 'undeclaredvar' and VarType ( undeclaredvar ) returns 'U'

Code: Select all

dime arr[1]
private var1, var2

var2 = "text"


? "Vartypes" , VarType ( var1 ) , VarType ( var2 ) , Vartype ( undeclaredvar ) && U C U


? "APrinters() I test"
? APrinters ( arr)  && uses the existing array 'arr'

? "APrinters() II test"  
? APrinters ( var1 )  && creates the array 'var1'

? "APrinters() III test"
? APrinters ( var2 )  && throws the runtime error "'var2' is no array" 

? "APrinters() IV test"
? APrinters ( undeclaredvar ) && creates the array 'undeclaredvar'


disp memo like arr

disp memo like var1 

disp memo like undeclaredvar 
regards
Karl-Heinz
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Passing Fox arrays by ref or value

Post by Karl-Heinz »

Guys,

the attached 2.8c XSharpFoxProAPrinters.viaef inludes a limited X# Version of VFP's APrinters() function.
Of course the parameter handling of a final X# APrinters() func must be completely different from the one i m currently using, but i just wanted to fill the array and compare the content with the array that VFP fills. Here, the X# and VFP array show the same content.

Code: Select all

FUNCTION Start( ) AS VOID
LOCAL n AS DWORD 	
DIMENSION a[1]
// LOCAL ARRAY a [1]                 


	// if there are printers the array will have five columns	
	IF ( n := APrinters ( @a , 1)) > 0
		? NTrim ( n ) + " Printers detected"
		?   
		ShowArray(a)
	ELSE 
		? "no printers detected"
	ENDIF		
    
	?
	?
	
	// if there are printers the array will have two columns
	IF ( n := APrinters ( @a ) )   > 0  // same as APrinters ( @a , 0 )
		? NTrim ( n ) + " Printers detected"
		? 
		ShowArray(a)
	ELSE 
		? "no printers detected"
	ENDIF		

	RETURN

And this is the VFP code that does the same:

Code: Select all

private n
DIMENSION a[1]
&& LOCAL ARRAY a [1]                 


	n = APrinters ( a , 1)  
	if n > 0
		&& the array will have five columns	
		? Str ( n ) + " Printers detected"
		?   
		Disp memo like a 
	ELSE 
		? "no printers detected"
	ENDIF		
    
	?
	?
	

	n = APrinters ( a )    && same as APrinters ( a , 0 )
	if n > 0
		&& the array will have two columns
		? Str ( n ) + " Printers detected"
		? 
		Disp memo like a
	ELSE 
		? "no printers detected"
	ENDIF
	
regards
Karl-Heinz
Attachments

[The extension viaef has been deactivated and can no longer be displayed.]

User avatar
robert
Posts: 4261
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Passing Fox arrays by ref or value

Post by robert »

Karl Heinz,
Thanks for the code.

We have discussed the issue of the arrays being passed to functions such as AFields() and APrinters().
Where FoxPro creates a new array when the variable does not exist, we have decided not to support that.
We have decided that the parameter MUST exist in X#.
Like I wrote before: in theory we could find a way to allow functions like APrinters() to create the array in the context of the calling function.
But we really see that as a bridge too far. We think it is really not too much to ask the user to create the variable before calling the function.

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Passing Fox arrays by ref or value

Post by Karl-Heinz »

Hi Robert,

>> We have decided that the parameter MUST exist in X#.

i agree. Does that mean that the user must pass an existing array to e.g. APrinters() by ref ?

regards
Karl-Heinz
User avatar
robert
Posts: 4261
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Passing Fox arrays by ref or value

Post by robert »

Karl-Heinz,
Karl-Heinz wrote: i agree. Does that mean that the user must pass an existing array to e.g. APrinters() by ref ?
No, there is no need to pass the existing array by ref.
Passing the array by value will work. We will resize the array inside APrinters() and the other functions that take an array.
Passing something else will not work because that would require a parameter by reference.

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

Passing Fox arrays by ref or value

Post by Karl-Heinz »

Robert,

if i want to change the array size in a function, without the need to pass the array by ref, the __FoxRedim() function seems to the way to go. Do you agree ? i changed in the APrinters() function the two DIMENSION to __FoxRedim(), and the caller code to APrinters ( a , 1) and APrinters ( a ). - now APrinters() works the same way as it does with VFP.

btw. i noticed that the __FoxRedim() already exists in 2.8.3.15

regards
Karl-Hinz

Code: Select all

FUNCTION Start() AS VOID
DIMENSION a[1]

a[1] = "xyz"
    
ShowArray ( a ) 
?
ChangeArraySizeWithOutByref ( a , 6 , 3 ) // 6 rows , 3 cols
	
ShowArray ( a )
?
ChangeArraySizeWithOutByref ( a , 8 , 0 )  // 8 rows , no cols
 
ShowArray ( a )

RETURN


FUNCTION ChangeArraySizeWithOutByref ( a , nRows , nCols ) AS DWORD CLIPPER 
	
	a := __FoxRedim( a , nRows, nCols )  
	
	RETURN 0	
	
User avatar
robert
Posts: 4261
Joined: Fri Aug 21, 2015 10:57 am
Location: Netherlands

Passing Fox arrays by ref or value

Post by robert »

Karl-Heinz,

Arrays are a reference type. When you pass them to another function you can do anything that you want with that array in the function. So changing the size is also not a problem.
You can pass an array by reference but considering the syntax for FoxPro arrays (in FoxPro) this does not make sense, since you cannot assign a new array with the FoxPro syntax. DIMENSION does not create a new array but resizes the array that already exists (keeping its contents).

Robert
XSharp Development Team
The Netherlands
robert@xsharp.eu
Post Reply