Interix and COM

Operating System

Services for UNIX 3.0 Technical Note

Abstract

This paper describes an approach for including UNIX applications under Interix as components in the Microsoft® Component Object Model (COM). It also describes a simple DLL that encapsulates a UNIX application and a Microsoft Visual Basic® client that uses that component. Also covered is how the interface is extended to allow an Excel spreadsheet to use a UNIX component.

On This Page

Encapsulating UNIX Applications as COM Components Encapsulating UNIX Applications as COM Components
Requirements Requirements
The Basics The Basics
Building the TIDEWRAP.DLL Building the TIDEWRAP.DLL
Building the Visual Basic Client Building the Visual Basic Client
Running the Excel Spreadsheet Running the Excel Spreadsheet
Future Directions Future Directions

Encapsulating UNIX Applications as COM Components

The Component Object Model is a binary-level specification that allows applications to be built using interchangeable components. Since the specification is binary, it's language-independent components can be written in C++, Java, C, or the KornShell. Client applications that make use of COM are largely Win32-based (although there's nothing in COM itself that restricts it to a Windows environment). Applications that use COM to bind components together can upgrade or customize those components dynamically. For more information about COM, see the books listed at the end of this paper.

The UNIX application used as an example is tide v1.6.2, an open source program that calculates tide tables based on a given longitude and latitude. It can write these tables to standard output or display them in an X window. This paper describes a method for encapsulating tide in a Win32 DLL (that is, as a COM object), demonstrates its inclusion in a Visual Basic client, and then extends the interface to allow tide to be installed as a component for an Excel spreadsheet.

The principles here can be used for any well-behaved UNIX system application.

The tide application consists of 13 source files and over 7,000 lines of code. (Version 2 is considerably larger.) It was ported to Interix by changing some of the paths in the files tide.c and xtide.c.

Requirements

