Raw Delphi

Delphi Without the VCL or the IDE


It is practically impossible to teach good programming style to students that have had prior exposure to BASIC; as potential programmers they are mentally mutilated beyond hope of regeneration.

--Edsger Dijkstra

 

Delphi is not VB. This is perhaps one of the best things you could say about it. It's well-known that Delphi is fast and efficient, while still supporting the drag-and-drop "RAD" model of development. Probably the most overlooked aspect of Delphi is its elegance-- not just the language, but also the design of the compiler and the environment itself. Very few people realize that, apart from the differences between an interpreted and a compiled language, Delphi differs from VB in a more fundamental way: it is a “real” language, with a real compiler and class libraries, and as such is actually closer in philosophy to C and C++.

What makes C the language of choice for building operating systems, utilities and games? While C may have a reputation for being terse and cryptic, it allows for very low-level programming; and by low-level we mean "close to the hardware". Being close to the hardware makes C programs run very fast and very memory-efficient, which is an absolute requirement for applications where speed is a priority, like operating systems and high-performance games. C gives programmers almost-total control; because of this, it has been called a "portable assembly language". Speed and efficiency are the reasons why most operating systems (DOS, all versions of Windows, Linux and all UNIX's) are written in C. Some new OS, like Windows XP are written mostly in C, with some parts in C++ (fortunately, there are no OS's written using Visual Basic).

Another property of "real" compilers is that they can create themselves, or rather, they can recreate themselves. Put another way, they can create programs with the same capabilities and as they have, consuming the same amount of resources, and running just as fast. In fact, many open-source compilers are distributed in C source form. To install these compilers, you would compile the new compiler's source using an older compiler (Of course, if there is no compiler yet on the system to begin with, then the it must be installed in some other way). Delphi, too, is capable of this: while the Delphi core compiler is written in C and assembly, the IDE (Integrated Development Environment) and all components are written in Delphi itself. If Borland were so inclined, they could choose to write the next version of the Delphi compiler using the previous version; this isn't the case because their C++ and Delphi compilers probably share common code. Naturally, even interpreted languages like Java, VB and Powerbuilder are written in C/C++. Historically, the benefits of interpreted languages like VB and Java have been portability, at the cost of speed and efficiency. Sun may disagree with this and produce all kinds of benchmarks, but until a Java hardware machine becomes widely available, Java will never match the pure raw speed of C.  Microsoft itself downplayed the advantages of compiled languages as compared to VB, until they came up with a compiled replacement for it in the form of DotNET. Understandably they are now big supporters of compilation (in fact, they did more than just make it a real compiler: arguably, DotNET programs will actually run faster than straight-compiled applications. More on this in another article.)

There really is no doubt that when speed and power are needed, C/C++ is the way to go, but of course there is a niche for VB and other interpreted languages: Java when portability is required, VB when a quick-and-dirty solution and finding cheap, readily available labor is required. But where does Delphi fit in? Most people seem to think that Delphi is in the class of "VB-like" programs, and it's true that Borland positioned it as "VB on steroids", but what I would like to show here is that Delphi is actually closer in spirit and performance to C and C++.

In fact, since C++ is so terse and complex, it takes a certain level of sophistication and discipline to be truly productive with it. For bread-and-butter development tasks, Delphi is more suitable, and neatly bridges the gap between VB and C++.

 

Compilation Process

To illustrate further, let us see how a “real” compiler’s inputs and outputs are structured. For example, here is how C and C++ process input files to make the final executable:








This is the generic C/C++ compilation process. On GUI platforms like Windows, to facilitate the so-called “event-driven” model, and to handle graphical elements like icons, menus and dialog boxes, an additional step was added: Resource compilation. Resources are created using a media-dependent editor such as an icon or bitmap editor, or even a sound recorder, and then a resource script is created using a text editor, which is then compiled with a resource compiler:







Here is the Delphi equivalent, which Delphi performs behind-the-scenes when using the IDE:







