In this topic
Motivation
ArcObjects, the backbone of ArcGIS Desktop, ArcGIS Engine, and ArcGIS Server applications, is a library of geographic information system (GIS) components written in Microsoft's COM. For more information, see the Microsoft Web site topic, COM: Component Object Model Technologies.
COM is a specification for writing reusable software components and an infrastructure for running those components. So far, it was only possible to consume ArcObjects from client programs written in a COM compliant language (for example, VC++, Visual Basic, Visual Basic for Applications [VBA], and so on) because they have access to the COM API and its runtime facilities. The lack of COM's runtime services for Java poses a challenge for Java programs wanting to use ArcObjects. Luckily, the Java COM interop (also referred to as Java interop) addresses these problems and provides a bridging solution for Java and COM centric ArcObjects to interoperate with each other. It basically provides a Java API for ArcObjects and the runtime libraries for running ArcObjects components within a Java application. See the following illustration:
Java interop hides a lot of COM details and presents a simplified workflow for the Java programmer. It provides Java APIs for ArcObjects by scouring through the information present in ArcObjects COM components, generates Java classes and interfaces corresponding to each type in ArcObjects, and uses standard Java Native Interface (JNI) C++ to communicate with the COM ArcObjects.
At the outset, this topic introduces a few necessary concepts of COM used within ArcObjects. It then develops the motivations for a Java COM interop in the context of ArcObjects and explains some of its internal working with the help of examples. It concludes with a description of the architectural differences between a Visual Beans application and a console application as brought about by the interop.
Understanding COM
It is essential to understand the principal elements of COM technology to appreciate the dynamics of Java interop. This section serves as an initial exposition of the necessary background. Microsoft COM technology, is a binary standard to specify a language's neutral way of creating components that can interact across processes and machine boundaries. The two most relevant aspects of COM technology to understand Java interop are interface based programming and its threading model.
Interface based programming
The COM specification strongly advocates interface based programming, wherein client programs always work with interfaces and never directly with concrete objects as they can do in Java. At runtime, depending upon the functionality needed from the components, client programs explicitly request an interface by invoking the method, QueryInterface, that every COM component implements.
Since COM components could potentially run in a distributed environment, the specification requires every component to manage its life span by maintaining reference counts. Client programs explicitly manipulate these reference counts on COM objects via two methods, AddRef and Release, the former to increment a reference count and the latter to decrement it. Given the diverse environments that COM components can run in, clients need a way to uniquely identify these components regardless of the host they are running on. This is achieved by the usage of a 128-bit globally unique identifier (GUID), which is used as an identifier for creating instances of COM components. These aspects of the specifications and more (not relevant to this topic) apply to ArcObjects.
Threading model
The runtime infrastructure for using COM libraries contains the plumbing code to discover and instantiate COM components and to facilitate method invocations between the components and their clients. Occasionally, the runtime is required to serialize and de-serialize the arguments of methods, depending on the threading models of the client and server. In COM, threading models of components and their runtime execution environments are described by the concept of an apartment.
Objects in COM can declaratively express whether or not they want to be thread affine. At runtime, depending on their declared threading attribute, such objects are running in a single threaded apartment (STA, strong thread affinity) or multithreaded apartment (MTA, no thread affinity). For a more exhaustive treatment on the supported threading models in COM, see the Microsoft Web site topic, INFO: Descriptions and Workings of OLE Threading Models. For the purposes of this topic, you will be concerned only with STA and MTA. The COM runtime provides the level of thread affinity requested by objects, by placing them in appropriate runtime environments.
In the following illustration, the first example shows two objects living inside an STA. Since these objects have a strong thread affinity, the COM runtime intercepts method calls on these objects originating from external threads and places them in a request queue to be serviced one at a time. This automatically achieves serialization and thread safety.
On the other hand, the second example shows objects having no thread affinity living in an MTA. An MTA could potentially house many threads, all of which have unrestricted access to objects living in that apartment. This distinction between STA and MTA is very important with regards to ArcObjects because all of its components have a strong thread affinity and expect to be hosted in an STA.
Consuming ArcObjects in a COM compliant language
Clients of ArcObjects are required to follow a skeleton workflow for using components from that library. This can be best described with the help of an example task of creating a Point ArcObjects and calling the setX method on it. Ordinarily, clients perform the following steps:
- Initialize the COM runtime and "enter" the STA. This ensures compatibility with the threading model of ArcObjects.
- Instantiate the Point object using its GUID to uniquely identify it first.
- Explicitly acquire the IPoint interface that defines the setX method, and if necessary, incrementing a reference count on the object.
- Call the method and upon finishing working with that interface, decrement the reference count.
- Uninitialize the COM runtime and "leave" the apartment before exiting.
Dynamics of Java interop
The Java interop provides a Java API for ArcObjects by generating Java classes for every ArcObjects class (Point), Java interfaces for every ArcObjects interface (IPoint), and Java proxy classes (IPointProxy) for every interface (generally, the name of an ArcObjects interface appended with the word Proxy). In addition, it also obtains and stores the metadata (for example, the GUID of an ArcObjects) inside the Java classes to be used at runtime. This allows Java applications to create ArcObjects and invoke methods on them.
The runtime aspects of the Java interop can be explained by showing how it allows creating an ArcObjects component and calling a method on that component from Java. You will use the same running example of creating a Point object and calling the setX method on it. Through the examples, discerning readers will appreciate how the interop emulates the workflow required of COM compliant clients consuming ArcObjects. The following illustration shows the dynamics of a method invocation on the Point object:
The following sequence of actions takes place when the Java code executes:
- The console application initializes the interop by making a call to EngineInitializer.initializeEngine(). Initializing the interop has the effect of the Java thread "entering" an STA as required by every client consuming ArcObjects.
- The interop intercepts the Point creation call, uses the GUID for the Point ArcObjects to call corresponding COM API function, which creates an instance of Point in the ArcObjects library. Finally, the client gets back an instance of a Java Point, which is a proxy to the underlying Point object.
- A method call on the Point object is intercepted by the interop. Using the metadata present in the Point object, it explicitly asks for the interface (IPoint) that declares the method (setX). It then converts any Java input arguments into C++ COM arguments and dispatches the method to the underlying ArcObjects. If the method call succeeds, the interop, in consultation with the prototype of the Java method, un-marshals any return values (none in this example) from C++ COM to Java and relays them back to the client.
The InterfaceProxy classes are primarily used to perform explicit casts between ArcObjects interfaces. Beginning at ArcGIS 9.2, Java developers can leverage Java style casting and runtime type checking idioms in their programs to cast between ArcObjects interfaces obtained as the result of calling a method. However, there are about 10 percent of ArcObjects that do not lend themselves to Java style casting for which developers have to resort to explicit interface casting using the InterfaceProxy classes. For more information on the instanceof and casting support in the ArcObjects Java API, and for examples on the usage of InterfaceProxy classes, see Casting and run time type checking (using instanceof).
Java interop and Visual Beans application
ArcGIS Engine applications written in Java are distinguished based on whether they are console applications or applications that present user interface (UI) elements for purposes, such as displaying maps. The latter, typically composed of out-of-the-box visual Java bean components that ship with the Java software development kit (SDK), are called Visual Beans applications. The internal architecture of a Visual Beans application is markedly different from that of a console application. Because a Visual Beans application is in principle a Java graphical user interface (GUI) application, its architecture is heavily influenced by the latter's threading model.
A Java GUI application typically consists of at least two threads (the main thread and the Abstract Window Toolkit [AWT] Event Dispatch thread). However, in a well designed moderately complex GUI application, there are almost always one or more additional worker threads to perform time consuming background tasks and help keep the UI responsive. Such Java programs obviously expect to use ArcObjects from any of the threads in their visual application. For example, creating a number of ArcObjects in the main thread before the UI is realized; performing GIS logic within event listeners; or having long running GIS tasks performed on worker threads, all of which can involve passing ArcObjects references across threads. The only way the interop can allow such an unrestrictive programming model without breaching the threading rules of ArcObjects, is by creating two separate runtime environments; one for Java and one for ArcObjects. The architecture of Visual Beans applications resulting from these constraints is shown in the following illustration:
The ArcObjects Thread shown in the preceding illustration as living in an STA is created by the interop and is meant purely for hosting ArcObjects, servicing method calls, and executing callbacks (from ArcObjects into Java). The interop ensures that instances of all ArcObjects components in the application are created on this thread. Since Java threads need to be logically separated from the ArcObjects thread, the interop houses all the Java threads in an MTA. By doing so, it also leverages the services of the COM runtime that intercepts ArcObjects method calls made from the Java threads in the MTA and places them in the ArcObjects thread's request queue to be picked and serviced one at a time.
This design ensures thread affinity for ArcObjects, at the same time enabling unrestrained access to the ArcObjects library from multiple threads in a visual Java application.
See Also:
COM: Component Object Model TechnologiesINFO: Descriptions and Workings of OLE Threading Models
Casting and run time type checking (using instanceof)