Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

ByVal in functions

29 views
Skip to first unread message

Dave Rado

unread,
Jan 11, 2002, 10:12:12 AM1/11/02
to
I'm confused about ByVal (in a function argumant); and by when you'd want to
use it and why. According to Help:

ByVal Optional. Indicates that the argument is passed by value.
ByRef Optional. Indicates that the argument is passed by reference. ByRef is
the default in Visual Basic.

Under ByVal it goes on to say:

A way of passing the value of an argument to a procedure instead of passing
the address. This allows the procedure to access a copy of the variable. As
a result, the variable's actual value can't be changed by the procedure to
which it is passed.

Under ByRef:

A way of passing the address of an argument to a procedure instead of
passing the value. This allows the procedure to access the actual variable.
As a result, the variable's actual value can be changed by the procedure to
which it is passed. Unless otherwise specified, arguments are passed by
reference.

~~~~~~~

To this bear of (very!) little brain, the above makes even less sense than
George W. on a bad day! Can anyone translate, into simple everyday English,
*why* ByVal is sometimes better than the default of ByRef, *when* it is
better, and what exactly the benefits of using it are? Surely if you simply
didn't want to be able to change the value, you'd use a constant rather than
a variable?

Many thanks, and regards

Dave


Dave Lett

unread,
Jan 11, 2002, 11:04:46 AM1/11/02
to
Hi Dave,

From what I've gleaned
ByVal is better because it won't change the value of your variable.
ByRef is more efficient when you have a large string (it might be too
inefficient to make a copy of it).

You wouldn't necessarily make it a constant:

'---------------------------------
Public Sub Main()
CallFunction (Activedocument.Fullname)
End Sub
'---------------------------------
Public Function CallFunction(ByVal DocName as String) as Boolean
' procedures
' if DocName exists on another drive in the same directory, for example
End Function


"Dave Rado" <dr...@onetel.net.uk> wrote in message
news:uUh3MLrmBHA.2248@tkmsftngp07...

Dave Rado

unread,
Jan 11, 2002, 11:51:59 AM1/11/02
to
Hi Dave

Thanks for the feedback. I'm still feeling a bit lost, though and am
probably being a bit thick.

I don't think you can be saying this, but it looks to me as if you're
saying that if, in your function you were to set the DocName variable to a
different value, then if you had used ByRef instead of byVal, the value of
the ActiveDocument's.Fullname would also change - but that can't be what
you're saying, so I'm still lost.

When you say "it won't change the value of your variable", I presume you
don't mean within the function itself, because I get no error when I try
changing it - for instance:

Public Sub Main()
CallFunction ("C:\temp\Doc1.doc")
End Sub
'---------------------------------
Public Function CallFunction(ByVal DocName As String) As Boolean
DocName = "C:\temp\Doc2.doc"
Msgbox DocName
'Returns "C:\temp\Doc2.doc"
End Function

~~~
Regards

Dave


"Dave Lett" <dl...@menusoft.com> wrote in message
news:%4E%7.6737$0g.1...@iad-read.news.verio.net...

Astrid

unread,
Jan 11, 2002, 12:01:25 PM1/11/02
to
Hi Dave,

AFAIK the difference is that that the value of the original argument you
pass, can change if you use byref.
If you use ByVal the original value is never changed, see the following
example:

--------------------------------------------------
Sub TestByRefByVal()
Dim sByVal As String
Dim sByRef As String
Dim sText As String

sText = "Original"
sByRef = TestByRef(sText)
Debug.Print sText & " " & sByRef
sText = "Original"
sByVal = TestByVal(sText)
Debug.Print sText & " " & sByVal

End Sub

Function TestByRef(ByRef sText As String) As String
sText = sText & sText
TestByRef = sText
End Function

Function TestByVal(ByVal sText As String) As String
sText = sText & sText
TestByVal = sText
End Function
--------------------------------------------------


Hope this helps,
regards,
Astrid

So that all can benefit from the discussion, please post all follow-ups to
the newsgroup.
Visit the MVP Word FAQ site at http://www.mvps.org/word/

"Dave Rado" <dr...@onetel.net.uk> schreef in bericht
news:uA23VCsmBHA.2212@tkmsftngp05...

Dave Rado

unread,
Jan 11, 2002, 1:15:13 PM1/11/02
to
Thanks Astrid. :-)

So correct me if I'm wrong, but there's no need to bother with ByVal
*unless* both the following conditions apply:

a) You are passing a variable to the function (rather than just a value),
AND
b) In the function itself, you have given the variable the same name as you
gave it in the calling procedure?

I.e. in your example, if you'd had:

Function TestByRef(sAnotherText As String) As String

Then there would be no advantage to using ByVal - *except* in the sense that
if you'd used ByVal, someone else could come along later and safely call
your function using the same variable name as you had used, and would have
no problems, but if you hadn't used ByVal, they'd have to be more careful.

And also (from what the other David said), the only reason that ByRef is the
default is that for very large strings it is more memory-efficient.

Do I get a gold star? <g>

Regards

Dave


"Astrid" <ast...@mvps.org> wrote in message
news:OxauGHsmBHA.1484@tkmsftngp03...

Dave Lett

unread,
Jan 11, 2002, 1:21:26 PM1/11/02
to
Sorry Dave,

I wasn't finished when something happened and my message was sent, and I was
still kind of in the thought process for the example I wanted to use.

