Mastering Delphi 7 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Mastering Delphi 7 [Electronic resources] - نسخه متنی

Marco Cantu

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید

Chapter 2 for details). When there are no more references to an object, the memory for it is reclaimed. This is also the basic idea of the CLR garbage collector, but the similarities stop there. The CLR's garbage collector can detect that two objects are still referring one to the other, but there is no other reference to them, so they can be discarded, something a reference counting system (like the one in Delphi) fails to handle.

Using a GC means you can create objects, reference one from another as you need, and simply forget about the hassles of deleting those objects. The system will do all that's needed for you. That's all you really need to know. Note that this implies that you don't need to balance object creation and destruction in your code architecture, nor do you need to use try/finally blocks to make sure objects are destroyed; these are just a couple of specific circumstances in which you'll benefit from a GC.

Only when you need to write low-level classes will you have to remember to free unmanaged resources, such as window or file handles. The CLR class System.Object contains a protected Finalize method that you can override to free up unmanaged resources. You must be aware of some important things before you begin overriding Finalize. (If you want some in-depth information on this topic, see the sidebar "Issues with Overriding Finalize.")








Issues with Overriding Finalize


Overriding the Finalize method is inefficient because it requires your object to make at least two trips through the garbage collector. The job of the garbage collector is to reclaim memory, and it can't do this until it runs your object's Finalize method. So, the garbage collector must give special treatment to all objects with Finalize methods. It puts them on a special list, which has the side effect of keeping them alive through this round of garbage collection (because there is now another reference to the object). Eventually, a thread walks through the special list, runs each object's Finalize method, and removes the object from the list. Removing the object from the list takes away the last reference to it, so next time the garbage collector runs, the object's memory can be reclaimed.

But inefficiency is not the only problem with finalizers. You also can never be sure when your object's finalizer will run. If you are relying on the Finalize method to give back unmanaged resources, the object might hold onto these resources longer than you expect.

When Finalize runs, it does not run in the same thread of execution as your object. This means all thread synchronization and blocking must be avoided, because it's not your thread you are blocking, it's the CLR's finalizer thread. On a related note, if an exception is thrown from Finalize the CLR catches it, not you, and the exception will be swallowed.











The recommended approach for freeing up unmanaged resources is to implement the dispose pattern. This is a strategy for providing an object both an automatic way to dispose the external resources it refers to when it is garbage collected and also a way to free the same resources upon the direct invocation of a method. In real terms, coding this pattern equates to implementing an interface called IDisposable. IDisposable consists of one method: Dispose. The IDisposable interface gives you deterministic control over freeing up resources used in your objects. Unlike Finalize, the Dispose method is public and is meant to be called by you (or users of your class), not by the garbage collector.

You may have read .NET literature explaining how the C# language implements destructor semantics by having the compiler create a Finalize method on your class behind the scenes. To implement the IDisposable interface in C#, you must do so directly and wire everything so that the Dispose method can be called directly or from the automatically generated Finalize method. This is not the way things work in Delphi for .NET.

Creating a destructor for your class does not cause the compiler to implement a finalizer behind the scenes. Instead, it causes the compiler to implement the IDisposable interface. But nothing is stopping you from implementing IDisposable directly, so if you want to rely on the compiler's behind-the-scenes magic, you must follow a strict pattern. Your class destructor must be declared exactly as shown here:


destructor Destroy; override;

When the compiler sees this declaration, it generates code to mark your class as an implementor of IDisposable. Then, using a feature of CIL, your destructor is marked as the implementation of the Dispose method (this can be done even though the name of the method is Destroy, not Dispose).

The usual way to dispose of objects in Delphi is to call the Free method on the object. In Delphi for .NET, the Free method is implemented so that it tests to see whether the object implements the IDisposable interface. If you follow the previous destructor signature, the compiler implements it for you; Free then calls Dispose, which winds up in your Destroy method.

