Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

VCL Overview

Supplied with C++Builder is a chart that schematically represents the Visual Component Library (VCL) object hierarchy. Not surprisingly, this chart has expanded with each new version of C++Builder. What you can't see, however, is the true complexity of the VCL. The explanation is simple: The VCL is much more than objects descending from other objects.

The VCL is based on what is known as the PME model, including properties, methods, and events. This architecture is joined with the Component Palette, Object Inspector, and IDE, giving developers a rapid approach to building applications known as Rapid Application Development (RAD). A developer can drop components onto a form and have a working Windows application almost without writing a single line of code. Obviously, writing code is required to make the application fully functional, but the VCL handles most of the work for you, making application development very expedient and more enjoyable. You can spend more time building the working blocks of your application rather than having to spend repetitive time with the framework of each Windows application you develop.

The remainder of this topic will take a very brief look at the hierarchy of VCL objects in general.

It All Starts at TObject

The VCL is fundamentally a group of objects that descend from the abstract class TObject. This class provides the capability to respond to the creation and destruction of objects, supports message handling, and contains class type and Runtime Type Information (RTTI) of its published properties.

RTTI enables you to determine the type of an object at runtime even when the code uses a pointer or reference to that object. As an example, C++Builder passes a TObject pointer to each of its events. This might be a mouse-click event or an object obtaining focus. Through the use of RTTI it is possible to perform a cast (dynamic_cast) to either use the object or determine the object type. The RTTI mechanism also enables testing an object type by using the typeid operator. The dynamic_cast operator is demonstrated later in this chapter. The C++Builder Language Guide online help provides additional information on this topic.

Descending from TObject are many simple nonpersistent data classes, wrappers, and streams, such as TList, TStack, TPrinter, TRegistry, and TStream, to name a few.

Persistent data, in terms of the VCL, refers to the mechanism of storing property values. The simplest example is the caption of a button or label. At design time you enter the caption in the Object Inspector. This caption is maintained between programming sessions and is available at runtime. The data is persistent. Nonpersistent classes, therefore, refers to simple classes designed to perform particular functions, but unable to save their state between sessions.

A wrapper can be described as a means of placing an envelope around the more complex Windows API. Wrappers allow for the creation of objects or components that are easier to use and can be used across multiple projects. Components, and other objects to some extent, shield you from the API and at the same time provide the convenience of using its powerful features in an easy-to-use fashion. Later chapters will provide additional information on these topics.

Another commonly used descendant of TObject is the Exception class, which provides many built-in exception classes for handling conditions such as, divide-by-zero and stream errors. With very minimal work this class can also be used to create custom classes for use in your own applications.

The other major branches of TObject include TPersistent, TComponent, TControl, TGraphicControl, and TWinControl.

TPersistent adds methods to TObject that enable the object to save its state prior to destruction, and reload that state when it is created again. This class is important in the creation of components that contain custom classes as properties. If the property needs to be streamed, it must descend from TPersistent rather than TObject. This branch includes many types of classes with the most common being TCanvas, TClipboard, TGraphic, TGraphicsObject, and TStrings. TPersistent descends to provide TComponent, the common base for all VCL components.

Property streaming refers to the mechanism by which the object's property values are written to the form's file. When the project is reopened, the property values are streamed (or read) back, thereby restoring their previous values.

TComponent objects provide the foundation for building C++Builder applications. Components have the capability to appear on the Component Palette, become parents for other components, control other components, and perform streaming operations.

There are two types of components—visual and nonvisual. Nonvisual components require no visual interface and are, therefore, derived directly from TComponent. Visual components are required to have the capability to been seen and interact with the user at runtime. TControl adds the drawing routines and window events required for defining a visual component. These visual components are divided into two groups: windowed (TWinControl) and nonwindowed (TGraphicControl).

TGraphicControl components are responsible for drawing themselves, but never receive focus. Examples include TImage, TLabel, TBevel, and TShape.

TWinControl components are similar to TGraphicControl except that they can receive focus, allowing for user interaction. These components are known as windowed controls, have a window handle, and can contain (or be the parent of) other controls.

Building on Existing Objects