You weren't being thick, I was only trying to show an example in which you
might not want to have the calling procedure use a constant. Here's a better
example:

'---------------------------------
Public Sub Main()
Dim doc As Document
For Each doc In Word.Documents
doc.Activate
Call TestFunction(ActiveDocument.FullName)
Next doc
End Sub
'---------------------------------
'---------------------------------
Public Function TestFunction(ByVal DocName As String) as Boolean


' procedures
' if DocName exists on another drive in the same directory, for example
End Function

'---------------------------------

Now, if you want to see a routine that will change the value of a variable,
then the following example will display a message box with 2 in it, even
though it appears as if the calling routine were passing 1 to TestFunction2
(and, therefore, making it easier to track down errors).

'---------------------------------
Public Sub Main()
Dim s As Integer
s = 1
Call TestFunction1(s)
Call TestFunction2(s)
End Sub
'---------------------------------
'---------------------------------
Public Function TestFunction1(ByRef s As Integer) As Integer
s = s + 1
End Function
'---------------------------------
'---------------------------------
Public Sub TestFunction2(ByRef s As Integer)
MsgBox s
End Sub
'---------------------------------

If you change the ByRef to ByVal, then the MsgBox displays 1. Also, if you
don't "properly" call the function (i.e., type "Call" in front of the
function you're calling), then the MsgBox will display 1.

FYI, rumor has it that the next release of VB will default to ByVal (not
ByRef).

"Dave Rado" <dr...@onetel.net.uk> wrote in message

news:uA23VCsmBHA.2212@tkmsftngp05...

AK Bohman

unread,
Jan 11, 2002, 1:35:29 PM1/11/02
to
Dave

> Do I get a gold star? <g>

You are!

As I've understood things. The ByRef is actually informing the function
about an address in memory (a pointer) where data is being stored - hence a
possibility to modify data. The ByValue is passing a copy of the stored data
to be used for a foolproof calculation of preparation of some kind.

This is what MS Office97 Developers Handbook, MS Press, Christine Solomon
says about pointers and handles (page 256):
-------------------
USING HANDLES AND POINTERS
Because of the Windows environment contains an unknown number of objects
(such as windows) at any given time and because the variables used in
programming can be quite large, Windows uses numeric identifiers to make
them easier to manipulate. Two types of numeric identifiers are available:
handles and pointers.

* A handle is a reference to number that Windows uses to identify an object,
such as a window or a task. Nearly every object in Windows has a handle, and
a large percentage of the API functions either require a handle as an
argument or return a handle. Some do both.

* A pointer is a memory address. Although pointers are widely used in
programming, VBA doesn't use them. If you look at the API documentation,
you'll see that many API functions takes pointers as arguments. When calling
these functions from VBA, use a variable of the type indicated by the
pointer and **don't** use the ByVal keyword. For example, if one of the
arguments to an API function is a pointer to a string - in other words, a
memory address that stores strings - use a string variable.
---------------
--
/Anna Bohman
Bra Utbildning AB, Sweden
a...@buab.se
http://www.buab.se
-------------------------------------------------------------------------

"Dave Rado" <dr...@onetel.net.uk> wrote
news:#mZLexsmBHA.1620@tkmsftngp02...

Dave Rado

unread,
Jan 11, 2002, 2:21:15 PM1/11/02
to
Hi Anna

~~~~~


* A pointer is a memory address. Although pointers are widely used in
programming, VBA doesn't use them. If you look at the API documentation,
you'll see that many API functions takes pointers as arguments. When calling
these functions from VBA, use a variable of the type indicated by the
pointer and **don't** use the ByVal keyword. For example, if one of the
arguments to an API function is a pointer to a string - in other words, a
memory address that stores strings - use a string variable.

~~~~~
That just confused me even more! <g> But then I've never seen any API
documentation - I just steal other people's code when I have to! I imagine
it would make more sense to me if I'd seen the documentation.

Regards

Dave

Dave Rado

unread,
Jan 11, 2002, 2:22:14 PM1/11/02
to

Hi Dave

Your second example makes good sense to me, and is similar to Astrid's
example. I'm not with you regatding the point of using ByVal in your first
example, though, as you're not paassing a variable to the function there,
you're passing a value that can't be changed. What am I missing? Also, did
you think my summing up in my reply to Astrid was accurate?

Regards

Dave

"Dave Lett" <dl...@menusoft.com> wrote in message

news:65G%7.6748$0g.1...@iad-read.news.verio.net...

Jay Freedman

unread,
Jan 11, 2002, 3:11:55 PM1/11/02
to
Hi, Dave,

Somebody's probably going to nuke me on this one, but I've rarely (maybe
never) see a ByVal declaration that *wasn't* involved in an API call.

The reason for this is that VBA doesn't really need ByVal within its own
world. All the examples I've seen that use only VBA are contrived to show
how it works, but it's stuff you'd never do in any "real" program.

In the world of API functions -- which are all written in C or C++, where
pointers are a fact of life -- a parameter that you pass to a function
*must* match the type of the variable declared in the function's header. If
the function expects a pointer, you pass a pointer (in VBA lingo, ByRef). If
the function expects an integer or a character string, you pass a value
(using ByVal), because passing a pointer instead will just get you an error.
The inter-language calling facility isn't smart enough to interchange
pointers and values.

--
Regards,
Jay Freedman
Microsoft Word MVP Word MVP FAQ site: http://www.mvps.org/word

"Dave Rado" <dr...@onetel.net.uk> wrote in message

