Showing posts with label Win32. Show all posts
Showing posts with label Win32. Show all posts

Thursday, August 3, 2006

What are interfaces?

Interfaces are a means of describing a unit of functionality without regard to how it is implemented. They provide a means of decoupling what an object does from how it does it. In some ways, they are similar to a pure abstract class definition.

A great example of a good use of interfaces is the Iterator interface in .NET. It is small and does one thing well: it provides the concept of a list of items without giving any detail about how that list is stored. Various types of container classes implement the Iterator interface, in different ways which are specific to how each container stores its data. Users of the Iterator interface can iterate through a collection of items without knowing if the collection is a linked list, a binary tree or coming from a remote machine through a TCP/IP socket (assuming there's some object which communicates through a socket and implements the Iterator interface).

In Delphi, they can also do two helpful things.

First, since an object can implement multiple interfaces, they can provide some of the useful features provided by multiple inheritance. For example, an object can implement an IStreamable interface, indicating that it knows how to stream itself to some streaming mechanism. It can also implement an IPerson interface indicating that it has Name, Home address and Birthdate properties. Interfaces implementations can also be delegated to properties. This allows a multiple classes with different object hierarchies to implement an interface but keep the implementation in a single helper class.

Second, they can provide a means of automatic lifetime management of objects. There's always the ongoing question of who's responsible for freeing created objects. Some environments, such as Java and .NET, use garbage collection which use various means of determining when an object is no longer used and getting rid of it. Historically, Delphi has said it's the programmers responsibility to call .Free when they are done with the object. Based on this, one best practice says the object which creates another object is responsible for freeing. Another is the owner pattern, the most familiar example is TComponent, where an owner is assigned who's responsible for freeing the object.

Reference counted interfaces are another means. Any object which descends from TInterfacedObject, or which implements IUnknown and incorporates reference counting, can use this method. Basically, it leaves the compiler and run-time system responsible for keeping track of how many references there are to an interface and destroying the implementing object when the last reference goes away.

This can be very handy in many situations. One example is the Factory pattern where the whole purpose is to decouple the creation of an object from the object using the created object. Another example is in threads, where objects may be passed between threads with no clear concept of owner. And finally, in lifetime management of normal objects, it can eliminate just busy-work housecleaning.

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.

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