Sun Java Solaris Communities My SDN Account Join SDN
 
Article

COM on the Solaris Operating System

 
By Craig Winter, July 2002  

Introduction


Many software companies are now developing Component Object Model (COM) applications on the Windows NT platform. COM is a language-independent component API developed by Microsoft. COM is not a part of UNIX®, thus it simply is not possible to compile and link COM applications on the Solaris Operating Environment (OE). However, some third-party software companies have licensed COM from Microsoft and have created UNIX implementations that support the building and execution of COM applications on the Solaris OE.

This report deals specifically with a pilot project that was conducted at a major software development company (referred to subsequently as "the client company") in the United States. The purpose of this project was to investigate the feasibility of creating a port to the Solaris platform of a COM application that had been developed solely on the Windows NT Visual C++ development environment. Since COM does not exist on the Solaris OE, it was necessary to use a third-party COM implementation to enable code written for Windows to be built and run on the Solaris platform.

Mainsoft and Software AG are two companies that sell and support COM implementations for the Solaris OE. Both companies were considered for this project, but for reasons that will be discussed later, the project used Mainsoft. Two other COM implementations became available after the inception of this project: COMsource, from The Open Group, and XPCOM, from Netscape. Another company, Bristol Technology, also offers a Solaris COM implementation, but at the time of this project, Bristol was engaged in a lawsuit with Microsoft. Certainly, any future COM-porting projects should review the product offerings from all of these companies.

Executive Summary


This project deals with the porting of the lowest level of the client company's code base. This portion of the application contains the code used for the generation and manipulation of geometry and spatial references. The functionality contained at this level is used throughout the entire spectrum of the client company's applications. These applications are COM intensive and use many coding techniques and Win32 function calls specific to the Microsoft environment.

Several obstacles had to be dealt with in order to successfully complete this project. The most important was the difference between the two compilers used, Microsoft's Visual C++ and Sun's SC 4.2 compiler for the Solaris OE. Many coding constructs and compiler options used in the Microsoft world are not ANSI standard and thus not supported on the Solaris platform. Also, the company made extensive use of Microsoft-specific extensions to the C++ compiler that do not exist on Sun's C++ compiler for the Solaris platform. Neither Mainsoft nor any of the other Win32 on UNIX implementations supports other APIs such as DAO (Data Access Objects). If an entire product line is to be fully supported on the Solaris OE, something will need to be done to replace the unsupported Windows-specific APIs with ones that will perform the same functions on UNIX.

The project revealed that making changes to the application's code helped avoid many of the compiler problems. Also, removing Microsoft-specific extensions and coding constructs can simplify the porting effort. In addition, changes could be made to Sun's 4.2 compiler for Solaris systems in order to support some of the ANSI standard functionality that it currently does not support.

Some problems disappear when using the Mainsoft Version 3.3 Build 67 release of MainWin, which supports the 5.0 compiler for the Solaris platform. Moreover, some changes also need to be made to the MainWin code to better support Native COM on the Solaris OE. (Please note: the 5.0 compiler shipped as part of Sun WorkShop compilers, while 6.0 was a Forte product, and the 7.0 compilers are available as Sun ONE Studio 7.)

The length of time it takes to port an application is directly related to the amount of Microsoft-specific extensions that are used in the application. First, you must determine how the extension is used in the application, and then you must implement an appropriate workaround to perform the same function in the Solaris OE. If the application uses tools that automatically generate code during the build process, this too will increase the porting time, as you must find another means to produce that code on UNIX. We would urge any ISVs to eliminate the use of Microsoft-specific extensions and code generation so that they can produce applications on any platform from a single source tree.

Once we overcame all of the obstacles to compiling and linking, the shared objects were built and registered on the Sun system just as they are on Windows. MainWin provided modified versions of the Microsoft tools used to perform these functions. In order to test the execution of the COM objects, we used a sample drawing program written by a developer. The program worked correctly and had the same look, feel, and functionality as it had on Windows. Performance numbers were not available since the sample program was quite small.

This effort proved to be highly successful in showing that a mature COM application built entirely in a Windows NT environment could be ported to the Solaris OE and function correctly.

Mainsoft


MAchine INdependent SOFTware (Mainsoft) is an Israel-based company that specializes in providing means of running COM and MFC (Microsoft Foundation Class) applications on UNIX platforms. Mainsoft's chief product is MainWin, which is used to run Windows on UNIX.