news:eUi7ZWtmBHA.2472@tkmsftngp04...

Dave Lett

unread,
Jan 11, 2002, 4:06:00 PM1/11/02
to
Hi Dave,

In your first message, you wrote "Surely if you simply didn't want to be
able to change the value, you'd use a constant rather than a variable".
Perhaps poorly conceived/executed, my first example (where you iterate
through the documents and pass the .FullName) was an attempt to demonstrate
that you can pass something that varies (and in my mind, therefore, a
variable) to another function and, in that case, you couldn't use a constant
(I'm thinking of Public Const sDoc as String = "Test.doc").

"Also, did you think my summing up in my reply to Astrid was accurate?" Not


entirely. You wrote:
a) You are passing a variable to the function (rather than just a value),
AND
b) In the function itself, you have given the variable the same name as you
gave it in the calling procedure?

I think that i disagree with part b) (if I understand you correctly). If you
run the following, then the MsgBox displays 2 (the variable of the function
[iAnyInteger] is not the same name as the variable [s] in the calling
procedure), which is why I disagree with part b, assuming I understand you
correctly (is that enough hedging?). If you add ByVal before iAnyInteger,
the MsgBox displays 1.


'---------------------------------
Public Sub Main()
Dim s As Integer
s = 1
Call TestFunction1(s)
Call TestFunction2(s)
End Sub
'---------------------------------
'---------------------------------

Public Function TestFunction1(iAnyInteger As Integer)
iAnyInteger = iAnyInteger + 1
End Function
'---------------------------------
'---------------------------------
Public Function TestFunction2(iAnyInteger As Integer)
MsgBox iAnyInteger
End Function
'---------------------------------

Here's the key to the above example: if you pass the variable s to the
parameter iAnyInteger, then s and iAnyInteger share the same memory
location, which means that if you change iAnyInteger in the routine, it also
changes the value of s. To extend, if you change (in the example above)
'---------------------------------
Public Function TestFunction2(iAnyInteger As Integer)
MsgBox iAnyInteger
End Function
'---------------------------------

To

'---------------------------------
Public Function TestFunction2(iSecondInteger As Integer)
MsgBox iSecondInteger
End Function
'---------------------------------

THEN
s, iAnyInteger, and iSecondInteger share the same memory location, and a
change to one of them affects the other two. This code change will display 2
in a MsgBox.

HTH

"Dave Rado" <dr...@onetel.net.uk> wrote in message

news:eNRBbWtmBHA.2472@tkmsftngp04...

Jonathan West

unread,
Jan 11, 2002, 5:05:42 PM1/11/02
to
Hi Dave,

Best to understand this with a little example. Run the following code and
note the values of i that you print.

Sub Main
Dim i As Long
i = 1
AddOneByVal i
Debug.Print i
AddOneByRef i
Debug.Print i
AddOneByRef (i)
End Sub

Sub AddOneByVal(ByVal num as Long)
num = num + 1
End Sub

Sub AddOneByRef(ByRef num as Long)
num = num + 1
End Sub

Passing a variable by value passes a copy of the variable to the called
routine, where you can modify it to your heart's content, and nothing will
happen to it in the calling routine

Passing it by reference passes the actual variable itself (strictly speaking
a pointer to it, but you don't need to get into all the details), so if you
modify it in the called routine, that modification also applies in the
calling routine.

If you enclose an argument in parentheses, when calling a subroutine, you
override the ByRef status of the argument, and it is passed by value.

Therefore given the subroutines I defined above, "AddOneByVal i" and
"AddOneByRef (i)" do exactly the same thing.

--
Regards
Jonathan West - Word MVP
MultiLinker - Automated generation of hyperlinks in Word
Conversion to PDF & HTML
http://www.multilinker.com
Word FAQs at http://www.multilinker.com/wordfaq
Please post any follow-up in the newsgroup. I do not reply to Word questions
by email

"Dave Rado" <dr...@onetel.net.uk> wrote in message

news:uUh3MLrmBHA.2248@tkmsftngp07...

Astrid

unread,
Jan 11, 2002, 5:38:03 PM1/11/02
to
Hi Dave,

> Do I get a gold star? <g>

Like Anna told you, you are (and have been for quit a while now .. )
But ok, we'll award you with 50 additional bonus points ;-)

Regards,

AK Bohman

unread,
Jan 12, 2002, 7:32:53 AM1/12/02
to
Jay

> If the function expects a pointer, you pass a pointer (in VBA lingo,
ByRef). If
> the function expects an integer or a character string, you pass a value
> (using ByVal), because passing a pointer instead will just get you an
error.

I basically agree. It is somewhat confusing studying the Win API syntax (not
skilled reading C/C++). A pointer can be defined both as a pointer to a
string and a pointer to a location. In the example below a function with
pointers to both a string an a location. The pointer itself is not the key,
as described in the previous quote I posted, but the pointer and type of
data.

Example Win API (Win32 Programming bible, R Simon, page 1019) :
RegOpenKeyEx(hkey, lpszSubKey, dwReserved, samDesired, phkResult)
lpszSubKey = Pointer to a null-terminated string that contains the name of
the subkey to open.
phkResult = Pointer to location where the handle to the key is stored.

Example of the same function in VBA:
Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" ( _
ByVal lngKey as Long, _
ByVal lpSubKey as String, _ 'Pointer
ByVal ulOptions as Long, _
ByVal samDesired as Long, _
phkResult as Long) _ 'Pointer
As Long

