Monday, January 2, 2006

What is the difference between Parent and Owner?

Parent is the Window control where the control is displayed. Owner is the component responsible for making sure it's destroyed.

When a component is created, an owner (which is another component) is specified as a parameter to the Create method. Unless the component is destroyed earlier, the owner will automatically free the component when the owner is destroyed. This is the purpose of ownership.

Controls that appear on the screen need to know where to paint themselves, hence the parent property. It specifies the visual container for the control. This property must be specified before a control can be displayed.

When a component is dropped on a form at design time, the owner is the form and the parent is the container on which it is dropped. For example, drop a group box on a form. Its owner and parent are both the form. Drop a check box on the group box. Its owner is the form and its parent is the group box.

The specification of owner and parent are at the programmer's discretion when controls are created dynamically and so the above relationships may not hold. This is especially true with composite components. With run-time creation of controls, the programmer may specify any component to be an owner (including a nil owner) and any window control as the parent. If a nil owner is specified, the programmer is responsible for explicitly freeing the object.

Why are some object properties zero in the watch window?

This has to do with how the watch window works and the way properties are implemented.

First, the watch window simply provides a view of an area of memory. You can tell it to view the memory in different ways. Because it knows some things about that memory, it can do a limited amount of interpretation: view the contents of character pointers, view the contents of records and so forth.

Second, a bit of explanation as to how properties work. When a component is developed, the component writer declares a property with:
property NameOfProp: TypeOfProp read PropReader write PropWriter;
where:
NameOfProp is the name of the property accessed in the Object Inspector and in code.
TypeOfProp is the type of the property, e.g. Integer, String, TStrings.
PropReader is either a variable of type TypeOfProp or a function with a return type of TypeOfProp.
PropWriter is either a variable of type TypeOfProp or a procedure with a parameter of type TypeOfProp.
Notice the two options for readers and writers. They can be either a variable or a method. Some properties directly access a variable whereas others are the result of code executing. Those properties that fall into the first class can be evaluated without side effects in the watch window, since it looks at memory. Those properties that fall into the second class cannot be evaluated in the watch window without potentially causing side effects.

Because of the possible side effect issue, early versions of Delphi simply did not display the property value. Due to requests to allow this, the debugger was made to optionally show properties which use readers. To see these types of properties, right click on the watch item and select properties. Then check the option to allow function results.

If working in earlier versions of Delphi, where this option is not available, there are several ways to get around the problem:
  1. Create a temporary variable immediately prior to where you want to view a property and assign the property value to the variable. Then put the watch on the temporary variable. You may need to go to the Options page and turn optimizations off.
  2. Put a ShowMessage call in the code where you would normally set the breakpoint and create a message that displays the desired property.
  3. Put a Memo component on the visible form and add a line that displays the desired property where you would normally set the breakpoint.
  4. Set a breakpoint. Right click on the breakpoint in the breakpoint list window and select properties. Show the advanced options. Uncheck Break. In the evaluation section, put in the property name. Now, every time that breakpoint is hit, rather than stopping the debugger, it will write a line to the event log.

Why do I get an AV when accessing an array?

The most common AV problem with arrays is accessing an element beyond the proper range. Make sure range checking is on, at least for debugging.

Why do I get an AV when accessing a pointer (including a PChar) or object?

In the following discussion, the term pointer is used to generically refer to variables of explicit pointer types, PChars and objects. This was written from a Delphi perspective, but the techniques are generally applicable.

Remember, declaring a variable of any pointer type only allocates 4 bytes for the pointer. The pointer does not refer to anything. Before it can be referenced, it must be assigned a value. Depending on context, the pointer's value may come from any number of sources, including: memory allocation routines, a return value from a function, a constant, a variable or, in the case of objects, the constructor method. Not performing this step is a sure way to AV. Always check the return value of memory allocation functions to verify that the pointer was assigned a valid value.

Another way to AV sometimes is to access a pointer whose contents have already been freed. Depending on many factors, sometimes this will work and sometimes it will AV. This is especially easy to do if the variable's scope is greater than the current procedure. One good practice, particularly when variables have an object or global scope is to set their value to nil immediately after freeing it. No, this is not automatically done. If nothing else, following this practice will eliminate the "sometimes" nature of the problem.

Assuming the pointer has been assigned a valid value and it has not been freed, the final item to check is the value of the pointer when the AV occurs. If it is the same value, then the memory is being referenced (possibly through a typecast) incorrectly. Accessing a freed object can cause this too. If it is a different value, then the next task is to track down why. Probably something else in the program is accessing memory incorrectly, not causing an AV but corrupting this pointer. On cause of this is writing to array elements outside the bounds of the array. Turn on the range checking option to help determine if this is happening.

