Coding Closer to the Metal – Using Borland Builder Without the VCL

(originally published in The Borland Developer Network)

Most RAD tool frameworks 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 to hide the 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, for the same reason 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 programis ultimately made of.

VCL makes development a great deal easier, and conversely 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 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 under 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 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 do this 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 Builder – about 100 lines, it will compile to just over 100k. Compare that to the equivalent in VCL, at over 300k, and you realize the advantages for certain programs (or 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 previously hid. As always, there is a tradeoff in coding.

Meshing Builder with Low-Level API Code

The actual code is 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 obligatory ‘Hello World’ message. While simple, it includes the basics of API-level windowing, and can serve as a springboard to 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. 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 could include other items as well. In addition, as a program grows, you are not limited to a single cppfile, the same as in any other 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 quite a bit harder to write than using VCL, it is much smaller, especially handy for distribution across the Internet. The Builder IDE provides 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 4.5 compiler (except for the occasional DOS work), and work exclusively in 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 Builder, it’s as easy as it gets.

Download Source Code

Comments are closed.