
Date: Sept 24 1999
From: Cade Roux
To: ron@oreilly.com
Subject: Objects and ByVal or ByRef
Ron,
I've never been able to find any reasonable explanation of the
behavior of ByVal and ByRef when parameters are Objects or Classes.
In actual use, using either keyword seems always to be equivalent to
ByRef, which makes sense to me, since any Object passed is actually
only an object reference anyway.
Is this correct?
Thanks,
Cade Roux
PS: Paul Lomax's
VB & VBA in a
Nutshell is excellent.
Hello Cade,
Your question is a really good one. For purposes of illustration, so that
we can try to figure out what's happening when we pass an object by value
or by reference, let's begin by creating a simple ActiveX DLL component,
CTestObject, that has a single property, a Long named Value. Its source
code is as follows: (click here for code example)
After the component is compiled and registered in the system registry, we
can access it from a VB project by adding a reference to it from the
References dialog.
Once we have a simple component to test, we can see what happens when we
pass a reference to it using both the BYVAL and the BYREF keywords. The
following procedure does just that, passing objTestR, an instance of the
CTestObject class, to the ChangeValue routine by reference, and passing
objTestV, another instance of the CTestObject class, to the ChangeValue
routine by value.
Click here for code example.
The ChangeValue routine, which consists of the following code, simply
modifies the value of the object's Value property and terminates.
Click here
for code example.
The result of running the code is shown in Figure 1. Note that, in each
case, the change to the Value property made by the ChangeValue subroutine
continues to be reflected in each object's Value property once the
procedure returns. We would seem to be safe in concluding that BYREF and
BYVAL are irrelevant when passing object references, and that objects
references are always passed by reference, since the modified property
value has been reflected in the calling program regardless of whether the
object reference was passed by reference or by value.
Figure 1
In fact, however, this is not the case. Object references can be passed
by value or by reference, and how they are passed can affect their value.
Consider, for instance, if our cmdPassRef_Click event procedure were in
turn to call a subroutine named ChangeNewObject instead of ChangeValue.
The source code for ChangeNewObject is:
(click here
for code example)
Note that the ChangeNewObject routine instantiates two new instances of
the CTestObject class and assigns them to the two objects passed to the
routine. It then sets the value properties of each object.
Figure 2 shows the result when control returns to the calling procedure.
Notice not only that the value of the object whose address was passed to
the ChangeNewObject routine by calue remains the same, but also that the
address as returned by the undocumented ObjPtr function also remains the
same. This is not the case, however, with the object whose address was
passed by reference to the ChangeNewObject routine; not only has its
Value property changed, but its address has as well.
Figure 2
So how do we explain this difference in behavior between the two code
fragments? Let's start with two apparently unrelated observations.
First, a basic characteristic of any form of object oriented programming
is encapsulation. For our purposes, what this means is that objects are
black boxes -- our programs never have direct access to an object (that
is, to its memory or its structure); instead, a program can access an
object either through a pointer to that object or through its published
interfaces, which consist of its properties and methods. In its handling
of objects, Visual Basic is very much like any other object-oriented
language: an object variable represents a reference to the object (that
is, a pointer to its address in memory), rather than the object itself.
Second, Visual Basic is not a language that makes extensive use of
pointers, and as a result working with pointers is not something that
most VB programmers are very familiar with. (It's not for no reason at
all that the ObjPtr, StrPtr, and VarPtr functions are officially
undocumented.) Even advanced VB programmers can know comparatively little
about pointers. This is one of the areas in which Visual Basic differs
most sharply from C and C++; it's safe to say that the C or C++
programmer who hasn't made extensive use of pointers probably hasn't done
any significant programming in either language.
As a result of this limited exposure to pointers, in the Visual Basic
world we tend to equate pointer with "by reference". That is, if you
receive a pointer to something, that something must be passed to a
subroutine by reference, and any changes made to the something are
reflected once control returns to the calling procedure. If you don't
receive a pointer to something, that something must be passed to a
subroutine by value, and that something remains intact even if its value
has been changed by the calling program.
This isn't really accurate, though. Any piece of data -- and a pointer is
decidedly a piece of data -- can be passed by reference. In the case of a
pointer that's passed to a subroutine by reference, you modify the data
by providing a new address to which the pointer points. The real
distinction here isn't between "data" and "pointer", as VB seems to
imply; it's between whether the original data or a copy of the data is
being passed to a subroutine.
So it should now be clear how and why our two sample programs worked as
they did. The ChangeValue procedure received pointers to two objects,
one passed by reference and one by value. However, the value of the
object variable passed by reference (that is, the value of the pointer)
wasn't modified within the ChangeValue routine. What was modified in the
case of both objects was the value of a property. And this modification
is reflected in both the object passed by value and the object passed by
reference, since there is only a single physical instance of each object
(that is, of objTestR and objTestV). Remember, when passing objects, it
is the pointer to the object, and not the object itself that is passed
and that is to be regarded as "data".
In contrast, the ChangeNewObject procedure actually assigns a new pointer
(that is, a new address, which in turn reflects a different instance of
an object) to the two object references passed to it. The address passed
to it by reference is modified when control returns to the calling
procedure, so that objTestR now points to a completely different object,
while the reference passed to it by value retains its previous value when
control returns and continues to point to the same physical object.
Note that, by default, parameters are passed to subroutines by reference.
This seems somewhat unusual, but probably was done for performance
reasons. Since passing anything by value involves making a copy of it so
that modification doesn't affect the original value, passing data by
reference is more efficient that passing it by value. In the case of
object variables, the following code showed that showed that passing an
object variable by reference was anywhere from 66% to 100% faster than
passing it by value.
Click here
for code example.
--Ron
Download the code examples:
Return to: Ron's Archive

|