In a real compiler, source code is translated into machine code (binaries) using a compiler, and may take on an intermediate form of object files or library files. At the lowest level, the source language can interface with the operating system’s API (e.g., the Win32 API in Windows, the standard C library in Unix, Int21h in DOS) and forms its link to the “real world”. The language can provide this by providing mappings or wrappers to the API, such as Delphi’s Windows.pas unit. Upon this basic level the higher-level libraries can be built; if the language is object-oriented, then a class library can be created. A good example of this is the Visual C++ Microsoft Foundation Classes (which has often been criticized for not being fully object-oriented). Another example is, of course, the Delphi VCL. Less well-known are Borland's old OWL (Object Windows Library) framework and the third-party C++ framework zApp, from Zinc.

In contrast, a VB program, though it looks like a normal .EXE file, is merely an intermediate form of code (so-called ‘p-code’) for interpretation by a runtime system. VB source is “compiled” into this intermediate form and embedded within an EXE file. When the executable is run, the EXE loads the VB runtime VBRUNXXX.DLL, and which then interprets the instructions embedded within the EXE. In a sense, VB code is compiled for the VB virtual machine, analogous to the way the Java Virtual Machine runs its programs. This is the reason why apps written with VB, especially ones downloaded from the ‘Net, are so prone to the “missing DLL” problem. It is also this interpretative nature that makes VB unsuitable for writing graphics-intensive apps (like games), system programs, drivers, etc. Though heroic attempts are often made to make VB do these things, they are at most, ugly hacks. Combined with confusing and un-orthogonal language syntax, together these make VB a programmer’s dream come true.

 

 

Delphi as C

Let us now delve deeper into the compiled nature of Delphi. Consider this simple program:


Program Hello(input, output);
{$APPTYPE CONSOLE}
begin
    writeln('Hello World!');
end.

To compile this on Delphi, you could create a normal application, then remove all forms files from the project and edit the .DPR file so that it contains the code above. Compile and run. For those who have thought all along that the standard DPR-DFM-PAS template is the only possible form of a Delphi application (and who have probably wondered why it seems needlessly complicated) this should be an eye-opener. But a more interesting way of compiling it is available. You do not even need to use the Delphi IDE: Just type the code above in any text editor, notepad will do, and save it as hello.pas. In the command prompt, type:

C:\> c:\Delphi\bin\dcc32 hello.pas
C:\> hello
DCC32.EXE is the Delphi command-line compiler, the compiler for "real men". But seriously, this illustrates the low-level nature of Delphi, very similar to C/C++. In fact, Visual C++ actually uses its command-line compiler to compile programs within its IDE. The compiler messages window at the bottom of the IDE is the output of the compiler CL.EXE, which shows how relevant command-line compilers still are. In fact, older versions of the Visual C++ IDE allow the user to actually replace the Microsoft compiler-- it's very rewarding to see the Visual C++ IDE compiling a C++ Builder application. For greater integration, though, Delphi uses another, DLL-version of the compiler for use within the IDE. Given this, you may be wondering why the Delphi command-line compiler is provided at all. The command-line compiler allows Delphi to be used within batch files, and, more importantly, with the powerful MAKE utility, arguably the cornerstone of console compilation, and the de-facto installer on Unix systems (and of course, it is also there for the benefit of real men.) To see the options available for DCC32, run it without any parameters. For your reference, these switches are shown below:

Borland Delphi Version 15.0
Copyright (c) 1983,2002 Borland Software Corporation

Syntax: dcc32 [options] filename [options]

  -A<unit>=<alias> = Set unit alias  -LU<package> = Use package
  -B = Build all units               -M = Make modified units
  -CC = Console target               -N<path> = DCU output directory
  -CG = GUI target                   -O<paths> = Object directories
  -D<syms> = Define conditionals     -P = look for 8.3 file names also
  -E<path> = EXE output directory    -Q = Quiet compile
  -F<offset> = Find error            -R<paths> = Resource directories
  -GD = Detailed map file            -U<paths> = Unit directories
  -GP = Map file with publics        -V = Debug information in EXE
  -GS = Map file with segments       -VR = Generate remote debug (RSM)
  -H = Output hint messages          -W = Output warning messages
  -I<paths> = Include directories    -Z = Output 'never build' DCPs
  -J = Generate .obj file            -$<dir> = Compiler directive
  -JP = Generate C++ .obj file       --help = Show this help screen
  -K<addr> = Set image base addr     --version = Show name and version

