August 1997

Using OWL classes in C++Builder

by Kent Reisdorph

When I first delved into C++Builder, as an OWL programmer I was a little dismayed to find I had to learn a new class library. Still, enough similarity existed between OWL and VCL that I could find my way around.

I noticed right away that VCL, like OWL, has classes called TRect, TPoint, and TSize. "Well, I know what to do with those!" I thought. But it didn't take long to realize that the VCL classes were like the OWL classes in name only--they were just wrappers around the Windows RECT, POINT, and SIZE structures, respectively. Bummer! I like those OWL classes!

Take TRect, for instance. It includes lots of overloaded constructors to let you easily create a TRect object. In addition, the overloaded operators and functions make manipulating rectangles easy with the OWL version of TRect. If only I could use the OWL TRect and other great OWL helper classes in my VCL applications.

In this article, we'll show how to incorporate some of the OWL helper classes in your C++Builder applications. In order to use the classes discussed in this article, you'll need Borland C++ 5.02. While you might be able to use classes from other versions of OWL, this article assumes that you have the C++ 5.02 update. We'll also discuss a couple of OWL classes that you probably don't know exist. Let's begin by looking at some specific classes you might want to use.

Winsys, here we come!

The TRect, TPoint, and TSize classes, along with a couple of other classes we'll discuss later, aren't really OWL classes at all. They're listed in the OWL help file, but they're actually what the OWL designers call Winsys classes. You can consider the Winsys classes part of the BC++ class library (Classlib). The BC++ 5.0 BC5\INCLUDE\WINSYS directory includes the headers for these files, and the directory BC5\SOURCE\CLASSLIB contains the classes' source files. Since these classes aren't OWL classes per se, you can use them independently of OWL.

Several other classes in the Classlib/Winsys family may be of use--TProfile (an INI file-manipulation class), TRegistry, TString, and others. However, VCL provides good classes for these needs, so you don't have to bother with the Winsys classes that deal with INI files, the registry, string manipulation, and so on.

Putting Winsys classes to work

Assuming you want to use some of the Winsys classes in your C++Builder applications, how do you go about it? There are several ways:
bulletAdd the appropriate Winsys source files directly to your application.
bulletLink the classes statically using BIDSF.LIB.
bulletLink the classes dynamically using BIDSF.DLL and the BIDSFI.LIB import library.

The method you use depends largely on your needs, although in most cases, I think you'll prefer the first method. Let's take a look at each of these options and how to implement them in a C++Builder application.

Adding Winsys source files to your project

