RegisterLog In/Log OutView Cart
O'Reilly Ron's VB Forum Ron's VB Forum
BooksSafari BookshelfConferencesO'Reilly NetworkO'Reilly GearLearning Lab
 


Traveling to
a tech show?

Hotel Search
Hotel Discounts
Discount Hotels
Chicago Hotels
Canada Hotels
California Hotels
Hotels




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
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
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



O'Reilly Home | Privacy Policy

© 2007 O'Reilly Media, Inc.
Website: | Customer Service: | Book issues:

All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.