Compiler switches: -$<letter><state> (defaults are shown below)
  A8  Aligned record fields           P+  Open string params
  B-  Full boolean Evaluation         Q-  Integer overflow checking
  C+  Evaluate assertions at runtime  R-  Range checking
  D+  Debug information               T-  Typed @ operator
  G+  Use imported data references    U-  Pentium(tm)-safe divide
  H+  Use long strings by default     V+  Strict var-strings
  I+  I/O checking                    W-  Generate stack frames
  J-  Writeable structured consts     X+  Extended syntax
  L+  Local debug symbols             Y+  Symbol reference info
  M-  Runtime type info               Z1  Minimum size of enum types
  O+  Optimization

 

For example, to compile a file Complex.dpr that uses components in the directory "c:/library">c:\library" with warnings disabled:

C:\> dcc32 -Ic:\library -W- complex

For the rest of this article, we will use the command-line compiler. But keep in mind that everything we do here can also be done within the IDE.

The {$APPTYPE CONSOLE} line is a compiler directive that tells the Delphi compiler to generate a console application. This program will compile on all Pascal implementations, from Pascal for VAX and Unix, to Turbo Pascal for DOS, all the way to any version of Delphi and Free Pascal. Depending on how it was compiled, it may run on DOS, all versions of Windows, Unix, etc. For compilers that do not support the $APPTYPE directive, it is simply ignored, since it is contained within a comment block. Borland’s method of implementing compiler directives through special comments is a very clever way of making the early Borland Pascal programs compatible with other compilers.

Notice that the above application uses none of Delphi's object-oriented features (this is another reason that it will compile on standard Pascal compilers); the program is plain Pascal without objects. Since Delphi's feature set is a superset of Pascal, Pascal is to Delphi as C is to C++.

HELLO.EXE, when run, produces console output. Assuming we compile under Windows, however, it is important to understand that it is NOT a DOS application: It is a full-fledged Win32 console application (unless compiled with Delphi 1 of course, in which case it is a 16-bit application no matter what OS it was compiled on), and has access to all win32 facilities like threads, memory-mapped files, virtual memory, etc. But is there a difference? After all, the above program, when run, produces the same output whether it was compiled in Delphi or Turbo Pascal for DOS. A "normal" Windows application (the default for the Delphi compiler) is expected to have a message loop (more on that later) and to create a few windows, at the very least. Thus the program:


Program Empty;
begin
end.

