Coding closer to the metal

By: David Pankhurst

Abstract: For blazingly efficient C++ -- when you need to wring the last drop of performance out of your code -- use C++Builder without the VCL. Here's how. By David Pankhurst.

Source code for this article may be downloaded by clicking here.

RAD development environments provide an easy way to work in Windows. Proof of their effectiveness is that many (if not most) programmers today working in Windows have never dealt with the CreateWindow() function, or crafted a switch...case statement for a Windows messaging loop.

Not that this is bad: One of the major benefits of RAD tools is they they hide complexity so that development goes faster. However, there can be advantages in getting close to the raw API level. Programs are smaller, since the extra abstraction layer is missing. Likewise, they are often faster. It opens opportunities to work with legacy code, which occasionally is written at this low level. And finally, being familiar with API-level Windows is good for a programmer, since it is the stuff every Windows program is ultimately made of.

VCL makes development a great deal easier, and working at the API-only level is much more complicated. Nevertheless, there can be occasions when the advantages outweigh the disadvantages. If you've weighed the pros and cons, and decided you need a project done solely at the API level, you've come to the right place, because C++Builder lets you do it, and gives you all the benefits of its IDE as well -- including Codeguard!

ROLLING YOUR OWN

Doing API-only programming is ridiculously simple with C++Builder.

First, create and save a new project. Then go into the Project Manager and remove the main form. What's left is the cpp file named after the project (typically Project1.cpp). Looking at it, you'll see something like this:

#include <vcl.h>
#pragma hdrstop
USERES("Project1.res");
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR,int)
{
  try
  {
    Application->Initialize();
    Application->Run();
  }
  catch (Exception &exception)
  {
    Application->ShowException(&exception);
  }
  return 0;
}

Your whole program is now under 20 lines, and of course it lacks anything visual. But the WinMain() function is going to be the starting point for our new program.

We need to remove the VCL from the program, most notably the Application object. (We need to keep the <vcl.h> reference in since that provides us with the Windows API definitions and helps with managing multiple files in the Project Manager.) We remove the VCL by replacing the WinMain code:

int WINAPI WinMain( HINSTANCE hInstance,     // handle - curr. instance
                    HINSTANCE hPrevInstance, // handle - prev. instance
                    LPSTR lpCmdLine,         // pointer to command line
                    int nCmdShow )           // show state of window
{
  g_hInstance=hInstance; // save instance for button creation later
  MSG msg ;
  WNDCLASS wndclass; // set up window
  wndclass.style         = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc   = WndProc;
  wndclass.cbClsExtra    = 0;
  wndclass.cbWndExtra    = 0;
  wndclass.hInstance     = hInstance;
  wndclass.hIcon         = NULL;
  wndclass.hIcon         = LoadIcon(hInstance,TEXT("PROGRAM_ICON"));
  wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wndclass.hbrBackground = (HBRUSH) COLOR_WINDOW;
  wndclass.lpszMenuName  = NULL;
  wndclass.lpszClassName = WINDOW_NAME;
  RegisterClass (&wndclass) ;
  // now create main window
  HWND hWnd = CreateWindow ( WINDOW_NAME, WINDOW_CAPTION,
                             WS_POPUPWINDOW | WS_CAPTION 
                               | WS_SYSMENU | WS_MINIMIZEBOX,
                             g_windRect.left,
                             g_windRect.top,
                             g_windRect.right-g_windRect.left,
                             g_windRect.bottom-g_windRect.top,
                             NULL,NULL, hInstance, NULL);
  ShowWindow(hWnd,SW_SHOW); // display window and process messages
  while (GetMessage(&msg,NULL,0,0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

We now also have to include a Windows message procedure (specified in wndclass.lpfnWndProc as WndProc), so we add that as well:

LRESULT CALLBACK _export WndProc(HWND hWnd,
                                 UINT message, 
                                 UINT wParam, 
                                 LONG lParam)
{
  switch (message)
  {
    case WM_CREATE: // init - create quit button along window bottom
    {
      RECT rect;
      GetClientRect(hWnd,&rect);
      CreateWindow("button","Goodbye,World",
                   WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                   5,
                   rect.bottom/2+5,
                   rect.right-10,
                   rect.bottom/2-10,
                   hWnd,0,g_hInstance,NULL);
    }
      return 0L;
    case WM_PAINT: // center a message on top half of window
    {
      PAINTSTRUCT ps;
      HDC hDC = BeginPaint(hWnd,&ps);
      RECT rect;
      GetClientRect(hWnd,&rect);
      SetTextAlign(hDC,TA_CENTER|TA_BASELINE); // make text center itself
      char *text="Hello World";
      rect.bottom/=2; // set to top half of client window
      ExtTextOut(hDC, // output text
                 rect.right/2,
                 rect.bottom/2,
                 ETO_OPAQUE,
                 &rect,
                 text,
                 strlen(text),
                 NULL);
      EndPaint(hWnd,&ps);
    }
      return 0L;
    case WM_COMMAND: // handle button press by quitting
      SendMessage(hWnd,WM_CLOSE,0,0);
      return 0L;
    case WM_CLOSE:
      DestroyWindow(hWnd); // perform wm_destroy
      return 0L;
    case WM_DESTROY:
      PostQuitMessage( 0 );
      return 0L;
  }
  return DefWindowProc (hWnd, message, wParam,lParam);
}

This, then, is your typical "hello world" program in C++Builder -- about 100 lines. It will compile to just over 100K. Compare that to the equivalent in VCL, which weighs in at more than 300K, and you realize the advantages for certain programs. (Of course, if you compile without statically linking the libraries or VCL the code is only about 20K, but then the libraries will need to be shipped, increasing the total size.)

However, the number of lines now needed highlights the complexity that the VCL classes usually hide. As always, there is a tradeoff in coding.

MESHING C++BUILDER WITH LOW-LEVEL API CODE

The code is actually very straightforward -- WinMain creates an ordinary window, and the WM_CREATE case in the message handler creates a button across the bottom half. In addition, the WM_PAINT case draws across the top half of the window with the traditional "Hello World" message. This simple example includes the basics of API-level windowing, and can serve as the basis for your own projects.

One detail shown in the downloadable source code that is not obvious here is the icon setting for wndclass.hIcon:

LoadIcon(hInstance,TEXT("PROGRAM_ICON"));

This icon refers to a resource in a separate file. C++Builder still manages the project, even at this level, and you can add resource files and access them. In the project, "extra.rc" is the resource for the icon, but it could include other items as well. Of course, as a program grows, you are not limited to a single cpp file, the same as in any other C++Builder project.

A HANDY WAY TO DEVELOP

Lest you think this is all theoretical, it isn't; I currently both sell and give away software developed at the API-only level. While it is quite a bit harder to write than VCL-based code, it is also much smaller, which is especially handy for distribution across the Internet. The C++Builder IDE gives me a straightforward compile environment, a familiar editor, and with version 5 I have the invaluable aid of CodeGuard for memory-leak checking. With this flexibility, I've been able to retire my Borland C++ 4.5 compiler (except for the occasional DOS work) and work exclusively in C++Builder, with enhanced productivity.

So if you need a small, light program, or just want to try coding at the bare metal level, try API-only coding. With C++Builder, it's as easy as it gets.


Server Response from: ETNASC04