Chapter 15 Meta-information Programming
- What is Meta-information Programming?
- TypeCodes and the Interface Repository
- The any and DynAny Types
- Dynamic Invocation Interface (DII)
- Dynamic Server Interface (DSI)
15.1 What is Meta-information Programming?
In many computer languages, when you write a program, you know at compilation time what data-types you will manipulate and the compiler performs strong type checking to ensure that you access those data-types correctly. Some programming languages defer type checking until runtime. In such languages, there is some meta information (also known as runtime type information) associated with objects; the runtime system of the language uses this information to check that a program accesses objects in a legal way. Some languages that use runtime type checking even allow programmers to inspect an object’s meta information in order to find out what operations the object has and the names and types of its instance variables. This capability is often called introspection or reflection.
The Smalltalk language is famous for its runtime type checking. Java provides compile-time type checking but also provides APIs for the java.lang.Class type that allow runtime type checking (and introspection) to be performed. In contrast, the C programming language does not provide meta information and C++ provides only very limited support for it with its dynamic cast capability.
15.1.1 Uses of Meta-information Programming
Most CORBA programs are written with compile-time knowledge of the IDL data-types that they will manipulate and the IDL interfaces that they will implement and/or invoke upon. However, CORBA also maintains meta information about objects and data-types, and makes it possible to write programs that make use of such meta information. This capability of CORBA is rarely used, but it does make it possible for some very important types of application to be written. For example:
It is common for an organization to have some existing
(“legacy”) applications that were built with one
middleware technology, want to build new applications
with a different middleware technology, and to somehow connect
the old and new applications to each other. The software that
connects between different middleware technologies (or connects
between different on-the-wire protocols) is usually called a
bridge or a gateway.
The basic principle of a gateway is to accept an incoming request using the on-the-wire protocol of one technology, perform data-type translation on parameters and then send a similar request using the on-the-wire protocol of the other technology. Writing a gateway for a specific set of APIs is usually tedious, highly repetitive work and if the APIs change then you have to rewrite the gateway for the new APIs. However, if the middleware technologies that are being bridged provide meta information then it usually possible to implement a generic gateway that can accept any incoming request using the on-the-wire protocol of one technology, use meta information to determine the quantity and types of parameters in the request, convert these parameters into the format of the other technology and then send a similar request using the on-the-wire protocol of this second technology.
A generic gateway is usually a bit slower than a type-specific gateway, but it has the big advantage that you have to implement just one generic gateway and it can be used to connect systems for any set of APIs. Some companies developed gateways from CORBA to DCE (an older middleware technology) and several CORBA vendors sell COM-to-CORBA or .NET-to-CORBA gateways. These gateways are usually built as generic gateways that utilize meta information.
- A debugger for a traditional language uses symbol table information (which is a form of meta information) embedded in executables to display variables in a human-readable format rather than just as blobs of binary data. If a middleware technology provides meta information then this makes it possible for companies to develop debugging tools that are better tailored to the needs of that middleware technology. It also makes it possible to develop some other useful tools that support the software development process. For example, when writing a CORBA server, it is useful to have a simulation client application that can be used to perform ad-hoc testing of the server, and vice versa. Some companies sell tools that make use of meta information to produce simulation clients/servers with little, if any, coding effort.
CORBA’s support for meta-information programming is spread over several distinct, but complementary, APIs. These APIs are discussed in the following sections.
15.2 TypeCodes and the Interface Repository
CORBA uses the CORBA::TypeCode type to represent meta information. The kind() operation provided by CORBA::TypeCode returns an enum value that specifies if the TypeCode represents one of the built-in types (long, boolean and so on) or a user-defined type (struct, union, interface and so on). If the TypeCode represents a user-defined type then more operations can be invoked upon it to determine the names and TypeCodes of the members of a struct or union and so on.
A TypeCode provides the meta information required by the ORB runtime system to determine how the bytes within a raw chunk of memory are laid out as the fields/components of a compound data-structure. However, the information provided by TypeCodes is insufficient, by itself, for general-purpose, meta-information programming. For example, a TypeCode does not specify the signatures of operations that are provided by an interface. Neither does a TypeCode provide a list of all the types (and nested modules) that are declared within a module. CORBA complements TypeCodes with the Interface Repository (IFR), which stores this more general meta information.
You can think of the IFR as being a database of meta information about IDL types. This database has a CORBA server “wrapper” around it; it is this wrapper that is called the IFR. The IDL interfaces of the IFR provide operations for storing meta information in the IFR and also operations for querying the contents of the IFR. The IFR organizes the meta information in a manner similar to how the internals of a compiler organize information in a parse tree. Because of this, querying meta information in the IFR is similar to traversing a parse tree in a compiler.
Although CORBA specifies the IDL interface of the IFR, CORBA does not specify how administration of the IFR is performed. Typically, a CORBA product provides a proprietary command-line tool that parses an IDL file and invokes operations on the IFR to store the parsed information in the IFR. The name of this command-line tool varies from one CORBA product to another. With some CORBA products, this tool is a stand-alone utility; with other CORBA products this functionality is provided as a command-line option on the IDL compiler.
The information provided by the IFR subsumes the information provided by TypeCodes. However, each call to the IFR is a remote call so it incurs the performance overhead of network latency. In contrast, querying the information in a TypeCode involves just a local operation call, so this is much faster. A common way to structure a meta information program is to use the APIs of the the IFR to navigate down to the parse-tree node of a particular type and then obtain a TypeCode that allows the details of that type to be examined much more efficiently with just local operation calls.
15.3 The any and DynAny Types
One of the IDL built-in types is called any. This serves a similar purpose to the void* type in C/C++ or the java.lang.Object type in Java: it is a way to pass around data when you do not have any compile-time knowledge of the type of the data. Of course, when doing this you need to have some way of finding out at runtime what is the type of the data. Internally, an any contains the raw data plus a CORBA::TypeCode (Section 15.2) that specifies the data’s type.
The any type is used about as infrequently as the void* type is used in C/C++ or java.lang.Object is used in Java. In other words, it is used extensively for some specialized programming tasks, but is irrelevant for many other, more general-purpose tasks.
When any is used in IDL, it is often used to define a type that holds a name and arbitrary value, as shown in Figure 15.1.
The IDL compiler generates operations that can be used to insert a user-defined type into an any, to query the type of data inside an any, and a type-safe way to extract data of a specified type from an any. These operations can be used by applications that have been compiled with the stub code (Section 1.4.5) of an IDL file. However, these APIs for inserting values into, and extracting values from, an any can be used only by applications that have been compiled with the stub code of the type that is embedded inside the any.
If an application wants to manipulate data embedded inside an any without being compiled with the relevant stub code then the application must convert the any into a DynAny, which is the base type of a hierarchy of local interfaces (Section 9.1). There are sub-types of DynAny for each IDL construct. For example, there are types called DynStruct, DynUnion, DynSequence and so on.
The operations on the DynAny interfaces allow a programmer to recursively drill down into a compound data-structure that is contained within the DynAny and, in so doing, decompose the compound type into its individual components that are built-in types. Operations on the DynAny interface can also be used to recursively build up a compound data-structure from built-in types.
15.4 Dynamic Invocation Interface (DII)
The dynamic invocation interface (DII) is a set of APIs that allows a client application to make invocations on an object reference (Chapter 10) without the client application being compiled with the stub code for either the relevant IDL interface or the types passed as parameters. A DII-based client typically does the following:
- The client application obtains an object reference from somewhere.
- The client can invoke the get_interface() operation on the object reference to access meta information in the IFR (Section 15.2), and so find out the signatures of operations defined for the object.
- The client uses the DynAny APIs to build up parameter values and then converts these DynAny objects to any objects.
- The target object reference, the name of the operation being invoked, and a sequence of parameter values/directions are added to a CORBA::Request pseudo-object. The specified operation is invoked by calling invoke() on this CORBA::Request object.
- The client examines inout/out parameters and the return value by using the APIs of DynAny.
The DII APIs are used when a client is compiled without knowledge of the IDL interfaces upon which it will make calls. This generally means that the client application does not have much hard-coded “business logic” to dictate what parameter values it should use when making remote calls, or even which operations it should invoke. Instead, the remote invocations made by a DII-based client are typically driven by some external meta-data. Two example uses of DII-based applications—gateways and test clients—were briefly discussed in Section 15.1.1. I now discuss the architecture of a DII-based test client in slightly more detail.
When writing a CORBA server, it is useful to have a simulation client application that can be used to perform ad-hoc testing of the server. A GUI-based “generic” test client could be built using the DII, and it might work as follows.
- The user specifies an object reference that the test client should use. This object reference might be obtained from the Naming Service (Chapter 4) or as a stringified IOR (Section 3.4.2) from a file.
- The GUI client retrieves the repository id from the object reference. It then uses this type information to obtain the signatures of its operations from the IFR. The GUI displays a menu of the operation names.
- The user selects an operation from the menu and the GUI then displays dialog boxes to prompt the user to provide values for all in and inout parameters. If a parameter is of a compound type then the GUI client will use the TypeCode to recursively drill down into the type and display dialog boxes for each component of the compound type. The compound value is built up with the aid of the DynAny APIs.
- When the user has provided all the parameter values, the GUI client creates a CORBA::Request object and calls invoke() upon it. When the operation returns, the GUI displays all the out and inout parameters, and the return value.
It is possible to imagine such a GUI that would record the parameter values inputted by the user and then generate a regression testing program that could be rerun independently of the interactive GUI.
15.5 Dynamic Server Interface (DSI)
The dynamic server interface (DSI) is often described as being the server-side equivalent of the DII. It is a set of APIs that allows a server application to process incoming requests on IDL interfaces for which it does not have the relevant stub code or skeleton code. Like the DII, the DSI can be used to build gateways or testing applications. For example, a DSI-based testing server might accept incoming requests and assign values to inout and out parameters based on values it obtains from a random number generator or a configuration file/database.