when compiled with Delphi, is actually a full-fledged Windows program (which doesn't create any Windows). Adding the {$APPTYPE CONSOLE} directive:

Program Empty;
{$APPTYPE CONSOLE}
begin
end.

makes it a console application, and at run time, Windows gives it a console window for output (using writeln, printf etc). To show that this is still a Windows application, try adding the {$APPTYPE CONSOLE} directive to the generic Windows program which we will show later on. Doing so creates an application with a console window, but also creates normal windows and a message loop. To show the similarity to C, we show the C equivalent below:


#include <windows.h>

int PASCAL WinMain( HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine,
    int nCmdShow )
{
   return 0;
}

 

If you have any more doubts that the previous Pascal program is indeed a 32-bit Windows application, take a look at the following very similar program:

Program Huge;
var p:pointer; begin    GetMem(p, 500000000); end.

This is a command-line program allocating around 500 megabytes of RAM. This would have been impossible in DOS, which can only address around 1 megabyte of memory, no matter how much RAM is actually present. (This works in Win32, even when there isn't 500 megabytes of physical RAM actually present on the machine, through the use of virtual memory. In fact, if you have more disk space, you could increase the allocation to a few gigabytes). In fact, Turbo Pascal for DOS will not even compile this. This stresses the point that a Windows console program is just like any other Windows application.

To further illustrate the similarity between Delphi and C we will next show a Delphi version of the classic “Hello World in Windows” as presented in the Windows Platform SDK.  For those not familiar with SDK-style development, you can find out more at the Microsoft website or in the Developer Network CD's. Suffice it to say that during the dawn of Windows development, (just over a decade ago) SDK-style development was the only method of developing GUI programs for Windows. The C-based SDK style is to Windows as assembler was to MS-DOS programming. To this day, all Windows programs are ultimately just complex versions of the older SDK-style programs --the VB run-time interpreter, Delphi VCL programs, Java applications-- they all have message loops and register Windows classes with the Windows API.

The “Hello World” program displays a blank window with a very simple "About" menu. The program has been modified here for simplicity; for example, the About dialog box has been replaced with a message box, and the header file has been removed. Also, we assume that there is already a compiled resource file (GENERIC.RES) with the main menu definition. From the original four files in the SDK example, what we have left is a single C source file.

Here it is:


/********************************************************************\
*  generic.c: Source code for generic                                *
*                                                                    *
*  Comments: Generic Application                                     *
*                                                                    *
*  Functions:                                                        *
*     WinMain      - Application entry point                         *
*     MainWndProc  - main window procedure                           *
*                                                                    *
*                                                                    *
\********************************************************************/

/*********************  Header Files  *********************/

#include <windows.h>

#define IDM_ABOUT      1000

/*********************  Prototypes  ***********************/

LRESULT WINAPI MainWndProc( HWND, UINT, WPARAM, LPARAM );

/*******************  Global Variables ********************/

HANDLE ghInstance;

/********************************************************************\
*  Function: int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int)    *
*                                                                    *
*   Purpose: Initializes Application                                 *
*                                                                    *
*  Comments: Register window class, create and display the main      *
*            window, and enter message loop.                         *
*                                                                    *
*                                                                    *
\********************************************************************/

int PASCAL WinMain( HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine,
    int nCmdShow )
{
   WNDCLASS wc;
   MSG msg;
   HWND hWnd;
   BOOL bRet;

   if( !hPrevInstance )
   {
      wc.lpszClassName = "GenericAppClass";
      wc.lpfnWndProc = MainWndProc;
      wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
      wc.hInstance = hInstance;
      wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
      wc.hCursor = LoadCursor( NULL, IDC_ARROW );
      wc.hbrBackground = (HBRUSH)( COLOR_WINDOW+1 );
      wc.lpszMenuName = "GenericAppMenu";
      wc.cbClsExtra = 0;
      wc.cbWndExtra = 0;

      RegisterClass( &wc );
   }

   ghInstance = hInstance;

   hWnd = CreateWindow( "GenericAppClass",
      "Generic Application",
      WS_OVERLAPPEDWINDOW|WS_HSCROLL|WS_VSCROLL,
      0,
      0,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL
   );

   ShowWindow( hWnd, nCmdShow );

   while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0 )
   {
      if (bRet == -1)
      {
         // handle the error and possibly exit
      }
      else
      {
         TranslateMessage( &msg );
         DispatchMessage( &msg );
      }
   }

   return (int)msg.wParam;
}

/********************************************************************\
* Function: LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM) *
*                                                                    *
*  Purpose: Processes Application Messages                           *
*                                                                    *
* Comments: The following messages are processed                     *
*                                                                    *
*           WM_PAINT                                                 *
*           WM_COMMAND                                               *
*           WM_DESTROY                                               *
*                                                                    *
*                                                                    *
\********************************************************************/

LRESULT CALLBACK MainWndProc( HWND hWnd, UINT msg, WPARAM wParam,
   LPARAM lParam )
{
   PAINTSTRUCT ps;
   HDC hDC;

   switch( msg ) {

/**************************************************************\
*     WM_PAINT:                                                *
\**************************************************************/

      case WM_PAINT:
         hDC = BeginPaint( hWnd, &ps );

         TextOut( hDC, 10, 10, "Hello, World!", 13 );

         EndPaint( hWnd, &ps );
         break;

/**************************************************************\
*     WM_COMMAND:                                              *
\**************************************************************/

      case WM_COMMAND:
         switch( wParam ) {
            case IDM_ABOUT:
            MessageBox(hWnd,"Hello World Application", "Hello World", MB_ICONINFORMATION | MB_OK);
            break;
         }
      break;

/**************************************************************\
*     WM_DESTROY: PostQuitMessage() is called                  *
\**************************************************************/

      case WM_DESTROY:
         PostQuitMessage( 0 );
         break;

/**************************************************************\
*     Let the default window proc handle all other messages    *
\**************************************************************/

      default:
         return( DefWindowProc( hWnd, msg, wParam, lParam ));
   }

   return 0;
}




We now begin to see why VB was such a hit when it came out: no one wants to have to write all this code just to show “Hello World”. Before VB, programmers had to write large switch statements to handle different messages. Dialog boxes, menus and strings had to be written in text format, and compiled with the resource compiler, and finally linked with the object files to produce the final executable. There was even an old saying that there was only really one Windows program; all others were just variations of it, with different cases for the switch statement, so similar did those early programs look. To be fair, we are working at the very lowest level of Windows development; after all, in DOS for example, where the "native" language was assembly, the early ".COM" programs all looked the same superficially (remember "ORG 100h"?). At this level, we use no class libraries or frameworks like MFC, or fancy IDE’s like Visual C++ or Delphi. This does not mean that we are handicapped in terms of functionality; everything that can be done using MFC/VCL etc. is possible at this level, using pure C and a command-line compiler. You could use Visual C++’s IDE to compile this, but it would not mean that we are using MFC, ATL etc., or even that we are using C++. The Developer Studio IDE and MFC are two distinct and separate products, and can be used separately. AS we said, previous versions allowed you to replace Visual C++’s compiler, say, with C++Builder’s and thus use VC++ to edit and compile BCB projects; MFC is regularly bundled with other C++ compilers and IDE’s, such as Watcom C++, and even C++Builder. For the above program, we are simply using the VC++ compiler as a plain-old-C compiler for Windows. No C++, no bells and whistles.


When run, the application looks like this:








Now for the Delphi version. Note that this is an almost line-for-line translation of the C source. The C-version comments have been left in:


Program Generic;

(********************************************************************\
*  generic.dpr: Source code for generic                              *
*                                                                    *
*  Comments: Generic Application                                     *
*                                                                    *
*  Functions:                                                        *
*     WinMain      - Application entry point                         *
*     MainWndProc  - main window procedure                           *
*                                                                    *
*                                                                    *
\********************************************************************)
uses Windows, Messages;
{$R generic.res}

(*********************  Prototypes  ***********************)

function MainWndProc(wnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; forward;

(*******************  Global Variables ********************)

var ghInstance: THANDLE;

const IDM_ABOUT = 1000;






(********************************************************************\
*  Function: int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int)    *
*                                                                    *
*   Purpose: Initializes Application                                 *
*                                                                    *
*  Comments: Register window class, create and display the main      *
*            window, and enter message loop.                         *
*                                                                    *
*                                                                    *
\********************************************************************)


function WinMain: LRESULT;
var
   wc: TWNDCLASS;
   msg: TMSG;
   hWnd: THANDLE;
   bRet: BOOL;
begin


   if HPrevInst = 0 then
   begin
      FillChar(wc, sizeof(wc), 0);

      wc.lpszClassName := 'GenericAppClass';
      wc.lpfnWndProc := @MainWndProc;
      wc.style := CS_OWNDC or CS_VREDRAW or CS_HREDRAW;
      wc.hInstance := hInstance;
      wc.hIcon := LoadIcon( 0, IDI_APPLICATION );
      wc.hCursor := LoadCursor( 0, IDC_ARROW );
      wc.hbrBackground := HBRUSH(COLOR_WINDOW+1);
      wc.lpszMenuName := 'GenericAppMenu';
      wc.cbClsExtra := 0;
      wc.cbWndExtra := 0;
      wc.hbrBackground:=GetStockObject(white_brush);


      RegisterClass(wc);
   end;

   ghInstance := hInstance;

   hWnd := CreateWindow( 'GenericAppClass',
      'Generic Application',
      WS_OVERLAPPEDWINDOW or WS_HSCROLL or WS_VSCROLL,
      0,
      0,
      integer(CW_USEDEFAULT),
      integer(CW_USEDEFAULT),
      0,
      0,
      hInstance,
      nil
   );

   ShowWindow(hWnd, CmdShow);


   bRet := GetMessage( msg, 0, 0, 0 );
   while bRet do
   begin
      if (integer(bRet) = -1) then
      begin
         // handle the error and possibly exit
      end
      else begin
         TranslateMessage( msg );
         DispatchMessage( msg );
      end;
      bRet := GetMessage( msg, 0, 0, 0 );
   end;

   Result := msg.wParam;
end;





(********************************************************************\
* Function: LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM) *
*                                                                    *
*  Purpose: Processes Application Messages                           *
*                                                                    *
* Comments: The following messages are processed                     *
*                                                                    *
*           WM_PAINT                                                 *
*           WM_COMMAND                                               *
*           WM_DESTROY                                               *
*                                                                    *
*                                                                    *
\********************************************************************)

