Mixing ActiveX with Java

Al uses JACOB, which is a library for running Java code under Windows to connect with ActiveX objects-for robotic control.


July 01, 2004
URL:http://www.drdobbs.com/jvm/mixing-activex-with-java/184405738

July, 2004: Mixing ActiveX With Java

Mimicking Microsoft's JVM interface

Al is DDJ's Java newsletter editor. He can be contacted at alwal-williams.com.


Robots and Servos


With the demise of Microsoft's JVM, Java programmers can't easily access ActiveX objects. Or can they? Although the Microsoft JVM is no more, the JACOB ("Java COm Bridge") open-source library essentially duplicates its ability to allow Java code running under Windows to connect with ActiveX objects.

Many specialized libraries are only available as ActiveX (or COM) objects, so using them from Java is an important trick to have in your toolkit. I recently encountered this problem when trying to integrate a custom hardware board with a Java program. The custom board's interface expected to talk to an ActiveX DLL. Rather than rewrite the library in Java, I decided to interface the existing library to my Java code.

There are several commercial solutions available, but I selected JACOB (written by Dan Adler and available at http://danadler.com/jacob/) because it closely mimics the Microsoft JVM's interface. This is both a strength and a weakness. The Microsoft JVM has plenty of documentation, so there are lots of examples and resources. The weakness is that the Microsoft JVM is disappearing, and JACOB depends on Microsoft documents for its primary documentation. However, there is an active user's group for people using JACOB, and the necessary files are available from the group.

If you are a Java purist, you've probably already stopped reading. After all, mixing anything with Java—and Windows-specific code, in particular—is sure to flare tempers. However, politics aside, it is often necessary to interoperate with other software. Like it or not, there is a large body of ActiveX code out there and being able to call it from Java simply expands the number of projects you can do in Java instead of using some other language.

Using JACOB

To show how JACOB works, I wanted to control the robot in Figure 1 from a PC running a Java program. Admittedly, this robot isn't a commercial project, but I use it to demonstrate a board that controls servo motors from a PC. Like a lot of commercial devices, the robot's wheels are servo motors, and the board provides an ActiveX DLL that lets you control up to eight motors. (For more information, see the accompanying text box entitled "Robots and Servos.")

Of course, to be practical, the robot would need to carry a Java-enabled PC (maybe a PC/104 board) or have a wireless serial connection. However, for my purposes here, I simply kept the robot tethered to a long RS-232 cable. My goal was to build a command-line program that could issue instructions to the robot's drive motors. You could then use the program in a batch file or even add the class to a JavaScript Interpreter (see my article about hardware control with JavaScript in "Low-Level I/O Control with Java" in Dr. Dobb's Java Programming E-Zine, http://www.ddj.com/downloads/).

Understanding ActiveX

If you've worked at all with JavaBeans, you won't find ActiveX mysterious. At the lowest level, an ActiveX object is simply any piece of code that exposes one or more interfaces. These interfaces are tables of function pointers. So an object might publish an interface where the fifth function in the table causes the object to, say, generate a report.

By itself, this provides encapsulation, but little else. The secret to ActiveX is that each interface contains at least three pointers (the first three, obviously) that perform the same function. In particular, these three functions make up the IUnknown interface.

This shows one form of ActiveX polymorphism. By ignoring all but the first three functions of an interface, you can treat any interface as an IUnknown interface (just as you can treat any Java object as type Object).

However, there is another, more common form of polymorphism used by ActiveX. One of the functions defined by IUnknown is QueryInterface. So suppose you are building a database for your local brick and mortar library. You have an object that exposes an imaginary IMedia interface. Because IMedia is a superset of IUnknown, you can call QueryInterface to discover if the object also has an IBook interface. If it does, then it must be a type of book. If it doesn't, then it isn't a book (perhaps it is a CD with an ICDRom interface). So by exploiting QueryInterface, you can treat a CD or a book as a type of media, which is, of course, polymorphism.

This is ActiveX at the core level. If you are a C programmer, there isn't much more to it than this. There are many predefined interfaces you can provide (or use), but they all assume that you know which slot in the interface table has the function you want to use.

For programming languages such as Visual Basic (a major user of ActiveX technology), this is too great of a restriction. Foreknowledge of the interface table amounts to early binding—the language tool has to know about the object you wish to access. So how can ActiveX perform late binding (where the runtime system resolves the function reference)?

IDispatch

The answer is through a specialized interface, IDispatch. This is a special interface that lets you refer to functions in an object via name or number. Because early binding is more efficient, some objects provide custom interfaces in addition to IDispatch. In fact, some objects provide dual interfaces that are IDispatch interface tables followed by custom functions. After all, programs expecting IDispatch will just ignore the extra functions.

IDispatch provides for three main items: properties, methods, and events. As you might expect, properties are quasi-variables, while methods are simply function calls. An event is a way to register a function with an object. The object can then use the function to communicate with the original caller. For example, a button object might call a programmer-defined function when users press a button.

ActiveX also supports many common data types. However, most variables used by IDispatch-implementing objects will be variants. This is very similar to an untyped Visual Basic variable. It can contain nearly anything (numbers, strings, dates, currency, object references, and even arrays).

Most ActiveX objects of interest will provide IDispatch and work through various properties, methods, and events. JACOB allows you to very easily access these parts of an ActiveX object. If the object doesn't support IDispatch, you won't be able to use JACOB to access it.

Using JACOB

JACOB uses a special class to represent an IDispatch interface. The constructor for this class takes a string that is usually the ActiveX server's progid. This is simply a short name that identifies the server (for example, Microsoft Excel's progid is Excel.Application). If you want to use the classid (a 128-bit number that is unique for each server), you can provide it using an alternate syntax.

