Previous Up Next

Chapter 3  Development of CORBA Applications

This chapter uses pseudocode to give a brief overview of the development of a CORBA client-server application. The pseudocode is is somewhat similar to C++ or Java, but the basic principles illustrated apply to CORBA development with other languages. Pseudocode is used in order to focus on the principles rather than getting sidetracked with the details of a particular language mapping.

3.1  Development of a Traditional Application

Figure 3.1 shows the structure of a traditional (that is, non-distributed), object-oriented application. One of more types (such as Account) are defined in their own source code files. Then the mainline of the program (main.cpp) creates one or more objects and invokes operations upon them.


// File: Account.h class Account { void deposit(...) { ... } ... // instance variables }; // File: main.cpp main(...) { Account obj = new Account(...); obj.deposit(...); }
Figure 3.1: Structure of a Traditional Application

3.2  Development of a CORBA Application

I now discuss what is required to write a distributed client-server application (using CORBA) that has similar functionality to the traditional application of Figure 3.1.

3.2.1  IDL Files and Generated Code

The first step in developing a CORBA application is to use IDL to define the public APIs of object types. This is shown in Figure 3.2. Notice that the IDL definition of Account is broadly similar to, say, a C++ definition. There are some minor syntactic differences, such as the class keyword is replaced with the interface keyword. However, a more important difference is that an IDL file just declares the public API—it does not contain any implementation details, such as the bodies of operations or instance variables.


// File: Account.idl interface Account { void deposit(...); };
Figure 3.2: Account.idl

Once the IDL file has been written, the developer runs it through an IDL compiler, for example:


idl Account.idl

Note that CORBA has not standardized on the names of IDL compilers or their command-line options, so the exact command used varies from one CORBA product to another. If you use a C++ CORBA product then the IDL compiler generates files containing C++ data types that correspond to the types defined in the input IDL file. Likewise, if you use, say, a Java or Cobol CORBA product then the IDL compiler generates files containing Java or Cobol data types. Among the data types generated are a proxy class called Account and a skeleton-code class (Section 1.4.5) called POA_Account.1 The pseudocode contents of these classes are shown in Figure 3.3.


// Generated code class Account { void deposit(...) { marshal request details into a binary buffer Send request buffer message to server Wait to receive reply from server if (reply buffer contains an exception) { unmarshal exception and throw it } else { unmarshal "out" parameters from reply buffer } } }; class POA_Account { abstract void deposit(...); void dispatch(...) { unmarshal "in" parameters from request buffer try { deposit(...); marshal "out" parameters into reply buffer } catch(...) { marshal exception into reply buffer } Send reply buffer to client } };
Figure 3.3: Code generated by IDL compiler

A client makes a remote invocation by invoking upon a (local) proxy object. The operations on the proxy object marshal (Section 11.2) the details of the invocation—the object key (Section 5.6.1) that uniquely identifies the target object in the server, the name of the operation being invoked and in/inout parameters—into a binary buffer and the contents of this buffer are transmitted to the server process that contains the target object. Then the proxy waits to receive back a reply message from the server and unmarshals the out/inout parameters and return value, if any, from the reply buffer. Alternatively, if the reply buffer contains an exception then the proxy unmarshals this and throws it.

The generated skeleton class (Figure 3.3) contains an abstract operation (a pure virtual member function in C++ terminology) for each IDL operation. This operation is not implemented in the skeleton class, but rather in a sub-class that is written by a developer. The skeleton class also contains some dispatch logic that unmarshals an incoming request, calls the appropriate operation—deposit() in our example—with the unmarshaled in/inout parameters. When this operation returns, the skeleton class then marshals the out/inout parameters and return value, if any, (or an exception) into a reply buffer and then transmits this back to the client application.

Developers do not need to know the low-level details of how proxies or skeleton classes work—only that they are part of the infrastructure that is used to delegate requests from a client application across a network to the “real” object in a server process.

3.2.2  Servant Classes

CORBA uses the terminology servant to mean an object in the host programming language (for example, C++, Java or Cobol) that implements the functionality of a CORBA object. In CORBA, a servant is not a CORBA object, but a servant does represent a CORBA object. A detailed discussion of this subtle distinction between a servant and a CORBA object is deferred until Chapter 5.


// File: AccountImpl.h class AccountImpl inherits POA_Account { void deposit(...) { ... } ... // instance variables };
Figure 3.4: Servant class

The servant class is not generated by the IDL compiler; instead, it is hand-written by developers. Developers can use whatever name they want for this class, but a common naming scheme is for the servant class to be composed of the name of the IDL interface combined with a suffix, such as Impl. For example, AccountImpl might be the name of the servant class for the Account interface. Pseudocode for this servant class is shown in Figure 3.4. The servant class inherits from the generated skeleton class and must provide an implementation for all the IDL operations, such as deposit() in our example.2 The servant class may contain constructors, instance variables and extra (non-IDL) operations to support the implementation of the IDL operations.

3.2.3  Server Mainline