There are several ways to find this. 1) Do a very careful code review until the offending code is found. 2) Establish breakpoints between the time the value is correct and when it is bad with a watch on the pointer that's being changed. 3) Remove code between creation and known corruption until the problem goes away. 4) Use a tool that detects memory overwrites.

What are general debugging techniques for AVs?

When you receive the dreaded Access Violation, the first thing to understand is what the computer is actually telling you. An AV is the Windows' description of an exception that was raised by the hardware itself. The hardware knows which processes should be accessing what memory and has detected a rogue process trying to read or write to memory that it does not own. It then tells the operating system which handles the error in various ways depending on the O/S and the context of the error.

So when you see the error with those cryptic long hex addresses, what the computer is telling you is that the instruction at the address of the named program tried to reference memory that it did not have rights to. That is all it means. It does not mean that the code at that address is necessarily the culprit, although it may. It does not mean that the named program is faulty, although it may. It simply means the hardware determined that the named program at that moment in time was not operating properly.

Another thing to keep in mind is that just because you do not have AVs does not mean you do not have a problem with memory access. It just means the hardware has not detected it. You may very well have problems but they do not extend past memory you do have rights to so the hardware does not detect it. Symptoms of this are when variables change value for no apparent reason and "correctly" running code suddenly starts AVing for no apparent reason.

Remember too, different operating systems map memory differently. There may be a problem on one platform that is not manifest on others. Many times people develop a program in Windows X and have no problems. They then run it in Windows Y, get AVs and blame the O/S. It cannot be their program since it runs just fine on the other system. No. They just have not found the problem on the other system. This is actually not unique to Pascal programs or the Windows O/S. For example, the same principle holds true for C programs in UNIX. Many times code that has run flawlessly for years on one version falls over when it is compiled on another platform.

Because of the inexact nature of AV reporting, they can be very trying to track down. Within the Delphi environment there are a couple common bugs to look for:
  1. Is it during object creation?
  2. Is it during object destruction?
  3. Is it when accessing a pointer (including a PChar) or object?
  4. Is it when accessing an array?

Why do I get AVs when accessing a TStrings' Objects property?

Typically, this is because a corresponding string has not been assigned.

TStringLists use what is called sparse memory management. This means that memory (including the memory for the object's pointer) is assigned to each element only when something is placed in the string. Therefore, when an object is referenced without the string, no memory is allocated but it is trying to be accessed anyway, hence the AV.

There are a couple solutions: 1) use the AddObject method, 2) assign a value to the corresponding string first.

Note: Objects stored in TStrings descendants do not get freed when the TStrings object is destroyed. The lifetime of anything you place in here must be explicitly maintained.
procedure example1;
begin
with TStringList.Create do
try
Objects[0] := TObject.Create; { This will AV }
finally
Objects[0].Free;
Free;
end;
end;

procedure example2;
begin
with TStringList.Create do
try
AddObject('This is one good way.', TObject.Create);
Add('This is another good way.');
Objects[1] := TObject.Create;
finally
Objects[0].Free;
Objects[1].Free;
Free;
end;
end;

Why do I get AVs when freeing an object?

This is generally caused by trying to free an already freed object.

The Free method is written to not destroy a nil pointer. This leads programmers to believe that Free will set the pointer to nil when it is done, therefore making Free safe to call again. This is not true. Why you ask? Because there may be multiple pointers to the same object. Consider example one.

It is generally a good programming practice, particularly when the object's variable has a scope larger than a procedure, to set the variable to nil immediately following a Free. See example two. Alternatively, in later versions of Delphi, the FreeAndNil method from the SysUtils unit could be called as in example three.
procedure example1;
var
obj1, obj2: TObject;
begin
obj1 := TObject.Create; { This creates an object }
obj2 := obj1; { There is still only one object, just two things pointing to it }
obj1.Free; { If Free set obj1 to nil, how would it know about obj2? }
obj2.Free; { This will cause an AV }
end;

procedure example2;
var
obj1: TObject;
begin
obj1 := TObject.Create;
obj1.Free; obj1 := nil; { This makes obj1.Free safe to call again }
obj1.Free; { This will not cause an AV }
end;

procedure example3;
var
obj1: TObject;
begin
obj1 := TObject.Create;
FreeAndNil(obj1); { This makes obj1.Free safe to call again }
obj1.Free; { This will not cause an AV }
end;

Why do I get AVs when creating an object?

There are three main reasons why you would get an AV on object creation:
  1. Error in the manner the Create method is called.
  2. Error in the Create method of the object.
  3. Other error in the program that is manifested during create.

1) Error in the manner the Create method is called.

