分享
 
 
 

The Delphi Object Model (PART III)

王朝delphi·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

The Delphi Object Model (PART III)

Sample Chapter of Delphi in a Nutshell

Product:

Delphi all versions

Category:

OO-related

Skill Level:

Scoring:

Last Update:

05/21/2000

Search Keys:

delphi delphi3000 article Object Model OOP interfaces

Times Scored:

11

Visits:

4947

Uploader: Stefan Walther

Company: bluestep.com IT-Consulting

Reference: Ray Lischner - O"Reilly

Question/Problem/Abstract:

Delphi's support for object-oriented programming is rich and powerful. In addition to traditional classes and objects, Delphi also has interfaces (similar to those found in COM and Java), exception handling, and multithreaded programming. This chapter covers Delphi's object model in depth. You should already be familiar with standard Pascal and general principles of object-oriented programming.

Answer:

Reprinted with permission from O'Reilly & Associates

Messages

You should be familiar with Windows messages: user interactions and other events generate messages, which Windows sends to an application. An application processes messages one at a time to respond to the user and other events. Each kind of message has a unique number and two integer parameters. Sometimes a parameter is actually a pointer to a string or structure that contains more complex information. Messages form the heart of Windows event-driven architecture, and Delphi has a unique way of supporting Windows messages.

In Delphi, every object--not only window controls--can respond to messages. A message has an integer identifier and can contain any amount of additional information. In the VCL, the Application object receives Windows messages and maps them to equivalent Delphi messages. In other words, Windows messages are a special case of more general Delphi messages.

A Delphi message is a record where the first two bytes contain an integer message identifier, and the remainder of the record is programmer-defined. Delphi's message dispatcher never refers to any part of the message record past the message number, so you are free to store any amount or kind of information in a message record. By convention, the VCL always uses Windows-style message records (TMessage), but if you find other uses for Delphi messages, you don't need to feel so constrained.

To send a message to an object, fill in the message identifier and the rest of the message record and call the object's Dispatch method. Delphi looks up the message number in the object's message table. The message table contains pointers to all the message handlers that the class defines. If the class does not define a message handler for the message number, Delphi searches the parent class's message table. The search continues until Delphi finds a message handler or it reaches the TObject class. If the class and its ancestor classes do not define a message handler for the message number, Delphi calls the object's DefaultHandler method. Window controls in the VCL override DefaultHandler to pass the message to the window procedure; other classes usually ignore unknown messages. You can override DefaultHandler to do anything you want, perhaps raise an exception.

Use the message directive to declare a message handler for any message. See Chapter 5 for details about the message directive.

Message handlers use the same message table and dispatcher as dynamic methods. Each method that you declare with the dynamic directive is assigned a 16-bit negative number, which is really a message number. A call to a dynamic method uses the same dispatch code to look up the dynamic method, but if the method is not found, that means the dynamic method is abstract, so Delphi calls AbstractErrorProc to report a call to an abstract method.

Because dynamic methods use negative numbers, you cannot write a message handler for negative message numbers, that is, message numbers with the most-significant bit set to one. This limitation should not cause any problems for normal applications. If you need to define custom messages, you have the entire space above WM_USER ($0F00) available, up to $7FFF. Delphi looks up dynamic methods and messages in the same table using a linear search, so with large message tables, your application will waste time performing method lookups.

Delphi's message system is entirely general purpose, so you might find a creative use for it. Usually, interfaces provide the same capability, but with better performance and increased type-safety.

Memory Management

Delphi manages the memory and lifetime of strings, Variants, dynamic arrays, and interfaces automatically. For all other dynamically allocated memory, you--the programmer--are in charge. It's easy to be confused because it seems as though Delphi automatically manages the memory of components, too, but that's just a trick of the VCL.

Components Versus Objects

The VCL's TComponent class has two fancy mechanisms for managing object lifetimes, and they often confuse new Delphi programmers, tricking them into thinking that Delphi always manages object lifetimes. It's important that you understand exactly how components work, so you won't be fooled.

Every component has an owner. When the owner is freed, it automatically frees the components that it owns. A form owns the components you drop on it, so when the form is freed, it automatically frees all the components on the form. Thus, you don't usually need to be concerned with managing the lifetime of forms and components.