There are several ways you can handle properties, methods, and events in JACOB (although events are handled a bit differently than in the original Microsoft JVM). In addition, JACOB provides classes to represent variants and other specialized ActiveX types.

For example, consider accessing data from Excel, as in Example 1. You can also call static members of Dispatch to achieve the same effect. When you call a method, you must provide an array of Variant objects that represent the arguments. Example 2, for instance, is a call to the robot's servo controller board.

There are also helper methods of the Dispatch class that take a varying number of arguments, if you prefer to use them. Example 3(a), for instance, is essentially the same as writing Example 3(b). You can find other syntax examples by reading the JACOB source or referring to the Microsoft documentation.

A Java Wrapper

The robot's servo controller board (a GP4; http://www.awce.com/gp4.htm) has an ActiveX object that provides several methods and a property to set the active COM port. Obviously, it would be possible to just write the robot controller program to use the ActiveX object directly via JACOB.

However, I will eventually write an actual Java object that interfaces with the board. With that in mind, I wrote a wrapper around the ActiveX object that simply provides the same methods and properties. Eventually, I'll replace this class with one that is pure Java and the remaining code will not require any changes.

Listing One is the result. The constructor creates the ActiveX object (the progid is AWCGP4DLL.GP4DLL). Each method corresponds to a method or property in the original object. For variety, I commented out the invoke call in the setMask method and replaced it with a Dispatch object method that does the same function.

Armed with this wrapper class, it is easy to use it to control the motors. Listing Two is a command-line program that does the job. The program accepts one or two speed arguments (which can range from -50 to 50). If you provide one argument, the motors move the robot forward (or backward if you use a negative number) with a speed proportional to the magnitude of the argument. With two arguments, you can set the speed and direction of each wheel independently. In either case, the program runs the motors for about three seconds and then stops them.

If you read the command-line program's source code, you'll notice that the code has no idea that the servo manipulation is being handled by ActiveX. All the JACOB code is restricted to the GP4 class in Listing One.

Pros and Cons

I could have rewritten the servo controller's library using javax.comm and handled it in native Java. I have no doubt that would be a better solution. After all, with the ActiveX component, the program only runs under Windows. With javax.comm, the program would operate with Linux, Macintosh, or Windows.

However, rewriting the module would take time—time to rewrite it and also time to test it. In this case, I had the luxury of having the source code to the ActiveX components, but if it were from a third party, it might be even more difficult to reproduce. What's more is that changes from that third party could be difficult to incorporate.

With a JACOB wrapper, it is fast and easy to incorporate the ActiveX code into a Windows-only Java program. Even if the ActiveX object added features later, it would be simple to either ignore them or add them to the wrapper with a minimum of effort.

Politics aside, there are times when you need to absorb some ActiveX code. The unfortunate legal wrangling has made that more difficult than it used to be. But thanks to JACOB, it's only a little more difficult.

DDJ



Listing One

/* Java wrapper for GP-4 ActiveX DLL. Requires:
   JACOB -- http://danadler.com/jacob/
   GP4 -- http://www.awce.com/gp4.htm
*/
import com.jacob.com.*;
import com.jacob.activeX.*;
public class GP4
{
    private ActiveXComponent servo;  // The COM object
    // Create ActiveX object
    public GP4() 
    { 
        servo=new ActiveXComponent("AWCGP4DLL.GP4DLL");
    }
    // Reset servo controller
    public void reset() 
    { 
        servo.invoke("Reset",new Variant[] {} );
    }
    // Set servo position
    public void setPosition(int chan, int pos) 
    {
        servo.invoke("SetPosition", 
              new Variant [] { new Variant(chan), new Variant(pos) });
    }
    // Enable channel
    public void enableChannel(int chan, boolean enable) 
    {
        servo.invoke("EnableChannel", 
            new Variant[] { new Variant(chan), new Variant(enable) });
    }
    // Turn groups of servos on/off
    public void setMask(int mask)
    {
        // Another way to do this
        // servo.invoke("SetMask", new Variant [] { new Variant(mask) });
        Dispatch.call(servo,"SetMask",new Variant(mask));
    }
    // Enable/disable all servos
    public void enable(boolean enflag)
    {
        servo.invoke("Enable", new Variant [] { new Variant(enflag) });
    }
    // Set the COM port
    public void setComPort(int port)
    {
        servo.setProperty("Comport",new Variant(port));
    }
}
Back to article