Pseudocode for a server mainline is shown in Figure 3.5. The most important pieces of code are indicated in bold.


// File: server_main.cpp int main(int argc, char* argv[]) { exit_status = 0; orb = null; try { orb = CORBA::ORB_init(argc, argv); ... // create POAs to contain servants sv = new AccountImpl(...); ... // activate (insert) sv into a POA exportObjRef(..., sv._this(), ...); ... // activate POA managers orb.run(); } catch(CORBA::Exception & ex) { cout << "Something went wrong: " << ex << endl; exit_status = 1; } // Terminate gracefully try { if (orb != null) { orb.destroy(); } } catch(CORBA::Exception & ex) { cout << "Something went wrong: " << ex << endl; exit_status = 1; } return exit_status; };
Figure 3.5: Pseudocode of server mainline

The ORB_init() function creates a new ORB object, which represents the CORBA runtime system.3 This function is passed command-line arguments (in C/C++ these are held in argc and argv). ORB_init() inspects pairs of command-line arguments of the form -ORB<name> <value> and interprets these name-value pairs as configuration values for the newly created ORB object. The recognized name-value pairs vary from one CORBA product to another, but they are typically used to specify information such as the diagnostic level for the CORBA runtime system, the port on which a server process should listen for incoming connections, or the name of a configuration file that contains additional name-value pairs of configuration information.

When a CORBA program is terminating, it must call orb.destroy(). This operation ensures that the CORBA runtime system is gracefully destructed before the program terminates.

If anything goes wrong when calling a CORBA API then an exception is thrown. For this reason, a try-catch clause surrounds most of the code in the main() function. This is to ensure that, even if an exception is thrown, the application can call orb.destroy().

Although Figure 3.5 shows just one servant being created, CORBA allows a server process to create an arbitrary number of servants for an arbitrary number of IDL interfaces. When a server implements several IDL interfaces, the server developer might want different servants to have different qualities of service (QoS). For example, if some servants are implemented in a thread-safe way then the CORBA runtime system in the server should allow concurrent dispatching of incoming requests to those servants. Conversely, if some other servants are implemented in a way that is not thread-safe then the CORBA runtime system should ensure that the dispatching of incoming requests to those servants is serialized. The way that CORBA allows one QoS to be associated with some servants and a different QoS to be associated with other servants is by letting the server create several POAs. POAs are discussed in details in Chapter 5, but, in essence, a POA is a collection of servants. The QoS of a POA is specified when the POA is created. When a servant is activated (inserted) into a POA then the servant takes on the same QoS as its containing POA.

After calling ORB_init(), the server typically creates one or more POAs to contain servants. Then servants can be created and activated (inserted) into these POAs. It is common for a server to initially create just one or two factories (Section 1.4.2.1) that can then be used to create more servants later.

Section 3.2.2 briefly mentioned that a servant represents a CORBA object. The _this() operation (which is defined in the generated skeleton class from which servants inherit) can be invoked on a servant to obtain an object reference for its corresponding CORBA object. A server program typically advertises one or more of its objects by exporting their object references to, say, a file (Section 3.4.2), the Naming Service (Chapter 4) or the Trading Service (Chapter 20). The exportObjRef() function in Figure 3.5 is not a CORBA API; rather it is just a pseudocode placeholder to denote the exporting of an object reference by some means.

When the server’s initialization is complete, it activates its POA managers (a discussion of which is deferred until Section 5.7) and then calls orb.run() to enter an event loop. In the event loop, the CORBA runtime system accepts connections from clients, reads incoming requests and dispatches them to the skeletons associated with servants. The orb.run() API does not return until orb.shutdown() is called. The orb.shutdown() operation is typically called from a signal handler or from the body of an IDL operation with a name like shutdown() or kill_server().

3.2.4  Client Mainline

Pseudocode for a client mainline is shown in Figure 3.6. The most important pieces of code are indicated in bold.


// File: client_main.cpp int main(int argc, char* argv[]) { exit_status = 0; orb = null; try { orb = CORBA::ORB_init(argc, argv); obj = importObjRef(...); obj.deposit(); } catch(CORBA::Exception & ex) { cout << "Something went wrong: " << ex << endl; exit_status = 1; } // Terminate gracefully try { if (orb != null) { orb.destroy(); } } catch(CORBA::Exception & ex) { cout << "Something went wrong: " << ex << endl; exit_status = 1; } return exit_status; };
Figure 3.6: Pseudocode of client mainline

Just as in a server, a client calls ORB_init() to initialize the CORBA runtime system, and later calls orb.destroy() to ensure that the CORBA runtime system is gracefully destructed before the program terminates.

The importObjRef() function in Figure 3.6 is not a CORBA API; rather it is just a pseudocode placeholder to denote the importing of an object reference by some means, such as from a file (Section 3.4.2), the Naming Service (Chapter 4) or the Trading Service (Chapter 20).

Once the client has a reference to an object in the server, the client can invoke operations upon it.

3.3  Critique of CORBA Application Development