When a form or component frees a component it owns, the owner also checks whether it has a published field of the same name as the component. If so, the owner sets that field to nil. Thus, if your form dynamically adds or removes components, the form's fields always contain valid object references or are nil. Don't be fooled into thinking that Delphi does this for any other field or object reference. The trick works only for published fields (such as those automatically created when you drop a component on a form in the IDE's form editor), and only when the field name matches the component name.

Memory management is thread-safe, provided you use Delphi's classes or functions to create the threads. If you go straight to the Windows API and the CreateThread function, you must set the IsMultiThread variable to True. For more information, see Chapter 4, Concurrent Programming.

Ordinarily, when you construct an object, Delphi calls NewInstance to allocate and initialize the object. You can override NewInstance to change the way Delphi allocates memory for the object. For example, suppose you have an application that frequently uses doubly linked lists. Instead of using the general-purpose memory allocator for every node, it's much faster to keep a chain of available nodes for reuse. Use Delphi's memory manager only when the node list is empty. If your application frequently allocates and frees nodes, this special-purpose allocator can be faster than the general-purpose allocator. Example 2-17 shows a simple implementation of this scheme. (See Chapter 4 for a thread-safe version of this class.)

Example 2-17: Custom Memory Management for Linked Lists

type

TNode = class

private

fNext, fPrevious: TNode;

protected

// Nodes are under control of TLinkedList.

procedure Relink(NewNext, NewPrevious: TNode);

constructor Create(Next: TNode = nil; Previous: TNode = nil);

procedure RealFree;

public

destructor Destroy; override;

class function NewInstance: TObject; override;

procedure FreeInstance; override;

property Next: TNode read fNext;

property Previous: TNode read fPrevious;

end;

// Singly linked list of nodes that are free for reuse.

// Only the Next fields are used to maintain this list.

var

NodeList: TNode;

// Allocate a new node by getting the head of the NodeList.

// Remember to call InitInstance to initialize the node that was

// taken from NodeList.

// If the NodeList is empty, allocate a node normally.

class function TNode.NewInstance: TObject;

begin

if NodeList = nil then

Result := inherited NewInstance

else

begin

Result := NodeList;

NodeList := NodeList.Next;

InitInstance(Result);

end;

end;

// Because the NodeList uses only the Next field, set the Previous

// field to a special value. If a program erroneously refers to the

// Previous field of a free node, you can see the special value

// and know the cause of the error.

const

BadPointerValueToFlagErrors = Pointer($F0EE0BAD);

// Free a node by adding it to the head of the NodeList. This is MUCH

// faster than using the general-purpose memory manager.

procedure TNode.FreeInstance;

begin

fPrevious := BadPointerValueToFlagErrors;

fNext := NodeList;

NodeList := Self;

end;

// If you want to clean up the list properly when the application

// finishes, call RealFree for each node in the list. The inherited

// FreeInstance method frees and cleans up the node for real.

procedure TNode.RealFree;

begin

inherited FreeInstance;

end;

You can also replace the entire memory management system that Delphi uses. Install a new memory manager by calling SetMemoryManager. For example, you might want to replace Delphi's suballocator with an allocator that performs additional error checking. Example 2-18 shows a custom memory manager that keeps a list of pointers the program has allocated and explicitly checks each attempt to free a pointer against the list. Any attempt to free an invalid pointer is refused, and Delphi will report a runtime error (which SysUtils changes to an exception). As a bonus, the memory manager checks that the list is empty when the application ends. If the list is not empty, you have a memory leak.

Example 2-18: Installing a Custom Memory Manager

unit CheckMemMgr;

interface

uses Windows;

function CheckGet(Size: Integer): Pointer;

function CheckFree(Mem: Pointer): Integer;

function CheckRealloc(Mem: Pointer; Size: Integer): Pointer;

var

HeapFlags: DWord; // In a single-threaded application, you might

// want to set this to Heap_No_Serialize.

implementation

const

MaxSize = MaxInt div 4;

type

TPointerArray = array[1..MaxSize] of Pointer;

PPointerArray = ^TPointerArray;

var

Heap: THandle; // Windows heap for the pointer list

List: PPointerArray; // List of allocated pointers

ListSize: Integer; // Number of pointers in the list

ListAlloc: Integer; // Capacity of the pointer list

// If the list of allocated pointers is not empty when the program

// finishes, that means you have a memory leak. Handling the memory

// leak is left as an exercise for the reader.

procedure MemoryLeak;

begin

// Report the leak to the user, but remember that the program is

// shutting down, so you should probably stick to the Windows API

// and not use the VCL.

end;

// Add a pointer to the list.

procedure AddMem(Mem: Pointer);

begin

if List = nil then

begin

// New list of pointers.

ListAlloc := 8;

List := HeapAlloc(Heap, HeapFlags, ListAlloc * SizeOf(Pointer));

end

else if ListSize >= ListAlloc then

begin

// Make the list bigger. Try to do it somewhat intelligently.

if ListAlloc < 256 then

ListAlloc := ListAlloc * 2

else

ListAlloc := ListAlloc + 256;

List := HeapRealloc(Heap, HeapFlags, List,

ListAlloc * SizeOf(Pointer));

end;

// Add a pointer to the list.

Inc(ListSize);

List[ListSize] := Mem;

end;

// Look for a pointer in the list, and remove it. Return True for

// success, and False if the pointer is not in the list.

function RemoveMem(Mem: Pointer): Boolean;

var

I: Integer;

begin

for I := 1 to ListSize do

if List[I] = Mem then

begin

MoveMemory(@List[I], @List[I+1], (ListSize-I) * SizeOf(Pointer));

Dec(ListSize);

Result := True;

Exit;

end;

Result := False;

end;

// Replacement memory allocator.

function CheckGet(Size: Integer): Pointer;

begin

Result := SysGetMem(Size);

AddMem(Result);

end;

// If the pointer isn't in the list, don't call the real

// Free function. Return 0 for success, and non-zero for an error.

function CheckFree(Mem: Pointer): Integer;

begin

if not RemoveMem(Mem) then

Result := 1

else

Result := SysFreeMem(Mem);

end;

// Remove the old pointer and add the new one, which might be the

// same as the old one, or it might be different. Return nil for

// an error, and Delphi will raise an exception.

function CheckRealloc(Mem: Pointer; Size: Integer): Pointer;

begin

if not RemoveMem(Mem) then

Result := nil

else

begin

Result :=SysReallocMem(Mem, Size);

AddMem(Result);

end;

end;

procedure SetNewManager;

var

Mgr: TMemoryManager;

begin

Mgr.GetMem := CheckGet;

Mgr.FreeMem := CheckFree;

Mgr.ReallocMem := CheckRealloc;

SetMemoryManager(Mgr);

end;

initialization

Heap := HeapCreate(0, HeapFlags, 0);

SetNewManager;

finalization

if ListSize <> 0 then

MemoryLeak;

HeapDestroy(Heap);

end.

If you define a custom memory manager, you must ensure that your memory manager is used for all memory allocation. The easiest way to do this is to set the memory manager in a unit's initialization section, as shown in Example 2-18. The memory management unit must be the first unit listed in the project's uses declaration.

Ordinarily, if a unit makes global changes in its initialization section, it should clean up those changes in its finalization section. A unit in a package might be loaded and unloaded many times in a single application, so cleaning up is important. A memory manager is different, though. Memory allocated by one manager cannot be freed by another manager, so you must ensure that only one manager is active in an application, and that the manager is active for the entire duration of the application. This means you must not put your memory manager in a package, although you can use a DLL, as explained in the next section.

Memory and DLLs

If you use DLLs and try to pass objects between DLLs or between the application and a DLL, you run into a number of problems. First of all, each DLL and EXE keeps its own copy of its class tables. The is and as operators do not work correctly for objects passed between DLLs and EXEs. Use packages (described in Chapter 1) to solve this problem. Another problem is that any memory allocated in a DLL is owned by that DLL. When Windows unloads the DLL, all memory allocated by the DLL is freed, even if the EXE or another DLL holds a pointer to that memory. This can be a major problem when using strings, dynamic arrays, and Variants because you never know when Delphi will allocate memory automatically.

The solution is to use the ShareMem unit as the first unit of your project and every DLL. The ShareMem unit installs a custom memory manager that redirects all memory allocation requests to a special DLL, BorlndMM.dll. The application doesn't unload BorlndMM until the application exits. The DLL magic takes place transparently, so you don't need to worry about the details. Just make sure you use the ShareMem unit, and make sure it is the first unit used by your program and libraries. When you release your application to your clients or customers, you will need to include BorlndMM.dll.

If you define your own memory manager, and you need to use DLLs, you must duplicate the magic performed by the ShareMem unit. You can replace ShareMem with your own unit that forwards memory requests to your DLL, which uses your custom memory manager. Example 2-19 shows one way to define your own replacement for the ShareMem unit.

Example 2-19: Defining a Shared Memory Manager

unit CheckShareMem;

// Use this unit first so all memory allocations use the shared

// memory manager. The application and all DLLs must use this unit.

// You cannot use packages because those DLLs use the default Borland

// shared memory manager.

interface

function CheckGet(Size: Integer): Pointer;

function CheckFree(Mem: Pointer): Integer;

function CheckRealloc(Mem: Pointer; Size: Integer): Pointer;

implementation

const

DLL = 'CheckMM.dll';

function CheckGet(Size: Integer): Pointer; external DLL;

function CheckFree(Mem: Pointer): Integer; external DLL;

function CheckRealloc(Mem: Pointer; Size: Integer): Pointer;

external DLL;

procedure SetNewManager;

var

Mgr: TMemoryManager;

begin

Mgr.GetMem := CheckGet;

Mgr.FreeMem := CheckFree;

Mgr.ReallocMem := CheckRealloc;

SetMemoryManager(Mgr);

end;

initialization

SetNewManager;

end.

The CheckMM DLL uses your custom memory manager and exports its functions so they can be used by the CheckShareMem unit. Example 2-20 shows the source code for the CheckMM library.

Example 2-20: Defining the Shared Memory Manager DLL

library CheckMM;

// Replacement for BorlndMM.dll to use a custom memory manager.

uses

CheckMemMgr;

exports

CheckGet, CheckFree, CheckRealloc;

begin

end.

Your program and library projects use the CheckShareMem unit first, and all memory requests go to CheckMM.dll, which uses the error-checking memory manager. You don't often need to replace Delphi's memory manager, but as you can see, it isn't difficult to do.

TIP:

The memory manager that comes with Delphi works well for most applications, but it does not perform well in some cases. The average application allocates and frees memory in chunks of varying sizes. If your application is different and allocates memory in ever-increasing sizes (say, because you have a dynamic array that grows in small steps to a very large size), performance will suffer. Delphi's memory manager will allocate more memory than your application needs. One solution is to redesign your program so it uses memory in a different pattern (say, by preallocating a large dynamic array). Another solution is to write a memory manager that better meets the specialized needs of your application. For example, the new memory manager might use the Windows API (HeapAllocate, etc.).

Old-Style Object Types

In addition to class types, Delphi supports an obsolete type that uses the object keyword. Old-style objects exist for backward compatibility with Turbo Pascal, but they might be dropped entirely from future versions of Delphi.

Old-style object types are more like records than new-style objects. Fields in an old-style object are laid out in the same manner as in records. If the object type does not have any virtual methods, there is no hidden field for the VMT pointer, for example. Unlike records, object types can use inheritance. Derived fields appear after inherited fields. If a class declares a virtual method, its first field is the VMT pointer, which appears after all the inherited fields. (Unlike a new-style object, where the VMT pointer is always first because TObject declares virtual methods.)

An old-style object type can have private, protected, and public sections, but not published or automated sections. Because it cannot have a published section, an old object type cannot have any runtime type information. An old object type cannot implement interfaces.

Constructors and destructors work differently in old-style object types than in new-style class types. To create an instance of an old object type, call the New procedure. The newly allocated object is initialized to all zero. If you declare a constructor, you can call it as part of the call to New. Pass the constructor name and arguments as the second argument to New. Similarly, you can call a destructor when you call Dispose to free the object instance. The destructor name and arguments are the second argument to Dispose.

You don't have to allocate an old-style object instance dynamically. You can treat the object type as a record type and declare object-type variables as unit-level or local variables. Delphi automatically initializes string, dynamic array, and Variant fields, but does not initialize other fields in the object instance.

Unlike new-style class types, exceptions in old-style constructors do not automatically cause Delphi to free a dynamically created object or call the destructor.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有