Mainsoft provides hotline support with access via e-mail. The MainWin product also includes a tool called "mwspr" that allows the developer to log problems. mwspr captures a description of the problem, as well as information about the development system, and emails them to Mainsoft. Mainsoft usually responds in a day or two.

The goal of this project was to see if a software company could establish concurrent builds of its product on both NT and the Solaris OE. Mainsoft promoted the use of its engineering services department to do a one-time port of the NT code to the Solaris platform. Engineering services are separate from developer and runtime support, and thus require funding from the ISV, a hardware partner, or an end user. Having Mainsoft port the code would only be helpful if the modified source was returned to the ISV so that the changes could be integrated into the code base. This would allow for compilation on both NT and the Solaris OE from a single source tree.

Other COM Implementations


  • Software AG offers a COM implementation but has no support for MFC-based applications.
  • The Open Group has made COM source code available for the Solaris OE. Although the APIs for OLE and Active X controls have not been included, the solution may still be considered for simpler COM applications.
  • XPCOM is part of Netscape's Mozilla and is another basic COM implementation that also does not provide APIs.
  • Bristol Technology's Wind/U product is a set of libraries and utilities that enable the creation of native UNIX, OpenVMS and OS/390 applications from Microsoft Windows API and Visual C++ source code. Despite its legal issues with Microsoft, Bristol Technology is still in business and does provide a COM solution.

About COM


COM allows for communication between components via binary interfaces. These components can be created from a number of different programming languages, such as C++, C, Java, Pascal, and so on. Components exist as Dynamic Link Libraries (DLLs) or as executables, and are connected through interfaces.

A COM interface is a specific memory structure that contains an array of function pointers. Each array element contains the address of a function implemented by the component. In C++, COM interfaces are implemented using abstract base classes that contain virtual functions.

Components are configured in the registry and are loaded and deleted by the application as needed, and reference counting is used to enable components to delete themselves when they are no longer required.

The most common method of controlling reference counting is to use Smart Pointers, which can be constructed in several ways. One of the more popular methods of constructing Smart Pointers is to use the Interface Definition Language (IDL) to describe interface data, which is then compiled with the MIDL compiler to create a type library.

The type library is a binary header file that the Visual C++ compiler imports when the components are compiled. The interfaces are created along with the references to the Smart Pointers.

A Globally Unique Identifier (GUID) is a 128-bit structure used to uniquely identify components and interfaces. Utilities exist that automatically generate GUIDs. These GUIDs contain information about the computer on which they were generated and the time they were generated, guaranteeing a unique number in the universe. When an interface is modified, a new GUID must be generated and used with the new version of that interface.

Two good references on COM are Inside COM by Dale Rogerson (ISBN 1-57231-349-8) and Essential COM by Don Box (ISBN 0-201-63446-5).

Experience at the Software Development Company


Porting Environment


Initially both the Mainsoft and Software AG COM implementations were considered for this particular project. Installing Software AG was quite involved and took over half an hour to complete. Although Software AG gave us a free evaluation copy of its product, it did not provide any documentation for an installation on the Solaris platform. To complete the installation on the Solaris OE, we had to follow Software AG's standard installation procedure and then rely on some notes that had been faxed to us by its staff. By comparison, the installation of Mainsoft's MainWin product was very clean and simple. Mainsoft provides a single script that installs the entire product from the CD.

We decided to begin the porting effort using MainWin because it appeared to be the cleanest implementation of the two products, and because it had the MFC support.

Mainsoft provided a C-shell to set up its environment variables. In order for this to work, a MainWin home environment variable had to be set first. To do this, we set up a script to set MainWin's home environment variable, MWHOME, source the MainWin .csh file, and set the LD_LIBRARY_PATH environment variable. Running this script sets up the entire MainWin development and execution environment.

One of the first things we did was compile and run the examples from the book Inside COM. This helped us become familiar with the MainWin scripts and the options that were used to create a makefile. Note: To avoid name mangling that can occur with C++ compilers, it was absolutely necessary to make sure that there was an Extern "C" statement in front of the main program in the client and in front of the DllMain function in the component.

Initially, the project was using MainWin Version 3.1 Build 65. Supporting Smart Pointers required three modified header files and an upgrade to MainWin Version 3.2 Build 66. The modified header files were downloaded from the Mainsoft web page. Version 3.2 of MainWin is compatible with Sun's 4.2 compiler for the Solaris platform, but does not work with Sun's 5.0 compiler for Solaris.