To build and run the example software, you will need:

  • A Microsoft® Windows NT®– or Windows® 2000–based or Windows XP system that has Services for UNIX 3.0 installed.

  • An Interix Software Development Kit to build the tide application.

  • Microsoft(r Visual C/C++ to build the DLL. The example code here was built with version 5.0 with the latest Microsoft service pack for MSVC 5.0; the ATL libraries are those of version 5.0 and 6.0.

  • Visual Basic® 5.0 or 6.0 to build the Visual Basic client.

  • Excel 97 to demonstrate the inclusion of the data in an Excel spreadsheet (Office 97 or later).

Note: To follow the examples, you should already know something about creating DLLs with the Active Template Library (ATL) and about packaging COM objects. The example client and the DLL are not tutorial programs. This is a demonstration of the COM principle.

The Basics

The tide application is the command-line version of xtide; tide writes to standard output rather than opening an X window. We define a COM interface to pass this information. The encapsulating DLL is written to invoke the Interix tide application with the correct command-line options; the routine interprets the output of the application and passes it to the client application.

Defining the interface and writing the DLL are not trivial, but the basic concept is simple. On an Interix system, the Win32 world can invoke an Interix application as arguments to the POSIX.EXE program. POSIX.EXE serves a variety of purposes in the Interix environment, but its primary role is to serve as the access mechanism for the Win32 subsystem of Windows NT to start an Interix process.

Architecture

The architecture of the completed system is shown below.

intrx01

The client makes a call to tidewrap.dll. The DLL invokes POSIX.EXE (and thereby the Interix subsystem) to run tide; the DLL captures the output and passes it back to VBTIDE.EXE. The DLL does not have exclusive use of the application; it could be invoked by another Interix user (logged in over telnet, perhaps) while it's being called by VBTIDE.EXE.

To build a DLL that wraps a UNIX application, you need to think like a UNIX programmer and like a COM programmer. The UNIX programmer has to think about getting the command to produce the correct output; the COM programmer has to think about capturing that output. The difficult part is to get the POSIX.EXE command line correct.

The Sample Code

When you unpack the tar file containing the COM example, it un-packs into two directories:

tidewrap

The code that defines the actual COM object, the tidewrap.dll dynamic link library. This directory also contains tides.xls, the Excel spreadsheet that uses the component, and the Visual Basic files for the VB client, VBTIDE.EXE.

tide

The code for the tide application, version 1.6.2.

Building the TIDEWRAP.DLL

Building the tidewrap.dll library is the process of encapsulating the application. In this example, we've followed these steps:

  1. Build the Interix application, tide. In this case, tide is a well-behaved UNIX system application that runs from the command line and writes its output to standard out.

  2. Define the COM interface (ITideTable in this case).

  3. Implement the interface in the DLL. The basic idea is to invoke POSIX.EXE to run the Interix application, capture the standard output, and pass that data.

  4. To do this, you need to get the application command line correct for POSIX.EXE. The developer needs to be careful if there is command-line quoting involved.

  5. Finally, you build the DLL.

This DLL depends on the ATL library. If you move the DLL to a system that does not have ATL installed, you'll need to copy the ATL run-time library to that system.

Building tide

The version of tide used for this project was 1.6.2. Alternate harmonics files are available from https://bel-marduk.unh.edu/xtide/files.html.

To build tide, follow these steps:

  1. Run the xmkmf command.

  2. Enter the command make depend. (If the Makefile is not correctly processed by the makedepend utility, check to make sure you are working on a local NTFS drive.)

  3. Type the command make

Only a few small modifications were made to the tide source to compile on Interix: the macro _ALL_SOURCE was defined in the Imakefile to provide access to additional APIs and macros in <math.h>, and a minor change was made to everythi.h and tide.c to support the multiple-rooted file system on Interix

At this point, you may want to experiment with the tide command. It supports a large number of options, but the only ones used here are

–nowarn

Disable the warning text that both tide and xtide print.

-list

Display a list of locations known to the program (and found in the harmonics file).

-location location

Display tides for the specified location. The location must be in the harmonics file.

To test that the tide program is working correctly, you can give the command:

$ tide –nowarn -list

You should see 1,700 or so lines of output like this:

Morro Bay, California
Baltimore (Chesapeake Bay), Maryland
Seattle, Washington

The xtide/tide programs can be built to look for the harmonics file in a specific location, but this requires source code changes. The easier solution is to use .tiderc and .xtiderc files in your home directory to specify the default location and the –nowarn option. A sample ~/.tiderc file might be:

-nowarn
-hfile /C/users/bob/src/InterixCOM/xtide-1.6.2/harmonics

If you have an X server, you may want to run the graphical version, even though it is not used in the DLL:

$ xtide –graph –location "San Francisco, Pier 22 , California

Defining the Interface

The file tidewrap.idl defines the interface ITideTable. The actual implementation of the interface (the class CTideTable) is in the file TideTable.cpp; the CTideTable class is declared in TideTable.h. The actual file was created by the MSVC ATL wizard.

Since we wanted to make the COM object useable from scripting applications like VBScript and JavaScript, the interface needs to support ActiveX automation; that is, it makes use of IDispatch::Invoke as well as its own custom interface.

The actual IDL file is straightforward.

// tidewrap.idl : IDL source for tidewrap.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (tidewrap.tlb) and marshalling code.
import i.oaidl.idlll;
import i.ocidl.idlll;
[
object,
uuid(22A6AA3E-7F2A-11D2-8DB3-006097ED27DA),
dual,
helpstring(i-ITideTable Interfacels),
pointer_default(unique)
]
interface ITideTable : IDispatch
{
[id(1), helpstring(iimethod ListLocationsle)] HRESULT ListLocations([in] 
BSTR partial, [out, retval] VARIANT *locations);
[id(2), helpstring(ismethod GetPrintableTableld)] HRESULT
GetPrintableTable([in] BSTR location, [out, retval] VARIANT *tidetable);
 };
[
uuid(22A6AA31-7F2A-11D2-8DB3-006097ED27DA),
version(1.0),
helpstring(i-tidewrap 1.0 Type Librarylp)
]
library TIDEWRAPLib
{
importlib(irstdole32.tlbla);
importlib(irstdole2.tlbll);
[
uuid(22A6AA3F-7F2A-11D2-8DB3-006097ED27DA),
helpstring(i-TideTable Classlh)
]
coclass TideTable
{
[default] interface ITideTable;
};
};

To register the interface (as embodied in the tidewrap.dll library), the nmake file calls the utility regsvr32.

Coding the DLL

The DLL contains code to assemble the command line to invoke POSIX.EXE correctly. The command line is passed to the Win32 CreateProcess() API; Win32 defines its own behaviors for command- line arguments, and you need to understand those behaviors in order to construct the arguments correctly. POSIX.EXE parses its command line and uses everything after the /c option to start a single Interix application. If the single application is a shell, then one could incorporate a single shell command (including such constructs as pipelines, do/done, etc.) using the shell's –c option.

The important work is done in the implementation of the ItideTable interface. (Remember, the interface specification is generic, but the Interix implementation must handle our case.)

The bulk of the work is done in DoInterixCmd() in the TideTable.cpp file. It invokes an Interix command, captures the command's standard output in a vector, sizes the vector, and then creates an array of BSTRs (Basic strings); the array is placed in a VARIANT and passed back. Eventually, the routine returns the exit status of POSIX.EXE. (This is the easiest exit status to capture; it's also the least useful.)

Handles for both stdin and stderr for the POSIX.EXE command line are created pointing to the null device, using CreateFile(). In this case, we used AtlReportError() to capture any errors or exceptions, since it works nicely with Visual Basic and Visual Basic for Applications.

Standard output is created as an anonymous named pipe using CreatePipe(). One end of the pipe, hstdout, is for the Interix process, and the other end, houtput, is for the object to read data.

The important things to note here:

  • The command in this implementation cannot take input; standard input is assigned the NUL (/dev/null) handle.

  • Line endings are removed from the output. This is because Interix processes use a line feed character as an end-of-line marker while Windows processes use a carriage return-line feed pair. Removing line endings from all output simplifies the design of the client.

In fact, the output could have been handled as a SAFEARRAY of BSTRs; the array need not have been returned in a VARIANT.