The C++Builder object-oriented architecture means faster application development through reuse of existing objects and supporting classes. Giving the component objects the capability to present their published properties to the developer via the Object Inspector provides the extra dimension that further improves the development cycle.

But, the Object Inspector does more than simply present the component's published properties for review or editing. In Figure 3.1 you can see the Object Inspector showing the common properties of TLabel, TEdit, TButton, and TCheckBox controls. Looking at the hierarchy of these controls, you can see that TLabel descends from TGraphicControl, and the remainder descend from TWinControl. The common ancestor for all four controls is therefore TControl (because TGraphicControl and TWinControl descend from TControl).

Figure 3.1Figure 3.1 Common properties of multiple selected components.

Figure 3.1 is, therefore, displaying all the common properties the components have inherited from TControl. If you change one of these properties while they are all selected, this change will be reflected in all the controls simultaneously.

Building objects from existing classes or objects enables you to develop additional functionality into the descendants while at the same time enabling the base classes to remain available for new descendants that require a common base with some additional unique features. Some developers might argue that this additional class and the associated RTTI contained within the hierarchy are only adding to the overhead of the application. This overhead is well worth the benefits this object model provides. Each of the objects and components provided with C++Builder can appear quite differently, but share common features that are inherited from ancestor objects, making the development process more streamlined.

Using the VCL

It is important to understand how the VCL differs from regular classes and objects. The VCL originates from Delphi. As a result, all VCL objects are created on the free store and referenced as pointers rather than static objects. Only VCL objects need to be created and used in this fashion. All standard C/C++ objects can be used in either fashion.

An example will illustrate this. The following code first shows how a standard C++ class can be created on the stack, and then on the heap. Then, it shows how VCL objects must be created.

The C++ class code is as follows:

class MyClass
{
private:
  int MyVar;
public:
  MyClass(void);
};

Creating an instance of this class on the stack is shown next. When the class goes out of scope, the memory allocated for the object is released automatically.

MyClass Tmp;
Tmp.MyVar = 10;
// do something

Next, you look at creating an instance of this class on the heap. It is the responsibility of the creator to destroy the object before it goes out of scope. Otherwise, the memory is not released, resulting in a memory leak.

MyClass *Tmp;
Tmp = new MyClass;
Tmp->MyVar = 10;
// do something
delete Tmp;

VCL objects are like the second example, created on the heap. If you attempt to create a VCL object on the stack, the compiler will complain with the error message, VCL style classes must be constructed using operator new.

C++Builder has also made provisions for automatic destruction of objects that have owners. Let's assume you create a TLabel object dynamically and pass this as the owner:

TLabel *MyLabel = new TLabel(this);

When MyLabel goes out of scope, you at first assume a memory leak has occurred. Objects of this nature don't have to worry about freeing themselves explicitly because the VCL has a built-in mechanism to free all child objects before an owner object is destroyed. The term parent object was carefully avoided. This will be explained further.

Nonvisual components have owners, whereas visual components have owners and parents. The easiest way to distinguish the two terms is to think of the owner as the creator and the parent as the container allowing the component to exist. Suppose you have a TPanel component on a form, and on this component are three label components. There are two possible scenarios, depending on who created the labels.

In the first scenario, you have the labels being dropped onto the panel at design time. In this case, the panel is the parent for each of the labels, but the application is the owner. When the application is closed, the VCL ensures that each of the child objects (the panel and the three labels) are all destroyed before the application itself is destroyed.

In another case, you might drop an aggregate component (a component made up of many components) onto a form. For this example you assume it is a panel with three labels on it. The component creates the panel, and then creates three labels to sit on that panel. The panel is still the parent of the labels, but now the owner of the panel and the three labels is the component that was dropped onto the form. If the component is destroyed, the panel and labels also will be automatically destroyed.

This is a great feature for design time objects, but if you create objects at runtime, you should delete them explicitly. You won't have any memory leaks if you don't, but it makes the code more readable and documents the intention of the code. The following code demonstrates creating these objects at runtime.

