In earlier chapters, we explored how SWT is built in layers: a small library of C code built with Java Native Interface (JNI) interacts with the underlying operating system to provide the necessary building blocks for more elaborate capabilities. One of the design goals of the native layer was for it to be very small, often providing simple wrappers around native APIs. Using this novel approach, the OTI/IBM team has been able to give programmers unprecedented access to the native capabilities of all supported platforms. In so doing, the team chose not limit itself to the features common to all platforms. Among these platforms, Microsoft Windows offers a unique capability that has appealed to Visual Basic programmers for many years: reusable binary objects, otherwise known as COM objects.
SWT/JFace programmers haven’t been left out; this appendix covers the nature and depth of COM support in SWT. Specifically, you’ll see how you can include ActiveX controls and OLE documents inside your applications in just a few SWT method calls. So that you can fully take advantage of this feature, we’ll first review some basic COM features and general principles.
Microsoft designed the Component Object Model (COM) to try to solve a simple problem: how to reuse binary objects. Previous solutions based solely on shared libraries (Dynamic Link Libraries [DLLs]) showed that they weren’t practical for C++ programming and that managing their proliferation on a given system was in itself a major cause of application problems. In the process of designing a replacement solution, Microsoft felt it should also address a new class of problems for the time: location transparency.
In the end, the new technology was built to provide a unique solution for situations using three distinct types of objects:
These are important concepts for anyone interested in doing COM with SWT/JFace.
COM is based on the notion of interfaces. Interfaces allow the logical grouping of functionalities as well as their discovery at runtime by querying the objects themselves. Each interface has an identifier (IID) that uniquely defines both the methods available and their physical placement relative to one another in memory. Physically, interfaces are organized into vtables (arrays of function pointers). The notion of physical ordering of these functions is crucial, as you’ll see when we investigate the details of SWT programming. COM makes widespread usage of Globally Unique Identifiers (GUIDs). Specific GUID uses include object identifiers, type library identifiers, and interface identifiers. The algorithm for generating these IDs is beyond the scope of this book, but it’s described on the Microsoft Developers Library web site (msdn.microsoft.com).
The way COM interfaces are versioned may surprise Java programmers: An interface that has been published can’t be modified. Instead, it must be extended via an entirely new interface. According to Microsoft’s best practices, the new interface should have the same base name followed by a version number that increases for each new version. For example, when Microsoft needed to give users more control over the web browser ActiveX control, it extended the original IWebBrowser interface with a richer IWebBrowser2.
Unlike the Java model, where class files contain enough metadata to allow the reflection API to return a complete description of objects and methods, the COM runtime discovery model is based on the existence of IUnknown, a core interface that all others extend. Given a specific GUID, QueryInterface returns a pointer to the interface implementation. The two other IUnknown functions, AddRef and Release, are responsible for tracking the number of references to the interface and returning all allocated resources to the operating system. Reference counting is an important aspect of COM programming and is the cause of many bugs that are difficult to identify.
SWT fully adheres to Microsoft’s guidelines for reference counting, but sometimes you’ll need to remember these simple rules: Clients are responsible for calling AddRef and Release on every interface they query; and both calls must be made on the same interface reference, to allow an object to track the references on a per-interface basis rather than for the whole object. These important functions are listed in table B.1.
IUnknown function |
Description |
---|---|
AddRef | Increases the reference count for this interface |
QueryInterface | Returns pointers to supported interfaces |
Release | Decreases the reference count for this interface |
Using this simple design, and with the aid of a small runtime library that provides support for registering, discovering, and instantiating objects, it’s possible to start creating powerful reusable binary entities using a language like C or C++. Objects can then be segregated into families based on their implementing predefined sets of interfaces, all deriving from the core IUnknown. Over the years, Microsoft has defined several such families: scriptable objects, ActiveX controls, active documents, and so on. Some of these definitions have gradually evolved toward fewer mandatory interfaces and more optional behaviors.
Although a powerful concept, this interface proved too complicated for high-level languages like VBScript, JScript, and the first versions of Visual Basic. To allow these interpreter-based languages (and other scripting languages) to access COM objects, Microsoft defined another key COM interface. IDispatch allows object capabilities to be queried by name rather than by interface identifier. Like all the other COM interfaces, IDispatch extends IUnknown. Each method that needs to be publicized is given a unique dispatch identifier that can be used to invoke it. COM automation (or just automation) is the process of letting client code interface with a COM object using the IDispatch-based discovery and invocation mechanism. The flexibility it provides comes at a price: the automation querying and invocation process is significantly slower that the default binary binding used for non-automation calls. The functions provided by this interface are shown in table B.2.
IDispatch function |
Description |
---|---|
AddRef | Increases the reference count for this interface |
GetIDsOfNames | Maps a single member and an optional set of argument names to a corresponding set of integer dispatch IDs (DISPIDs) |
GetTypeInfo | Gets the type information for an object |
GetTypeInfoCount | Retrieves the number of type information interfaces that an object provides (either 0 or 1) |
Invoke | Provides access to properties and methods exposed by an object |
QueryInterface | Returns pointers to supported interfaces |
Release | Decreases the reference count for this interface |
SWT provides methods for two of these functions. Provided with an optional list of method names, GetIDsOfNames() returns a list of matching dispatch IDs (DISPIDs). These can be used in subsequent calls to the Invoke() method to trigger their execution. Using this simple mechanism, objects can expose both methods and properties. COM recognizes four reasons to call Invoke():
You may wonder why there’s no way to get the value of a property that is a reference to an object. This situation is covered by DISPATCH_PROPERTYGET via the fact that all automation methods manipulate a universal data type called Variant. A variant is a unique way to represent all the possible data types supported by automation-capable languages. You can find the nature of the content of a given variant by ORing predefined constants. Possible contents include a simple string (BSTR in COM parlance), a primitive type, or a reference to an object. Object references come in two flavors: a reference to an IUnknown and a reference to an IDispatch instance. Variants are a rich data type, and at this point SWT supports only a portion of the complete specification. Notably absent is support for Safe-Arrays (the Visual Basic way of dealing with arrays) and user-defined types (the Visual Basic types).
The ability to reuse the capabilities of an external object by embedding the object directly inside your application lies at the heart of client-side COM programming. COM provides two types of user experiences for interacting with embedded objects. In the first scenario, the embedded object retains its own interface that users see in a window that’s separate from the main application window. In the second scenario, known as in-place activation, the user can interact with the embedded object without leaving the container document or application. In this scenario, the container and the embedded object collaborate to provide a composite menu bar where commands and features from both applications are available at the same time. The richer of the two scenarios, in-process activation is also more complicated to program, because it requires (among other things) that mouse and keyboard events be routed properly. COM allows both local servers (standalone EXEs like Microsoft Word) and in-process servers (DLLs) to be in-place-activated.
In both scenarios, the container must implement a number of predefined COM interfaces in order for the embedded object to communicate with it. Microsoft refers to these mandatory interfaces as sites. The complete description of all the site interfaces and the features they provide is beyond the scope of this book. In most cases, the interfaces implemented by the SWT programmers are enough; however, in some situations you’ll need to extend one of the default site classes and implement additional site interfaces. Hosting an ActiveX control versus an OLE document requires the implementation of two sets of COM interfaces, described in tables B.3 and B.4. One of the examples later in this appendix shows how to create a custom container by extending an existing one.
Interface |
Description |
---|---|
IAdviseSink | Receives general notifications from the embedded object |
IOleClientSite | Manages general communication with the container |
IOleInPlaceSite | Manages in-place activation of the hosted control |
IOleDocumentSite | Provides a means to activate a hosted document |
IUnknown | The fundamental COM interface |
Interface |
Description |
---|---|
IAdviseSink | Receives general notifications from the embedded object |
IOleControlSite | Manages general communication with the container |
IOleInPlaceSite | Manages in-place activation of the hosted control |
IUnknown | The fundamental COM interface |
For the most part, instantiating a COM object is a straightforward task. The simplest case involves a single call to CoCreateInstance(). However, more complex object-instantiation strategies involve calling OleCreate() and passing it an instance of IStorage that represents either a new document or one that you intend to edit; or calling CoGetClassObject() to obtain an instance of an ActiveX IObjectFactory or IObjectFactory2, and then calling their respective CreateInstance()or CreateInstanceLic() method to obtain the new object’s IUnknown instance.
You use the latter approach to instantiate ActiveX controls that need a license key to operate. Microsoft added licensing to ActiveX controls in order to prevent programmers from creating their own applications by reusing the controls redistributed with other applications; you had to purchase the development version of the controls to use them. SWT contains a default implementation of these instantiation strategies, but you may need to replicate them inside your code to address current limitations or bugs in SWT. The beauty of the SWT COM library layering is that you’ll be able to do so easily.
The COM object embedding model includes a rich mechanism for dispatching and handling events. It’s based on several simple notions. A sink interface is an interface exposed by a COM client; it’s called by a COM server as a callback for the purpose of sinking (handling) event notifications. In order for a server to dispatch events to a client’s sink interface, the client code needs a process to pass a sink reference to the server. This is done via server connection points. A connection point is the server-based mechanism that handles notification to the client’s sink interface. A server exposes all of its connection points by implementing the IConnectionPointContainer interface. Clients call FindConnectionPoint(), passing it the GUID of an interface to get a reference to the IConnectionPoint exposed by the server for this sink interface. IConnectionPoint contains the Advise() and Unadvise() methods to start and stop the flow of incoming events on the sink interface, respectively.
The COM threading model is based on the notion of apartments. An apartment is an arbitrary construct meant to help the COM runtime library make the right decisions about how to route method calls to a COM object. The simplest of all scenarios is the single-threaded apartment (STA) model where the runtime takes care of all concurrency problems (which happens when multiple threads in the client code call the same method on the same object at the same time) transparently. This is done by creating a hidden window and using Windows’ default message-passing mechanism to ensure that all the method calls are serialized.
In the multithreaded apartment (MTA) model, each COM object must be multi-thread-aware to ensure that no data corruption can arise as the result of two simultaneous calls from two distinct client threads. In recent versions of Windows, Microsoft has added more threading models that are beyond the scope of this book.
Like Swing, SWT doesn’t contain any synchronization code to guard against resource corruption due to concurrency. The rationale behind this decision is that in most cases, the performance trade-off is too great to be justifiable. Consequently, synchronization is entirely your responsibility. To avoid introducing synchronization code, the SWT team chose to support only the STA threading model where no special code is required; all synchronization issues are handled by the COM runtime.
True to the general philosophy adopted for SWT, support for COM comes in the form of a minimal amount of C code coupled with a series of Java classes building on these foundations. The interesting side effect is that SWT COM programming is very similar to C++ COM programming. This can be a double-edged sword. The bonus is that whenever you’re in doubt about how to use a specific SWT feature, you can look for help in the Microsoft documentation. The trade-off is that you’ll sometimes be looking at unusual and nonintuitive Java code. As you get more familiar with code from both languages, you’ll come to enjoy the outcome and forget about the means.
In the following sections, we’ll cover the parts of the SWT COM library shown in figure B.1. We’ll begin with a tour of the native language part of the library and then look at the user-oriented Java classes that use it. The OleEventTable, OleEventSink, and OlePropertyChangeSink classes are only visible inside the org.eclipse.swt.ole.win32 package.
When you look at figure B.1, don’t be fooled by the names of the packages. The code inside org.eclipse.swt.internal.ole.win32 isn’t just for SWT developers, and you’ll often find yourself referring to it for advanced SWT COM applications.
As we discussed, the COM threading model supports several options. The COM specification mandates that every thread that wants to use COM must first call the runtime to specify a threading model. SWT performs this mandatory initialization for you inside a static block located in the COM class. The default threading model is apartment-threaded; therefore you don’t have to do anything special to ensure that all calls to embedded COM objects are serialized.
The class has no constructor or instance members, but it contains a long series of constants and static methods. The static methods are mostly native methods named after their C equivalents. Their role is to expose the COM runtime to Java. The method signature is often identical to the original Windows API. So, when in doubt, look at the original Microsoft documentation for explanations and examples.
Half the constants are instances of another class from the same package. SWT stores objects and interfaces unique identifiers using the GUID class For the most part you need not concern yourself with GUID because it contains no methods. However, it has an interesting public static final field named sizeof that contains the size of the structure in bytes. The COM SWT code uses this pattern in all classes that map to a native structure (more on this in section B.2.2).
Chances are, all the interfaces you’ll ever need to access are defined in COM as public constants. The following examples were taken from the source code and show how you should define any interface not already included:
public static final GUID IIDIUnknown = IIDFromString("{00000000-0000-0000-C000-000000000046}"); public static final GUID IIDIDispatch = IIDFromString("{00020400-0000-0000-C000-000000000046}");
IIDFromString() returns an instance of GUID based on the string representation of an interface identifier. Similarly, CLSIDFromProgID() and CLSIDFromString() let you find an object’s GUID from the same string representation or from the object’s program identifier. To instantiate a COM object, you need to find either of these two values. The IsEqualGUID() method lets you compare two GUIDs for identity, which you need to do to implement the QueryInterface() when creating custom COM interfaces.
The COM class also contains the low-level SWT code to access native drag and drop: the RegisterDragDrop(), RevokeDragDrop(), and DoDragDrop() methods. The latest version of SWT introduced a new cross-platform level of abstraction that uses this code. More interestingly, the class lets you manipulate OLE storage files. StgCreateDocfile() allows you to create a new file; StgIsStorageFile() lets you test if a file is an OLE storage file; a call to ReleaseStgMediums() is necessary to release the memory allocated by COM to an open storage file; and StgOpenStorage() is used to open a storage file.
If you’re familiar with Visual Basic, you know how simple it is to deal with strings. This simplicity comes at a price for people programming with lower-level languages. OLE strings, otherwise known as BSTR, can be manipulated using the SysAllocString(), SysFreeString(), and SysStringByteLen() methods. Sometimes COM requires that newly allocated memory be placed under the control of its runtime (often the case when dealing with automation) to allow sharing of that memory between different process address spaces. You can use CoTaskMemAlloc() and CoTaskMemFree(), respectively, to allocate or free blocks of memory compatible with COM.
Several of the methods from the COM class come with multiple prototypes, due to the strongly typed nature of Java. For example, the MoveMemory() method comes in 16 different flavors, one for each of the main types of COM structures you may have to manipulate. Keep in mind that using them takes you one step closer to function pointers (and therefore dangerously closer to memory leaks) than the makers of Java intended.
The most unconventional part of the COM class is a series of VtblCall() native static methods with different parameter combinations. These cover the method signatures for all the COM interfaces supported by SWT. The first two parameters are the index of the method that needs to be called in the vtable followed by the address of the table. The native code uses the index to find the address of the method to call inside the vtable and calls it with the remaining parameters. In the following example, int COM.VtblCall(int fnNumber, int ppVtbl, GUID arg0, int[] arg1) is called to implement the QueryInterface() method from IUnknown:
public int QueryInterface(GUID riid, int ppvObject[]) { return COM.VtblCall(0, address, riid, ppvObject); }
Although it isn’t composed of native methods, the COMObject class belongs to the lower level of the COM library. Its purpose is to provide a Java representation of the vtable at the heart of every COM object. The class contains 80 public methods with the same signature—public int methodXX(int[] args)—and an equal number of matching callbacks prototyped static int callbackXX(int[] callbackArgs). Each method is a placeholder for the matching method inside the vtable of the COM interface.
By default, the 80 methodXX() methods return a constant called COM.E_NOTIMPL that tells the caller that the method isn’t implemented. This avoids COM errors and gives you room to implement complex COM interfaces. All COM interfaces extend one another to form a hierarchy, and each level of inheritance translates into an extension of the methods in the vtable. Provided that some of the standard COM interfaces are two or three levels down from IUnknown, the creators of SWT have tried to anticipate future growth.
The class constructor is an array of int. Its size is the number of methods in the vtable, and its content is the number of parameters each vtable method takes. Be sure you don’t make any mistakes when you create this array, or you’ll be in for difficult debugging and crashes. Internally, the constructor uses this information to create an array of callbacks, one for each of the methods in the vtable. The native layer uses these callbacks to invoke Java code when COM needs to invoke a method from your interface.
Table B.5 contains the program identifiers for several common applications you may encounter in your exploration of the SWT COM library.
Program identifier |
Description |
---|---|
Shell.Explorer | Internet Explorer |
Word.Application | Microsoft Office Word application (as an out-of-process server) |
Word.Document | Microsoft Office Word document |
Excel.Application | Microsoft Office Excel application (as an out-of-process server) |
Excel.Sheet | Microsoft Office Excel document |
Excel.Chart | Microsoft Office Excel chart |
PowerPoint.Show | Microsoft Office PowerPoint presentation |
Visio.Drawing | Visio document |
PDF.PdfCtrl.5 | Adobe Acrobat PDF Reader 5.0 |
MediaPlayer.MediaPlayer | Windows Media Player |
Agent.Control | Microsoft Agent control |
DHTMLEdit.DHTMLEdit | DHTML Edit control for IE5 |
InternetShortcut | Internet shortcut |
If you don’t find the application you’re looking for, open the standard Microsoft Registry Editor that comes with Windows and look under the key My ComputerHKEY_CLASSES_ROOT. It contains a list of IDs for all the applications installed on your machine. Figure B.2 shows the program ID for the Web Browser control (the reusable part of Internet Explorer). Unless you have a specific reason not to do so, it’s good practice to leave the terminating version number out of the name. COM uses the CurVer key to find out which version is current and uses it automatically.
Microsoft created this mechanism to allow the transparent migration of applications. However, some vendors don’t follow this guideline; in this case you’ll have to keep the version number as an integral part of the name (see Adobe Acrobat in the table B.5).
The org.eclipse.swt.internal.ole.win32 and org.eclipse.swt.ole.win32 packages sit directly above the native library. The second package contains all the high-level code necessary to write COM client code with SWT. As shown in figure B.1, three classes consist of implementation details that aren’t exposed outside of the package boundaries. OleEventTable() is a lookup mechanism that maps an event type to a specific listener, and OleEventSink() is the heart of SWT’s ability to receive and dispatch COM events to your code; it contains a partial IDispatch implementation that can be a source of ideas for how to implement one yourself. Note that the OLE class contains mostly constants and a utility method to convert COM errors into SWT exceptions.
By now you’re familiar with the role played by the IUnknown COM interface in the discovery process of COM features. Even though it’s described as a COM interface, the SWT team chose to implement it as a Java class. Its constructor takes one parameter: int address. Its value is the address of the vtable containing the implementation of the interface. All the methods of the COM IUnknown interface are implemented as normal Java methods with parameters similar to those of the native COM counterpart. These methods are implemented by calling COM.Vtbl-Call() with all the parameters and the index of the native method in the vtable. The following snippets show the implementation of QueryInterface() and AddRef(), which are the first two physical methods in the IUnknown COM interface:
public int QueryInterface(GUID riid, int ppvObject[]) { return COM.VtblCall(0, address, riid, ppvObject); } public int AddRef() { return COM.VtblCall(1, address); }
Figure B.3 is a class diagram showing all the COM interfaces declared in the SWT COM library. In section B.3.3, we explain how to add new interface declarations when these aren’t enough.
To embed a COM object (ActiveX control or OLE document) in your SWT application, you need to create a container window. As you’ve seen, COM mandates that this container implement certain interfaces in order for the embedded object to dialog with it. SWT provides the OleFrame class for that purpose. Although the class is derived from Composite, it makes no sense to assign it a layout or try to add Control children. The only reason for inheriting from Composite is to access the window-management capabilities offered by Composite, in order to implement the IOleInPlaceFrame COM interfaces.
Internally, OleFrame handles sizing, menu management, and window placement. Looking at figure B.3, you’ll notice that IOleInPlaceFrame isn’t on the diagram. SWT doesn’t require that you declare a COM interface (create a class derived from IUnknown that lists all its methods) in order to implement it. All that is required is that you create an instance of COMObject with public methods mapping the methods exposed by the COM interface.
When activated, OLE documents negotiate with the container to display their menu bar. Using public methods from OleFrame, your application can contribute menu items to the final menu bar. When adding menu items, you can choose between three locations on the menu bar—File (on the far left), Container (in the middle), or Window (far right, before Help)—by calling SetFileMenus(), SetContainerMenus(), and SetWindowMenus(), respectively. All three methods take an array of SWT MenuItem. Just before displaying the menu bar, the embedded object calls IOleInPlaceFrame.InsertMenus(); at that point OleFrame merges your items with those from the embedded object.
The OleClientSite class implements a complete COM site. Aside from implementing the mandatory IUnknown, IOleClientSite, and IAdviseSink, the class also implements in-place activation via the optional IOleInPlaceSite and IOleDocumentSite.
This class contains several useful public methods. doVerb() plays an important role in the activation process, as you’ll see later. SaveFile() can be used to save the current OLE document to a normal file (includeOleInfo = false) or to a storage file (includeOleInfo = true). showProperties() lets you display all the properties of the embedded COM object in a separate window, provided the object implements the ISpecifyPropertyPages interface. The only control you have over the window is its title, because it’s created by the COM runtime via a call to the standard COM.OleCreatePropertyFrame().
queryStatus() is a helper method that accepts the ID of a command and returns a bitwise OR’d combination of OLECMDF_SUPPORTED, OLECMDF_ENABLED, and OLECMDF_LATCHED indicating the status of the specified command. This method is usually called before a call to exec() to verify that the command is available before executing it. Both functionalities are based on querying the embedded object for its IOleCommandTarget interface and subsequently calling the QueryStatus() or Exec() method on this interface.
You may be faced with situations where the site doesn’t implement some interfaces that are necessary to provide more control over the embedded COM object. Fortunately, the SWT team structured the code in a way that makes it easy to extend. You need to follow three rules:
1. The constructor of your derived class must call the parent constructor before anything else.
2. You need to override the protected createCOMInterfaces (where you create one COMObject instance for each COM interface you want your site to support) and disposeCOMInterfaces (where you call the dispose() method for each of the COMObject instances previously created).
3. You must ensure that your new QueryInterface() method properly delegates to its parent (we present a complete example in section B.3.3).
The OleControlSite class inherits from OleClientSite and provides the extended capabilities necessary to host an ActiveX control. The visible differences from the parent class are full support for events and property change notifications from the ActiveX control, simplified access to well-known control properties (straight method calls instead of COM automation calls), and direct access to ambient properties.
You can access the container’s ambient properties via calls to the setSiteProperty() and getSiteProperty() methods. For example, the Web Browser control has properties that determine whether the browser should support embedded ActiveX controls or JavaScript scripts. The font as well as color of the ActiveX control are accessible via setFont(), getFont(), setBackground(), getBackground(), setForeground(), and getForeground().
addEventListener() allows you to register instances of OleListener in order to receive specific events. You can register a single listener instance for several event types, in which case your handler will probably contain an if statement based on the value of OleEvent.type. Alternatively, you can register different listeners for the various events your application needs to handle. When you’re no longer interested in receiving an event, don’t forget to call the removeEventListener() method. Similar methods exist for receiving notifications about changes in the value of a property.
Dealing with dispatch interfaces (COM interfaces that extend IDispatch) can lead to code that’s hard to read, involving many Variant instances copied to and from arrays. OleAutomation provides a wrapper to manipulate the functionality delivered by the IDispatch COM interface. Two constructors are available. The first takes an instance of OleClientSite (remember that OleControlSite extends OleClientSite, which guarantees that the class works for both ActiveX controls and OLE documents) and uses package-level methods to obtain the client’s private instance of the org.eclipse.swt.internal.ole.win32.IDispatch class. The second constructor directly takes an instance of IDispatch as its unique parameter. Both constructors acquire an ITypeInfo reference via a call to QueryInterface() on the object.
Internally, this reference is used in the implementation of all the public methods involved in describing the dispatch interface. This gives you access to the complete type library for the interface. The first method, getTypeInfoAttributes(), returns a structure of type TYPEATTR that contains a high-level description of the interface. The description includes such information as the interface GUID, the number of variables it exposes, and its total number of methods. The following code snippet shows how to access all the variables of an automation interface:
Besides providing a description of the interface, OleAutomation also contains methods to simplify accessing the properties of the COM object (setProperty() and getProperty()). As you may recall from the general discussion on COM and automation, properties are exposed via public getters and setters rather than data. Consequently, setProperty() and getProperty() are convenience wrappers around the more general invoke() method. All automation calls into the object ultimately translate into a call to IDispatch.Invoke() using one of the four standard dispatch codes (DISPATCH_XXXX constants from COM) as the fourth parameter. Finally, getIDsOfNames() is a thin wrapper that simplifies calling GetIDsOfNames() from the underlying IDispatch interface.
You should now have an idea about how to embed a COM object in an SWT-based application. In this section, we’ll put this understanding to the test and write some examples, as well as explore patterns that will help you write more complex code.
Before we dive further into the full life cycle of a COM object embedded inside an SWT container, we’ll write the simplest possible example. Despite its simplicity, listing B.1 shows several important aspects of SWT COM programming. Its purpose is to embed an instance of Internet Explorer inside a SWT frame and display the home page of the Manning Publications web site.
package com.swtjface.AppB; import org.eclipse.swt.widgets.*; import org.eclipse.swt.*; import org.eclipse.swt.events.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.ole.win32.*; public class SimpleBrowser { private Shell shell; private OleAutomation automation; public Shell open(Display display) { this.shell = new Shell(display); shell.setLayout(new FillLayout()); OleFrame frame = new OleFrame(shell, SWT.NONE); OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer"); automation = new OleAutomation(controlSite); boolean activated = (controlSite.doVerb(OLE.OLEIVERB_INPLACEACTIVATE) == OLE.S_OK); this.openURL("html://www.manning.com/"); shell.open(); return shell; } public void openURL(String url) { int[] rgdispid = automation.getIDsOfNames(new String[]{"Navigate", "URL"}); int dispIdMember = rgdispid[0]; Variant[] rgvarg = new Variant[1]; rgvarg[0] = new Variant(url); int[] rgdispidNamedArgs = new int[1]; rgdispidNamedArgs[0] = rgdispid[1]; Variant pVarResult = automation.invoke(dispIdMember, rgvarg, rgdispidNamedArgs); } public static void main(String[] args) { Display display = new Display(); Shell shell = (new SimpleBrowser()).open(display); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }
The first step creates the container where the COM object (ActiveX control or OLE document) will be embedded. For that we instantiate an OleFrame, as follows:
Display display = new Display(); Shell shell = new Shell(display); OleFrame frame = new OleFrame(shell, SWT.NONE);
SWT provides two site implementations, one for OLE documents (OleClientSite) and one for ActiveX controls (OleControlSite). When in doubt about which to use for a COM object you want to embed, look at its type library to see if it implements either the IOleDocument or IOleControl interface. To do so, you can use Microsoft’s OLE View, which is distributed with all major development tools and presented by msdn.microsoft.com (see figure B.4).
The constructor for OleClientSite lets you create a site based on a program ID (see table B.5 for a list of program IDs for known applications) or a filename. In the latter case, the shell does all the work of matching it to the corresponding program ID based on the file extension. Behind the scenes, OleClientSite transparently creates an instance of IStorage that it then passes to the object:
OleClientSite clientSite = new OleClientSite(frame, SWT.NONE, "Word.Document"); File file = new File ("C:\file1.doc"); OleClientSite clientSite = new OleClientSite(frame, SWT.NONE, file);
Embedding an ActiveX control is much simpler and only involves creating an instance of OleControlSite by passing its constructor the control’s program ID:
OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");
Creating an object isn’t enough to make it become visible inside its container. It needs to be activated by the container. Activation is controlled by the doVerb() method. Table B.6 lists all the possible values for the method’s unique parameter. In most cases, you’ll us OLE.OLEIVERB_INPLACEACTIVATE to activate an ActiveX control in place. OLE documents work differently; the COM runtime must first start the associated application and ask it on behalf of your container to open the document in the site you supply.
ProgID |
Description |
---|---|
OLE.OLEIVERB_DISCARDUNDOSTATE | Closes the OLE object and discards the undo state |
OLE.OLEIVERB_HIDE | Hides the OLE object |
OLE.OLEIVERB_INPLACEACTIVATE | Opens the OLE object for editing in place |
OLE.OLEIVERB_OPEN | Opens the OLE object for editing in a separate window |
OLE.OLEIVERB_PRIMARY | Opens the OLE object for editing (the action that occurs when a user double-clicks on the object inside the container) |
OLE.OLEIVERB_PROPERTIES | Requests the OLE object properties dialog |
OLE.OLEIVERB_SHOW | Shows the OLE object |
OLE.OLEIVERB_UIACTIVATE | Activates the UI for the OLE object |
People have reported trouble activating certain documents, particularly Microsoft Office documents. In these cases, better results have been reported using OLE.OLEIVERB_SHOW as the activation verb.
Also remember that OLE documents are complete applications hosted inside your container. It’s a common mistake to activate an OLE document in a container that doesn’t have a menu bar—some applications will forgive you, others won’t. Remember: always create your application’s menu bar before activating an OLE document, as in the following snippet.
At this point, we’ve successfully activated an OLE document inside an SWT container, and the user would like to save her changes. The first step is to call isDirty() from the OleClientSite to test that the file needs saving. When it is the case, we can proceed to call the save() method, passing it a standard java.io.File instance and a boolean indicating whether to save the file as a standard file (includeOleInfo = false) or a storage file (includeOleInfo = true). It’s a good practice to save the document to a temporary file and replace the original file only if the operation is successful. The following snippet illustrates the process:
if (clientSite.isDirty()) { File tempFile = new File(file.getAbsolutePath() + ".tmp"); file.renameTo(tempFile); if (clientSite.save(file, true)){ // save was successful so discard the backup tempFile.delete(); } else { // save failed so restore the backup tempFile.renameTo(file); } }
The examples we’ve looked at so far have been simple in the sense that the container was only dealing with a single embedded object. However, nothing prevents your container from embedding several objects. OLE documents negotiate with their container about the content of the main menu toolbar. Consequently, your container must implement a strict control over the activation process. You should activate each COM object individually when doing so makes the most sense—for example, upon receiving a mouse double-click event—and deactivate them before activating the next one. To deactivate an object, call the deactivateInPlaceClient() method from its OleClientSite.
When you’re done working with an embedded COM object, you must remember to perform one last task. The class diagram in figure B.1 showed that OleFrame, OleClientSite, and OleControlSite are all subclasses of org.eclipse.swt.widgets.Composite. As such, it’s imperative that you dispose of them by calling dispose(). Unless you’ve saved all changes made to an OLE document prior to calling dispose(), they will be lost.
To access automation properties, you must first get their dispatch identifier (dispid) by calling getIDsOfNames() on the OleAutomation wrapper. You can use the resulting int in subsequent calls to getProperty() or setProperty(). Also keep in mind that all property values have the Variant type, which is the standard automation data type. A common source of mistakes is improperly initializing a variant by selecting the wrong type attributes, especially when dealing with variants that are references rather than contain data. The following snippet reads the Document property from an automation server:
OleAutomation server = new OleAutomation(...); int[]documentID = server.getIDsOfNames(new String[]{"Document"}); if (documentID == null) return null; Variant pVarResult = server.getProperty(documentID[0]);
One of the compelling reasons for embedding COM objects (ActiveX controls or OLE documents) in your application is the ability to inherit portions of the object’s native user interface inside your own. However, this is only a small part of the possible interactions.
Using the invoke() method from OleAutomation gives you access to the entire object model of a COM component. Using it is a simple extension of what we did to access a property. A call to getIDsOfNames() with the name of a command returns its dispid, and once again all values have the Variant type. (Note that dealing with Microsoft Word presents some unique problems: When you’re invoking a command that doesn’t return a value, it’s preferable to use the invokeNoReply() method.)
SWT also gives you access to the event model of an embedded COM object by registering instances of OleListener with an OleControlSite. You’ll have to dig inside each type library to find the specific int value for the event you wish to subscribe to. As we’ve discussed, you can either register the same listener for multiple events or use distinct listeners for each event.
All handlers implement one unique method: void handleEvent(OleEvent event). By reading the type library, you’ll find the number and type of parameters associated with the event. SWT copies them into an array that’s publicly accessible from the event as public Variant[] arguments. The parameters are ordered from left to right in the prototype from the type library. For example, the following information is from the Excel 10.0 type library. In this scenario, SeriesIndex would be arguments[0] and PointIndex would be stored in arguments[1]:
dispinterface ChartEvents { ... [id(0x00000602), helpcontext(0x00010602)] void SeriesChange( [in] long SeriesIndex, [in] long PointIndex); ... }
What seems at first like a disconcerting way of writing Java code has identifiable patterns that are repeated throughout. Understanding them is the key to writing more serious COM-related code with SWT. Let’s review some of these patterns.
In some instances, you’ll be forced to deal with the fact that C/C++ strings are terminated by the null-end-of-string character (