Building the POSIX.EXE Command Line

The only curious part about the command line passed to POSIX.EXE is that format of the command line is Win32 up to the name of the program being invoked, but the arguments to the Interix command are in the Interix format. For example, from the DOS command line, you might enter:

C:\WINNT\POSIX.EXE C:\Interix\bin\ksh -c tide -nowarn –list

It's a very good idea to open a CMD.EXE console window and experiment in order to get the command line correct. You might even start in an Interix shell window to get the shell command right; then switch to a CMD.EXE command to get that shell command correctly embedded in an invocation of POSIX.EXE; then move that POSIX.EXE command line into the DLL source code. For example, to search the list for locations that shouldn't be used:

Ksh$ tide –nowarn –list | grep "Don't Use"

Once this line accomplishes what it should, you can work with POSIX.EXE, testing the quoting necessary there:

C:\> POSIX.EXE /C ksh –c 'tide –nowarn –list | grep "Don\'t"'

This is the command line you'll eventually use (adding more \ characters because it's now a C string):

Fullcmd = "POSIX.EXE /C ksh –c 'tide –nowarn –list |grep \"Don\\'t 
Use\"'"

The Fullcmd string can be used as the argument to the Win32 CreateProcess() call, as in DoInterixCmd(). The command shouldn't be made in a system() call. The system() call implicitly passes the argument to a shell, but this command line will not necessarily be parsed by the shell.

The command line submitted to POSIX.EXE need not use a shell to parse its contents. For example, the command used in ITideTable::GetPrintableTable() doesn't use a shell at all; it asks POSIX.EXE to invoke the tide command directly. The command used in ITideTable::ListLocations() does use a shell because it invokes grep in a pipeline.

Building the Visual Basic Client

The client code is encapsulated in the files VBTIDE.vbp, VBTIDE.vbw, and Form1.frm.

The client itself is simple. It consists of a single form (see illustration, next page). You can enter any regular expression in the first field; the list displayed in the listbox will be filled with the locations that match. Before you can build the client, you'll need to add a reference to the type library:

  1. Start Visual Basic.

  2. Select VBTIDE.

  3. Select Project | References… in order to ensure that the project references include the registered tidewrap.dll file.

  4. Select the corresponding type library (in this case, the TideTable 1.0 Type Library).

    intrx02

Before running the client, you need to ensure that the tide command is in your Windows path. If you've typed make install for tide then tide is installed in /usr/X11R5/bin. In a CMD.EXE window, you can type:

path=%path%;C:\SFU\usr\X11R5\bin;C:\SFU\bin

(You can simplify this by installing tide in C:\SFU\bin.)

The actual code of the client is quite straightforward.

In the DoSearch_Click() subroutine, the LocationArray is declared as type VARIANT. The Itf variable is the connection to the COM object. After calling ListLocations, the For loop fills the LocationArray. As usual with Visual Basic and COM objects, the function in the DLL is simply a property of the Itf variable.

(Error handling has been deleted for space reasons.)

Option Explicit
Private Itf As TIDEWRAPLib.TideTable
Private Sub DoSearch_Click()
Dim LocationArray As Variant
Dim i As Integer
Set Itf = New TIDEWRAPLib.TideTable
On Error GoTo except
Locations.Clear
LocationArray = Itf.ListLocations(SearchString.Text)
For i = LBound(LocationArray) To UBound(LocationArray)
Locations.AddItem LocationArray(i).
Next
TideList.Text = i.l.
Exit Sub
End Sub

The GetTides_Click() subroutine is nearly identical, except that it calls the GetPrintableTable property and fills the TideList.Text.

Running the Excel Spreadsheet

The supplied spreadsheet tides.xls will make use of the TideTable library. Follow these steps:

  1. Ensure that the tide program is in your Windows path, as already demonstrated.

  2. Run the spreadsheet.

  3. In cell A1, fill in a location; then activate the COM component macro with a CTRL-T.

If you look at the Visual Basic for Applications code provided, you'll see it's similar to the Visual Basic code. The difference is in the use of the data. The Excel macro includes a PlotTides() function which puts the data into spreadsheet cells and then builds a graph from those cells: There are also additional calculations for current.

Future Directions

Microsoft intends to provide additional COM programming support in future releases of the Interix Professional Software Development Kit. These features will be added over time. Among other planned features, the SDK will support:

  • Return exit status of the Interix process.

  • Handling of standard input and standard error as well as standard output. (In this version, they're simply connected to /dev/null.)

  • RPC as an integration strategy for distributed COM.

  • Monolithic threads-based communication (as though Win32 applications could have Interix-enabled threads).

Recommended Reading

Essential COM, Don Box, Addison-Wesley, 1998. ISBN 0-201-63446-5.

Inside COM, Dale Rogerson, Microsoft Press, 1997. ISBN 1-57231-349-8.

Beginning ATL COM Programming, Grimes and Stockton, Templeman and Reilly, Wrox Press, 1998. ISBN 1-867000-11-1.

Active Template Library: A Developer's Guide, Tom Armstrong, M&T Books, 1998. ISBN 1-55851-580-1.