Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to determine if a parameter was passed by value or reference?

Status
Not open for further replies.

foxmuldr3

Programmer
Jul 19, 2012
166
US
In a case like this, how can you know if the parameter was passed in by value or reference?

Code:
SET UDFPARAMS TO Value
lcVar = "Hi, mom!"
my_function(lcVar, @lcVar)

FUNCTION my_function
LPARAMETERS tcP1, tcP2
* How to determine if tcP1 was passed by value or reference?
* How to determine if tcP2 was passed by value or reference?

I've asked Bing COPILOT, and ChatGPT, and they both say using TYPE() or VARTYPE() will tell me, but that's not correct ... unless I'm missing something.

--
Rick C. Hodgin
 
Good question. But as far as I know, it is not possible. At least, I've never come across any way of doing this. Then again, I don't recall ever having a reason to do it.

Presumably it comes down to communication between the person who wrote the function and the person calling it. In other words: Documentation.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Hello Rick,

run this and you'll see a difference in the description of the variables:
Code:
Set Udfparms To Value
_prefix_var = "Hi, mom!"
my_function(_prefix_var, @_prefix_var)

FUNCTION my_function
LPARAMETERS _prefix_P1, _prefix_P2

List Memory LIKE _prefix_* to file vars.txt
Modify File vars.txt

The text file output shows that TCP2 is referring to the LOCAL variable lcVar of the caller.
[pre]_PREFIX_VAR
(hid) C "Hi, mom!" 00007fnt00a9
_PREFIX_P1 Local C "Hi, mom!" my_function
_PREFIX_P2 Local _prefix_var[/pre]

This also shows LIST MEMORY can show you all variables, even if they are not in the current scope. They are listed with (hid).

_PREFIX_P2 (formerly tcP2) is listed as referring to _prefix_var, which is the variable passed in by refrerence.

The listing shows the variable name instead of the type, I think that#s where you got the wrong advice the type is showing that.

I sssume the actual name table entry is not mimicking this exactly, storing another name table index instead of the variable type, what's created when VFP executes the call is the creation of the name table entry for the tcP2 variable, which will simply point to the same veriable structure as lcVar and thereby these names become synonyms for the time being. Removing tcP" from the name table index when the function ends also only removes that name table entry, not the struct that it points to, as lcVar still points to the sme struct, so there will be the usual ref count mechanism determining when the actual memory struct of the variable is removed.

So using that different name within the function automatically sets the same struct which normally could even be out of the scope of the function like here a local vaiable, thus the function doesn't need declaration of which variables are sent over by value or by reference, the variable passing mechnism of the runtime knows.

This way the @ prefix in the code of the caller is suffienct and is no needd for a parameter specification by val or by ref as is known from VB, for example with the ByVal or ByRef modifiers of VB(A) Declare/Function/Sub parameter declarations.

Chriss
 
Thank you, Chris. Would not have thought to look there. I don't think I've ever done a LIST MEMORY where I have passed by reference values. In recent years I've used pass by reference a lot more often for multiple return values.

--
Rick C. Hodgin
 
It's obviously not very quick to write and read a file to find out whether a variable is by ref, when it is listed with another variable name instead of a data type letter. And other ways, like LIST MEMORY printing it's output to a printer or the screen isn't very useful either. I wonder why it would matter, you either design a function/method to act on a parameter assuming it was passed in by reference or not.

That any other programming language I know asks you to specify whether parameters are by val or by ref is more natural, as it normally only makes sense to pass in something by ref, when the change of that value is used as return value, as you say yourself, you used this mechanism to return multiple values. Very often you're both the user and programmer of such a function.

I just experimented a bit. My idea might not work exactly the way I assume under the hood of the VFP runtime, of course. But I wouldn't have expected what I get when calling native functions that way, erroring with misleading messages, STR(@lnValue), for example, will error with the message "function name is missing )". Of course the name isn't missing a closing paranthesis, the pass by reference is not expected and leads to such nonsense.

The only native method I know allowing by ref passed in variables that are used for output of return values is the Grids GridHitTest method.

But for any user defined function your choice of passing in by value or by reference is a choice of the calling code only. So that it's not necessary to declare it is not a very useful feature, is it? If a function isn't programmed to set a parameter to a useful (return) value, what else is the use case of by ref passed in parameters? If you fear by ref passed in parameters could "spy" on the code, in the worst case you'd need to actively release a parameter, though laziness and the automatic release of variables let nobody program that way. Otherwise you may be revealing a value you'd want to only be known within the function. But it needs knowledge of the caller about the code, whether that's a hack that can be used. Even if a variable has a valuable secret value like a password a user entered, it may not have that value at the time the function returns and the caller can't influence when the function returns. Never mind, that's just thinking about absurd cases. For example, when aimming for passwords you'd surely watch out for values of textbox controls, not use such a hack. I can't even think of a straight forward case of that being a security concern and wouldn't start thinking about code that releases variables or sets them to empty/null or other useless values before "risking" to return them by ref.

