CORBA and Delphi
If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.
This paper will look at what CORBA is, what it does, how it compares with other, similar distributed application architectures and how it works in Delphi 4 and later.
During the paper, we will see a CORBA object developed within a CORBA server application, and a CORBA client application will also be manufactured. These applications will talk to each other on the same machine, and also across a network link so you can see all the mechanisms required for the successful implementation and execution of a distributed CORBA application.
Since Delphi obscures a lot of what goes on in CORBA-based applications, additional explanation will focus on what happens behind the scenes and under the hood, so you can appreciate the level of support given by default from your development tool of choice.
The intent of this paper is to help you appreciate CORBA and the development of CORBA applications in the Delphi environment, and also to get an understanding of many of the strange terms, phrases and acronyms used within the world of CORBA.
Click here to download the files associated with this paper.
CORBA is an acronym for the Common Object Request Broker Architecture. It is a specification managed by the OMG (Object Management Group), a consortium of more than 800 companies who are striving for a standard distributed object computing architecture.
The specification's goal is to allow you to create distributed object-oriented applications in a fashion where you can use remote objects just as if they were local. In order to accommodate the potential for a network to be between the client and the server applications, CORBA uses stubs and skeletons to act as proxies on both sides.
CORBA specifies the infrastructure for objects to communicate within, called the Object Request Broker, or ORB. The ORB is the object bus across which objects transparently interact, be it locally or remotely. So the ORB is responsible for dealing with communication across the network in some fashion.
Whilst an ORB is really a concept in the specification, the code that implements the ORB we use in Delphi lives in some DLLs in the VBroker\Bindirectory under Program Files\Borland or Program Files\Inprise.
The Basic Object Adapter (BOA) is responsible for connecting object implementation's to the ORB. The object calls the BOA to tell it that it is ready to receive client requests, and the BOA then connects it to the ORB. The BOA can also be asked to hide and unhide objects from public view (termed object deactivation and activation).
The Basic Object Adapter is a particular type of Object Adapter (OA) which is suitable for most object implementations. Since the CORBA specification does not dictate how the BOA functionality should work, it is to be replaced some time soon with the POA (Portable Object Adapter) to provide more compatibility between ORB vendors.
Visigenics was a company taken over by Inprise in the recent past. Visigenics marketed a well-respected CORBA ORB implementation called VisiBroker. Delphi 4 Client/Server ships with version 3.2 of this software. Delphi 5 Enterprise comes with VisiBroker 3.32.
Client Stubs And Server Skeletons
When a CORBA client application asks to talk to a CORBA server object, the client is presented with a client stub object. This stub is a proxy for the real CORBA object (which may be implemented remotely or locally), and pretends to implement the interface actually implemented by the server object.
When any interface methods are called, the stub invokes the ORB on the client machine. The ORB then uses a SmartAgent to locate an implementation of the required object.
On the server machine, the server's ORB generates a server skeleton object which then talks to the CORBA object as if it were the client application. The skeleton object communicates with the BOA in order to tell the ORB when the server object is ready.
The primary job of the stub and skeleton duo is to marshal method arguments from one process to the other (possibly the other side of the network). Fortunately, these stubs and skeletons are automatically generated as you build CORBA objects in Delphi.
How Does CORBA Compare With DCOM?
Distributed COM (DCOM) is used for building distributed object systems, as is CORBA. However there are a number of differences.
DCOM is controlled by Microsoft, and is primarily found on Win32 platforms. CORBA is organised by a large committee and has implementations running on many platforms. CORBA implementations come from vendors, not the specification's management group.
DCOM is free, and built into your Win32 operating system. CORBA implementations have a tendency to cost money.
CORBA can offer many pre-defined services, such as persistence, security, transactions and licensing. DCOM itself does not offer this, but gets a lot nearer when you combine it with Microsoft Transaction Server (MTS). COM+ will make the two architectures more similar.
CORBA ORB implementations have been shipping for longer than DCOM, and so could be considered more proven.
Interfaces In CORBA Applications
CORBA applications are built around the notion of interfaces. A client application only talks to a CORBA object through exposed interfaces. In Delphi, the core interface is IObject, which is the same as IUnknown. Guy Smith-Ferrier's session at DCon '99 should give you a good introduction to interfaces.
Isn't Delphi's CORBA Support Based On COM?
This myth needs to be dispelled. However, it is easy to draw this conclusion due to the way Delphi supports CORBA.
You build CORBA objects using Delphi's type library editor. Type libraries historically relate to COM, as COM servers typically ship with a type library describing all the interfaces etc. that are available from them. Delphi's support for developing CORBA objects uses the type library editor simply because it was easy for the R&D team to do Delphi already supported this development model for COM objects.
When building a CORBA object the type library editor generates the stub-and-skeleton unit, and the type library binary file ultimately gets compiled in as a Windows resource. However, it is not actually used at run-time. So whilst you do carry around a COM type library, your CORBA application will not make use of COM services at run-time. Additionally, when the CORBA server is executed, it will not scatter references to itself through the Windows registry (as COM applications do).
That said, the fact that the object is built hand-in-hand with a type library means that if you want, you can design one object that acts simultaneously as a CORBA object and a COM object. So this strange way of designing CORBA objects ends up allowing a neat way of working with two different distributed object protocols.
The core interface in a CORBA object, IObject is defined in Delphi to be the same as IUnknown. IUnknown is defined in the COM specification, but a Delphi implementation of IUnknown does not cause any dependencies on COM binaries.
Creating A CORBA Object In A CORBA Server
Here we will develop an application containing a CORBA object that will function as a CORBA server. Later, we will develop client applications that will talk to the object in the CORBA server, be it running on the same machine, or across a network on another machine.
First of all, create a new application and, to identify it, drop a label on the form and set the caption to suggest that this is a CORBA server application. Now save the files. The example that accompanies this paper is a project called Server.Dpr.
Now choose File | New... and from the Multitier page choose a CORBA Object.
Figure 1: Asking Delphi to create a new CORBA object
In the CORBA Object Wizard dialog, set the class name to Information, as this object will be able to supply a couple of pieces of information to its clients. Leave the Instancing: option set to Instance-per-client so that each client will get a new instance of the server object. Also, leave the Threading Model: option set to Single-threaded so we don't have to worry about thread conflicts.
Figure 2: The CORBA Object Wizard
What you see when you accept the settings in the wizard is a new unit containing a new class called TInformation. TInformation is the class that will implement the CORBA object and is declared to implement an interface called IInformation. Save this implementation unit (the sample one is called ServerImpl.Pas).
The initialisation section of the unit creates an instance of a TCorbaObjectFactory. This factory object will be mentioned again later.
Listing 1: The CORBA object implementation unit
unit ServerImpl; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, ComObj, StdVcl, CorbaObj, Server_TLB; type TInformation = class(TCorbaImplementation, IInformation) private { Private declarations } public { Public declarations } end; implementation uses CorbInit; initialization TCorbaObjectFactory.Create('InformationFactory', 'Information', 'IDL:Server/InformationFactory:1.0', IInformation, TInformation, iMultiInstance, tmSingleThread); end.
The IInformation interface is defined in a unit that appears in this implementation unit's uses clause, called Server_TLB.
Listing 2: A CORBA interface defined in the stub-and-skeleton unit
IInformation = interface(IDispatch) ['{435FC5C1-49B3-11D3-96EC-0060978E1359}'] end;
Server_TLB is a type library import unit, but since we are making a CORBA object here, it doubles as a stub-and-skeleton unit. In other words, Server_TLB defines the client-side stub object and the server-side skeleton object.
Listing 3: CORBA stub and skeleton class definitions from the stub-and-skeleton unit
TInformationStub = class(TCorbaDispatchStub, IInformation) public end; TInformationSkeleton = class(TCorbaSkeleton) private FIntf: IInformation; public constructor Create(const InstanceName: string; const Impl: IUnknown); override; procedure GetImplementation(out Impl: IUnknown); override; stdcall; published end;
Since Server_TLB is manufactured by Delphi's type library editor, you should not edit it directly. Instead, any changes in the unit should come from using the type library editor itself.
Choose View | Type Library to see the type library editor. You should see the IInformation interface defined there.
Figure 3: Delphi's Type Library Editor showing your CORBA interface
For most trouble-free use of the type library editor whilst adding to the interface definition, you should choose Tools | Environment Options..., go to the Type Library page and set the Language option to say Pascal, rather than IDL.
Now select the IInformation interface and either press the Method speedbutton, or right-click and choose New | Method. Give the method a name of CurrentDateAndTime and then switch from the Attributes page to the Parameters page. Now you can change the return type of the routine from None to TDateTime.
Add two more methods to the interface, WhichMachine and WhichUser, both of which should also be parameterless and return a WideString, and then press the Refresh speedbutton.
Figure 4: The interface now has three methods defined
Before dealing with the implementation of these methods, you should try the following. Go back to Tools | Environment Options... | Type Library and change the language back to IDL. Now check out the type library editor's Parameters page for any of the methods just added. You can see that the methods actually return a HResult (a long integer). The value we asked to be returned from the method is actually fed back through a pass-by-reference parameter.
Over in the ServerImpl unit you should now have three empty method implementations, one for each interface method, waiting to be filled in. Possible code for the methods is shown in Listing 4.
Listing 4: Possible implementations for the interface methods
function TInformation.CurrentDateAndTime: TDateTime; begin Result := Now end; function TInformation.WhichMachine: WideString; var Buf: array[0..MAX_COMPUTERNAME_LENGTH] of Char; Len: DWord; begin Len := SizeOf(Buf); if not GetComputerName(Buf, Len) then Result := SysErrorMessage(GetLastError) else Result := Buf end; function TInformation.WhichUser: WideString; var Buf: array[0..255] of Char; Len: DWord; begin Len := SizeOf(Buf); if not GetUserName(Buf, Len) then Result := SysErrorMessage(GetLastError) else Result := Buf end;
The declarations of the methods in the class in the interface section of the unit are each marked with the safecall directive. This hides the HResult business from us, and deals with it on our behalf.
The general rule is that COM and CORBA objects should not allow arbitrary exceptions that occur to go unhandled and wind their way back to the client. A safecall routine has an implicit exception handler around it, and any exception is converted to a HResult that indicates an error. If no exception occurs, a success-indicating HResult is returned.
Interface Definition Language (IDL)
Most CORBA aware development tools use IDL files to help build client applications. Whilst COM uses a (binary) type library to describe what is on offer, CORBA uses an IDL file. IDL stands for the Interface Definition Language, and an IDL file defines, in a language-neutral way, all interfaces etc. available from a CORBA server.
Delphi is aware of two variants of IDL, Microsoft IDL (MIDL)and CORBA IDL. Whilst for Delphi programmers, Microsoft IDL is not much to worry about, Delphi CORBA developers need to be aware of CORBA IDL. If you ship a CORBA server, you should have an IDL file ready to distribute to anyone who needs to access it.
The Delphi type library editor can generate an IDL file in either of these IDL variants. To generate a CORBA IDL file, locate the Export tool button and press the arrow next to it. Be careful not to push the button itself, as this defaults to generating Microsoft IDL. The arrow creates a small menu where you can choose to create a CORBA IDL file.
Figure 5: Exporting your interface to IDL
The IDL file for the server created so far is shown in the Listing 5.
Listing 5: A CORBA IDL file defining your interface and its factory interface
module Server { interface IInformation; interface IInformation { double CurrentDateAndTime(); wstring WhichMachine(); wstring WhichUser(); }; interface InformationFactory { IInformation CreateInstance(in string InstanceName); }; };
You can see that there are actually two CORBA objects represented here. One is the server we created, and one is a factory object. At run-time, when the server application is invoked, the only CORBA object that gets automatically created is the small factory object.
If someone eventually requests to speak to the IInformation interface, the factory will bring an instance of the TInformation object to life. No TInformation is created by default, as it may potentially consume a lot of resources unnecessarily.
So a CORBA aware development tool can take an IDL file and generate a client stub from it. C++ tools do this with a utility called IDL2CPP. Java tools do this with IDL2JAVA. Delphi does not have an IDL2Delphi at the time of writing this paper, but I have been led to believe that Delphi 5 Enterprise will have one available (available for download from the Web).
Requirements For A CORBA Server
For a CORBA server to run at all, and also for a CORBA client to locate a CORBA server, certain criteria must be met, as described here.
In the case of the VisiBroker ORB, you must ensure there is an active TCP/IP stack available for a CORBA server to successfully start up. If you use, for example, a Windows 95 laptop computer and have a tendency to remove the network PC card when disconnected from the network, your CORBA servers will not start unless you find some other way to create a TCP/IP stack.
How Clients Connect To Your Server
In order for CORBA client applications to connect to your server application, it must be running (by default). At this point, the standard CORBA infrastructure, as held up by the SmartAgent (see later), will be able to see any instantiated CORBA objects in the application. If the server is not running, you can still make your objects available by registering them with the Object Activation Daemon (OAD).
Making Your CORBA Server Known To The Universe
Let's see how to advertise your server's services to the masses on the network.
The primary requirement for CORBA applications to work (with VisiBroker as the ORB of choice) is to have a VisiBroker SmartAgent running somewhere on your local network. A SmartAgent will only run if the aforementioned requirement for an active TCP/IP stack is met.
Its job is to provide a dynamic, distributed directory service. It acts as a lookup service for CORBA clients who request the services of an object that resides in a CORBA server application. When multiple servers implement the same object, SmartAgents provide load balancing. If a server falls over, they also attempt to restart it.
SmartAgents are located by the ORB via a network broadcast message. When one has been found, further communication is by a point-to-point UDP protocol (more resource-friendly than a TCP connection).
You can have as many SmartAgents running on your local network as you want, but the requirement is for one to be running. A SmartAgent can be started as a simple application (maybe in your Startup group). On NT it can also be started as a service.
Object Activation Daemon (OAD)
A SmartAgent on its own will only be able to locate objects in running CORBA server applications. If you do not wish to have some of your servers running, you can register them with the OAD. The OAD stores information about the object and the application that implements it in an Implementation Repository. When the details are in the Implementation Repository, the OAD will pretend to be the registered object to satisfy the SmartAgent when a client asks for a given CORBA object. When a client tries to connect to an object registered with the OAD, the OAD starts it up and steps out of the picture.
To register a CORBA server object with the OAD, the OAD must be running. Assuming it is, you could register the above server with the following (rather lengthy) command-line:
oadutil reg -r IDL:Server/InformationFactory:1.0 -o Information -cpp c:\projects\server.exe
The string following the -r switch is the (case-sensitive) Repository ID of the factory class and follows the pattern IDL:A/BFactory:1.0, where A is the project name, and B is the class name you entered into the CORBA Object Wizard.
You can get help on OAD registration by running the command-line:
oadutil reg -?
To remove an object from the OAD's registration list you issue the same command-line, but use the unreg switch instead of the reg switch. Shutting the OAD does not unregister all objects registered with it, but makes them unavailable until the OAD is restarted (or the servers that implement those objects).
Some client applications won't have been built with the help of an IDL file. As a consequence, they will not have a client stub available. This is somewhat parallel to trying to do COM Automation when you do not have a type library to import. In the case of COM you do late-bound Automation using a Delphi Variant variable, and Delphi generates code that does what is needed through IDispatch.
In the case of a CORBA application, you can also do late bound access of a server object, through the Dynamic Invocation Interface (DII). However, for this to work, the IDL file for the server object must be registered with an Interface Repository. This allows clients to programmatically enquire what your interfaces have to offer.
To access an Interface Repository, an instance of it must be running on the local network.
To register an interface (contained within an IDL file) with the Interface Repository Server, use IREP.EXE, e.g.
irep -console MyRepository c:\Projects\Server.idl
To register another interface with a running Interface Repository Server use IDL2IR.EXE, e.g.
idl2ir -ir MyRepository c:\Projects\AnotherFile.idl
An Object Accessible Through CORBA And COM
Delphi can have one class accessible through these two distributed object architectures. The basic idea is to start out with a COM object (or Automation object). From this point, you take yourself to the implementation unit, containing the COM class that implements the interface you have been designing. In the editor, right-click and choose Expose as CORBA Object.
What happens is that several units are added to the uses clause: CorbInit, CorbaObj and ComCorba. Also, the TComObjectFactory object that gets created in the initialisation section gets accompanied by the creation of a TCorbaComObjectFactory object.
Creating A CORBA Client Application
This is really just as easy as making a COM client application. Make a new application and add the server project's stub-and-skeleton unit (Server_TLB) to the project. Now add the name of this unit to the form unit's uses clause.
You can now declare an interface reference variable as a field of the form through which we can access the CORBA server object. The form's OnCreate event handler will (hopefully) create a connection to the server object, and call two of the object's methods. A timer is used to regularly call the other method.
Listing 6 shows the important parts of the code. You should see that the interface reference is declared as type IInformation. Also, the client connects to the server using the factory utility class defined in the stub-and-skeleton unit. This class (TInformationCorbaFactory) talks to the factory object in the CORBA server and asks it to create an instance of the target CORBA object.
Listing 6: Code for a CORBA client application
uses ..., Server_TLB; type TForm1 = class(TForm) ... public InfoServer: IInformation; end; procedure TForm1.Form1Create(Sender: TObject); begin InfoServer := TInformationCorbaFactory.CreateInstance(''); Label1.Caption := 'Object running on ' + InfoServer.WhichUser + '''s machine, which is called ' + InfoServer.WhichMachine; Timer1.Enabled := True end; procedure TForm1.Timer1Timer(Sender: TObject); begin try Caption := DateTimeToStr(InfoServer.CurrentDateAndTime) except Caption := 'Information server' end end;
Using The Stub-And-Skeleton Unit From Delphi CORBA Servers
The stub-and-skeleton unit is the CORBA equivalent of a COM type library import unit. It is the sort of file that would be generated for non-Delphi CORBA objects if we had an IDL2Delphi tool available.
The file does not contain any of the implementation of the CORBA object itself, but does define all interfaces exposed by the object. It also contains the definition of the client side stub object (the proxy for the CORBA server object, provided so the client does not know the object is on another machine). Along with the stub object can be found the server side skeleton object (the proxy for the client application, provided so the server does not know it is being accessed from a remote location).
Considerations For Non-Delphi CORBA Objects
Delphi 4 does not ship with an IDL to Delphi converter, but Delphi 5 should do soon. However, as long as you have the IDL file, you can still access the CORBA server in question, using the Dynamic Invocation Interface.
Dynamic Invocation Interface (DII)
This is a late-bound solution for talking to CORBA servers that you do not have Delphi stubs available for. This primarily means CORBA servers written in other languages.
To show the idea, we will make another client application that will talk to the previously created CORBA server, but will do so using DII. Before DII will work, the IDL file for the server must be registered with an Interface Repository running somewhere on the local network.
The type library editor can manufacture a suitable IDL file for this server. A suitable command-line for getting the IDL file into an Interface Repository using IREP.EXE was mentioned earlier and is:
irep -console MyRepository c:\Projects\Server.idl
This starts a console mode Interface Repository.
Now to make the replacement client application. Make a new application and add CorbaObj to the form unit's uses clause.
Now declare two generic interface reference variables as fields of the form. One of these will connect to the ever-running information factory object. The factory object, you may recall, has a CreateInstance method that is used to produce an instance of the real information object. The reference to the information object will be stored in the second generic interface reference variable.
These generic interface reference variables will be of type TAny. Delphi's TAny type maps down to the CORBA Any type, but is implemented as a Variant. In other words, it is not far removed from Automating a COM server through a Variant variable.
The form's OnCreate event handler creates a connection to the server factory object, through which we create an instance of our information object. Having done this, we can call two of the object's methods. A timer will then be used to regularly call the other method. The important parts of the code are shown in Listing 7.
Listing 7: Code for a CORBA client using DII
uses ..., CorbaObj; type TForm1 = class(TForm) ... public InfoFactory, InfoServer: TAny; end; procedure TForm1.Form1Create(Sender: TObject); begin InfoFactory := CorbaBind('IDL:Server/InformationFactory:1.0'); InfoServer := InfoFactory.CreateInstance('Server'); Label1.Caption := 'Object running on ' + InfoServer.WhichUser + '''s machine, which is called ' + InfoServer.WhichMachine; Timer1.Enabled := True end; procedure TForm1.Timer1Timer(Sender: TObject); begin try Caption := DateTimeToStr(InfoServer.CurrentDateAndTime) except Caption := 'Information server' end end;
In this paper we covered a lot of ground on the subject of CORBA, the specification for platform-independent and language-independent distributed objects. We saw how CORBA objects can be built in Delphi CORBA server applications, and then how CORBA objects can be used in Delphi-based CORBA client applications. The particular CORBA ORB supplied with Delphi, VisiBroker, has been discussed where appropriate and its various support applications that supply the required CORBA infrastructure have been described.
After going through this paper, you should have a good appreciation of what CORBA is and how CORBA applications are put together.
Click here to download the files associated with this paper.
Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware. If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.
Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi award in 2000.