I'm starting to agree with Dave, the more I read the more confused I get.
This is what MS Office 2000, Visual Basic programmers guide, page 397
states:

"Note: Although the ByVal keyword appears in front of some arguments of type
String, strings are always passed to Windows API function by reference."

On the same page higher up it says:
"Also many arguments passed to DLL functions are passed by value. By
default, arguments in VBA are passed by reference, so it's imperative that
you include the ByVal keyword in the function definition when the DLL
function requires that an argument be passed by value. Omitting the ByVal
keyword in a function definition may cause an application error in some
cases. In other cases the VBA run-time error number 49 "Bad DLL calling
conventions" may occur.

Passing an argument by reference passes the memory location of that argument
to the procedure being called. If the procedure modifies the argument's
value, it modifies the only copy of the argument, so when execution returns
to the calling procedure, the argument contains the modified value."

And a paragraph further down:
"Because passing by reference allows an argument to be modified in memory,
if you incorrectly pass an argument by reference, the DLL function may
overwrite memory that it should not, causing an error or otherwise
unexpected behavior. Windows maintains many values that should not be
overwritten. For example, Windows assigns to every window a unique 32-bit
identifier called a handle. Handles are always passed to API functions by
value, because if Windows were to modify a window's handle, it would no
longer be able to track that window."

--
/Anna Bohman
Bra Utbildning AB, Sweden
a...@buab.se
http://www.buab.se
-------------------------------------------------------------------------

"Jay Freedman" <jay.fr...@verizon.net> wrote
news:#lQv5wtmBHA.2472@tkmsftngp04...

Dave Rado

unread,
Jan 12, 2002, 11:45:29 AM1/12/02
to
Hi Dave

Re:
~~~~~~


b) In the function itself, you have given the variable the same name as you
gave it in the calling procedure?

I think that i disagree with part b) (if I understand you correctly). If you
run the following, then the MsgBox displays 2 (the variable of the function
[iAnyInteger] is not the same name as the variable [s] in the calling
procedure), which is why I disagree with part b, assuming I understand you
correctly (is that enough hedging?). If you add ByVal before iAnyInteger,
the MsgBox displays 1.

~~~~~~

Ahh, I hadn't realised that - thanks.

Regards

Dave


Dave Rado

unread,
Jan 12, 2002, 11:53:27 AM1/12/02
to
Hi Anna

~~~~~~~


I'm starting to agree with Dave, the more I read the more confused I get.

~~~~~~~
<g>

~~~~~~~


This is what MS Office 2000, Visual Basic programmers guide, page 397
states:

"Note: Although the ByVal keyword appears in front of some arguments of type
String, strings are always passed to Windows API function by reference."

~~~~~~~
That certainly sounds like an oxymoron! The other quotes you included futher
down seemed to make sense, though.

Regards

Dave


Dave Rado

unread,
Jan 12, 2002, 11:48:03 AM1/12/02
to
Thanks Jonathan

Interesting about the effect of the brackets - the way they force ByVal! I
wonder if that's an anomaly, or whether there's some reason for it? <g> The
fact that you don't get this behaviour if you use Call AddOneByRef(i), or if
you make the called procedure a function and use Num = AddOneByRef(i),
makes it look to me a bit like an anomaly?

I'm also interested in what you (and others) think about Jay's challenge -
his suggestion that, although the code examples you, Dave and Astrid
provided all illustrate the difference very well, you'd be hard put to find
any *real world* projects where your code wouldn't work properly unless you
used ByVal. Have you ever written a VBA project in anger that didn't make
API calls, and in which using ByVal really mattered?

BTW, I can think of an example of where you might *prefer* ByRef - if
calling a procedure and passing a range variable as an argument, you might
want to know, in your calling procedure, where the range had ended up. I've
always made my range variables Public in that scenario, not realising that I
didn't have to ...

Regards

Dave

"Jonathan West" <jw...@mvps.org> wrote in message
news:eknoAzumBHA.580@tkmsftngp02...

Jonathan West

unread,
Jan 12, 2002, 4:30:17 PM1/12/02
to
Hi Dave

"Dave Rado" <dr...@onetel.net.uk> wrote in message

news:eep7Hq4mBHA.1004@tkmsftngp07...


> Thanks Jonathan
>
> Interesting about the effect of the brackets - the way they force ByVal! I
> wonder if that's an anomaly, or whether there's some reason for it? <g>
The
> fact that you don't get this behaviour if you use Call AddOneByRef(i), or
if
> you make the called procedure a function and use Num = AddOneByRef(i),
> makes it look to me a bit like an anomaly?

No, it's deliberate. Note that for a function call, there is no space
between the function name and the opening parenthesis. When forcing a ByVal
parameter on a subroutine call, the VBA editor inserts a space.

I keep track of some discussions among the VB MVPs, and this is a known and
documented behaviour from way back. I couldn't find anything about it in the
VBA help though. Maybe it was assumed that us Office types are too thick to
take advantage of it!

By the way, it works with function calls as well, you just have to put a
*second* pair of parentheses round the argument. Try this code.

Sub Main()
Dim i As Long
Dim j As Long


i = 1
AddOneByVal i
Debug.Print i
AddOneByRef i
Debug.Print i
AddOneByRef (i)

Debug.Print i
j = AddOneByValFunction(i)
Debug.Print i
j = AddOneByRefFunction(i)
Debug.Print i
j = AddOneByRefFunction((i))
Debug.Print i
End Sub