TPanel *Panel1;
TLabel *Label1, *Label2, *Label3;
Panel1 = new TPanel(this);
Panel1->Parent = Form1;
Label1 = new TLabel(this);
Label1->Parent = Panel1;
Label2 = new TLabel(this);
Label2->Parent = Panel1;
Label3 = new TLabel(this);
Label3->Parent = Panel1;
// set other properties such as caption, position, etc
// do something
delete Label1;
delete Label2;
delete Label3;
delete Panel1;

The panel is created with the application as the owner and the form as the parent. The labels are then created similarly, with the difference being that the panel is made the parent of the labels. You could move the labels to another panel just by changing the Parent property. It is possible to delete the panel so that the labels would be deleted automatically, but I prefer to delete everything explicitly. This becomes more important when dealing with real-world applications in which global pointers are shared. It is also good practice to set the pointers to NULL (or zero) after they have been deleted (unless they are about to go out of scope). If another part of your application tries to delete the object after it has been destroyed, and you haven't nulled the pointer, an access violation is certain.

As mentioned earlier, the use of std::auto_ptr offers a mechanism for exception safe, class, and function local allocation of VCL objects.

The C++ Extensions

C++Builder has added extensions to the C++ language to make it a powerful product capable of utilizing the VCL and fitting seamlessly into the PME model. Some programmers consider the issue of C++ extensions controversial, but they can be useful, so long as they make the product powerful, maintain compatibility with ANSI standards, and provide for a shorter development cycle. In all cases, C++Builder manages this very well.

The extensions are listed next with a brief description. Please note that each of the extensions is prefixed with two underscores.

The __automated Class Extension

Object linking and embedding (OLE) does not provide type information in a type library. The automated section of a class that is derived from TAutoObject or a descendant of TAutoObject is used by applications supporting OLE automation. The required OLE automation information is generated for member functions and properties declared in this section.

class TMyAutoClass : public TAutoObject
{
public:
  virtual __fastcall TMyAutoClass(void);

__automated:
   AnsiString __fastcall GetClassName(void) { return("MyAutoClass"); }
};

The last point to make is that the declared method is of type __fastcall (described a little later). This is required for automated functions and causes the compiler to attempt to pass the parameters in the registers rather than on the stack.

The __classid(class) Class Extension

The __classid extension is used to bridge the gap between the VCL, RTTI functions, and the C++ language. It is used when an RTTI function requires class information as a parameter.

As an example, the RegisterComponentEditor() method enables you to create and register a custom editor for your components. My business produces a security component package (MJFSecurity) specifically for C++Builder. This package includes a number of custom components designed for protecting applications from piracy. One of these components is called TAppLock, and it uses a custom editor called TAppLockEditor. As part of the Register() method of the package, the editor is registered using the following code:

namespace Applock
{
  void __fastcall PACKAGE Register()
  {
    // ... code omitted from here
    RegisterComponentEditor(__classid(TAppLock), __classid(TAppLockEditor));
  }
}