function MainWndProc(wnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
   ps: TPAINTSTRUCT;
   dc: HDC;
begin

   case msg of

(**************************************************************\
*     WM_PAINT:                                                *
\**************************************************************)

   WM_PAINT: begin
         dc := BeginPaint( wnd, ps );
         TextOut( dc, 10, 10, 'Hello, World!', 13 );
         EndPaint( wnd, ps );
         end;

(**************************************************************\
*     WM_COMMAND:                                              *
\**************************************************************)

   WM_COMMAND: begin
         case (wParam) of
         IDM_ABOUT: MessageBox(wnd, 'Hello World Application', 'Hello World', MB_ICONINFORMATION or MB_OK);
         end;
         end;

(**************************************************************\
*     WM_DESTROY: PostQuitMessage() is called                  *
\**************************************************************)

   WM_DESTROY: PostQuitMessage( 0 );

(**************************************************************\
*     Let the default window proc handle all other messages    *
\**************************************************************)
   else Result := DefWindowProc( wnd, msg, wParam, lParam );

   end;


end;



begin
  WinMain;
end.





Again, you can see that it is almost a line-for-line translation of the C version. This is Delphi at the lowest level: no VCL, no forms objects, and not even any OOP code. And you don’t even need the IDE to compile it. Again, to compile and run this from the command line:
C:\> c:\delphi\bin\dcc32 generic.dpr
C:\> generic
You may notice that the size of executables built this way are very small, and load very fast. This is because a normal MFC or VCL application contains the framework’s “baggage”, a large part of which is not actually used. The “SDK-style” development we’ve seen above contains none of this. When using MFC, Visual C++ does attempt to minimize code bloat by moving the library into a DLL which all MFC-based applications share at run time (if the developer so chooses); however this creates a new set of problems (the MFCXXX.DLL problem, just like the VB VRUNXX.DLL problem), since many applications become extremely sensitive to the version of MFC they link with.

As an interesting aside, let’s compare the sizes of the SDK-style “Hello World” programs above (as generated with default settings):


Compiler Size  (in bytes)
Visual C++ (Debug) 159,820
Visual C++ (Release) 28,672
Delphi 16,384

The Delphi version is actually smaller than the C versions.

The tediousness of straight-SDK C development (even today, Visual C++ development is not much easier), made developers flock to VB and VB-style environments like PowerBuilder -- but they lost the low-level power of compiled languages in the process (and spawned a generation of, ahem, well, you know, VB programmers). This is where Delphi bridges the gap: it can do straight-SDK development if necessary, and yet it can also be as easy to use as VB. It can be used to create Powerbuilder-style database applications on the one hand, while it can be used to build a graphics-intensive game replete with assembly code on the other. Combined with a truly object-oriented class library (the VCL) it is no wonder that when Delphi came out, even the VB magazines were agog over it. Understandably, Microsoft felt threatened and took steps to remedy the situation. They also did not overlook the talents of Delphi’s chief architect, Anders Hejlsberg: he is now a key figure behind Microsoft’s new DotNET initiative, and is the chief architect of the C# programming language.


It should be clear by now that Delphi is more than just the IDE, and that it is more than just the VCL. Delphi will still be Delphi if even just the compiler is considered. In fact, if you were so inclined, you could build your own class libraries to replace the functionality you need from the VCL. For example, you could take the SDK-style "Hello World" application presented above, and make it object-oriented. You may want to do this if, for example, you didn't need all of the VCL's "bells and whistles", but you still want to work with an object-oriented Application object. Here's a very simple (and probably useless) example of how you might do it:

//
//    My own application unit.
//
unit MyApplication;

interface
uses Windows, Messages;

type
  TApplication = class
    procedure Run;
    constructor Create;
  private
    handle : THANDLE;
  end;

var Application: TApplication;
implementation

function AppWndProc(wnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; forward;

constructor TApplication.Create;
var
   wc: TWNDCLASS;
   hWnd: THANDLE;
begin
   FillChar(wc, sizeof(wc), 0);

   wc.lpszClassName := 'MyAppClass';
   wc.lpfnWndProc := @AppWndProc;
   wc.style := CS_OWNDC or CS_VREDRAW or CS_HREDRAW;
   wc.hInstance := hInstance;
   wc.hIcon := LoadIcon( 0, IDI_APPLICATION );
   wc.hCursor := LoadCursor( 0, IDC_ARROW );
   wc.hbrBackground := HBRUSH(COLOR_WINDOW+1);
   wc.cbClsExtra := 0;
   wc.cbWndExtra := 0;
   wc.hbrBackground:=GetStockObject(white_brush);


   RegisterClass(wc);

   handle := CreateWindow( 'MyAppClass',
      'My Application',
      WS_OVERLAPPEDWINDOW or WS_HSCROLL or WS_VSCROLL,
      0,
      0,
      integer(CW_USEDEFAULT),
      integer(CW_USEDEFAULT),
      0,
      0,
      hInstance,
      nil
   );
end;


procedure TApplication.Run;
var bRet: BOOL;
    msg: TMSG;
begin
   ShowWindow(handle, CmdShow);

   bRet := GetMessage( msg, 0, 0, 0 );
   while bRet do
   begin
      if (integer(bRet) = -1) then
      begin
         // handle the error and possibly exit
      end
      else begin
         TranslateMessage( msg );
         DispatchMessage( msg );
      end;
      bRet := GetMessage( msg, 0, 0, 0 );
   end;
end;


function AppWndProc(wnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
// handle events here
var
   ps: TPAINTSTRUCT;
   dc: HDC;
begin
   case msg of
   WM_DESTROY: PostQuitMessage( 0 );
   else Result := DefWindowProc( wnd, msg, wParam, lParam );
   end;
end;



initialization
    Application := TApplication.Create;
end.

//
//    Sample client for MyApplication.pas
//
Program MyApp;
uses MyApplication;
begin
    Application.Run;
end.

It may look a little contrived, but the point is that the paradigm put forth by the VCL is not the only "right" way of doing things. You could always create your own-- Delphi gives you much more freedom than what the manuals actually say.


 

Forms without the IDE

It may seem that you need the IDE to make forms, but again, you could do it just by using a text editor and the command-line compiler. Starting with version 4, Delphi saves .DFM files in text format (one of the rare things that Visual Basic pioneered). Previously, forms were saved in the standard windows resource format, as a custom resource. The format of a .DFM file is as follows:

object <Name>: <Class>
object <Name>: <Class>
  <PropertyName> = <Value>
  <PropertyName> = <Value>
  <PropertyName> = <Value>
       . . .
  <PropertyName> = <Value>

  object <Name>: <Class>
    <PropertyName> = <Value>
    <PropertyName> = <Value>
    <PropertyName> = <Value>
         . . .
    <PropertyName> = <Value>
  end
end

Note that objects can be nested. Each object block corresponds to an object in the corresponding .PAS file. To associate the .DFM file with a .PAS file, use the load resource directive {$R <form-filename>}. The wildcard version {$R *.dpr} uses the source file's name without the .PAS extension.

As a sample, let's create a basic windows app, similar to the one the IDE would make if we selected  File | New | Application from the menu. Using any text editor (again, notepad will do), create the following files:

// Unit1.dfm
object Form1:TForm1
end
 
// Unit1.pas
unit Unit1;
interface
uses Forms;
type
   TForm1 = class(TForm)
   end;

var
  Form1: TForm1;

implementation
{$R *.dfm}
end.
 
// ManualForm.dpr
Program ManualForm;
uses
   Forms,
   Unit1;
begin
   Application.Initialize;
   Application.CreateForm(TForm1, Form1);
   Application.Run;
end.

Compile on the command line using: "dcc32 ManualForm". When run, shows a very simple window:

 

 

When a property name=value line is not present, the default value for that property is used. Now let's make a more interesting sample program, one that contains event handlers:

 
// MyUnit.dfm
object MyForm:TMyForm
   object HelloButton:TButton
   Left = 40
   Top = 40
   Caption = 'Click Me'
   OnClick = MyEventHandler
   end
end
 
// MyUnit.pas
unit MyUnit;
interface
uses Forms, StdCtrls;
type
   TMyForm = class(TForm)
      HelloButton : TButton;
      procedure MyEventHandler(Sender: TObject);
   end;

var
  MyForm:TMyForm;

implementation
{$R *.dfm}
uses Dialogs;

procedure TMyForm.MyEventHandler(Sender: TObject);
begin
   ShowMessage('Hello There!');
end;

end.
 
// MyApp.dpr
Program MyApp;
uses
   Forms,
   MyUnit;
begin
   Application.Initialize;
   Application.CreateForm(TMyForm, MyForm);
   Application.Run;
end.

 

Be careful about specifying event handler names, though: the compiler doesn't check if they exist! When this happens, your program will abort with an exception. When the program above is run, it produces the following output:

 

 

Of course, Delphi gives you yet another choice in making forms: you could manually create each form using code only, without any form resource file (.DFM), and then setting each property in code. You can even add buttons, panels, etc to a form at run time:

// ManualForm2.dpr
Program ManualForm2;
uses Forms, StdCtrls, Dialogs;

type
    TMyForm = class(TCustomForm)
        procedure MyEventHandler(Sender: TObject);
    end;

    procedure TMyForm.MyEventHandler(Sender: TObject);
    begin
        ShowMessage('Hello There!');
    end;


var myForm: TMyForm;
    myBtn: TButton;


begin
    myForm := TMyForm.CreateNew(nil);

    myBtn := TButton.Create(myForm);
    myBtn.Parent := myForm;
    myBtn.Left := 40;
    myBtn.Top := 40;
    myBtn.Caption := 'Click Me';
    myBtn.OnClick := myForm.MyEventHandler;
    myForm.ShowModal;
end.

Notice that this program doesn't use the VCL's TApplication object.

 

 

In Conclusion

It should be clear by now that comparing Delphi to other so-called "RAD" or "Fourth-generation" tools is not really fair; like comparing apples to oranges, comparing Delphi to interpreted, toy languages is a pointless exercise. Performance-wise, it is to C++ that we must look for comparison, and to tools like Visual C++. But as should be clear from the many comparisons to VB, Delphi is far easier ("more RAD", if you will)  than Visual C++, and so there is some truth to the phrase that Delphi has the "Ease of VB with the Power of C++".

But now, with the advent of .NET, there is a new arena for tools to compete in; and with Microsoft's Visual Studio.NET and Borland's soon-to-be released Octane, the match is one not to be missed.

 

 


(c) 2002 emil santos

codexterity
ems ATSIGN codexterity PERIOD com