2.3 Object references

Object references

Object references are variables that point to objects. In Visual FoxPro you never have direct access to objects, but you can talk to them through references or pointers. This doesn't make a big difference, especially because it's easy to use object references.

To create an object reference to a new object, you use either CreateObject() or NewObject(). Both functions return references to the new object and this reference is copied into whatever variable you provide.

Here's a simple example:

oForm = CreateObject("Form")

The variable oForm is now a reference to the new object. You can create a second reference to the same object by simply copying the existing reference like so:

oForm2 = oForm

Unlike what many people believe, this does not copy the whole object, but only the contents of the variable, which is the object reference. You could not copy the object itself because you don't have access to the actual object, as mentioned before. To create a second object that is unique to oForm2 you would have to do a second CreateObject().

You can now start communicating with that object, and it doesn't matter what reference you use. There is no real difference between these two lines of code:

oForm.Caption = "Changing the caption through oForm"
oForm2.Caption = "Changing the caption through oForm2"

Object lifetime

The lifetime of an object reference determines an object's lifetime. An object stays alive as long as there are references to it. You create an object, provide a FoxPro variable as a reference to it, and the object is alive. As soon as you assign a different value to the variable or simply release it, the object goes away.

Let's continue with the example above. Make sure the form oForm is visible, otherwise you'll have a hard time following the example. When we first created the object, we provided the oForm reference, which kept the object alive. Later on, we added the second reference, which linked to the same object. The question is: "What happens when the first reference goes away?" Well, let's try it by removing this reference. You can do this with any of the following commands:

RELEASE oForm
oForm = .NULL.
oForm = "Something else"

As you can see, the form still remains in memory, because oForm2 is a separate, independent reference to this object.

Now if you think about this, it is powerful as well as dangerous. If you simply copy object references, you also have to clean up after yourself; otherwise you might end up trying to release objects and they simply don't go away. This makes it hard to find bugs, because there is no way to tell what references point to that object.

Many objects also have a Release() method. This is a very good way to remove objects from memory, because these methods try to release all the references that point to the actual object. Let me give you a little example to demonstrate why this is the case. Let's assume you had an object called oForm. Later on during runtime, you created additional references to this object. This kind of scenario happens often in large applications. Here's some sample code that constructs such a scenario:

oForm = CreateObject("Form")
oForm2 = oForm

To release the oForm object, the oForm2 reference must be cleared as well. This could be done by assigning a new value to the references or by releasing them using the RELEASE command:

RELEASE oForm
oForm2 = .NULL.

Unfortunately, this gets really tricky, especially in generic algorithms. Most likely you won't be able to keep track of all the references using an automated mechanism. Using the Release() method takes care of this issue beautifully:

oForm.Release()

All known references are automatically released by the object, and we don't have to take care of our own garbage. This saves us many lines of source code and a lot of sleepless nights. This works fine for all variable references. However, it might not work well if you also have property references, which I'll discuss in the next section.

THIS, THISFORM, THISFORMSET and Parent

The Visual FoxPro keywords THIS, THISFORM and THISFORMSET are special kinds of object references. Visual FoxPro maintains these references on its own. They always point to the current object (THIS) or to an object that's a parent (container) of the current object. THISFORM points to the current form and (you guessed it) THISFORMSET to the current form set. Here are some examples that use these references:

THIS.Execute()
THISFORM.Caption = "Invoice Form"
THISFORMSET.frmToolbar.cmdSave.Enabled = .T.

Parent is a little different from these references. Parent is a property of every object in Visual FoxPro. THIS, THISFORM and THISFORMSET are stand-alone variable references. Also, unlike the Parent reference, they change all the time depending on the currently active object.

The Parent property of each object that is contained in another object has a reference to the parent that remains the same as long as the object stays in memory. Parent is usually used together with THIS, as in the following examples:

THIS.Parent.Refresh()
THIS.Parent.Parent.cmdOK.Enabled = .F.

As you can see, Parent references another object that can have a Parent reference as well. The second line might be in a Page that's in a PageFrame that's in a Container that has an OK button. If this PageFrame were in a form, it could be referenced like this:

THISFORM.cmdOK.Enabled = .F.

Only use Parent in members of composite classes. Don't assume a class will be used in a certain container object, because this would limit the reusability of the class. Keep in mind that every object should be a black box that can exist in any environment whatsoever.

The same logic applies for THISFORM, but some objects can only exist in forms, such as CommandButtons. It is impossible to instantiate and use a command button outside a form. For this reason, it's safe to use THISFORM as a reference in a command button as long as you don't assume that the form has nonstandard properties or methods.

Variable references

The most common type of object reference is a variable reference. It is a regular FoxPro variable that points to an object.

Variable references follow all the same rules as regular FoxPro variables. They can be public, private or local, and they can be passed or received as parameters. This simple program demonstrates the use of variable references:

LOCAL loForm
loForm = CreateObject("Form")
DisplayForm( loForm )
* Some more code would follow here