Sub AddOneByVal(ByVal num As Long)


num = num + 1
End Sub

Sub AddOneByRef(ByRef num As Long)


num = num + 1
End Sub

Function AddOneByValFunction(ByVal num As Long)


num = num + 1

End Function

Function AddOneByRefFunction(ByRef num As Long)


num = num + 1

End Function

Things can get really hairy if you are not careful. Think carefully about
what results you would expect to see from the following code fragment. Then
run it to see if you are right :-)

Sub TestingByRef()
Dim i As Long, j As Long
i = 2
j = 3
i = i + AddBadlyByRef(i, j)
Debug.Print i
i = 2
j = 3
i = AddBadlyByRef(i, j) + i
Debug.Print i
i = 2
j = 3
i = AddBadlyByRef((i), j) + i
Debug.Print i
End Sub

Function AddBadlyByRef(num1 As Long, num2 As Long)
num1 = num1 + num2
AddBadlyByRef = num1
End Function

Now, having seen the results, try and work out *why* they are like that!

>
> I'm also interested in what you (and others) think about Jay's challenge -
> his suggestion that, although the code examples you, Dave and Astrid
> provided all illustrate the difference very well, you'd be hard put to
find
> any *real world* projects where your code wouldn't work properly unless
you
> used ByVal.

No, there aren't any. This is because you always have the option in the
called routine of not changing the value of the parameter that has been
passed to it, but instead assigning its value to another variable, and
modifying that instead. Effectively, you manually implement ByVal within the
called routine. I suspect that many people who are not aware of the
availability and meaning of ByVal do this right now.

> Have you ever written a VBA project in anger that didn't make
> API calls, and in which using ByVal really mattered?

There is potentially a speed issue. It is probably quicker to use ByVal than
to take a copy. I haven't ever attempted to benchmark it, and to be honest,
the time spent in Word VBA programs dealing with simple variables is usually
trivial compared to the time the program usually spends manipulating objects
in the Word object hierarchy. Therefore, any difference is unlikely to have
a significant performance benefit for us.

As I've shown above, there is also potentially a readability & debugging
issue. If you have a big project, minimizing potential interactions between
routines is a thoroughly good idea. Therefore, from the point of view of
defensive programming, you ought to set all parameters to be passed ByVal
unless you specifically want to return a modified value. If you do want to
pass the modified value back to the calling routine, you should explicitly
declare the parameter using ByRef, to avoid confusion. I must admit that I
don't bother to do this in my own programs, though I know that some of the
VB MVPs do.

It might have been better for the default parameter passing convention to
have been ByVal. However, passing ByRef came first in BASIC, and ByVal was
an option added later. You might be interested to know that in VB.NET the
default has been changed, and unless specified otherwise, all parameters are
passed ByVal. If and when VB.NET comes to Office, that is one thing (out of
several dozen) that might cause a fair bit of confusion. The use of
parentheses when calling subroutines and functions has also been changed,
and that *will* cause confusion!

>
> BTW, I can think of an example of where you might *prefer* ByRef - if
> calling a procedure and passing a range variable as an argument, you might
> want to know, in your calling procedure, where the range had ended up.
I've
> always made my range variables Public in that scenario, not realising that
I
> didn't have to ...

Arrghh. My trivial little code sample only dealt with simple types. As far
as I can tell, the rules for objects are different and altogether more
obscure, and quite frankly I can't say I understand them sufficiently to be
able to explain them here with any degree of confidence. I'll see if I can
get a coherent description of this from one of the VB MVPs. The principles
involved should apply to any objects, so I wouldn't expect that there is
anything exceptional about the Word object model in this respect. If I find
out anything more, I'll report back here.

Jay Freedman

unread,
Jan 13, 2002, 12:06:03 AM1/13/02
to
"Dave Rado" <dr...@onetel.net.uk> wrote in message
news:eABjIq4mBHA.1516@tkmsftngp07...

The reason strings are treated this way is that there's no way to know
in advance how big a string will be at run time, and therefore how
much memory to allocate on the stack when setting up the function
call. The interpreter or compiler always knows exactly how many bytes
a Long or a Single needs, but Strings are unpredictable. So VBA passes
only a pointer containing the address of the first byte of the string.
(Actually, that's a highly sanitized version of what really happens...
if you want an eyeful, look up BSTRING in the MSDN Library.)

Dave Rado

unread,
Jan 13, 2002, 5:13:10 AM1/13/02
to
Hi Jay

That may explain why they have to be passed ByRef, but it doesn't explain
why the statement isn't an oxymoron. :-)

And I duck whenever I see an eyeful coming, I'm afraid! <g>

Regards

Dave

"Jay Freedman" <jay.fr...@verizon.net> wrote in message
news:uPumQA$mBHA.2112@tkmsftngp02...

Dave Rado

unread,
Jan 13, 2002, 5:17:26 AM1/13/02
to
Hi Jonathan

~~~~~~~


I keep track of some discussions among the VB MVPs, and this is a known and
documented behaviour from way back. I couldn't find anything about it in the
VBA help though. Maybe it was assumed that us Office types are too thick to
take advantage of it!

~~~~~~~
Or maybe we're not nerdish enough! <g,d&rv,vf>

~~~~~~~


By the way, it works with function calls as well, you just have to put a
*second* pair of parentheses round the argument.

~~~~~~~
And with Call.