Let's create a project and declare a class with a destructor that follows the pattern. Then you'll use ILDASM to inspect the generated code. The code is available in Listings 24.1 and 24.2.

Listing 24.1: The Project of the DestructorTest Example







program DestructorTest;
{$APPTYPE CONSOLE}
uses
MyClass in 'MyClass.pas';
var
test : TMyClass;
begin
WriteLn ('DestructorTest starts');
test := TMyClass.Create;
test.Free;
WriteLn ('DestructorTest ends');
end.











Listing 24.2: The Unit of the DestructorTest Example







unit MyClass;
interface
type
TMyClass = class
public
destructor Destroy; override;
end;
implementation
destructor TMyClass.Destroy;
begin
WriteLn ('In destructor (which is actually the
IDisposable.Dispose method)'
);
end;
end.











After compiling this program you can run it; but we are not so much interested in running the program as in inspecting it with ILDASM. Start ILDASM and select File | Open. Navigate to the directory where DestructorTest.exe is located, and open it. You will see a window similar to that shown in Figure 24.2.


Figure 24.2: The ILDASM output for the DestructorTest example

The tree entry labeled MyClass represents the namespace created to hold all the symbols in the unit. Expand the MyClass node and notice the entries for TMyClass and Unit. Delphi for .NET creates a CLR class for each unit to implement initialization and finalization and also to let you still write global routines; they become methods of that Unit class. Expand the node for TMyClass and notice the Destroy node, which has a pink block. This node represents the Destroy method in TMyClass. Double-click the Destroy node to open a window displaying the full IL code for this method, as shown in Figure 24.3.


Figure 24.3: The ILDASM IL window for the Destroy destructor

The line you're looking for uses the .override directive:

.override [mscorlib]System.IDisposable::Dispose

This is an explicit override, and it says that the Destroy method is the implementation of Dispose in the IDisposable interface. The .override directive is how the thread of execution winds up in your destructor when Dispose is called from Free.





Note

The way IDisposable was dealt with illustrates a familiar pattern in the ongoing work on the Delphi for .NET compiler. Unlike C#, Delphi is an old language with a large and loyal following and a sizable existing code base. Borland could not sweep everything aside and mandate a new way of doing things that would break all existing code. The implementation of IDisposable was done in a way that should allow Delphi programmers to continue using familiar paradigms and minimize the impact of porting their existing code to the .NET platform.



Garbage Collection and Efficiency


Among programmers, the use of a garbage collector (GC) is the topic of many debates. Most programmers like the fact that a GC helps them reduce the chance of memory management errors. Some programmers, though, fear that a GC might not be efficient enough, a fear that often prevented the widespread use of this technology. The inefficient GC of the early Java virtual machines didn't really help in this respect. Even Microsoft has a hard time convincing all programmers to use its GC, up to the point that they overrate their solution depicting it as perfect. Even in Microsoft technical documentation about the GC, you'll find lots of hype and little facts.





Note

This is not to say that the GC doesn't do an adequate job; quite the contrary. But it works differently than it is presented. At present, your only way to know how the GC works is to write test case programs and study their effect.


As a starting point for your exploration, you can use the GarbageTest example. Use a Windows memory analysis tool to see how the memory is affected by program execution. The GarbageTest example declares the following class of large objects (about 10 KB):


type
TMyClass = class
private
data: Integer;
list: array [1..10240] of Char;
s: string;
public
constructor Create;
end;

The simplest test code is the following:


for i := 1 to 10000 do
begin
mc := TMyClass.Create;
WriteLn (mc.s);
end;

Writing this simple program in Delphi will flood the memory, because there is no call to Free. In .NET, however, the GC reuses the single memory block, so memory consumption is flat. If you save each object in an array declared as

objlist: array [1..10000] of TMyClass;

the memory consumption will increase at each cycle of the loop, causing problems! Keeping the object references in memory and then releasing them regularly or randomly can provide more complex tests. You'll find a few snippets in the program code, but you'll have to exercise your knowledge and imagination to build other significant test cases.

/ 279