FUNCTION DisplayForm( loFormReference )
* This function is used to set forms visible
IF VarType( loFormReference ) = "O" AND loFormReference.BaseClass = "Form"
loFormReference.Visible = .T.
ENDIF
RETURN

This code defines a local variable that is used as an object reference for a form object. We then call the DisplayForm() function and pass the object reference as a parameter. The function verifies that the reference is valid and displays the form. Then the program focus returns to the previous procedure, some additional code executes, and finally the program ends and the local object reference goes out of scope; the object goes away as well.

As you learned earlier in this chapter, you never have access to the actual object. The only form of access to objects is through the references or pointers. These pointers know the memory address of the objects. If you pass this memory address to another function, it will still point to the same address and therefore to the same object. For this reason, there is no need to pass object pointers by reference, because the result will be almost the same (all right, you might save a byte or two of memory). Many people actually believe that FoxPro checks to see whether the passed parameter is an object, and if so, automatically passes it by reference. For the reasons described above, this is not the case and it isn't necessary.

Property references

Object references don't have to be variables. They can also be properties of other objects. Here's an example that makes use of this:

oForm = CreateObject("MyForm")
oForm.oBehavior = CreateObject("SomeBehavior")

DEFINE CLASS MyForm AS Form
oBehavior = .NULL.
ENDDEFINE

DEFINE CLASS SomeBehavior AS Custom
* Lots and lots of code goes here
ENDDEFINE

Note that the behavior object created does not become a member object of the form, meaning it's not contained or owned by that form. The form just contains a reference to that object. We could simply create another reference to the behavior object and release the form, and the behavior object would remain in memory.

Using property references is also called "composition by reference." The cool part is that many objects may share one other object (I'll call it a behavior object for this example). The first object created also instantiates the behavior object. All other objects would simply create additional references to the existing behavior object. As soon as all objects are released, the last reference to the behavior object also goes away, and the behavior object is released as well. This may or may not make sense depending on the scenario you are dealing with. If you create a car object, for instance, every car should get its own engine, but if you are creating a house class, not every house should get its own power plant.

For some mysterious reason, FoxPro doesn't handle property references as well as variable references. As I mentioned earlier, the Release() method takes care of variable references, but it sometimes forgets about property references. This results in objects that accidentally stay alive. I refer to these objects as Goof-Monsters.

Cleaning up after yourself

Whenever you create new object references or copy existing ones, you take the risk of creating object references that might keep objects alive after they are supposed to be released. For this reason, you need to be very careful and clean up after yourself, meaning you have to release all the references you created. As I mentioned earlier, this is especially true for property references, so the form class I used in the last example should also make sure the property is reset in the form's destructor:

DEFINE CLASS MyForm AS Form
oBehavior = .NULL.

FUNCTION Destroy
THIS.oBehavior = .NULL.
ENDFUNC
ENDDEFINE

"But Markus," one might say, "when this object is released the property goes away, so how could this result in an outstanding reference?"

Well, good point. It seems like this would never be a problem, but it is in real-world scenarios. Sometimes object references can become cyclic over many steps. Let's assume you create a form that has a pageframe. The pageframe has several pages and one of them has a button. For some reason, the button has a reference to the pageframe, which has a reference to the form. This is a typical use of a chain of responsibility that could be used to handle errors or to somehow delegate messages. (I'll discuss the chain-of-responsibility pattern in Chapter 10, "Patterns.") Now imagine that the form always sets a reference to the current control. This could be done in the GotFocus event of the button, for instance. In this case you would have a circular reference: from the button to the pageframe to the form and back to the button. This seems like a stupid thing to do, but it could happen very easily, because object systems grow very complex and there is no way to see all the references.

If cyclic relations exist, it might happen that a whole group of objects doesn't go away, because the internal references keep them alive. This can cause some weird effects. Forms remain on the screen, but they aren't functional because they are only kept alive by objects that live within that form. Those objects, on the other hand, can't go away either, because the references to each other keep them alive. However, all public references to these objects are gone and they are linked only internally, making it impossible to communicate with them. They just sit there, eat up your memory, and cause random GPFs every now and then. I've worked on projects where we spent several man-months trying to find mysterious crashes and memory leaks, only to discover that one object reference wasn't released properly. Unfortunately, this kind of bug is extremely hard to find. First, you don't have any tools that show object references, and second, you don't really know where to look because the problem might be in any line of your whole project. Unfortunately, there is no obvious misbehavior, either, when the problem occurs. All you can do is step through the whole code, make educated guesses, make little changes every now and then, and see how the system behaves over the next couple of hours. If nothing has changed, you can start over, step through every single line, make your next educated guess and hope for better luck. If it still doesn't work, you can try again the next day.

I make it a basic rule to reset every property that might have been used as an object reference in the destructor of the class. I usually have a special method that does that. I call this method from the destroy event of the class, or I can decide to clean up during runtime, like after a process has ended. This technique is called a Garbage Collection.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net