This is probably the most frequent problem with people new to creating objects dynamically in Delphi. When you create an object variable in the var block of a procedure, you are actually allocating memory for a pointer. The first thing you need to do before actually using the object is to create an instance of it, or cause the pointer to point to something meaningful. Most people realize this is done with the create method. The problem is in getting the call correct. Example one below is syntactically correct but it doesn't work. Why? Because variable Obj1 is an uninitialized pointer. The correct way to call Create is shown in examples two, three and four.
procedure example1; { How not to do it! }
var
obj1: TMyObject;
begin
obj1.Create; { This will result in a AV }
obj1.SomeMethodCall;
obj1.Free;
end;

procedure example2; { This works }
var
obj2: TMyObject;
begin
obj2 := TMyObject.Create; { This is correct }
try
obj2.SomeMethodCall;
finally
obj2.Free;
end;
end;

procedure example3; { This works }
var
obj3: TMyObject;
begin
obj3 := TMyObject.Create; { This is correct }
with obj3 do
try
SomeMethodCall;
finally
Free;
end;
end;

procedure example4; { This is for trivial use of a temporary object }
begin
with TMyObject.Create do
try
SomeMethodCall;
finally
Free;
end;
end;

2) Error in the Create method of the object.

This case would be most probable when using a new object that has not been thoroughly debugged and there is a problem somewhere in the constructor or a subsequently called method. General debugging techniques need to be applied to the object in question to find this type of problem. A good principle to follow when creating an object is to simultaneously create a test unit for that object. The test unit should provide opportunities to verify all aspects of an object's functionality. Doing so will provide a limited environment in which to find problems and will increase confidence of an object's trustworthiness when it is used in a larger context.

When debugging new objects of your own descended from TComponent, one thing to keep in mind is any published property that is an object, must be created in the constructor. This is because the streaming mechanism will try to assign values to the object after the Create has been called. Another related item to remember is that items that are dependent on streamed properties need to be handled in the Loaded procedure, not the Create. An indication that there is a problem with uninitialized properties is a AV when trying to access the property in the object inspector.

Example one shows both problems. The component writer tried to be good and conserve resources by not creating the bitmap until it was set. This works as long as the property is not published or the component is only created at run time. However, it fails when it is a published property and created at design time. The object inspector uses the write method (SetBitmap) and so it is created OK during design time. However, when the project is run, the streaming mechanism tells the object to load itself, bypassing SetBitmap. The same thing happens when the project is closed and later reopened.

The Area property is a contrived example of having to use the Loaded property. If this was a real component you would want to either compute it each time and not worry about storing it or compute it in an assignment procedure for the Height and Length properties.
interface

type
TExample1 = class(TComponent)
private
FBitmap: TBitmap;
FArea,
FWidth,
FHeight: Word;
procedure SetBitmap(value: TBitmap);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Bitmap: TBitmap read FBitmap write SetBitmap;
property Area: Word read FArea;
property Width: Word read FWidth write FWidth;
property Height: Word read FHeight write FHeight; end;

implementation

constructor TExample1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FArea := Width * Height; { Width and Height haven't been assigned yet }
end;

procedure TExample1.SetBitmap(value: TBitmap);
begin
if not Assigned(FBitmap) then
FBitmap := TBitmap.Create;
FBitmap.Assign(value);
end;

destructor TExample1.Destroy;
begin
FBitmap.Free;
inherited Destroy;
end;

Example two shows how to initialize the object and use Loaded correctly.
interface

type
TExample2 = class(TComponent)
private
FBitmap: TBitmap;
FArea,
FWidth,
FHeight: Word;
procedure SetBitmap(value: TBitmap);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Loaded; override;
published
property Bitmap: TBitmap read FBitmap write SetBitmap;
property Area: Word read FArea;
property Width: Word read FWidth write FWidth;
property Height: Word read FHeight write FHeight;
end;

implementation

constructor TExample2.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FBitmap := TBitmap.Create;
end;

procedure TExample2.SetBitmap(value: TBitmap);
begin
FBitmap.Assign(value);
end;

destructor TExample2.Destroy;
begin
FBitmap.Free;
inherited Destroy;
end;

procedure TExample2.Loaded;
begin
inherited Loaded;
FArea := Width * Height;
end;

3) Other error in the program that is manifested during create.

One thing to always keep in mind when dealing with an AV problem: the problem may not be where the error currently occurs. An AV in essence says that your program tried to access memory that does not belong to it. If something in the program clobbered memory, you may just now be trying to access memory with a pointer that was corrupted earlier during execution.

One way to eliminate this as a possibility, and a good technique in general, is to verify an object's integrity in a stand-alone unit test. For every object that you create, also create a project that tests just the object without any other interactions. This builds confidence that the object is functioning correctly. If the object crashes in the test program, you know the object or test program is at fault, a limited set of circumstances. If the object works by itself but crashes when included in a larger system, that is a clue it may not be the object but something else. Just remember though, it does not rule out the object, it just removes suspicion. Something can corrupt memory and not provide any visible signs... for a while.