Listing Two
/* The robot driver */
public class ServoDrive
{
    // General-purpose pause
    {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {}
    }
    // Convert a string into a speed number
    private static int setSpeed(String v,int defspeed)
    {
      int speed;
      try {
          speed=Integer.parseInt(v);  // read integer
      }
      catch (Exception e) { speed=0; }
      if (speed==0) speed=defspeed;   // or use default
    return speed;
    }
    // The main code
  public static void main(String[] args)
  {
      GP4 servos=new GP4(); // create a servo controller
      int servoA=7;         // the wheels are on servo #7 and #6
      int servoB=6;
      // read speeds
      int speedA=setSpeed(args.length>0?args[0]:"",25);
      int speedB=setSpeed(args.length>1?args[1]:"",-speedA);
      // set the COM port
      servos.setComPort(1);
      // Turn servos on
      servos.enable(true);
      // Set them to run
      servos.setPosition(servoA,speedA);
      servos.setPosition(servoB,speedB);
      pause(3000);  // 3 seconds
     // disable all servos so they stop at once
      servos.setMask(0);
      // Reset both servos
      servos.setPosition(servoA,0);
      servos.setPosition(servoB,0);
      // And reenable them (although they are stopped now
      servos.setMask(0xFF);
 }
}
Back to article

July, 2004: Mixing ActiveX With Java

ActiveXComponent xl = 
  new ActiveXComponent("Excel.Application");
System.out.println("version="+xl.getProperty("Version"));
xl.setProperty("Visible", new Variant(true));

Example 1: Accessing data from Excel.

July, 2004: Mixing ActiveX With Java

servo.invoke("SetPosition", 
  new Variant [] { new Variant(chan), 
                   new Variant(pos) });

Example 2: A call to the servo controller board.

July, 2004: Mixing ActiveX With Java

(a)

Variant f = new Variant(false);
Dispatch.call(workbook, "Close", f);


(b)
workbook.invoke("Close",
   new Variant [] { new Variant(false) });

Example 3: Using Dispatch class helper methods.

July, 2004: Mixing ActiveX With Java

Figure 1: A JACOB-controlled robot. Photo courtesy of Patrick Williams.

July, 2004: Mixing ActiveX With Java

Robots and Servos

The robot used in this article is a model of simplicity. The frame is made from Radio Shack perf boards and a small piece of bass wood (available from almost any hobby shop). The large wheels are also from a hobby shop and are made for model airplanes. A few angle braces and a small caster from Home Depot complete the mechanical construction.

The two drive wheels are inexpensive servo motors made for radio-control vehicles. These make motor drive very simple. Each motor shaft connects internally to a potentiometer that controls a pulse-generating circuit. To move the motor, you send a pulse to it approximately every 20 ms. The motor generates its own pulse and compares the two pulses. Suppose the potentiometer in the motor (which is connected to the shaft) is set so that the internal circuit generates a 1-ms pulse. If you send a 1-ms pulse, the motor will not move. However, if you send, for example, a 1.2-ms pulse, the motor will move until the internal circuit is also generating a 1.2-ms pulse. Or, if you sent a 0.8-ms pulse, the motor would move in the opposite direction to make the internal pulse match the pulse you supply.

Normally, these servos don't rotate. They simply move in an arc (useful for an airplane's control surface or a car's steering wheel, for example). However, it is possible to modify the servo so that its potentiometer is not connected to the shaft (you also have to remove the stops that prevent it from rotating through 360 degrees). If you adjust the potentiometer so that the servo generates a 1.5-ms pulse, you can easily control the motor. A 1.5-ms pulse makes the servo hold position. A shorter pulse makes the motor rotate in one direction and a longer pulse makes the motor rotate in the opposite direction.

You can even control the speed of the motor by controlling the length of the input pulse. A 1.6-ms pulse makes the motor move more slowly than a 2-ms pulse because the motor perceives more error with a 2-ms pulse. Of course, with the potentiometer disconnected from the shaft, the motor can never correct the perceived error, so the motor just continues to turn as long as you keep supplying pulses.

This is perfect for controlling a small robot. Since the two servos are mounted back to back, if you feed the same pulse to both motors, the robot will spin in place. That's because if you rotate both motors clockwise (for example), they will spin in opposite directions since they are back to back.

The solution is to turn one wheel clockwise and the other one counterclockwise. This requires two separate signals, one to drive each wheel. Of course, if you want the robot to turn, you can send identical pulses to each wheel.

—A.W.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.