The pseudocode of a traditional application, shown in Figure 3.1 is much more concise than the pseudocode of a corresponding CORBA application, shown in Figures 3.2, 3.4, 3.5 and 3.6. In fact, there are just 9 lines of pseudocode for the traditional application, compared to 52 lines of pseudocode for the corresponding CORBA application, which is almost a 5-fold increase in the number of lines of pseudocode. From such a comparison, it is easy to conclude that “development of CORBA is 5 times more complex than development of traditional applications”. However, such a conclusion is incorrect. This is because the additional steps required in a CORBA application—such as calling ORB_init() and orb.destroy(), creating POAs, importing/exporting object references and so on—are the same regardless of whether you write a small CORBA application or a much larger one. When you write a real CORBA application (as opposed to just pseudocode), the amount of CORBA infrastructure code required is usually dwarfed by the amount of “business logic” code.

3.4  Miscellaneous Notes

3.4.1  resolve_initial_references()

As discussed in Section 3.2.3, the ORB_init() function creates a new ORB object, which represents the CORBA runtime system. One operation defined on the ORB type is resolve_initial_references(). Some people find the name of this operation to be unintuitive, but the name makes a lot of sense if you know the following two pieces of information:

  1. In CORBA terminology, resolve means lookup or find, so this operation is used to “lookup” or “find” something.
  2. This operation is slightly misnamed. It should actually have been called resolve_initial_reference() (that is, “reference” instead of “references”) because it is used to lookup one item each time it is called.

The resolve_initial_references() operation is a bootstrapping API that is used to find other parts of the CORBA infrastructure. It takes a string parameter that indicates which piece of CORBA it should find. Many of the CORBA Services (Section 1.6) are found by calling this operation. For example, you can obtain a reference to the Naming Service by calling this operation with the parameter "NameService". Likewise, you can obtain a reference to the Notification Service by passing "NotificationService" as a parameter to this operation.4 In each case, the value returned by this operation is an object reference of the base type Object, and the programmer must narrow (CORBA equivalent of a typecast) this to the appropriate type before using it.

The resolve_initial_references() operation is used not only to find CORBA Services; it is also used to obtain references to pieces of infrastructure that are part of the same process. For example, it is used to find Current objects (Chapter 13), a factory to create DynAny objects (Section 15.3), the root POA (Section 5.5), and so on.

The resolve_initial_references() operation is not shown in the pseudocode of Figures 3.5 or 3.6. However, real application code would use resolve_initial_references() to access the root POA (which is required for creating other POAs in which to store servants) and to access, say, the Naming Service or Trading Service for importing/exporting object references.

3.4.2  Stringified Object References

The ORB provides an operation called object_to_string() that converts an object reference (proxy) into a stringified object reference, and another operation called string_to_object() that converts a stringified object reference back into a proxy. The object_to_string() operation works by marshaling (Section 11.2) the IOR into a binary buffer, converting the contents of this buffer into hexadecimal and prefixing the result with "IOR:".

These object_to_string() and string_to_object() operations provide one way for a server to advertise an object to client applications. For example, a server could stringify an object reference and write this string to a file. A client application could read this stringified IOR from the file and call string_to_object() to convert it back into a proxy. Other ways for a server to advertise objects are discussed in Chapters 4 and 20.

Many CORBA products provide a utility that can parse a stringified object reference and print out the details embedded within it. Although such utilities are common, they are not standardized by the OMG so the names and “look and feel” of these utilities tends to vary between different CORBA products. Here are the names of some of these utilities:

3.4.3  The Tie Approach to Implementing Servants

Section 3.2.2 mentioned that a servant class inherits from a generated skeleton class. Actually, CORBA supports two approaches for associating a servant class with the skeleton class. One way is for the servant class to inherit from the skeleton class, as discussed in Section 3.2.2. The other way is for the IDL compiler to generate a class—usually called a tie class—that inherits from the skeleton class and this tie class then delegates to a servant class (which, in this case, does not inherit from the skeleton class).

In languages (such as C++) that support multiple inheritance of classes, the inheritance approach is usually preferred. In languages (such as Java) that support only single inheritance of classes, the tie (delegation) approach is often preferred since this allows the servant class to use its single inheritance for a non-CORBA purpose. The tie approach is also useful if a servant’s state is to be persisted in an object database. This is because the object database should be used to persist the instance variables of just the servant class and not persist any instance variables inherited from CORBA infrastructure, such as a skeleton class.


1
The name of the skeleton-code class varies from one language mapping to another. For example, for an interface called Account, the C++ class is POA_Account while the corresponding class in Java is called AccountPOA.
2
Although this inheritance-based approach to implementing servants is common, it is not the only mechanism available. CORBA also supports a delegation-based approach to implementing servant classes, but a discussion of that is deferred until Section 3.4.3.
3
This function is called ORB_init() in most language mappings, but has the slightly different name of ORB.init() in Java. The ORB_init() name is used in the pseudocode of this book.
4
The CORBA specification defines the parameter names that are used to find different CORBA services.

Previous Up Next