~~~~~~~


Things can get really hairy if you are not careful. Think carefully about
what results you would expect to see from the following code fragment. Then
run it to see if you are right :-)

~~~~~~~
Nice brain teaser. :-)


~~~~~~~


No, there aren't any. This is because you always have the option in the
called routine of not changing the value of the parameter that has been
passed to it, but instead assigning its value to another variable, and
modifying that instead.

~~~~~~~
Well if we're talking workarounds, another is to reset the value of
the variable in the calling procedure, immediately before or after each
call - which I suspect is probably what I would have done if the need had
ever arisen (I can't honestly remember whether it ever has). But I think Jay
was questioning whether the need ever does genuinely arise in the real-world
VBA projects we write? (Though I take your point about defensive
programming). And as I pointed out in my previous email, sometimes one might
actually prefer the value of a variable to be changed by the function it was
passed to. One limitation of functions is that in the ordinary way, you can
only get one result back from them; but if you pass variables ByRef, then
presumably you can get as many results back from a single function as you
like?


~~~~~~~


As I've shown above, there is also potentially a readability & debugging
issue.

~~~~~~~
Which, surely, is the real reason for not using brackets to specify ByVal?
Much less readable than writing "ByVal", and doesn't even save you any
typing time, really.


~~~~~~~


If you have a big project, minimizing potential interactions between
routines is a thoroughly good idea. Therefore, from the point of view of
defensive programming, you ought to set all parameters to be passed ByVal
unless you specifically want to return a modified value. If you do want to
pass the modified value back to the calling routine, you should explicitly
declare the parameter using ByRef, to avoid confusion. I must admit that I
don't bother to do this in my own programs, though I know that some of the
VB MVPs do.

~~~~~~~
LOL! So in summary, everyone should specify ByVal or ByRef, but do as I say,
and not as I do! <g>


~~~~~~~


If and when VB.NET comes to Office, that is one thing (out of
several dozen) that might cause a fair bit of confusion. The use of
parentheses when calling subroutines and functions has also been changed,
and that *will* cause confusion!

~~~~~~~
It already has (see thread "How to add a document to another document") -
seems like a pointless change to me, but I guess I must be missing
something(). <g>


~~~~~~~


Arrghh. My trivial little code sample only dealt with simple types. As far
as I can tell, the rules for objects are different and altogether more
obscure, and quite frankly I can't say I understand them sufficiently to be
able to explain them here with any degree of confidence. I'll see if I can
get a coherent description of this from one of the VB MVPs. The principles
involved should apply to any objects, so I wouldn't expect that there is
anything exceptional about the Word object model in this respect. If I find
out anything more, I'll report back here.

~~~~~~~
You're right, ByVal appears not to work with range variables - I just tried
the following:

Option Explicit

Sub Main()

Dim MyRange As Range
ActiveDocument.Range.Text = "Hello World"
Set MyRange = ActiveDocument.Range(0, 0)
MoveRangeByVal MyRange
MyRange.Select
Stop
'MyRange moved!!

Set MyRange = ActiveDocument.Range(0, 0)
MoveRangeByRef MyRange
MyRange.Select
Stop
'MyRange moved, but this time you'd expect it to

End Sub

Sub MoveRangeByVal(ByVal MyRange As Range)
MyRange.End = ActiveDocument.Range.End
End Sub

Sub MoveRangeByRef(ByRef MyRange As Range)
MyRange.End = ActiveDocument.Range.End
End Sub

***

Yet, based on what Anna's been quoting from her learned tomes, it seems that
objects are regularly passed ByVal in API calls - unless I'm
misunderstanding her quotations, which is extremely probable!

Regards

Dave


John Nurick

unread,
Jan 13, 2002, 5:12:31 PM1/13/02
to
On Fri, 11 Jan 2002 15:11:55 -0500, "Jay Freedman"
<jay.fr...@verizon.net> wrote:

>Somebody's probably going to nuke me on this one, but I've rarely (maybe
>never) see a ByVal declaration that *wasn't* involved in an API call.
>
>The reason for this is that VBA doesn't really need ByVal within its own
>world. All the examples I've seen that use only VBA are contrived to show
>how it works, but it's stuff you'd never do in any "real" program.

It's a matter of idiom, really. I cut my programming teeth in Pascal,
where the default is to pass parameters (arguments) by value, and one
cheerfully writes code like this (air code alert!) which cheerfully
uses the parameters as local variables:

function Nonsense(X: Integer; Y: Integer): Integer;

begin
X := inc(X) * X / 11;
Y := OtherFunction(Y);
Nonsense := X + Y;
end;

That seemed natural (to me at least) in Pascal, but I've never got
into the habit of doing it in VBA. Certainly the Pascal convention is
safer because there is no danger of introducing bugs caused by a
procedure quietly changing the value of a variable outside itself.
John Nurick [Access MVP]

Please do not respond by email, but to the newgroup.

Jay Freedman

unread,
Jan 13, 2002, 8:26:35 PM1/13/02
to
"John Nurick" <jnu...@onebox.com> wrote in message
news:gas34ucm91coto45s...@4ax.com...

Yes, that convention in Pascal has always made more sense to me (one
of my responsibilities at work is support of a 15-year-old network
configuration program written in Pascal and Algol).

Of course, Pascal has its own demons, especially in large and poorly
documented programs (see above <g>). The biggest one is nested
procedures that may contain local variables with the same names as
variables declared in an outer scope -- or may not, so you can have a
rat's nest of procedures that call each other and may or may not
change variables that they all share. It's damned hard, coming in from
the outside, to determine which variable is affected by any particular
statement.

Dave Rado

unread,
Jan 14, 2002, 7:54:59 AM1/14/02
to
What I realy meant to say was that I still don't see how a variable can
simultaneously be passed ByVal and not be passed byVal - that still seems
like an oxymoron to me. Is the VBA engine somehow holding the "ByVal"
variable in one area of memory and passing a copy of it on to the DLL
"ByRef", behind the scenes?

Regards

Dave

"Dave Rado" <dr...@onetel.net.uk> wrote in message

news:ebYN#wBnBHA.956@tkmsftngp05...

Jay Freedman

unread,
Jan 14, 2002, 9:56:18 AM1/14/02
to
Hi, Dave,

No, nothing that devious. <g> If you put ByVal on a String parameter, VBA
simply ignores that request and passes a pointer (ByRef) anyway, because
DLLs always refer to strings through pointers.

--
Regards,
Jay Freedman
Microsoft Word MVP Word MVP FAQ site: http://www.mvps.org/word

"Dave Rado" <dr...@onetel.net.uk> wrote in message
news:Oh6uAsPnBHA.2084@tkmsftngp04...

Dave Rado

unread,
Jan 14, 2002, 10:36:00 AM1/14/02
to
Hi Jay

So really, you may as well use ByRef in the first place, for strings (or not
bother to specify)?

Regards

Dave

"Jay Freedman" <jay.fr...@verizon.net> wrote in message

news:eGPTiuQnBHA.1600@tkmsftngp07...

AK Bohman

unread,
Jan 14, 2002, 11:04:02 AM1/14/02
to
Dave,

The same idea hit me but it didn't feel right to call out one thing when
something else is expected (even if substituted on the way). I tested on the
WinAPI function I most frequently use, for checking the availability of
certain disk drives.

When running the code below you get an incorrect result if replacing or
omitting the ByVal.

Public Declare Function GetDriveType Lib "kernel32" Alias _
"GetDriveTypeA" (ByVal sDrive As String) As Long

'The result returned by the function
'Unknown = 0
'NoRootDir = 1
'Removable = 2
'Fixed = 3
'Remote = 4
'CD = 5
'RamDisk = 6

Sub ldskjfklsdj()
MsgBox GetDriveType("u:\")
End Sub

--
/Anna Bohman
Bra Utbildning AB, Sweden
a...@buab.se
http://www.buab.se
-------------------------------------------------------------------------

"Dave Rado" <dr...@onetel.net.uk> wrote news:O#dg$FRnBHA.1220@tkmsftngp03...

Jonathan West

unread,
Jan 14, 2002, 11:18:39 AM1/14/02
to
Hi Dave

> Arrghh. My trivial little code sample only dealt with simple types. As far
> as I can tell, the rules for objects are different and altogether more
> obscure, and quite frankly I can't say I understand them sufficiently to
be
> able to explain them here with any degree of confidence. I'll see if I can
> get a coherent description of this from one of the VB MVPs. The principles
> involved should apply to any objects, so I wouldn't expect that there is
> anything exceptional about the Word object model in this respect. If I
find
> out anything more, I'll report back here.
> ~~~~~~~
> You're right, ByVal appears not to work with range variables - I just
tried
> the following:
>

Right. That seems consistent with the following KB article from VB4.

http://support.microsoft.com/default.aspx?scid=kb;EN-US;q138517&GSSNB=1

The summary is as follows:

"When you pass an object by value to a procedure, you can modify its
properties in the procedure. Using ByVal with an object parameter affects
how the object can be redefined in the procedure. If an object variable is
passed to a procedure by using the ByVal keyword and the object parameter is
set to a different object, the object variable still references the original
object. Conversely, if an object variable is passed to a procedure by
reference and the object parameter is set to a different object, the object
variable references this different object. This article provides examples
that highlight the functionality of the ByVal keyword."

From that, it seems that your code is working as expected, in that you are
changing the properties of MyRange, but not attempting to redefine myRange
to a completely separate Range object.

Interesting.

Dave Rado

unread,
Jan 14, 2002, 12:09:51 PM1/14/02
to
Hi Anna

In that case I'm still totally confused. :-)

Regards

Dave


"AK Bohman" <a...@REMOVEbuab.se> wrote in message
news:eUMV5VRnBHA.460@tkmsftngp03...

Klaus Linke

unread,
Jan 14, 2002, 2:12:37 PM1/14/02
to
> In that case I'm still totally confused. :-)

Hi Dave,

The best exposition (though -- or perhaps because -- it's the short
version of a 40 page chapter) is in Steve Roman's article
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnovb
a00/html/LightningStrings.asp

Greetings, Klaus

Dave Rado

unread,
Jan 14, 2002, 2:59:12 PM1/14/02
to
Hi Jonathan

~~~~~~~~~~~


The summary is as follows:

"When you pass an object by value to a procedure, you can modify its
properties in the procedure. Using ByVal with an object parameter affects
how the object can be redefined in the procedure. If an object variable is
passed to a procedure by using the ByVal keyword and the object parameter is
set to a different object, the object variable still references the original
object. Conversely, if an object variable is passed to a procedure by
reference and the object parameter is set to a different object, the object
variable references this different object. This article provides examples
that highlight the functionality of the ByVal keyword."

From that, it seems that your code is working as expected, in that you are
changing the properties of MyRange, but not attempting to redefine myRange
to a completely separate Range object.

~~~~~~~~~~~

Yes, I've just demonstrated the accuracy of that with the following code:

Sub Main()

Dim MyRange As Range
ActiveDocument.Range.Text = "Hello World"
Set MyRange = ActiveDocument.Range(0, 0)
MoveRangeByVal MyRange
MyRange.Select
Stop

'This time MyRange didn't move

Set MyRange = ActiveDocument.Range(0, 0)
MoveRangeByRef MyRange
MyRange.Select
Stop

'MyRange moved, as you'd expect it to

End Sub
'---------------------------------


Sub MoveRangeByVal(ByVal MyRange As Range)

Set MyRange = MyRange.GoTo(What:=wdGoToBookmark, Name:="\page")
End Sub
'---------------------------------


Sub MoveRangeByRef(ByRef MyRange As Range)

Set MyRange = MyRange.GoTo(What:=wdGoToBookmark, Name:="\page")
End Sub
'__________________________

But it rather begs the question: why? From the perspective of a VBA
programmer, there's no difference between moving a range using Goto (where
you have to use Set) and by some other means (where you don't) - which is
why I used Goto in my code: given which, the distinction seems to me to be
entirely artificial; and given that, I don't see why they made ranges
suppport ByVal at all! Ours is not to reason why? <g>

Regards

Dave


Dave Rado

unread,
Jan 14, 2002, 4:42:22 PM1/14/02
to
Hi Klaus

I must admit that even though Steve Roman has clearly tried to make his
article intelligible even to idiots like me, some of it was still over my
head (I really need to get hold of "API Calls For Dummies"! <g>). But he
wrote near the end:

"In brief, the reason is that when VB sees that a string is being passed to
an API function, it makes a copy of the array in ANSI format (rather than
Unicode) and passes the ANSI version to the function. "

Isn't that the same as saying that the VBA engine is holding the "ByVal"


variable in one area of memory and passing a copy of it on to the DLL

"ByRef", behind the scenes? Or am I misunderstanding?

Regards

Dave


"Klaus Linke" <fotosatz...@t-online.de> wrote in message
news:a1v95r$c0t$03$1...@news.t-online.com...

Klaus Linke

unread,
Jan 14, 2002, 6:32:57 PM1/14/02
to
Hi Dave,

>> The best exposition is ...
Should have said "the best exposition I know" -- which isn't much ;-)


> I must admit that even though Steve Roman has clearly tried to make
his
> article intelligible even to idiots like me, some of it was still
over my
> head (I really need to get hold of "API Calls For Dummies"! <g>).
But he
> wrote near the end:
>
> "In brief, the reason is that when VB sees that a string is being
passed to
> an API function, it makes a copy of the array in ANSI format (rather
than
> Unicode) and passes the ANSI version to the function. "
>
> Isn't that the same as saying that the VBA engine is holding the
"ByVal"
> variable in one area of memory and passing a copy of it on to the
DLL
> "ByRef", behind the scenes? Or am I misunderstanding?

Guess I'll have to buy the book to find out :-/

But as far as I understand it (which may well be totally wrong) VB
always passes an ANSI string to API functions.
When VB was born, there were only ANSI strings in the outside world
(Win3.11 ...).
The conversion is automatically made when passing the string, and
strings that are passed back are automatically converted back to
Unicode. It has nothing to do with "ByVal" or "ByRef", it's just an
additional complication.
If you want to pass a string to a function that only accepts Unicode,
you'll have to pass a byte array.
Or you can also use StrPtr() to avoid the conversion to a byte array
(if I interpret the following code correctly, which I have swiped from
some newsgroup posting):

Public Declare Function MQPathNameToFormatName Lib "mqrt.dll" _
(ByVal lpwcsPathName As Long, _
ByVal lpwcsFormatName As Long, _
lpdwCount As Long) As Long

tmp1$ = "mymachine\myqueue"

Result = MQPathNameToFormatName(StrPtr(tmp1$), _
StrPtr(FormatName), FormatNameLength)

Greetings, Klaus


Jonathan West

unread,
Jan 15, 2002, 6:01:27 AM1/15/02
to

"Dave Rado" <dr...@onetel.net.uk> wrote in message
news:u$gFg0TnBHA.2460@tkmsftngp04...

>
> But it rather begs the question: why? From the perspective of a VBA
> programmer, there's no difference between moving a range using Goto (where
> you have to use Set) and by some other means (where you don't) - which is
> why I used Goto in my code: given which, the distinction seems to me to be
> entirely artificial; and given that, I don't see why they made ranges
> suppport ByVal at all! Ours is not to reason why? <g>

That presumably is because objects in general support ByVal, according to
the rule described, and a Range is just another object. With Goto, I think
that this issue is more that Goto is method which returns a new object,
which you are choosing to assign back to the same object variable. Other
methods (such as Move) modify the properties of the object that the method
is being applied to.

The question is not so much why ByVal can be applied to Ranges but rather
why MS chose to define so many different ways of modifying the properties of
a Range. I suspect the answer to that is a historical one, in that Ranges
acquired most of the properties and methods of the Selection object, and the
Selection object in turn took up most of what could be done to the selection
in WordBasic. Unless MS had done that, the automatic conversion from
WordBasic to VBA would have been even more flaky that it was in practice in
the upgrade from Word 95 to Word 97.

0 new messages