Adding the source files directly to your project will probably make the most sense for the majority of your C++Builder applications; this is the most complete method and results in the fewest headaches for the programmer. You achieve virtually the same effect as static linking using the static library (which we'll discuss in the next section), but you gain one important benefit: You can use namespaces to distinguish the Winsys classes from their VCL counterparts.

We'll get back to namespaces in a minute, but first, let's take a look at how to add the source files to your project. Implementing this method is a four-step process:

  1. Add the required source files to the project.
  2. Change the Include path in the Project Options dialog box.
  3. Add the include directives to include the necessary headers.
  4. Add a define to the Conditional Defines field in the Project Options dialog box.
First, you must decide which source files you need to add to your project. Listing A contains a program that uses three Winsys classes: TRect, TUIMetric, and TSystem. You'll find these classes in the source files GEOMETRY.CPP, UIMETRIC.CPP, and SYSTEM.CPP, respectively. In this case, you'll add these files to your C++Builder project.

The easiest way to add the files is to use C++Builder's Add File To Project command. To access this feature, simply click the Add File To Project button on the C++Builder speed bar. Doing so opens the file-selection common dialog box. Navigate to your \BC5\SOURCE\CLASSLIB directory and select the GEOMETRY.CPP, UIMETRIC.CPP, and SYSTEM.CPP files. To add the files all at once, hold down the [Ctrl] key while clicking on each of the filenames. Then, click the Open button, which closes the dialog box and adds the source files to your project.

Next, choose Project... from the C++Builder Options menu. In the resulting Project Options dialog box, click on the Directories/Conditionals tab. At the end of the Include Path field, add the path to the INCLUDE directory in which you've installed Borland C++ 5.02. In our case, we added the text

;c:\bc5\include
as shown in Figure A. Be sure to use a semicolon to separate the path you type from the other paths, and take care not to remove the paths already listed. This step is necessary because when you compile the application, the Winsys files will need to include other Winsys headers that C++Builder must be able to locate.

Figure A: Enter the path to the INCLUDE directory where you installed BC++ 5.02. Figure A

As long as you're in the Project Options dialog box, let's take care of step 4 in this process by adding a symbol to the Conditional Defines field. Type the following in that field:

BI_NAMESPACE
This entry will tell the compiler to use the ClassLib namespace when it compiles the Winsys files. (For more information about namespaces, see "The Magic of Namespaces" below.) Click OK to close the dialog box.

For the final step (besides writing code), you need to include the headers for the files. For example, the include directive for the GEOMETRY header looks like this:

#include <winsys\geometry.h>
Since you've already added the base path (C:\BC5\INCLUDE, for example) to the project's include path, you don't need to type the entire path to the Winsys directory when you include the Winsys headers. This point is important: You must place the include directive for the Winsys classes above any VCL includes. Otherwise, the compiler will confuse classes with common names (the VCL TRect versus the Winsys TRect, for example). Take a peek at Listing A to see where we placed the includes for the Winsys files.

At this point, you're ready to type some code and compile. You can begin using the Winsys classes just as you would use VCL classes--with one important distinction. For more information on that distinction, see "The Magic of Namespaces."

Statically linking Winsys classes

You may prefer to link to the pre-built static libraries that come with Borland C++ 5.02 rather than adding the source files directly to your project. Doing so might be desirable if you want to use a number of the Winsys classes in one of your applications. You might find it easier just to add one library file to your project than to add a dozen or more source files. Again, you're dealing with a multi-step process:

  1. Add the Borland C++ BIDSF.LIB file to the project.
  2. Change the Include path in the Project Options.
  3. Add the include directives to include the necessary headers.

In step 1, you'll again use Add File To Project to add the static library file. In this case, add \BC5\LIB\BIDSF.LIB to your project--the Winsys classes reside in this static library file.

Steps 2 and 3 are identical to their counterparts discussed in the previous section. Notice that you don't add the Conditional Define for BI_NAMESPACE in this case. The static library is built without namespaces enabled, so adding that define would have no effect.

This method has a minor drawback. Since the ClassLib namespace hasn't been defined, you can't use it to differentiate between the two versions of TRect that the compiler must deal with. But, because the Winsys version of TRect was compiled without a namespace, it's in the global namespace by default.

As a result, you can modify an example shown in "The Magic of Namespaces" and use code like this:

using ::TRect;
TRect rect;

The double colon by itself signifies the global namespace. This method gives you a little less control over namespace pollution, but it's viable nonetheless. Once you've taken the steps we've outlined, you're ready to begin using the Winsys classes in your applications.

Dynamically linking the Winsys classes

If you want to use the Winsys classes in a suite of programs, it may make more sense to use the routines in a DLL rather than statically linking them to your application. When you link dynamically, you can use one DLL for all your applications (or DLLs) that use the library routines contained in the DLL, thereby avoiding code duplication.

VCL itself can't be used in a DLL--and for that reason, all C++Builder applications are statically linked. However, that fact doesn't stop you from using the Winsys classes in a DLL. The DLL has already been built for you, so all you have to do is put it to use. Using the Winsys classes in a DLL requires the following steps:

  1. Add the Borland C++ BIDSFI.LIB file to the project.
  2. Change the Include path in the Project Options.
  3. Add the include directives to include the necessary headers.

This list might look like the one from the previous section, but if you look closely you'll see that the library file is BIDSFI.LIB, not BIDSF.LIB.BIDSFI.LIB is the import library file for the BIDS52F.DLL, which you'll use for the Winsys classes.

To add the import library file, use C++Builder's Add File To Project feature to add the BIDSFI.LIB file (located in the \BC5\LIB directory) to your C++Builder project. Steps 2 and 3 are the same as in the previous section.

Whenever you add an import library file to a C++Builder project, the associated DLL will automatically load when your application starts. You'll need to be sure that BIDS52F.DLL is either in the application's directory or somewhere on the path--you can probably find BIDS52F.DLL in your \BC5\BIN directory. As with the previous methods, once you've set up the project to use the DLL, you can start using the Winsys classes as you would any VCL classes.

An example, please

As we mentioned, Listing A contains a sample program that uses three of the Winsys classes. To try this example, start with a new project. Place a Label component and a Button component on the form. Then, enter the code in the form's OnCreate handler and the button's OnClick handler as found in Listing A. (We've highlighted the code you need to enter in color.)

As you look over the code in Listing A, notice the FormCreate() function. This function uses the TSystem class to determine which operating system the user is running. It then displays the operating system information in a Label component.

The Button1Click() function uses Winsys TRect objects to create and manipulate two rectangles. The first rectangle changes the size and position of the application's window. The second rectangle displays a string on the form using the Windows API function DrawText(). The code determines the X position of the displayed text by using TUIMetric to obtain the height of a standard menu. The code finds the Y position by using TUIMetric to obtain the width of a vertical scroll bar.

When you use the Winsys TRect with API functions rather than the VCL TRect, the fourth parameter of the DrawText() function requires a Windows RECT structure pointer. For reasons that are beyond my understanding, the VCL TRect class doesn't have an operator to convert a TRect pointer to a RECT pointer. However, the Winsys version of TRect does have such a conversion operator. If you used a VCL TRect, then the DrawText() function would require a cast, and you'd have to write it like this:

DrawText(Canvas->Handle, "HelloThere!", -1, (RECT*)&rect, DT_SINGLELINE);

Because the Winsys TRect class has a RECT* conversion operator, you can write the DrawText() function without a cast, like so:

DrawText(Canvas->Handle, "Hello There!", -1, &rect, DT_SINGLELINE);

While this difference probably isn't enough to compel me to use the Winsys TRect on a regular basis, it's an added benefit to using the Winsys TRect over using the VCL TRect. The same benefit ratio is true of the TPoint and TSize classes. For more information on Winsys classes, check out "Undocumented Winsys"

Listing AMAINFORM.CPP

//---------------------------------------------------
#include <winsys\geometry.h>
#include <winsys\uimetric.h>
#include <winsys\system.h>
#include <vcl.h>
#pragma hdrstop
#include "MainForm.h"
//---------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}

//---------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  // Create a string.
  String s = "Operating System: ";
  // If we're running Win95 append "Win95" to the end
  // of the string.
  if (TSystem::IsWin95()) 
    s += "Win95";

  // Otherwise must be NT so add "Windows NT" to
  //  the end of the string. Also add the NT version
  // number and the build number to the string.

  else {
    s += "Windows NT ";
    s += String((int)TSystem::GetMajorVersion());
    s += ", Build No. " +
    String((int)TSystem::GetBuildNumber());
  }
  // Display the string in the label.
  Label1->Caption = s;
}

//---------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  // Tell the compiler we want the global version
  // of TRect and not the VCL version.
  using ClassLib::TRect;
  // Create a TRect object.
  TRect wndRect;
  // Get the window rect using API GetWindowRect.
  GetWindowRect(Handle, &wndRect);
  // Inflate the rect by 100 pixels all around.
  wndRect.Inflate(100, 100);
  // Move the window to the new size and location.
  MoveWindow(Handle, wndRect.Top(), wndRect.Left(),
    wndRect.Width(), wndRect.Height(), true);
  // Make x the height of a menu.
  int x = TUIMetric::CyMenu;
  // Make y the width of a vertical scrollbar.
  int y = TUIMetric::CxVScroll;  
  // Create a TRect object using x and y as the top
  // and left coordinates.
  TRect rect(x, y, 200, 40);  
  // Use it in the API DrawText call.
  DrawText(Canvas->Handle, "Hello There!", -1, &rect, DT_SINGLELINE);
}
//---------------------------------------------------

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.