The RegisterComponentEditor() method takes two parameters as pointers to TMetaClass (C++Builder's representation of the Delphi class-reference type). The TMetaClass for a given class can be acquired by using the __classid operator. The compiler uses the __classid operator to generate a pointer to the vtable (virtual table) for the given class name.

For further information on MJFSecurity, go to http://www.mjfreelancing.com/.

The __closure Class Extension

Standard C++ enables you to assign a derived class instance to a base class pointer, but does not enable you to assign a derived class's member function to a base class member function pointer. Listing 3.1 demonstrates this problem.

Listing 3.1 Illegal Assignment of a Derived Class Member Function

enum HandTypes {htHour, htMinute, htSecond};

class TWatch
{
public:
  void MoveHand(HandTypes HandType);
};

class TWatchBrand : public TWatch
{
public:
  void NewFeature(bool Start);
};

void (TWatch::*Wptr)(bool);    // declare a base class member
                  // function pointer
Wptr = &TWatchBrand::NewFeature;  // illegal assignment of derived
                  // class member function

C++Builder includes the __closure keyword to permit the previous situation. Listing 3.2 uses the classes from Listing 3.1 to demonstrate how this is done.

Listing 3.2 Assigning a Derived Class Member Function to a Base Member Function Pointer by Using __closure

TWatchBrand *WObj = new TWatchBrand; // create the object
void (__closure *Wptr)(bool);     // define a closure pointer
Wptr = WObj->NewFeature;      // set it to the NewFeature member function
Wptr(false);             // call the function, passing false
Wptr = 0;               // set the pointer to NULL
delete WObj;             // delete the object

Note that you can also assign closure pointers to the base class member functions, as Listing 3.3 shows.

Listing 3.3 Assigning Closure Pointers to Base Class Member Functions

void (__closure *Wptr2)(HandTypes);
Wptr2 = WObj->MoveHand;
Wptr2(htSecond);

The __closure keyword is predominantly used for events in C++Builder.

The __declspec Class Extension

VCL classes have the following restrictions imposed on them:

  • No virtual base classes are allowed.

  • No multiple inheritance is allowed.

  • They must be dynamically allocated on the heap by using the global new operator.

  • They must have a destructor.

  • Copy constructors and assignment operators are not compiler generated for VCL-derived classes.

The __declspec keyword is provided for language support with the VCL to overcome the previously mentioned items. The sysmac.h file provides macros that you should use if you need to use this keyword. The __declspec variations are discussed next.

__declspec(delphiclass, package)

sysmac.h defines the macro DELPHICLASS as __declspec(delphiclass, package). The delphiclass argument is used for the declaration of classes derived from TObject. If a class is translated from Delphi, the compiler needs to know that the class is derived from TObject. Hence, this modifier is used.

Similarly, if you create a new class that is to be used as a property type in a component, and you need to forward declare the class, you need to use the __declspec (delphiclass, package) keyword for the compiler to understand. Listing 3.4 shows an example of forward declaring a class where a new component has a pointer to that class type.

Listing 3.4 Forward Declaring a Class to Use as a Member of Another Class

class TMyObject;

class TMyClass : public TComponent
{
private:
  TMyObject *FMyNewObject;

public:
 __fastcall TMyClass(TComponent *Owner) : TComponent(Owner){}
 __fastcall ~TMyClass(void);

};

class TMyObject : public TPersistent
{
public:
 __fastcall TMyObject(void){}
 __fastcall ~TMyObject(void);
};

Listing 3.4 will compile because the compiler only has a reference to the TMyObject class. If you need to include a property of type TMyObject, you need to tell the compiler the class is derived from a descendant of TObject. The modified code is shown in Listing 3.5.

Listing 3.5 Using DELPHICLASS to Forward Declare a Class Required as a Property of Another Class

class DELPHICLASS TMyObject;

class TMyObject;

class TMyClass : public TComponent
{
private:
 TMyObject *FMyNewObject;

public:
 __fastcall TMyClass(TComponent *Owner) : TComponent(Owner){}
 __fastcall ~TMyClass(void);

__published:
 __property TMyObject *NewObject = {read = FMyNewObject};
};

class TMyObject : public TPersistent
{
public:
 __fastcall TMyObject(void){}
 __fastcall ~TMyObject(void);
};

sysmac.h also defines a closely related macro RTL_DELPHICLASS as __declspec (delphiclass). This macro is used when nonpackaged RTL (runtime library) functionality is required in a class. Because components are created in design time packages or runtime/design time packages, you will find the DELPHICLASS macro used.

__declspec(delphireturn, package)

sysmac.h defines the macro DELPHIRETURN as __declspec(delphireturn, package).

The delphireturn parameter is used internally by C++Builder to enable classes created with C++Builder to support Delphi's built-in data types and language constructs. Examples include Currency, AnsiString, Variant, TDateTime, and Set.

Similar to __declspec(delphiclass), there is also a macro defined for __declspec(delphireturn). This macro, RTL_DELPHIRETURN, is used when Delphi's semantics are required in nonpackaged classes.

__declspec(dynamic)

sysmac.h defines the macro DYNAMIC as __declspec(dynamic). The dynamic argument is used for dynamic functions and is valid only for classes derived from TObject. These are similar to virtual functions except that the vtable information is stored only in the object that created the functions. If you call a dynamic function that doesn't exist, the ancestor vtables are searched until the function is found. Dynamic functions effectively reduce the size of the vtables at the expense of a short delay to look up the ancestor tables.

Dynamic functions cannot be redeclared as virtual and vice versa.

__declspec(hidesbase)

sysmac.h defines the macro HIDESBASE as __declspec(hidesbase). The hidesbase parameter is used to preserve Delphi semantics. Imagine a class called C1; derived from it is another class called C2. If both classes contain a function called foo, C++ interprets C2::foo() as a replacement for C1::foo(). In Delphi, C2::foo() is a different function to C1::foo(). To preserve this in C++ classes, you use the HIDESBASE macro. Listing 3.6 demonstrates its use.

Listing 3.6 Using HIDESBASE to Override Class Methods in Descendant Classes

class TMyObject : public TPersistent
{
public:
 __fastcall TMyObject(void){}
 __fastcall ~TMyObject(void);
  void __fastcall Func(void){}
};

class TMyObject2 : public TMyObject
{
public:
 __fastcall TMyObject2(void){}
 __fastcall ~TMyObject2(void);
  HIDESBASE void __fastcall Func(void){}
};

__declspec(hidesbase, dynamic)

sysmac.h defines the macro HIDESBASEDYNAMIC as __declspec(hidesbase, dynamic). This is used when Delphi's semantics need to be preserved for dynamic functions. The HIDESBASE macro is used when you need to preserve the way Pascal overrides methods in descendant classes. The HIDESBASEDYNAMIC macro does the same for dynamic methods.

__declspec(package)

sysmac.h defines the macro PACKAGE as __declspec(package). The package argument in __declspec(package) indicates that the code defining the class can be compiled in a package. This enables classes to be imported and exported from the resulting BPL file.

__declspec(pascalimplementation)

sysmac.h defines the macro PASCALIMPLEMENTATION as __declspec(pascalimplementation). The pascalimplementation argument indicates that the code defining the class was implemented in Delphi. This modifier appears in a Delphi portability header file with an .hpp extension.

The __fastcall Keyword

The __fastcall keyword is used to declare functions that expect parameters to be passed in registers. If the parameter is a floating-point or struct type, the registers are not used.

In general, use the __fastcall keyword only when declaring VCL class member functions. Using this modifier in all other cases will, more often than not, result in reduced performance. Typical examples of its use are found in all form class and component member functions. You should also note that the __fastcall modifier is used in name mangling.

The __property Keyword

The __property keyword is used to declare properties in classes, even non-VCL classes. Properties are data members, with the following additional features:

  • They have associated read and write methods.

  • They can indicate default values.

  • They can be streamed to and from a form file.

  • They can extend a property defined in a base class.

  • They can make the data member read-only or write-only.

Refer to Chapter 4, "Creating Custom Components," for a more detailed look at properties.

The __published Keyword

The __published keyword is permitted only in classes descendant from TObject. Visibility rules for the __published section of a VCL class are the same as the public section, with the addition of RTTI being generated for data members and properties declared. RTTI enables you to query the data members, properties, and member functions of a class.

This RTTI is useful in event handlers. All standard events have a parameter passed of type TObject*. This parameter can be queried when the type of sender is unknown. The following example shows how this is done when the OnButtonClick event is called from either a button click or a manual call such as Button1Click(NULL). Note that you can also do a manual call, passing the pointer of another button such as Button1Click(Button2). Listing 3.7 shows how this can be implemented.

Listing 3.7 Using RTTI to Query an Object Type

void __fastcall TForm1::Button1Click(TObect *Sender)
{
TButton *Button = dynamic_cast<TButton *>(Sender);
if (Button)
  {
  // do something if a button was pressed.
  }
else
  {
  // do something when a call was made
  // passing a NULL value for *Sender)
  }
}

VCL and CLX

Although the underpinnings of VCL and CLX are very different (which is why VCL is not portable across platforms), from a usage perspective they are almost identical, providing largely the same components in the same hierarchy with the same methods and properties. Look in the help to see if a component is VCL, CLX, or both.

NOTE

If you create a new CLX Application or new CLX MDI Application, you get a project that only supplies CLX components on the component palette. These projects are available from the File, New menu, or from the dialog that is presented when you pick File, New, Other (CLX Application is on the New page and CLX MDI Application is on the Projects page).

  • + Share This
  • 🔖 Save To Your Account