Delphi provides wizards and classes to make it easy to create distributed applications based on the Common Object Request Broker Architecture (CORBA). CORBA is a specification adopted by the Object Management Group (OMG) to address the complexity of developing distributed object applications.
As its name implies, CORBA provides an object-oriented approach to writing distributed applications. This is in contrast to a message-oriented approach such as the one described for HTTP applications in "Creating Internet server applications". Under CORBA, server applications implement objects that can be used remotely by client applications, through well-defined interfaces.
Note: COM provides another object-oriented approach to distributed applications. For more information about COM, see "Overview of COM technologies". Unlike COM, however, CORBA is a standard that applies to platforms other than Windows. This means you can write CORBA clients or servers using Delphi that can communicate with CORBA-enabled applications running on other platforms.
The CORBA specification defines how client applications communicate with objects that are implemented on a server. This communication is handled by an Object Request Broker (ORB). Delphi's CORBA support is based on the VisiBroker for C++ ORB (Version 3.3.2) with a special wrapper (orbpas.dll) that exposes a subset of the ORB functionality to Delphi applications.
In addition to the basic ORB technology, which enables clients to communicate with objects on server machines, the CORBA standard defines a number of standard services. Because these services use well-defined interfaces, developers can write clients that use these services even if the servers are written by different vendors.
If you are already doing object-oriented programming, CORBA makes writing distributed applications easy, because it lets you use remote objects almost as if they were local. This is because the design of a CORBA application is much like any other object-oriented application, except that it includes an additional layer for handling network communication when an object resides on a different machine. This additional layer is handled by special objects called stubs and skeletons.
On CORBA clients, the stub acts as a proxy for an object that may be implemented by the same process, another process, or on another (server) machine. The client interacts with the stub as if it were any other object that implements an interface. For more information about using interfaces, see "Using interfaces".
However, unlike most objects that implement interfaces, the stub handles interface calls by calling into the ORB software that is installed on the client machine. The VisiBroker ORB uses a Smart Agent (osagent) that is running somewhere on the local area network. The Smart Agent is a dynamic, distributed directory service that locates an available server which provides the real object implementation.
On the CORBA server, the ORB software passes interface calls to an automatically-generated skeleton. The skeleton communicates with the ORB software through the Basic Object Adaptor (BOA). Using the BOA, the skeleton registers the object with the Smart Agent, indicates the scope of the object (whether it can be used on remote machines), and indicates when objects are instantiated and ready to respond to clients.
Stubs and skeletons provide the mechanism that allows CORBA applications to marshal interface calls. Marshaling
For any interface call, the caller pushes arguments onto the stack and makes a function call through the interface pointer. If the object is not in the same process space as the code that calls its interface, the call gets passed to a stub which is in the same process space. The stub writes the arguments into a marshaling buffer and transmits the call in a structure to the remote object. The server skeleton unpacks this structure, pushes the arguments onto the stack, and calls the object's implementation. In essence, the skeleton recreates the client's call in its own address space.
Stubs and skeletons are created for you automatically when you define the object's interface. Their definitions are created in the _TLB unit that is created when you define the interface. You can view this unit by selecting it in your implementation unit's uses clause and typing Control-Enter. For more information about defining the object's interface, see "Defining object interfaces".
The Smart Agent (osagent) is a dynamic, distributed directory service that locates an available server that implements an object. If there are multiple servers to choose from, the Smart Agent provides load balancing. It also protects against server failures by attempting to restart the server when a connection fails, or, if necessary, locating a server on another host.
A Smart Agent must be started on at least one host in your local network, where local network refers to a network within which a broadcast message can be sent. The ORB locates a Smart Agent by using a broadcast message. If the network includes multiple Smart Agents, the ORB uses the first one that responds. Once the Smart Agent is located, the ORB uses a point-to-point UDP protocol to communicate with the Smart Agent. The UDP protocol consumes fewer network resources than a TCP connection.
When a network includes multiple Smart Agents, each Smart Agent recognizes a subset of the objects available, and communicates with other Smart Agents to locate objects it can't recognize directly. If one Smart Agent terminates unexpectedly, the objects it keeps track of are automatically re-registered with another available Smart Agent.
For details about configuring and using Smart Agents on your local networks, see "Configuring Smart Agents".
When the server application starts, it informs the ORB (through the Basic Object Adaptor) of the objects that can accept client calls. This code to initialize the ORB and inform it that the server is up and ready is added to your application automatically by the wizard you use to start your CORBA server application.
Typically, CORBA server applications are started manually. However, you can use the Object Activation Daemon (OAD) to start your servers or instantiate their objects only when clients need to use them.
To use the OAD, you must register your objects with it. When you register your objects with the OAD, it stores the association between your objects and the server application that implements them in a database called the Implementation Repository.
Once there is an entry for your object in the Implementation Repository, the OAD simulates your application to the ORB. When a client requests the object, the ORB contacts the OAD as if it were the server application. The OAD then forwards the client request to the real server, launching the application if necessary.
For details about registering your objects with the OAD, see "Registering interfaces with the Object Activation Daemon".
Typically, CORBA clients use static binding when calling the interfaces of objects on the server. This approach has many advantages, including faster performance and compile-time type checking. However, there are times when you can't know until runtime what interface you want to use. For these cases, Delphi lets you bind to interfaces dynamically at runtime.
Before you can take advantage of dynamic binding, you must register your interfaces with the Interface Repository using the idl2ir utility. "Registering interfaces with the Interface Repository" describes how to do this.
For details on how to use dynamic binding in your CORBA client applications, see "Using the dynamic invocation interface".
Two wizards on the Multi Tier page of the New Items dialog let you create CORBA servers:
In addition, you can easily convert an existing Automation server to a CORBA server by right-clicking and choosing Expose As CORBA Object. When you expose an Automation server as a CORBA object, you create a single application that can service both COM clients and CORBA clients simultaneously.
To start the wizard, choose File|New to display the New Items dialog. Select the Multi Tier page, and double-click the appropriate wizard.
You must supply a class name for your CORBA object. This is the base name of a descendant of TCorbaDataModule or TCorbaImplementation that your application creates. It is also the base name of the interface for that class. For example, if you specify the class name MyObject, the wizard creates a new unit declaring TMyObject which implements the IMyObject interface.
The wizard lets you specify how you want your server application to create instances of this object. You can choose either shared or instance-per-client.
Note : The instance-per-client model is not typical of most CORBA development, but works, rather, like the COM model, where the lifetime of a server object is governed by client usage. This model allows Delphi to create servers that act as CORBA and COM servers simultaneously.
In addition to the instancing model, you must specify the threading model. You can choose Single- or Multi-threaded.
With traditional CORBA tools, you must define object interfaces separately from your application, using the CORBA Interface Definition Language (IDL). You then run a utility that generates stub-and-skeleton code from that definition. However, Delphi generates the stub, skeleton, and IDL for you automatically. You can easily edit your interface using the Type Library editor and Delphi automatically updates the appropriate source files. For more information on defining interfaces using the Type Library editor, see "Working with type libraries."
The Type Library editor is also used for defining COM-based type libraries. Because of this, it includes many options and controls that are not relevant to CORBA applications. If you try to use these options, (for example, if you try to specify a version number or help file), your settings are ignored. If you create a COM Automation server which you then expose as a CORBA server, these settings apply to your server in its role as an Automation server.
In the Type Library editor, you can define your interface using Object Pascal syntax or the Microsoft IDL that is used for COM objects. Specify which language you want to use when defining your interfaces on the Type Library page of the Environment Options dialog. If you choose to use IDL, be aware that the Microsoft IDL differs slightly from the CORBA IDL. When defining your interface, you are limited to the types listed in the following table:
Note: Instead of using the Type Library editor, you can add to your interface by right-clicking in the code editor and choosing Add To Interface. However, you will need to use the Type Library editor to save an .IDL file for your interface.
You can't add properties that use parameters (although you can add get and set methods for such properties). Some types (such as arrays, Int64 values, or Currency types) must be specified as Variants. Records are not supported in the Client/Server version.
Your interface definition is reflected in the automatically generated stub-and-skeleton unit. This unit is updated when you choose Refresh in the type library editor or when you use the Add To Interface command. This automatically generated unit is added to the uses clause of you implementation unit. Do not edit the stub-and-skeleton unit.
In addition to the stub-and-skeleton unit, editing the interface updates your server implementation unit by adding declarations for your interface members and providing empty implementations for the methods. You can then edit this implementation unit to provide meaningful code for the body of each new interface method.
Note: You can save a CORBA .IDL file for your interface by clicking the Export button while in the Type Library editor. Specify that the .IDL file should use CORBA IDL, not Microsoft IDL. Use this .IDL file for registering your interface or for generating stubs and skeletons for other languages.
When you define CORBA object interfaces, two unit files are automatically updated to reflect your interface definitions.
The first of these is the stub-and-skeleton unit. It has a name of the form MyInterface_TLB.pas. While this unit defines the stub class that is only used by client applications, it also contains the declaration for your interface types and your skeleton classes. You should not edit this file directly. However, this unit is automatically added to the uses clause of your implementation unit.
The stub-and-skeleton unit defines a skeleton object for every interface supported by your CORBA server. The skeleton object is a descendant of TCorbaSkeleton, and handles the details of marshaling interface calls. It does not implement the interfaces you define. Instead, its constructor takes an interface instance which it uses to handle all interface calls.
The second updated file is the implementation unit. By default, it has a name of the form unit1.pas, although you will probably want to change this to a more meaningful name. This is the file that you edit.
For each CORBA interface you define, an implementation class definition is automatically added to your implementation unit. The implementation class name is based on the interface name. For example, if the interface is IMyInterface, the implementation class is named TMyInterface. You will find code added to this class's implementation for every method you add to the interface. You must fill in the body of these methods to finish the implementation class.
In addition, you may notice that some code is added to the initialization section of your implementation unit. This code creates a TCorbaFactory object for each object interface you expose to CORBA clients. When clients call your CORBA server, the CORBA factory object creates or locates an instance of your implementation class and passes it as an interface to the constructor for the corresponding skeleton class.
Note: The use of factory objects that indirectly create your CORBA server objects is not typical of most CORBA development. Instead, it follows the model used by COM server applications, and enables Delphi to build servers that act as COM and CORBA servers simultaneously. Factories allow CORBA servers to implement the instance-per-client model. Clients of CORBA servers built with Delphi use the CorbaFactoryCreateStub function, which handles the details of instructing the factory on the CORBA server to create a CORBA object.
While it is not necessary to register your server interfaces if you are only using static binding of client calls into your server objects, registering your interfaces is recommended. There are two utilities with which you can register your interfaces:
You can create an Interface Repository for your interfaces by running the Interface Repository server. First, you must save the .IDL file for your interface. To do this, choose View|Type Library, and then, in the Type Library Editor, click the Export button to export your interface as a CORBA .IDL file.
Once you have an .IDL file for your interface, you can run the Interface Repository Server using the following syntax:
irep [-console] IRname [file.idl]
The irep arguments are described in the following table:
Once the Interface Repository server is running, you can add additional interfaces by choosing File|Load and specifying a new .IDL file. However, if the new .IDL file contains any entries that match an existing .IDL entry, the new .IDL file is rejected.
At any point, you can save the current contents of the Interface Repository to an .IDL file by choosing File|Save or File|Save As. This way, after you exit the Interface Repository, you can restart it later with the saved file so that you don't need to reimport all changes to the initial .IDL file.
You can also register additional interfaces with the Interface Repository using the idl2ir utility. While the Interface Repository server is running, start the idl2ir utility using the following syntax:
idl2ir [-ir IRname] {-replace} file.idl
The idl2ir arguments are described in the following table:
Entries in an interface repository can't be removed while the Interface Repository server is running. To remove an item, you must shut down the Interface Repository server, generate a new .IDL file, and then start the Interface Repository server, specifying the updated .IDL file.
Before you can register an interface with the Object Activation Daemon (OAD), the OAD command-line program must be running on at least one machine on your local network. Start the OAD using the following syntax:
oad [options]
The OAD utility accepts the following command line arguments:
Once the OAD is running, you can register your object interfaces using the command-line program oadutil. First, you must export the .IDL file for your interfaces. To do this, click the Export button in the Type Library editor and save the interface definition as a CORBA .IDL file.
Next, register interfaces using the oadutil program with the following syntax:
oadutil reg [options]
The following arguments are available when registering interfaces using oadutil:
For example, the following line registers an interface based on its repository id:
oadutil reg -r IDL:MyServer/MyObjectFactory:1.0 -o TMyObjectFactory -cpp MyServer.exe -p unshared
Note: You can obtain the repository ID for your interface by looking at the code added to the initialization section of your implementation unit. It appears as the third argument of the call to TCorbaFactory.Create.
When an interface becomes unavailable, you must unregister it. Once an object is unregistered, it can no longer be automatically activated by the OAD if a client requests the object. Only objects that have been previously registered using oadutil reg can be unregistered.
To unregister interfaces, use the following syntax:
oadutil unreg [options]
The following arguments are available when unregistering interfaces using oadutil:
When you write a CORBA client, the first step is to ensure that the client application can talk to the ORB software on the client machine. To do this, simply add CorbaInit to the uses clause of your unit file.Next, proceed with writing your application in the same way you write any other application in Delphi. However, when you want to use objects that are defined in the server application, you do not work directly with an object instance. Instead, you obtain an interface for the object and work with that. You can obtain the interface in one of two ways, depending on whether you want to use static or dynamic binding.
To use static binding, you must add a stub-and-skeleton unit to your client application. The stub-and-skeleton unit is created automatically when you save the server interface. Using static binding is faster than using dynamic binding, and provides additional benefits such as compile-time type checking and code-completion.
However, there are times when you do not know until runtime what object or interface you want to use. For these cases, you can use dynamic binding. Dynamic binding does not require a stub unit, but it does require that all remote object interfaces you use are registered with an Interface Repository running on the local network.
Tip: You may want to use dynamic binding when writing CORBA clients for servers that are not written in Delphi. This way, you do not need to write your own stub class for marshaling interface calls.
Stub classes are generated automatically when you define a CORBA interface. They are defined in a stub-and-skeleton unit, which has a name of the form BaseName_TLB (in a file with a name of the form BaseName_TLB.pas).
When writing a CORBA client, you do not edit the code in the stub-and-skeleton unit. Add the stub-and-skeleton unit to the uses clause of the unit which needs an interface for an object on the CORBA server.For each server object, the stub-and-skeleton unit contains an interface definition and a class definition for a corresponding stub class. For example, if the server defines an object class TServerObj, the stub-and-skeleton unit includes a definition for the interface IServerObj, and for a stub class TServerObjStub. The stub class is a descendant of TCorbaDispatchStub, and implements its corresponding interface by marshaling calls to the CORBA server. In addition to the stub class, the stub-and-skeleton unit defines a stub factory class for each interface. This stub factory class is never instantiated: it defines a single class method.
In your client application, you do not directly create instances of the stub class when you need an interface for the object on the CORBA server. Instead, call the class method CreateInstance of the stub factory class. This method takes one argument, an optional instance name, and returns an interface to the object instance on the server. For example:
var
ObjInterface : IServerObj;
begin
ObjInterface := TServerObjFactory.CreateInstance('');
...
end;
When you call CreateInstance, it
Note: If you are writing a client for a CORBA server that was not written using Delphi, you must write your own descendant of TCorbaStub to provide marshaling support for your client. You must then register this stub class with the global CORBAStubManager. Finally, to instantiate the stub class and get the server interface, you can call the global BindStub procedure to obtain an interface which you then pass to the CORBA stub manager's CreateStub method.
The dynamic invocation interface (DII) allows client applications to call server objects without using a stub class that explicitly marshals interface calls. Because DII must encode all type information before the client sends a request and then decode that information on the server, it is slower than using a stub class.
Before you can use DII, the server interfaces must be registered with an Interface Repository that is running on the local network. For more information about registering interfaces with the Interface Repository, see "Registering interfaces with the Interface Repository".
To use DII in a client application, obtain a server interface and assign it to a variable of type TAny. TAny is a special, CORBA-specific Variant. Then call the methods of the interface using the TAny variable as if it were an interface instance. The compiler handles the details of turning your calls into DII requests.To get an interface for making late-bound DII calls, use the global CorbaBind function. CorbaBind takes either the Repository ID of the server object or an interface type. It uses this information to request an interface from the ORB, and uses that to create a stub object.
Note: Before calling CorbaBind, the association between the interface type and its Repository ID must be registered with the global CorbaInterfaceIDManager.
If your client application has a registered stub class for the interface type, CorbaBind creates a stub of that class. In this case, the interface returned by CorbaBind can be used for both early binding (by casting with the as operator) or late (DII) binding. If there is no registered stub class for the interface type, CorbaBind returns the interface to a generic stub object. A generic stub object can only be used for late (DII) calls.
To use the interface returned by CorbaBind for DII calls, assign it to a variable of type TAny:
var
IntToCall: TAny;
begin
IntToCall := CorbaBind('IDL:MyServer/MyServerObject:1.0');
...
Once an interface has been assigned to a variable of type TAny, calling it using DII simply involves using the variable as if it were an interface:
var
HR, Emp, Payroll, Salary: TAny;
begin
HR := CorbaBind('IDL:CompanyInfo/HR:1.0');
Emp := HR.LookupEmployee(Edit1.Text);
Payroll := CorbaBind('IDL:CompanyInfo/Payroll:1.0');
Salary := Payroll.GetEmployeeSalary(Emp);
Payroll.SetEmployeeSalary(Emp, Salary + (Salary * StrToInt(Edit2.Text) / 100));
end;
When using DII, all interface methods are case sensitive. Unlike when making statically-bound calls, you must be sure that method names match the case used in the interface definition.
When calling an interface using DII, every parameter is treated as a value of type TAny. This is because TAny values carry their type information with them. This type information allows the server to interpret type information when it receives the call.
Because the parameters are always treated as TAny values, you do not need to explicitly convert to the appropriate parameter type. For example, in the previous example, you could pass a string instead of a floating-point value for the last parameter in the call to SetEmployeeSalary:
Payroll.SetEmployeeSalary(Emp, Edit2.Text);
You can always pass simple types directly as parameters, and the compiler converts them to TAny values. For structured types, you must use the conversion methods of the global ORB variable to create an appropriate TAny type. Table 28.7 indicates the method to use for creating different structured types:
When using these helper functions, you must specify the type code that describes the type of record, array, or sequence you want to create. You can obtain this type dynamically from a Repository ID using the ORB's FindTypeCode method:
var
HR, Name, Emp, Payroll, Salary: TAny;
begin
with ORB do
begin
HR := Bind('IDL:CompanyInfo/HR:1.0');
Name := MakeStructure(FindTypeCode('IDL:CompanyInfo/EmployeeName:1.0',
[Edit1.Text,Edit2,Text]);
Emp := HR.LookupEmployee(Name);
Payroll := Bind('IDL:CompanyInfo/Payroll:1.0');
end;
Salary := Payroll.GetEmployeeSalary(Emp);
Payroll.SetEmployeeSalary(Emp, Salary + (Salary * StrToInt(Edit3.Text) / 100));
end;
Two global functions, ORB and BOA, allow you to customize the way your application interacts with the CORBA software running on your network.
Client applications use the value returned by ORB to configure the ORB software, disconnect from the server, bind to interfaces, and obtain string representations for objects so that they can display object names in the user interface.
Server applications use the value returned by BOA to configure the BOA software, expose or hide objects, and retrieve custom information assigned to an object by client applications.
When writing a CORBA client application, you may wish to present users with the names of available CORBA server objects. To do this, you must convert your object interface to a string. The ObjectToString method of the global ORB variable performs this conversion. For example, the following code displays the names of three objects in a list box, given interface instances for their corresponding stub objects.
var
Dept1, Dept2, Dept3: IDepartment;
begin
Dept1 := TDepartmentFactory.CreateInstance('Sales');
Dept1.SetDepartmentCode(120);
Dept2 := TDepartmentFactory.CreateInstance('Marketing');
Dept2.SetDepartmentCode(98);
Dept3 := TSecondFactory.CreateInstance('Payroll');
Dept3.SetDepartmentCode(49);
ListBox1.Items.Add(ORB.ObjectToString(Dept1));
ListBox1.Items.Add(ORB.ObjectToString(Dept2));
ListBox1.Items.Add(ORB.ObjectToString(Dept3));
end;
The advantage of letting the ORB create strings for your objects is that you can use the StringToObject method to reverse this procedure:
var
Dept: IDepartment;
begin
Dept := ORB.StringToObject(ListBox1.Items[ListBox1.ItemIndex]);
... { do something with the selected department }
When a CORBA server application creates an object instance, it can make that object available to clients by calling the ObjIsReady method of the global variable returned by BOA.
Any object exposed using ObjIsReady can be hidden by the server application. To hide an object, call the BOA's Deactivate method.
If the server deactivates an object, this can invalidate an object interface held by a client application. Client applications can detect this situation by calling the stub object's NonExistent method. NonExistent returns True when the server object has been deactivated and False if the server object is still available.
Stub objects in CORBA clients can send identifying information to the associated server object using a TCorbaPrincipal. A TCorbaPrincipal is an array of bytes that represents information about the client application. Stub objects set this value using their SetPrincipal method.
Once the CORBA client has written principal data to the server object instance, the server object can access this information using the BOA's GetPrincipal method.
Because TCorbaPrincipal is an array of bytes, it can represent any information that the developer finds useful to send. For example, clients with special privileges can send a key value that the server checks before making some methods available.
Once you have created client or server applications and thoroughly tested them, you are ready to deploy client applications to end users' desktops and server applications on server-class machines. The following list describes the files that must be installed (in addition to your client or server application) when you deploy your CORBA application:
In addition, you may need to set the following environment variables when you deploy your CORBA application.
Note: For general information about deploying applications, see "Deploying applications".
When you deploy your CORBA application, there must be at least one smart agent running on the local network. By deploying more than one smart agent on a local network, you provide protection against the machine that is running the smart agent from going down.
You deploy smart agents so that they divide a local network into separate ORB domains. Conversely, you can connect smart agents on different local networks to broaden the domain of your ORB.
To start the Smart Agent, run the osagent utility. You must run at least one Smart Agent on a host in your local network. The osagent utility accepts the following command line arguments:
For example, type the following command in a DOS box or choose Run from the Start button:
osagent -p 11000
This starts the Smart Agent so that it listens on UDP port 11000 rather than the default port (14000). Changing the port which the Smart Agent uses to listen for broadcast messages allows you to create multiple ORB domains.
It is often desirable to have two or more separate ORB domains running at the same time. One domain might consist of the production version of client applications and object implementations while another domain could include test versions of the same clients and objects that have not yet been released for general use. If several developers are working on the same local network, each may want a dedicated ORB domain so that the testing efforts of different developers do not conflict with each other.
You can distinguish between two or more ORB domains on the same network by using a unique UDP port number of the osagents in each domain.
The default port number (14000) is written to the Windows Registry when the ORB is installed. To override this value, set the OSAGENT_PORT environment variable to a different setting. You can further override the value specified by OSAGENT_PORT by starting the Smart Agent using the -p option.
If you start multiple Smart agents on your local network, they will discover each other using UDP broadcast messages. Your local networks are configured by specifying the scope of broadcast messages using the IP subnet mask. You can allow a Smart Agent to communicate with other networks in two ways:
Consider the two Smart Agents depicted in the following figure. The Smart Agent on network #1 listens for broadcast messages using the IP address 199.10.9.5. The Smart Agent on network #2 listens using the IP address 101.10.2.6.
The Smart Agent on network #1 can contact the Smart Agent on network #2 if it can find a file named agentaddr which contains the following line:
101.10.2.6
The Smart Agent looks for this file in the directory specified by the VBROKER_ADM environment variable.
When you start the Smart Agent on a host that has more than one IP address (known as a multi-homed host), it can provide a powerful mechanism for bridging objects located on separate local networks. All local networks to which the host is connected can communicate with a single Smart Agent, effectively bridging the local networks without an agentaddr file.
However, on a multi-homed host, the Smart Agent can't determine the correct subnet mask and broadcast address values. You must specify these values in a localaddr file. You can obtain the appropriate network interface values of the localaddr file by accessing the TCP/IP protocol properties from the Network Control Panel. If your host is running Windows NT, you can use the ipconfig command to get these values.
The localaddr file contains a line for every combination of IP address, subnet mask, and broadcast address that the Smart Agent can use. For example, the following lists the contents of a localaddr file for a Smart Agent that has two IP addresses:
216.64.15.10 255.255.255.0 216.64.15.255 214.79.98.88 255.255.255.0 214.79.98.255
You must also set the OSAGENT_LOCAL_FILE environment variable to the fully qualified path name of the localaddr file. This allows the Smart Agent to locate this file.
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.