Chriss
 
I had a case today where I needed to modify some legacy code. A filename was being passed in, and it needed to be updated with FULLPATH() to have the full directory because the app it was being sent to was being launched in its own folder, and wasn't able to see the file without the directory. The passed in parameter was referenced several times and I wasn't sure if I wanted to update it with the FULLPATH(), or create a new variable and make all the changes later in the code. So, I thought I would see if it was passed by reference or value in the code, and if by value I could just update the passed parameter knowing I wouldn't update the remote variable as well.

It just occurred to me in the moment, and I wondered how I could test for that condition.

--
Rick C. Hodgin
 
I see, isn't it easy enough to see from where it's called with the code references search? I know there are cases a call might be constructed in a variable and done by macrosubstitution, but that's an exotic case.

Anyway, I guess you now can find out or already did. A simple solution would also be to copy the parameter into a local variable as the first line of code. At that time adding the fullpath(). Then change all further code to use the local variable instead of the parameter.

Then the question becomes why would someone pass in a parameter by ref, when it's known the function doesn't change and only use the parameter value? I mean you first even need to know this difference of passing parameters exists and how to code it with the @ prefix and then you would expect something from it, i.e. a change. Anyway, just thinking out loud.

Chriss
 
Just out of curiosity I asked Bing (Copilot) the same question, it "made up" the function ISBYREF(). You can google and find out such a function exists elsewhere.

I read such "lies" are done by such LLMs. Not intentionally, they are just predicting the next word.

You can configure that you want precise answers, I haven't tried, but if I remember now, I've already seen a question of someone here with code that also had a function not existing. Seems more people than I thought already make use of AI and think it could actually help them to not need to learn themselves.

For reasons of that and other issues I don't think I will seriously use it as my copilot for programming. Maybe when entering a new language, but I fear I'd just need as much looking up specifications when mending code not working than when building it up myself.

Chriss
 
I think if a copilot could train on my own code, and my own usage by watching me work over weeks, then it would be beneficial to me. But as they are today, they're only sometimes helpful, they're not in sync with me and my developer style, they are sometimes flat wrong, and they have years to go before they're solid tools.

That being said, the world will soon be overrun with mediocre developers who can ask the right questions to get AI to quickly generate usable code no matter how ugly or inefficient.

It's going to be a disaster imho. Now is the time to press in and hone your personal skills for when we have to fight back against the machines! :)

--
Rick C. Hodgin
 
Rick,

This goes back to what I said earlier about communication. If I write a function that requires a parameter to be passed by reference, I would go out of my way to ensure that anyone using my function was aware of that fact - in the same way that I would let them know what data types were expected, the range of legal values, and so on, etc.

Of course, it's useful for a function to be able to check that its parameters conform to the requirements, but ultimately it's up to the programmer to make sure he or she is using the function correctly.

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Agree. This is a legacy code base maintained by developers of various skills over literally 35 years since Multi-user FoxBASE+ 2.0. It isn't always well documented.

My passing by reference question was a passing thought. Turned into a bit of a hunt for a solution.

--
Rick C. Hodgin
 
I wonder why you wanted to tackle this at runtime. At first I thought you wanted to find out about thius mechanism for reimplementing it in your FoxPro clone.

Well, and then - also by finding out what actually happens at runtime in some calls you still only probe and don't necessarily cover all cases using or not using by reference parameters.

So either you record this for a longer session and test many cases to become sure or - what I think is more straight forward - why don't you do code anlysis, i.e. a search for @ anbd SET UDFPARMS ?
(as there also is SETUDFPARM TO REFERENCE and then you don't need @. Though I never understood why you'd want that mode for all parameters, anyway.)

I'd say legacy code would neither use @ or SET UDFPARMS rather private vars to have the same mechanism even without passing the variables in, just letting them have extended scope.

That of course needs a white box construct, you program a function with usage of variables it neither declares itself nor receives as parameters. But from what I know private vars where very popular, mostly because you don't need to declare them first, not because you intentionally want the extended variable scope. But the extended scope can also be used intentionally.

A function that by intention uses a private variable which it neither declares itself nor receives as parameter will reveal that by code where such a variable "falls from the sky". In that sense it would even be documenting that this is the intention, if you don't make the (also very probable) assumtion someone just forgot to declare a variable. It just becomes mysterious, if the code first reads a variable, not assigns a value to it. Then it must assume it exists - as a private variable.

Chriss
 
Just thought about that once more.

