C++ COMponent Magic Project
A COM Aspect for C++

Last updated June 10, 1998
For questions and comments, contact:
Andrew Miller

Xerox ScanSoft, Inc. (www.scansoft.com)


Overview


The goal of this project is to devise two languages that may be used to automagically convert existing C++ classes into COM objects and interfaces (for use with Microsoft Visual C++).  The first language (COM-Comp) is used to define components and interfaces and map interface methods to C++ methods.  The second language (COM-Glue) is used to specify component and interface IDs and COM registry information which are used to glue these components together.  When these two languages are combined with a C++ class header file, the program will automatically generate C++ source files containing:

  • a COM in-proc server with full implementations of
    -DllRegisterServer
    -DllUnRegisterServer
    -DllGetClassObject
    -DLLCanUnloadNow

  • a COM component wrapping around the C++ class

  • an implementation of IUnknown

  • user-specified interfaces linked to user-specified C++ methods

  • a COM component class factory

  • a DEF file defining these entry points


The benefit of such a program is that:
1)  You can create your own custom COM interfaces using existing C++ code with minimal work.
2)  Using COM, you force clients of your objects to recover gracefully when certain interfaces are not unavailable.
3)  You can rapidly create COM prototypes
4)  A minimal understanding of COM is required to produce highly functional COM components.

component COMImage

Project Phases

Definition
This document serves as the project defintion and description.

COM Example -
Last Updated 5/12/98
Create an example COM in-proc server showing how COM works.  For this, I will use the COMImage component illustrated above.  It will not actually perform any image processing.  Instead, it will simply display the names of methods as they are called.  The COM component will be tested using a simple console-based application that I will produce.  Click here to download a the COMImage sample component (includes DevStudio workspace, project files, source code, and pre-built component and client).

Design the COM-Comp Language
Create the language that will be used to generate COM objects that implement interfaces using the provided C++ classes.

Design the COM-Glue Language
Create the language that will be used to glue COM components and interfaces together.

Create the Weave Tool

Write a tool which takes COM-Comp, COM-Glue, and a C++ header file and generates a COM in-proc server.

  • Use a subset of the C++ grammar for the Java Compiler Compiler from SUN to parse C++ header files.  The grammar is available here.  There is another project that embedded .jj files in the Demeter/Java environment here.

  • Write a CD for COM-Comp and COM-Glue and behavior files to parse the C++ syntax trees and generate the code necessary for COM objects and in-proc servers


Application
Apply the tool to the example C++ class illustrated above along with some sample COM-Comp and COM-Glue programs.

Report
Discuss how well everything worked.


COM-Comp
This language provides a way to map COM interfaces to existing class methods.  The picture above gives an example of such a mapping.  It is not shown here, but the existing C++ method arguments are used to define the COM interface method arguments.  The exception to this rule is that any C++ method return types will be added to COM interface methods as "out" arguments so that each COM interface method can return the COM standard HRESULT value.  So,  if in the example above, the save() method were defined in C++ as:

bool save(char* szFilename)

the corresponding COM interface method would be defined as:

HRESULT save(bool* result_arg, char* szFilename)


COM-Glue
This language binds component and interface names to unique COM identifiers.  In addition to identifying CLSID and IID, it also needs the programmer-friendly component name, the version independent prog ID, and the Prog ID.

COM-Aspect
Ths COM-Aspect language fuses the COM-Comp and COM-Glue languages into one (all files from the 6/9/98 Extention):

      Language Grammar: comaspect.cd
      COMAspect Behavior: comaspect.beh
      Sample Useage: sample1.input

      Modified C++ Grammar: cplusplus.jj


Implentation Details
Overview
The COMAspect weaver was implemented using a combination of manually coded Java, Demeter/Java generated Java and JavaCC generated Java.  The code breakdown is a follows:

Total Java Files: 123
Java Files Manually Created: 8
Demeter/Java Files: 2
JavaCC Files: 1

Within COMAspect,  the main component is an object-graph that results from parsing the COM-Aspect input file.  For each component defined within the aspect language, an object-graph is created as a result of parsing the C++ header file.  COMAspect uses Demeter/Java traversals to navigate through these object graphs to generate a COM wrapper around the user-specified C++ class.

JavaCC + DemeterJava
The C++ JavaCC grammar was a good place to start but it quite complex.  The idea was to use this grammar to get JavaCC to generate not only the C++ parser, but Java classes representing the different (non)terminals in the C++ grammar.  At the time I was unaware of any tool to generate the classes automatically so I implemented this myself.  I began by creating a sample C++ header file and running through the debugger, noting which grammar rules were excercised.  This was a nice way of coming up to speed quickly on all the strange names in the grammar and it helped me decide which portions of the grammar I could filter out.  The result of this filtering is a set of 18 Java classes used to represent a subset of the C++ language describing C++ class declarations. To define these classes in Java, I used the Demeter/Java class dictionary file because it allowed me to easily and clearly describe a complex class hierarchy.  Within this section of the CD file, I used the "noparse" keyword to tell Demeter/Java not to generate a parser for these classes (since I allready had a the full C++ parser).  Building the project works as follows:
Run Demeter/Java through the "generate" step
Run the DemJava generated JavaCC file through JavaCC
Copy any hand-generated Java files into the "gen" directory
Copy the CPlusPlus.jj file into the gen directory
Run CplusPlus.jj through JavaCC
Run DemJava with "compile"

