The ActiveX Foundry (Views: 102)
The ActiveX Foundry
It's spring again, a time when many eyes look to Borland for a new generation of Delphi development tools. This spring brings us the third incarnation of Delphi, a lush garden of new tools and technologies designed to expedite business-critical data handling and analysis.
The most exotic fruits of Borland's year-long labors include the ActiveX Component Foundry, Business Object Broker, multi-tier Remote Data Brokers, Distributed COM, Web Deployment, and a suite of IDE features known collectively as Code Insight. These are just the big ones - items major enough to earn buzzwords on the box cover.
There are so many new technical bits in Delphi 3 that it's difficult to grasp the scope of the whole product simply by looking at its technical pieces. Let's first take a high-level tour, focusing on how Delphi 3's new tools enable new ways to solve tough development and deployment problems. After all, you have a whole year of Delphi Informant articles ahead of you to pick apart the technical bits.
ActiveX Component Foundry
Delphi 3 goes completely overboard to support, adopt, and internalize Microsoft's ActiveX technology initiative. Delphi 3 is to ActiveX creation as Delphi 1 was to Windows application creation. Through a combination of new language extensions, new classes, and new design-time wizards and tools (see Figure 1), Delphi 3 cuts through the Microsoft rhetoric to deliver what Microsoft has been trying to do for ages: a development environment that makes creation, debugging, deployment, and maintenance of ActiveX controls, COM servers, and COM interfaces simple, easy, and reliable.
Figure 1: One aspect of the ActiveX Component Foundry, a page of wizards makes it short work to create an ActiveX control. Delphi 3 makes creation, debugging, deployment, and maintenance of ActiveX controls, COM servers, and COM interfaces simple, easy, and reliable.
Microsoft's Component Object Model specification, COM, is the standard to which all OLE objects are implemented. COM is the low-level stuff; OLE is a service built on COM. Depending on who you talk to at Microsoft, ActiveX is the new name for OLE Controls (OCXs, the 32-bit replacement for VBXs), the new name for all things formerly known as OLE, or the new name for all things new. Pessimists are already assuming the latter definitions. In any case, ActiveX is also a group of services and standards built on COM interfaces.
Create ActiveX Controls from VCL Components
Delphi 3 has a new wizard that generates an ActiveX control class wrapper around the VCL component you specify (see Figure 2). It's as simple as that. If you write VCL components for a living, you're now just a few button clicks away from selling those components as ActiveX controls to the Visual Basic and C++ markets. If you work in a mixed-tool environment, you can develop your core business objects as VCL components, then spit out ActiveX versions of your work for folks hopelessly shackled to other tools.
Figure 2: Delphi 3 has a new wizard that generates an ActiveX control class wrapper around the VCL component you specify. It's as simple as that.
Create COM Servers and Automation Servers from Scratch
A new OLE type library (typelib) editor (see Figure 3) makes short work of defining a new COM interface definition, saving it into an OLE-standard typelib file, and generating a source code unit with the interface type declaration. You create new COM interfaces whenever you build a new COM server, be it a visual ActiveX control or a non-visual data processing server.
Figure 3: A new OLE type library editor makes short work of defining a new COM interface definition, saving it into an OLE-standard typelib file, and generating a source code unit with the interface type declaration.
OLE typelibs are symbol files that tell other applications what methods are available in your COM server, and how to call them. Just as the IDE form designer and source code editor are linked two-way tools (i.e. modifications to one are reflected immediately in the other), the new typelib editor is also a two-way tool - modifications made to the Pascal interface type declaration source code are reflected in the typelib editor, and vice-versa.
Generate Pascal Declarations from Typelibs
As a side effect of the extensive typelib editor work, you can also generate Object Pascal source-code constants and interface type declarations from any OLE typelib. You can think of this as a vast expansion of the OCX wrapper class generation in Delphi 2. If you can obtain a typelib file for a COM object you want to use in Delphi (ActiveX controls are required to have a typelib), creating Pascal interface declarations to use that COM object are a snap, and considerably more accurate than trying to mechanically convert ambiguous C header files into Pascal declarations. Unfortunately, some COM objects do exist without typelibs; Microsoft's DirectX is probably the biggest offender in this category.
Create VCL components from ActiveX controls
The ActiveX wrapper class generation found in Delphi 2 has been expanded to take advantage of new language features like interface types, and new buzzwords like ActiveX. You can now directly access interfaces provided by ActiveX controls and take advantage of other control features that were previously only available through variant variables (see Figure 4).
Figure 4: You can now directly access interfaces provided by ActiveX controls and take advantage of other control features that were previously only available through variant variables.
Another nifty spin-off of the core ActiveX development work is the creation of an ActiveX control to encapsulate an entire Delphi form (again, see Figure 1). This is necessary to support ActiveX property pages, but it's also handy for creating mini-application modules that can be automatically downloaded over the Internet and displayed inside a Web browser such as Microsoft Internet Explorer 3.0. The Web browser sees the thing as an ActiveX control, but you can pack an entire application into it.
To support Web-deployed ActiveX controls, the Delphi 3 IDE includes tools to digitally sign and seal your .DLL or .EXE file with your Software Publisher digital certificate, and deliver it to a directory of your choosing (see Figure 5). This signature is checked by the Microsoft Internet Explorer 3.0 Web browser after downloading the ActiveX control as part of an HTML document to verify that the file is from who it says it's from, and that the file has not been tampered with or corrupted in transfer.
Figure 5: The new Web Deployment Options dialog box. To support Web-deployed ActiveX controls, Delphi 3 can digitally sign and seal your .DLL or .EXE file with your Software Publisher digital certificate, and deliver it to a directory you select.
The IDE deployment wizard can also bundle and compress multiple files into the Microsoft .CAB file format, generate .INF files needed for a downloadable component to refer to required modules downloadable separately, and generate an HTML object tag for you to paste into your Web page, to refer to your downloadable component (see Figure 6).
Figure 6: The Web deployment wizard can also bundle and compress multiple files into the Microsoft .CAB file format, generate .INF files needed for a downloadable component to refer to required modules downloadable separately, and much more.
Delphi 3 supports Distributed COM, Microsoft's newest implementation of COM that enables an application on one machine to talk to an application on another across the network wire. Basically, DCOM is the heir-apparent to Remote Procedure Calls (RPC). Delphi 3's remote datasets use DCOM to make the hop from the client machine to the middle-tier data broker. You can implement your own middle-tier business logic by creating a COM server in Delphi 3, and call its methods using interfaces in the client application. DCOM takes care of the network transport; you just have to ask for it.
New Interface Type
OLE objects are always accessed through COM interfaces - abstract, virtual, base classes that define a group of related functions, but not their implementation. All COM interfaces are derived from the IUnknown standard interface, which defines simple reference-counting methods and a QueryInterface method to gain access to other interfaces supported by that object.
By far the most common programming error when using OLE objects is forgetting to increment or decrement the reference count of interfaces onto which you're holding. If you forget to call AddRef on an interface, the object behind that interface may delete itself if some other action causes the object's reference count to drop to zero. Subsequent use of the interface pointer you held onto will cause an access violation. If you forget to call Release on an interface when you're finished with it, the object behind that interface will remain in memory, never to be freed, because its reference count is artificially inflated.
Delphi 3 eliminates this debugging nightmare by adding a new standard type to the Object Pascal language definition: the interface type. An interface type declaration looks much like a class type declaration, but an interface type is only a declaration - it has no implementation of its own. An interface is like a standardized subset of methods that an object can implement. Regardless of what else the implementing object implements, you know that if it implements the XYZ interface, you can use the XYZ methods on it. Furthermore, you can obtain the XYZ interface from the implementing object, and use it without knowing anything about the implementor's class type.
In use, interface variables are initialized, reference-counted (through standard IUnknown methods), and released automatically by compiler-generated code, just as long strings and variants are dynamically allocated, reference counted, and released in Delphi 2. In Delphi 3, transferring values between interface variables or passing them as parameters is now as simple and reliable as transferring integer or string values. In many respects, passing interface values around is safer than passing object instances around, because interface reference-counting eliminates the question of who is responsible for freeing the object.
MI = Multiple Interfaces, not Multiple Inheritance
The flip side of interfaces is how they are implemented by an object. An OLE object may support many different interfaces, such as for streaming, printing, or drawing on the screen. Delphi 3 extends the declaration syntax of the class type, so you can declare a class as an implementor of one or more interface specifications. The Delphi compiler takes care of binding declared methods in the interface types to implemented methods in the class type. No tables of macros of pointers to functions, to crosswire with a typo - the compiler does it all. When something isn't quite right, the Delphi compiler tells you where and what you've missed in your declarations. For example, an interface-implementing class must implement all methods declared in the interface type. If you forget one of the interface methods, the compiler will remind you, just as it reminds you when you declare a method in a class type, but forget to give it a method body.
When a class implements an interface, instances of that class type are assignment-compatible with variables of the interface type. You can take an object instance and assign it to an interface variable, and the compiler will do the magic of extracting the correct interface pointer from the object instance automatically. You can also do late-bound (run-time) interface extraction using the as typecast operator, which calls the implementor's QueryInterface method to obtain the desired interface at run time.
Note that while an object may implement multiple interfaces, this is not the same as multiple inheritance; you are not inheriting any implementation details from the multiple interfaces.
What this means is that implementing OLE objects (such as ActiveX controls and custom COM servers) is now almost trivial. Delphi 3 requires none of the unintelligible tables of macros of pointers that Microsoft's ActiveX SDK heaps upon itself. Where Microsoft implements ActiveX as a system of macros and C++ template classes on top of the C/C++ language, Delphi implements ActiveX by incorporating the essential enabling technologies into the Object Pascal language and VCL classes. Why bother with macros and well-meaning source code conventions when you can have the compiler do the dirty work for you?
Delphi 3's Component palette includes three new heavy-hitters: an all-new version of QuickReport, powerful charting capabilities in TeeChart, and the DecisionCube interactive crosstab (see Figure 7). You can embed TeeCharts in QuickReport reports, as well as link a TeeChart to the DecisionCube to graphically display the crosstab data on-the-fly.
Figure 7: Delphi 3's Component palette includes three new heavy-hitters, including the powerful DecisionCube.
Application Deployment: Web or Otherwise
If you've ever installed multiple Delphi applications on the same machine, you've probably wondered if there was some way to share the VCL component code between the multiple applications. Well, now there is: a Delphi package. A package is a special .DLL which contains and exports one or more units for applications or other packages to share. A package is different from a .DLL in that it's Delphi-specific (non-Delphi apps should not try to link directly to a package .DLL) and you don't have to change any Delphi source code to use it.
In compiler parlance, packaging is a code-generation option (see Figure 8), which means it should have no effect on the semantics of your source code. When your Delphi application is compiled to use the VCL core package, for example, the compiler generates code to reference the Forms unit in the VCL package .DLL instead of placing the Forms unit code in your .EXE. The result is that your .EXE size drops from around 200KB to less than 20KB. With packages, the .EXE file contains only your application logic and form resources. This also makes ActiveX control .DLLs extremely small - about 25KB - far smaller than Microsoft's 50KB minimum ActiveX template-based control library, or 800KB minimum, MFC-based ActiveX control library.
Figure 8: The new Packages page of the Project Options dialog box. A package is a special .DLL which contains and exports one or more units for applications or other packages to share.
The price for packages is that the package .DLL must contain every bit of code and data that its member units define in their interface sections. This makes the core VCL package weigh in at just over 1MB. (Because most units in the core VCL package are used by the simplest blank form application, and that minimal application produces a 150KB .EXE file, that should tell you something about the value of smart linking.)
Packages are a great way to reduce the overall size of a suite of applications, and open up interesting options for such bandwidth-sensitive applications as ActiveX controls deployed over the Internet (as objects embedded in HTML documents) or network-deployed shareware. The common packages could be bundled separately from the main application file set, so that folks who already have the packages don't have to download them again. Better yet, you could refer to the Borland Web site as the source for the Delphi core packages instead of bundling them yourself, and consuming disk space on your file server.
How many times have you started to write a function call statement, but forgotten what parameters that function call requires? Wouldn't it be great if you could type a function name and hit a hotkey to show the function's parameter declaration, right there in the editor? Wouldn't it be great if it showed functions you created, as well as the Borland-documented stuff? Wouldn't it be wild if it would help you fill in the parameters too?
Delphi's Code Insight provides all of this, and much more. Its Code Parameters feature uses the compiler to determine what function you're trying to use, what its parameters are, and the types of those parameters. Moreover, it's nearly instantaneous and non-intrusive. (Beware of similar-sounding features in other products, which only give you help on functions defined by the tool vendor. Delphi uses the compiler symbols to give you help on all functions in your project - Borland's, yours, and all third-party units used by your project.)
Using code templates, you can define standard code blocks (if/then/else, begin/end, for loops, while loops, etc.) with shortcut names to insert into the editor with just a keystroke or two.
The Code Completion feature helps you enter field names and parameter values by displaying a pop-up list of identifiers that are type-compatible with the source code expression to the left of the editor cursor. For example, typing:
"Caption := IntToStr(ProgressBar1."
and pressing a hotkey will show all the integer properties and functions available on the form's ProgressBar1 component. The helper knows that ProgressBar1 is a component, and that IntToStr requires an integer type parameter, so it shows you the things in ProgressBar1 that can provide an integer-compatible value.
For debugging, the Tool Tip Expression Evaluation feature shows the values of variables in a hint balloon as your mouse moves over the source code symbols in the Code Editor. I may never use the Evaluate/Modify dialog box again!
To make it easier to debug ActiveX control .DLLs and COM servers, particularly when they are used by non-Delphi applications, you can tell the Delphi IDE debugger to run a particular .EXE in order to debug the current .DLL project. So, you can compile your ActiveX control .DLL project, set a few breakpoints in the source code, tell the debugger that the host .EXE for your .DLL is VB.EXE, select Run | Run, and Visual Basic is displayed. Tell VB to load your ActiveX control .DLL - and bing! - execution stops at a breakpoint in the Delphi debugger. You can step, evaluate, watch (and so forth) items in your .DLL while it's being used by VB.
To enable BDE-less remote datasets (and to respond to a common customer request), Delphi 3 virtualizes all database activity through a - now abstract -TDataSet class. BDE awareness is introduced in a new TDataSet descendant, TBDEDataSet, which serves as the ancestor of TDBDataSet and the familiar TTable, TQuery, and TStoredProc classes (see Figure 9).
Figure 9: Class hierarchy of TDataSet for Delphi 2 and 3. In Delphi 3, TDataSet is now abstract and has a new ancestor, TBDEDataSet. The Delphi 3 BDE now features direct links to Access and DB2, as well as dBASE, Paradox, ODBC, Informix, InterBase, Microsoft SQL Server, Oracle, and Sybase.
Delphi 3 also implements support for "thin-client" remote datasets as a descendant of the base TDataSet class, independent of the BDE. This abstraction of the dataset will also enable third parties to implement Delphi dataset support for other data providers and file formats, without resorting to fate-tempting BDE .DLL hacks.
Breaking Up Client/Server
Seasoned SQL database folks can rattle off all sorts of weaknesses and liabilities of the industry standard two-tier SQL client/server application model. For example, the client machine and application are often intimately bound to the SQL server's network name and SQL dialect or vendor. BDE aliases allow you to re-vector server references on a client machine without recompiling the client application, but those aliases are still on the client machine. If your SQL server goes down and you have to prop up your business with a backup machine, how do you make all your clients automatically talk to the backup machine instead?
Another problem with two-tier is related to centralization of business rules and data policies. In the standard two-tier SQL model, the rules that determine data relationships and links within the database must be implemented on either the server or the client. SQL has proven itself to be an adequate tool for describing and managing data, but is terrible for implementing the programming logic required for complex business rules, such as non-tabular tax calculations or least-cost resource allocation. This means enterprise-wide business rules tend to be implemented in the client application instead of on the centralized server, inflating the size of the client application and creating a maintenance liability.
Multi-Tier Remote Data Brokers
The solution to these and many other weaknesses of the traditional two-tier SQL model is to break the direct connection between the client application and the server. Multi-tier data models make the client application talk to an intermediate machine or service (a broker), which can then process or forward the information to an appropriate server. The client never talks directly to the final SQL server that owns the actual data, so the client application doesn't need to know how to talk SQL - the client application can speak simply and frankly to the intermediate data broker, and the broker can carry the burden of speaking SQL to the data servers, and fret with maintaining connections to multiple data servers - SQL and otherwise. Because business rules and other data-handling logic can live on a middle tier broker, a significant portion of what you've been calling your client application can be moved off the client machine and onto centrally managed servers. What's more, the middle-tier broker can be implemented - and debugged - using real programming tools (such as Delphi, of course) instead of primitive SQL stored procedures (see Figures 10 and 11).
Figure 10: The two-tier SQL server model.
Figure 11: The multi-tier server model.
Delphi 3 opens the floodgates to multi-tier distributed application development with the introduction of the remote dataset. A remote dataset looks and acts like any other dataset (TTable, TQuery), serving rows of data to data-aware controls. The difference is that a remote dataset doesn't require the presence of a full database engine on the client machine - the dataset talks to a second machine (the middle-tier data broker) that contains the database engine, complete with querying, filtering, and SQL connectivity intelligence. With no BDE to install or configure on the client machine, remote datasets enable you to reduce the size and complexity of your client application's file set by an order of magnitude.
With only a handful of middle-tier machines connecting to your SQL servers, Delphi 3's remote datasets could save you an enormous amount of money in SQL server connection licenses alone. Disconnecting the client from the server also opens many options for failover and server load balancing, simply by causing the middle-tier broker to forward client requests to the least busy machine in a server farm.
There are far too many exciting new features in Delphi 3 to cover in one article, or even to try to absorb in one sitting. I'd love to rattle on about the Web-server dispatch and database components for building Netscape and Microsoft Internet Server extension .DLLs, or the extensive support for DIB image formats and direct pixel memory pointers in TBitmap, the new TJPEGImage class, or the new "globalization" of the Delphi RTL and VCL classes to support multi-byte character sets in Asian locales, or the all-new documentation set and online Help, but for now a tease will have to do. So many ideas, so little time.
<< Back to main page