In 1995, a few years after the introduction of Visual Basic, Borland introduced its first version of Delphi. At first glance, Delphi was very similar to Visual Basic. It came with an integrated development environment, complete with a visual designer used to construct user interfaces by dropping controls on forms, changing their properties and associating code with events. Delphi came with its own component model, but also supported VBX components built for Visual Basic. (Delphi 1.0 was a 16-bit environment. Later versions would add support for the 32-bit OCX/ActiveX components of later versions of Visual Basic.)
On closer inspection, the similarities were only skin deep. Delphi used the object-oriented Object Pascal language (later renamed the Delphi programming language), and came with a fast, optimizing compiler with a built-in assembler. Developers had full access to the Windows application programming interface from their applications, and could create shared libraries available to code written in languages such as C, C++ and even Visual Basic.
Contemporary versions of Delphi are owned and sold by Embarcadero Technologies, and target 32-bit Windows versions, with a planned 64-bit version. A separate product, using a slightly different programming language, is available for Microsoft’s .NET platform as Delphi Prism.
Classes that descend from the TComponent class are referred to as “components” in Delphi, and may be manipulated in a visual editor. (This usage of the “component” word is inconsistent with the terminology used in this thesis.) Delphi has language-level support for properties that are used to visually customize such objects. Properties that are used in this way must come with extended runtime type information, partly so that their names can be presented to the developer in the visual environment. There is an additional “access specifier,” published, for this purpose, which is identical to public in most respects, but stores extended type information. Delphi also has “controls” that feature graphical user interfaces at runtime (in other words, a slider or a check box is both a “control” and a “component,” whereas a non-visual object that enables a developer to set up a database connection for use by data-aware controls is only a “component”). Such objects extend the TControl class which itself extends TComponent.
As of Delphi 3, code may be packaged as a special kind of shared library, a package, and these packages indeed qualify as components (with the notable exception that packages do not provide version information in a standardized way, and multiple versions cannot easily be loaded at the same time). A Delphi package may declaratively specify what other packages it depends on (Lischner 2000:7).
Delphi takes a very aggressive approach to supporting COM, and goes as far as making COM a part of the core language (although most of the COM support resides in the runtime system, and the COM language features are very much optional). The memory layout of Delphi classes is compatible with COM, enabling Delphi classes to function as COM classes if certain additional rules are followed (Lischner 2000:71). Interfaces are part of the Delphi language, at least partly for the sake of COM compatibility. Regardless of whether COM is used, all Delphi interfaces must extend the COM IUnknown interface, and be assigned a runtime name in the form of a UUID (the Delphi language provides a convenient UUID literal syntax for this purpose, making UUIDs very readable).
As all interfaces are known to be reference counted, Delphi calls IUnknown::AddRef() and IUnknown::Release() automatically; the former when references are assigned, the latter when references go out of scope (much like a C++ smart pointer). Delphi also has special language-level support for IUnknown::QueryInterface(): the as operator (which is normally used for “safe” typecasts1) uses the IUnknown::QueryInterface() operation when the first operand is an interface reference (Lischner 2000:54).
While it can be argued that mandating the use of IUnknown as a base interface does not constitute bringing COM itself into the language (as interface navigation and reference counting are arguably useful for interfaces, regardless of whether COM is used), Delphi does have explicit language-level constructs that solely exist to support COM; a sampling follows:2
Accessing an object through the IDispatch interface normally means that the compiler cannot perform static type checking, and that errors are only signaled at runtime. Delphi provides the dispinterface keyword for declaring such an interface, enabling errors to be caught at compile-time. dispid keywords can be used to manually assign the dispatch identifiers expected by IDispatch::Invoke() (Lischner 2000:179).
Delphi introduces the safecall calling convention explicitly for the benefit of COM. This calling convention is identical to the stdcall calling convention expected by COM (and all other standard Windows functions), but adds exception “firewalls.” As COM uses error return values in preference to exceptions, Delphi exceptions cannot cross a COM call boundary. The safecall calling convention solves this problem. If a Delphi method, declared with this keyword and serving as the implementation of a COM operation, throws an exception, the exception is caught and converted to the HRESULT return value expected by COM. Calling a COM operation works in much the same way—the HRESULT value is checked automatically, and an exception is thrown if the return value does not indicate success (Lischner 2000:326).
In order to refer to a variable whose type is not known, a strongly typed language needs what is sometimes called a variant type. Variables of such a compile-time type can change their type at runtime. The ScriptableReturnValue_t type, shown in Listing 4.5, is an example of a simple variant type. Variant types are often, as in the preceding listing, implemented as a tagged union in C. Delphi has a dedicated type for variants, simply named Variant. In particular, Delphi also has the type OleVariant, which is restricted to COM-compatible types (Lischner 2000:270). Both are built-in primitive types, and have no formal declaration outside of the compiler implementation.
Delphi’s language-level support for COM makes for unusually clean COM code, although it is unusual with language features specifically tailored to a particular platform.3 Most of Delphi’s COM support is confined to its runtime system, though, which provides classes such as TComObject that can be extended to easily create classes compatible with COM, and TComObjectFactory which implements IClassFactory (Calvert 1999:386). The integrated development environment includes COM “wizards” that help developers with various COM-related tasks without requiring much knowledge of COM’s inner workings. For instance, Delphi can automatically wrap a Delphi control as an ActiveX control, usable in a variety of environments, including Visual Basic. Delphi language bindings can be generated for COM components by “importing” their type libraries.
Delphi is a good example of a language that integrates very closely with COM, making writing and using COM objects less daunting. Microsoft also provides such tools, notably in the form of Visual Basic 6, and the Active Template Library, a set of template-based C++ classes for creating COM objects.
- A safe typecast checks the validity of the cast at runtime, using runtime type information, and throws an exception if the cast is not valid. Delphi also supports traditional Pascal typecasts, which are not checked.
- As COM has no native support for implementation inheritance, forwarding calls is often more natural than using implementation inheritance when writing a COM class (and arguably better practice in other scenarios as well, given the reservations expressed in section 4.3.2). The Delphi keyword implements is, while not COM-specific, very useful when used with COM, as it introduces language-level support for forwarding. Using this keyword, an object, implementing a set of interfaces, can delegate the implementation of any number of its implemented interfaces to other objects it maintains references to. While this can obviously be implemented by manually forwarding calls to the internally referenced objects, this keyword makes for less verbose code.
- Languages such as C# and Java allow developers to associate code with arbitrary metadata. Had Delphi had such support at the time when COM support was introduced, it is conceivable that some of its COM-specific language support would have been in the form of such metadata. This is the route taken by C#/.NET for COM compatibility (discussed in section 5.6.2).