Generated Code
Some of the generated code is completely generic and does not need to be modified based on the user input.  These files are referred to as the "boilerplate" files which reside in the .\templates subdirectory and are copied into the output directory at runtime.  Other files are based on template files which contain special macros that get filled in at runtime.  The template files are also located in the .\template directory and all end with ".tpl".  Template macros always start with "{{" and end with "}}".  During program execution, these macros are located and used as insertion/replacement points for run-time generated C++ code.

The  Demeter Experience
Demeter's Traversals and the Visitor pattern were a perfect fit for this project.  The addition of in-line traversals (aspects) has reduced the amount of administrative code that must be produced.  Some items that would be extremely useful are:
  • Traversal Abort - several of my traversals were "search" traversals where I wished to find a single object and then return.  Demeter does not provide a way to abort from a traversal.  It would be nice if a keyword were added to the language.  My suggestion for implementation would be to internally throw and catch an exception.

  • Automatic Host Referencing - several items in my Visitors were references to previous hosts.  It would be nice if Demeter allowed me to name the hosts in the traversal declaration so that I could refer to them during the traversal without having to explicitly add them to my visitor.  This would work toward making the visitors extremely easy to understand.


Distribution
June 9, 1998 (See Extention 1 for details)
Download COMAspect (self-extracting zip!)  You'll get everything listed in the June 8th plus:
COMASPect
  • Supports most user defined types


COM Sample
  • Updated client/object/C++ class to show off user defined types


June 8, 1998
Download
COMAspect (self-extracting zip)!  You'll get the following:
COMAspect
  • Java executable

  • Source code + MS-J++ Project/Workspace

  • interface samples


COMSample
  • Client/Object MS-C++ workspace and sample C++ class

  • Simple client that communicates with interfaces defined in sample1.input


Usage
Java: the Java class containing main is Main (see args below)
DOS: comaspect inputfile [/o output-directory] [/i header-file-directory]
output-directory is the directory in which generated code will be placed
header-file-directory is the directory in which the C++ header file is located

COMAspect must be run from a directory containing the included subdirectory called templates.

The comaspet.bat file will need to be updated to point to your Java installation as well as your Demeter/Java installation.


Output
If execution completes successfully, the following files will be created in the output directory:

Generated (click to see files generated for example above)
cmpnt.h
                  - component definitition
cmpnt.cpp
              - component implementation
iface.h
                    - interface declaration + GUIDs
factcomp.cpp
        - register component with class factory

Boilerplate
cfactory.cpp          -class factory
cfactory.h              - class factory
cmpnt.def              - COM DLL entry points
comentry.cpp        - COM DLL entry points
guids.cpp              - used to define GUIDs
registry.h                - registry helper routines
registry.cpp            - registry helper routines
server.cpp              - COM in-proc server
server.h                  - COM in-proc server

Results of Application
Not only is the tool is an excellent way to convert existing technology into COM objects with minimal work, but it is also a very easy way to prototype interfaces.  COMAspect was applied to 2 sample input files to demonstrate different approaches of creating COM interfaces for the same C++ class. 

For this example, a simple C++ class, CImage, was created with methods corresponding those described in the example above.  The implementation of each method is just a simple printf shows that the object's method was called.

The first interface example follows directly from the example given above.  It generates three interfaces representing the IImageInfo, IPersistence, and IImageProcessing interfaces.

The second example takes a different approach and creates a basic image interface, IBasicImage and an extended image interface, IExtendedImage. 

Both examples demonstrate that existing classes can be advertised as offering multiple levels of functionality which means that the component could be sold in a multi-tier pricing scheme.  Using COMAspect, you can rapidly have customer's using custom COM interfaces against existing C++ code.

In addition, the COMAspect language serves as a simple encapsulation of COM by exposing only  the customizable portions.  It is not necessary for COMAspect developers to understand topics such as interface implementation, component registration, and component class factories.

Known Bugs
C++ Parser
There are several problems with the C++ Parser generated with the grammar listed above:
  • header file must contain a blank line at the end or a parse error will occur

  • the following items must begin with a capital letter or a parse error will occur: variable names, structure, class and typedef declarations.


COMAspect
  • if you define multiple components that reference the same header file, the header file will be included twice.  If you have not set up your header files to only get included once, program compilation will fail.

  • If multiple components are defined and implement the same interface, the interface is incorrectly declared twice.

  • External declarations are ignored


Project Extentions

  • automatically convert base classes into components and simulate inheritance via aggregation

  • automatically generate an implementation of IDispatch for support of OLE Automation

  • automatically update client code to use COM interface

  • support user defined types, structures, classes, etc


Extention 1 - June 9, 1998
User defined types are a great part of C++.  Since it would be such a shame to loose them, I spent a little extra time adding support for the following user defined types:  struct, class, typedef, and enum.  A few notes on the implementation:

In order to export the definition of user defined types from the component, we must either generate the code to declare similar types (but with different names to avoid collision) or include the original class header file in the interface declaration.  The first approach is the correct approach but also requires a significant amount of work for the current implementation.  As a quick and dirty approach, COMAspect will #include the original class header file in the COM interface declaration file (iface.h).  The positiive side of this approach is that it's very easy to implement.  The negative side of this approach is that it requires you to include your original C++ class declaration (and whatever it included) when you deliver your component to a customer. 

For quick prototyping this approach should work fine but future implementations should "do the right thing".
 
Adding the additional type support to COMAspect turned out to be incredibly easy.  It required 1 change to my DemeterJava CD, 3 changes to my BEH file and around 5 lines in the C++ grammar (JJ file).