The three files that we downloaded to enable support of Smart Pointers were:

    comip.h
    comdef.h
    comutil.h

They were contained in the tar file:

    nativecom.tar.Z

These files replaced their old versions in the directory:

    /usr/local/mainwin/userx/public/sdk/inc/

Smart Pointer Implementation


The first problem to resolve was that of the Smart Pointer implementation. Smart Pointers are used to handle reference counting in a COM application, and are used extensively in the application's code. Typically Smart Pointers are defined in .idl files written in the Interface Definition Language. The MIDL compiler then processes these files to create a Type Library with the .tlb suffix. The Type Library is a binary file that contains header information such as function definitions. The Visual C++ compiler reads the binary library file when it comes across the #import statement, as it would a standard header file, in order to create the Smart Pointer definitions.

There is no #import directive in UNIX compilers, so another process must be used in order to employ Smart Pointers. A .h (include) file can be created when the MIDL compiler is used to compile the .idl files. This, however, is not sufficient because it does not produce the same header information as when the Type Library is imported; when processing the .tlb file there are keywords that are used with the #import directive that determine how the information is processed.

Fortunately, the #import directive creates two header files that contain the same information as when the .tlb file is imported. These files are the .tlh (primary) and .tli (secondary) header files. (The .tli file is included in the .tlh file, but a .tli file was not used at the client company.) Using the #include compiler directive, the .tlh file can be used in place of the .tlb file. The restriction here is that the .tlb file must first be created and processed on the Windows NT side before the .tlh and .tli files can be used on the UNIX side.

As part of the client company's implementation of Smart Pointers, we made changes to the file comip.h. This file also had been modified by Mainsoft and was one of the files that we retrieved from Mainsoft's web page. Since both the MainWin and the client company's versions of comip.h were based on the Microsoft version, our client's comip.h file was compared to the Microsoft version, and the changes were added to the MainWin version. Ideally we would like the client's code to be able to run with the comip.h file supplied by Mainsoft.

We encountered a forward referencing error when compiling the .tlh file. Almost all of the enumerated structure definitions needed to be placed in front of their typedef statements. To accomplish this, we removed the enum definitions from the .tlh file, placed them in another .h file, and included that file at the top of the .tlh file.

Another problem was that the named GUID constants' initialization statements were declared extern, which caused a compiler error. The extern keyword needed to be removed from these declarations in order to allow the file to compile. This was not desirable because there were comments at the top of the .tlh file that declared it a compiler-generated file with the warning "DO NOT EDIT!" Since the .tlh file was generated on the Windows NT platform, we were not able to see if modifying the .idl files would have any effect on the location of the enum statements.

Compiler Differences


The next group of issues has to do with functionality that is not available on Sun's 4.2 compiler for Solaris.

The Microsoft Visual C++ compiler recognizes several #pragma statements that are meaningless to the Solaris compiler. The main one is #pragma once, which is used in just about every .h file to ensure that the code is included only once in the include (.cpp or .h) file. To deal with this, the #pragma once statement was replaced with the following compiler directives:

    #ifndef __filename_h__
    #define __filename_h__
        body of code...
    #endif

Other #pragma statements that gave compiler errors were simply removed from the code.

The Solaris compiler also does not recognize the mutable keyword. This keyword is used to allow modification of variables that are not supposed to be modified, such as those in the private section of a class. In this project, mutable was defined in order to allow compilation, and statements that referenced the mutable variable were modified. For example, with the variable xMin declared as mutable in the private section of the WKS class, the statement:

    xMin = NumConst;
	

was changed to:

    ((WKS*)this)->xMin = NumConst;
	

in order to "cast away" const.

The 5.0 compiler for Solaris supports namespace, but the 4.2 compiler for Solaris does not. Because of this, the namespace keyword had to be removed from the code and its functionality replaced. namespace adds a name, followed by a double colon, to the front of a variable or class name. For example, if the code reads:

    namespace SUN {
        class Micro {}
    }

The class would be referenced either by specifying:

    SUN::Micro {}
	

or by having the line:

    using namespace SUN
	

appear before any references to the Micro class in other files.

One of the client company's directories used namespace extensively, so removing it from the code was a very labor-intensive process. If you undertake this effort, be careful to make sure that only namespace-related prefixes are removed.

Building the Application


Other items necessary for building the COM application have to do with modifications to the makefile and files that need to be created in order to hold information important to the use of Smart Pointers.