A project wide code search for passing parameters by reference must also include all calls by DO ... WITH as mentioned by SET UDFPARMS:
VFP help said:
Using SET UDFPARMS TO VALUE does not affect the WITH clause in the DO command, which, by default, passes arguments to parameters by reference.
LIST MEMORY confirms that, again, besides being able to change the passed in variable confirms it, too, of course.

On the other hand calling a prg without DO but with the shorter syntax [tt]prgname(params)[/tt] will not cause passing by reference, so only the WITH clause of DO does so. Which also means I have to say programmers not ware of that could pass parameters by reference unknowingly.

This mechanism also includes DO somefunction in some.prg WITH parmeters, so any function could be called with parameters by reference without the @ prefix, too.

Also note:
VFP help said:
WITH ParameterList
Specifies parameters to pass to the program or procedure. The parameters listed in ParameterList can be expressions, memory variables, literals, fields, or user-defined functions. By default, parameters are passed to programs and procedures by reference. [highlight #FCE94F]You can pass a parameter by value by placing it in parentheses[/highlight].

And obviously passing in literal values insstead of variables means passing them by value, too, as only variables can be passed by reference.

Chriss
 
Good call, Chris. That looks like a possible way to find passes by reference in existing code. But I think what Rick wants is a way to do it at run time. In other words, some code that can be called on entry to a function or procedure to check that the calling code has correctly passed its parameters. (I might be wrong about that.)

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike,

Rick pointed out very well why he looked for the possibility in his post from 12 Jan 24 21:03. And I get it as a one time analysis to decide how to change a function, introducing a new variable decoupling from a variable passed in by reference or not. It was never intended for permanent runtime decision to have code catering for both cases.

Rick wnted to avoid needing to change much of the code:
foxmuldr3/Rick said:
...or create a new variable and make all the changes later in the code

The concrete feaqr was changing a parameter with FULLPATH would effect the behavior of the calling code after returning.

You could simply always change code to use local variables and decouple from by ref passed in values, if you change from this...
Code:
(L)Parameters tcDirectory, tcFile &&whatever original parameter names
...to thiss...
Code:
(L)Parameters tP1, tP2 && new parameter names
LOCAL tcDirectory, tcFile &&original parameter names, now declared as LOCAL variables
tcDirectory=m.tP1
tcFile=m.tP2
...

That way the rest of the code also does not need any change Rick feared to need to do. The parameter renaming has no effect on the code calling, and creating LOCAL variables with the previous paramter names means code can run unchanged. And, obviously, you could also keep all but the one parameter name you want to decouple from.

Anyway, the only reason I posted this last bit of information is, that it could easily mean my earlier judgement that by ref passing parameters is rarely used in legacy code, but DO is usually used in legacy code I wonder if the other syntax allowed was not available back then.

I'm still also inclined to say that such an analysis at runtime isn't giving a better quality of knowing how parameters are passed in geneeral. The question is do you cover all calls coming to the function/prg from anywhere else in the project code. I'd be more sure I catch all cases by searching the code for all calls and seeing how they are made and also whether UDFPARMS is set anywhere at all.

Chriss
 
Chris Miller said:
And I get it as a one time analysis to decide how to change a function, introducing a new variable decoupling from a variable passed in by reference or not. It was never intended for permanent runtime decision to have code catering for both cases.

Correct. And it was more of a mental exercise and a realization why I've seen code where Microsoft does that seemingly unnecessarily at time in some of their old VFP examples.

It would be nice to have a feature like this:
Code:
FUNCTION my_function
LPARAMETERS tcDirectory, tcFilename

* Add this ability:
VALIZE(tcDirectory)
VALIZE(tcFilename)

Where you force convert them to value for your local use. It would allow logic like this:

Code:
IF NOT ADDBS(UPPER(FULLPATH(tcDirectory))) == UPPER(tcDirectory)
    VALIZE(tcDirectory)
    tcDirectory = ADDBS(UPPER(FULLPATH(tcDirectory)))
ENDIF

It would allow the ability to switch the local parameter passed by reference or value, to now be a value from this point forward regardless of how it was passed in.

In logic, it would be like this:
Code:
void my_function(char* directory, char* filename)
{
    char _directory[_MAX_PATH];

    make_fullpath(_directory, directory);
    if (strcmp(_directory, directory) != 0)
        directory = &_directory;  // Make it be our local copy, but continue using directory as a pointer
}

You basically create a local copy of the variable and still continue to use the pointer, but the local copy is hidden, unlike the above example where it was required to be coded.

I just naturally think of things like this while I'm coding. How it might be be nice to have a particular feature or ability to do something that's not currently available, how it would make the code nicer.

A lot of features I added to Visual FreePro were for supporting legacy code. I created a new LOGICALX data type, which allowed you to convert a boolean .T./.F. value to now be a .T./.F./ plus also a range of other settings (up to 252 additional as it uses one byte to store ASCII 0..255 internally. When evaluated as a boolean it would always return .t. and .f., but when evaluated as a logicalx() it would retrieve the actual value, which is basically a small integer in the range of 0=.f., 32=.f., 255=.t. 1 to 126=other possible settings which evaluate to .f., and 127-254=other possible settings which evaluate to .t.). It was a way to allow new code to run atop legacy code (without changing any legacy code or its use of the logical values), but knowing now in the new code that it's not only .t. or .f., but also other settings which allow for expansion. The old code returned .t. or .f. if it connected to the remote server. The new one returns a logicalx extension, which evaluates to .t. or .f. in legacy code, but in logicalx() testing, would return the actual value, which could indicate a connection speed setting, or a reason for failure.

Lots of other ideas like that went into Visual FreePro's design. Anytime you'd see the X extended on to something, like LOGICALX(), DATEX(), DATETIMEX(), etc.

--
Rick C. Hodgin
 
The feature you want I'd rather implement as an annotation of BYVAL to the parameter list, when you absolutely want to prevent a call by reference.

The other thing you can always do is copy the incomping patameters, as I demonstrated, it just has the often unnecessary step to copy the parameters. Where you never change a parameter it wouldn't matter whether it's passed in by ref or by val.

And all in all your new VisualFreeePro could simple use what I already observed earlier other languages also do and make it a part of the function parameterization declaration whether parameters are received by value (default) or by reference.

I don't know the reson DO...WITH makes by ref passing of parameters the default, I could imagine because that means the entries for the parameter variables just need to point back to the by ref passed variables aka just define a sysnonym, if they are variables in the call, and that makes it faster than copying the value into a new real local parameter variable.

The way Foxpro does this makes no good sense, though it is clearly limited to user defined functions, therefore the setting also is called [highlight #FCE94F]UDF[/highlight]PARMS. The counter example of GridHitTest() also only really works with byref parameters and demonstrates that it's a point you define when designing the function, not when you call them.

Chriss
 
I like the BYVAL idea.

A lot of my C++ code has the C# ref keyword used in it so I can tell at the point of calling a function what it is.

Code:
#define ref

I think a kind of negotiated contract is what's required. The function defines what it needs and that's cast in stone when explicitly stated. When not, UDFPARAMS is used, or BYVAL/BYREF, or the other syntaxes like @var, or (var).

--
Rick C. Hodgin
 
Hello Rick,

allowing both the contract definition approach and the choice by the caller in your own language surely is allowing simpler migration from VFP code, but isn't it undermining the parameter byval/byref declaration approach when the call can still pass in byref where it's not demanded or foreseen by the UDF?

Or are you talking about the trick to #defne some keywords to be replaced by nothing, so the VFP code can have the syntax without it having a real effect? Just for documentation.

Does it work? Let's see...
Code:
Clear
On Error ? 'Error in Line'+Transform(Lineno())+':'+Message()

Create Cursor test (id integer, selfreference integer)
Index on selfreference tag ref

#Define REF
Local lnCounter 
lnCounter = 1
Do While Reccount()<10
   Insert into test values(Ipp(@m.lnCounter), m.lnCounter)
EndDo
Locate
Browse Name myBrowse Nowait
Store 40 To myBrowse.Top, myBrowse.left

Set Order To ref
Seek 5
? selfreference 

Function Ipp(tnIndex As Integer REF)
   m.tnIndex = m.tnIndex+1
   Return m.tnIndex-1

Function ppI(tnIndex As Integer REF)
   m.tnIndex = m.tnIndex+1
   Return m.tnIndex

Function Imm(tnIndex As Integer REF)
   m.tnIndex = m.tnIndex-1
   Return m.tnIndex+1

Function mmI(tnIndex As Integer REF)
   m.tnIndex = m.tnIndex-1
   Return m.tnIndex

See thread184-1821501 for the origin of the post/pre increment/decrement functions using byref parameters.

It does not help that VFP is case insensitive here. But you see there's a danger in defining a short named constant to mean nothing, as something with that name could already exist, like an index. SET ORDER TO REF still works as SET ORDER TO is a valid command, too, but then the SEEK fails for no obvious reasons, if you don't know about that constant.

At least the functions work and acessing the field selfreference does not get altered to instead try to access a field called selference, the search&replace preprocessor mechanism that replaces constants with their definition is limited to whole words, but it still can catch you.

That problem of course exists with any constant, also when it isn't just replaced by nothing but by whatever it's defined as. So that's not only a danger of empty definitions, but a general warning about using too short constant names. And a reminder that even going by the norm to have constant all uppercase does not help to prevent such name collisions. Your new language better does not imitate that, maybe it is a good idea to make it cse sensitive, though I'm not sure it would cause more damage than help.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top