Some parameters that needed to be set in the makefile to add support for specific functionality were:

-DMW_MSCOMPATIBLE_VARIANT
added to the APP_CPPFLAGS parameter to eliminate compiler errors caused by the use of variant.
-DSTRICT
added to the APP_CPPFLAGS parameter to resolve the difference between HWND and HDC
-D_MT
added to the APP_CPPFLAGS parameter to add support for threaded applications.

When declaring interface pointers as Smart Pointers, there was a problem that caused the program to call incorrect functions. For this reason, lines that read like this:

    IWin32ShapePtr ipPixelGeometry = pPoly:
	

had to be rewritten like:

    IWin32ShapePtr ipPixelGeometry(pPoly);
	

and the line:

    ATL_DECLARE_UUIDOF(IWin32Shape)
	

was added to the uuid.cpp file. The uuid.cpp file was not part of the original code and had to be created from scratch.

The first line in the file was:

    #include <atldef.h>
	

with lines such as:

    ATL_DECLARE_UUIDOF(IPersistVariant)
	

used to declare the interface pointers.

The line:

    ATL_MODULE = 1
	

had to be added to the makefile in order to specify that the ActiveX Template Library be used.

Issues Specific to the 4.2 Compiler for the Solaris Platform

Issues arose with the Solaris 4.2 compiler regarding the forward referencing of a template class. In order to compile correctly, the line:

    friend class ClassFactory;
	

was changed to:

    friend class ClassFactory<class T>;

Declaring templates in the private area of a class is not allowed on Solaris. These declarations had to be moved to an area of the code where they could be seen globally.

The Solaris compiler does not allow functions that are defined in private areas as static. Once the static definition was removed, the code compiled. The impact this may have on the intended functionality of the code is not known.

The Solaris compiler does not seem to fully expand some nested macros. There are instances where the nesting is three or more levels deep and the compiler does not understand the macros. As a result, it was necessary to manually expand the code so that the compiler could understand it.

The Solaris compiler does not support the use of explicit functions. To address this issue, this keyword was simply removed everywhere it was found.

The phrase "Overloading Ambiguity" was written out by the compiler in several different areas. Performing the proper casting often remedied this problem, as shown in the following example:

The line:

    _name = (_bstr_t) _variant_t(*name);

was modified with a temporary variable:

    VARIANT tmp_var;
    {
        tmp_var = _variant_t(*name);
        _name = (_bstr_t)tmp_var;
    }

Another instance of Overloading Ambiguity had to do with the != operator. The compiler gave an error message for the definition:

	inline bool operator != 
	  (ISpatRefPtr & ipSR1, ISpatRefPtr & ipSR2)
	

The not equal operator had to be renamed in order to avoid the conflict:

	inline bool operatorNotEqual 
	  (ISpatRefPtr & ipSR1, ISpatRefPtr & ipSR2)
	

There were several places where inline functions were not allowed because the body of the function had not yet been defined at the time the function was referenced. In many instances the inline keyword was simply removed. In other cases, the inline keyword was moved from in front of the prototype statement and placed in front of the body of the code in the .cpp file.

The Solaris compiler does not recognize an anonymous structure within an anonymous union declared in the private area of a class. To handle this issue, both the union and the structure had to be named; then those names were added to the references in the code.

Some of the statements using the ? : construct needed parentheses around the assignment statements or a bizarre message would appear. An example of code that would not compile is:

    bFromOnNode ? pEdge->u.s.m_FromNode = 1 
	  : pEdge->u.s.mToNode = 1;

This needed to be changed to:

    bFromOnNode ? (pEdge->u.s.m_FromNode = 1) : 
	  (pEdge->u.s.mToNode = 1);

There are cases where the compiler "complained" about the left side of the equation not being an lvalue. The following line resided in a case statement and would not compile:

    (unsigned char)(*(m_args[paramCount-1].pbVal)) = retVal.bVal;

The following modified line compiled correctly:

    *(unsigned char*)(m_args[paramCount-1].pbVal) = retVal.bVal;

Another example of a problem with an lvalue showed up as an error related to the & operator. In this case, the compiler complained about the following line, stating that the & operator could only be applied to a variable or other lvalue. This confusion may have been caused by the fact that there were different Point functions throughout the code:

    pTangent->PutCoords(&Point(tangentFrom), 
	  &Point(tangentTo));

The code had to be broken up so that the & operator was applied only to properly declared lvalue variables and the correct Point function was used:

    ISV__Point from(tangentFrom);
    ISV__Point   to(tangentTo);
    pTangent->PutCoords(&from, &to);

The client company uses a Microsoft shortcut when reinitializing the members of an anonymous structure within an anonymous union. In the following code, m_Bits(0) zeros out the remaining members of the structure:

    AbcImpl::AbcImpl()
    : m_Bits(0)
    {
      m_IsEmpty = 1;
    }

This code had to be rewritten so that each member of the structure would be addressed individually. The following code also accounts for the namespace issue and shows how the union and structure were named:

    ISV__AbcImpl::ISV__AbcImpl()
    {
      u.s.m_IsEmpty = 1;
      u.s.m_AnglesValid = 0;
      u.s.m_RadiusValid = 0;
      u.s.m_IsCCW       = 0;
      u.s.m_IsMinor     = 0;
      u.s.m_IsLine      = 0;
      u.s.m_IsPoint     = 0;
    }

There were places in the code where a static_cast had to be added in order to compile correctly, and other areas where it had to be removed. Adding the static_cast was done when Smart Pointers were involved, as in the line:

    polygonGraph.Load(IGeometryPtr(this), false);

which needed to be expanded to resolve inheritance along with adding the static_cast for the Smart Pointers:

    polygonGraph.Load(static_cast<IGeometry*>
	  (static_cast<IPolygon*>(this)), false);

Removing a static_cast was done in cases where it was involved in a logical expression. The line:

    if ((from.x * from.y > c_Fudge) == static_cast<bool>(u.s.m_IsCCW))

had the static_cast removed and was rewritten as follows:

    if ((from.x * from.y > c_Fudge) == (bool)(u.s.m_IsCCW))

Several of these problems can be fixed by modifying the client company's code so that it will compile on both the Microsoft Visual C++ and Solaris compilers. This is preferable to trying to modify the Solaris compiler to compile the Microsoft-specific code.

Issues Specific to Microsoft Features


Some functions that are used by the client company are specific to the Microsoft world. Most of them have names that are slightly different on UNIX. Also, some definitions do not exist on UNIX.

MainWin has added a definition for the function _unlink. It is located in:

	...mainwin/userx/public/sdk/inc/crt/io.h
	

and had to be included with ifdef statements:

    #if defined(unix)
    #include <io.h>
    #endif

The function name isnan had to be used instead of the Microsoft version called _isnan. The function name copysign had to be used instead of the Microsoft version called _copysign. Also, the statement:

    #include <math.h>

had to be added to any file that uses the _copysign function.

The Visual C++ compiler allows for the use of optional variables in a function argument list. Adding the following line to the top of the .cpp file permitted this to work on the Solaris compiler:

    _variant_t vtMissing (DISP_E_PARAMNOTROUND, VT_ERROR);

The definitions:

    UINT32
    __int16
    __int32

do not exist in UNIX or in the MainWin definitions, and were defined respectively in the client company's code as:

    UINT
    _int16
    _int32

Issues with Mainsoft


In order to deal with compiler errors that result from the use of large integers, instances of LowPart and HighPart need to have a u. placed in front of them. This means that references such as:

    foo->LowPart
    foo->HighPart

need to be changed to:

    foo->u.LowPart
    foo->u.HighPart

The following line did not compile and complained about an "unexpected type name" when initializing a static template variable:

    template 
    const unsigned short 
    BaseArray::c_ElementSize = sizeof ELEMENT;

Simply placing () around the word ELEMENT resolved the problem:

    template 
    const unsigned short 
    BaseArray::c_ElementSize = sizeof (ELEMENT);

We investigated this problem with different Solaris compilers on different systems, and it was only found to be a problem when compiling in the MainWin development environment.

Also, there is no support of DAO by Mainsoft.

Conclusion


With the use of third-party software such as MainWin, it is possible to compile and execute true COM applications on the Solaris platform. While Java technology or any open API is the correct solution to developing platform-independent applications, porting COM applications to Solaris provides companies with an interim method of offering products on Sun systems. This project has shown that a company that develops COM applications on Windows can easily expand its customer base by offering fully functional versions on the Solaris platform.


Resources


Solaris Developer Connection has an NT Migration section with information on porting issues, post-porting tasks, migration tools, and more.


About the Author


Craig Winter is a member of the technical staff in Sun's Market Development Engineering group. For over six years he has been specializing in ISV application support for various MCAD, MCAE, and GIS vendors.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.