The EiffelCOM Wizard
OVERVIEW
The COM standard allows software components written in different languages to
communicate with each other. Unfortunately, building COM compliant applications
requires the development of large amount of code only dedicated to support the
technology. The EiffelCOM wizard was designed to free programmers from writing
the plumbing code.
The EiffelCOM wizard is a powerful tool that enables the fast development of
COM components in Eiffel and access COM components from Eiffel systems. It
consists of a series of dialogs, which ask about the component’s
characteristics. The wizard needs the data to produce an Eiffel system skeleton,
the code to access or create a component, and the component-specific runtime
libraries.
The wizard allows Eiffel developers with little COM
knowledge to develop and reuse COM components. The design of the generated code
follows the Eiffel standards and is familiar experienced Eiffel users. The only
prerequisite to use the EiffelCOM wizard is an understanding of the Interface
Definition Language. IDL describes a component. Standard compilers
can generate Type Libraries from IDL files. Tools that need
information on a given component, such as the EiffelCOM wizard, Visual Basic,
etc. analyze Type Libraries. The IDL syntax is very close to C and easy
to learn.
The wizard generates code from a Type Library and additional information given
by the user. The generated code consists of Eiffel classes, C/C++ files, and
library files. The wizard automatically produces library files from generated C
and C++ code. You do not need to modify generated C/C++ code to build your
EiffelCOM system.
Code Generation Process
|
The wizard automatically compiles the generated C
and Eiffel code. To produce a Type Library corresponding to a given IDL file
the wizard uses MIDL, the MICROSOFT IDL compiler. You may also provide
the wizard with a Type Library directly. For the rest of the manual a COM
Definition File will refer to the input file given to the wizard (either an
IDL file or a Type Library). If you want to start from an existing Eiffel
system, the wizard accepts an Eiffel class and generates an IDL file from it
and then proceeds as above.
THE WIZARD
This chapter details the wizard and code generation. The wizard has seven
different dialogs. The third dialog is different depending on whether the
wizard have started from an Eiffel system, an IDL file, or a Type Library. Once
the dialogs have been completed, the wizard generates a Type Library if needed,
then analyzes the Type Library, and generates the code.
Introduction Dialog and Main Window
The EiffelCOM wizard can be launched from the Windows start menu:
Start->Programs->EiffelXX->EiffelCOM Wizard
|
where EiffelXX corresponds to your Eiffel installation (e.g. Eiffel45).
The following window will be displayed:
EiffelCOM Wizard Main Window and Introduction Dialog
|
The introduction dialog lets you choose between opening an existing project or creating a new
one either from a COM definition or from an Eiffel project. Creating a new project will
open the
Generated Code Type Dialog.
Opening an existing project will display an Open File Dialog from which you can
select a previously saved EiffelCOM project.
The four buttons at the bottom of the dialog Back, Next,
Cancel and Help are common to all dialogs displayed throughout the
execution of the wizard. Next validates all the values entered in the
current dialog and activates the next one. Back discards all the values
entered in current dialog and displays the previous one. Cancel exits the
dialog and discards all the values entered. Finally, Help brings up this
manual.
EiffelCOM
Wizard Main Window
|
The main window includes a toolbar and a menu. The first three buttons on the toolbar correspond to the first three entries in the File menu: New, Open and Save. New resets all the information previously entered in the wizard. Open brings up an Open File Dialog that can be used to retrieve a previously saved EiffelCOM project. Save is used to save the current project. A project is defined by all the values entered in the wizard. A project can be saved only after the wizard has been run. The file extension for an EiffelCOM project is ewz.
The second menu, Build, includes the entries Launch Wizard and Generate (no wizard) corresponding respectively to the last two toolbar buttons. The former activates the
Generated Code Type Dialog.
while the later launches the generation with the current settings and bypasses the dialogs. This last button can be used only when a project has been loaded or when the wizard has been run once.
The third menu, Help, includes Help and About EiffelCOM. Help
brings up this manual. About EiffelCOM brings About EiffelCOM
Dialog that gives the version number.
About EiffelCOM Dialog
|
Required File
Before you launch the wizard make sure you have a COM definition file or an
Eiffel project ready for processing.
Generated Code Type Dialog
The first dialog asks if you want to access or build a component.
If you want to access an existing component then the generated code
will be for a client. If you choose to build a new one, the generated code
will be for a COM server. Choose the server or client check box to specify
which kind of project you want to work on. You may specify both in the case
where both the component and its client(s) will be written in Eiffel.
Generated Code Type Dialog
|
EiffelCOM supports the following
component executable types:
In-Process: These components are
Dynamic Link Libraries (DLLs) that will be loaded into the client process. The
server runs in the same process as the client.
Out-of-process: These components
are executable files that can be accessed through the network. Clients and
servers run in different processes and may even run on different machines.
In-process components are Active-X
like components; they are usually smaller than local or remote components and
used by bigger application (often through a high level language). Local
components run on the same machine as a client. Remote components run on
different machine and can act as middleware in a three-tier client server
architecture. Choose the kind of component you want to access or create.
If you do not want to compile the generated code
automatically, check the checkboxes at the bottom of the dialog.
Definition File Dialog
This dialog asks for the location of a COM definition file for the project. A
COM definition file is either an IDL file or a Type Library. An IDL file is
usually provided when building a new component since all the sources are
available. However, when it comes to accessing an existing component, the
sources might not be available. A Type Library is often embedded into the
component and includes enough information for the wizard to generate the code.
Definition File Dialog
|
The wizard accepts the following type extensions: idl,
tlb, ocx, olb, dll, and exe.
Definition File Dialog, file extensions
|
The destination folder is the directory where the wizard saves generated
files. The directory should be empty. If the wizard were to overwrite an
existing file it would first back it up and emit a warning message. If any of
the entered values are not correct when the Next button is pressed the
wizard displays a warning message.
Eiffel Project File Dialog
The Eiffel project file Dialog asks for the location of an Eiffel project file
(*.epr), the Ace file of the project, the name of the Eiffel class that
will be converted into an IDL file, and the cluster name to which the Eiffel
class belongs.
Eiffel Project File Dialog
|
Destination Folder Dialog
The Destination Folder Dialog asks for the destination
folder path where the wizard can safe generated files.
Destination Folder Dialog
|
IDL Marshalling Definition Dialog
This dialog is displayed only for a server project and if the chosen definition file is an IDL file. It is used to specify how marshalling will work for the component. The first choice that has to be made is whether the component will be accessed through Automation (using IDispatch) or through the interface's virtual table (for additional information on Automation versus virtual table access, please consult COM).
IDL Marshalling Definition Dialog
|
If you choose Automation, then the Universal marshaler will be used (for
additional information on Marshalling please see the chapter COM). If you
specify Virtual Table access, then you have the choice between using the
Windows Universal marshaler and the marshaler generated from the definition
file. Since this dialog is displayed only when the definition file is an IDL
file, choosing Standard Marshalling will force the wizard to compile the
marshaler from the code generated with the MIDL compiler. Standard marshalling
should be used whenever some interface functions make use of non-Automation
compatible types.
Type Library Marshalling Definition Dialog
The Type Library Definition Dialog is displayed only for a server project
when the definition file is a Type Library. You have to choose between
Automation and Virtual Table access and between Universal and Standard
marshaling.
Type Library Marshalling Definition Dialog
|
Because the definition file is a Type Library, the wizard cannot compile the
Standard marshaler without additional information. This is the reason for
having an extra text field for the path to the marshaler (also known as
Proxy/Stub pair or just Proxy/Stub). The Proxy/Stub is a Dynamic Link Library
that is used to marshal the data on the wire for a given component.
Final Dialog
The last dialog offers a choice of different output
levels. By default, the wizard will display errors, warnings and generic
information. You can choose not to see warnings and/or extra information.
Final Dialog
|
This dialog also asks whether you wish to continue even though an error occurred while compiling a file.
The Finish button closes the dialog and starts the processing. You can save the
project after the wizard finishes.
COM Definition File Processing
Six phases are involved in the COM definition file processing:
IDL Compilation: If the definition file is an IDL file, the wizard
compiles the IDL file into a Type Library and if Standard Marshalling have been
chosen, produces the marshaler from the generated C files.
Type Library Parsing: The wizard analyzes the type library and transforms the
information it needs to generate the code into internal representation.
Code Generation: The
wizard generates both the Eiffel and C/C++ code from the information gathered
during the previous step.
C/C++ Compilation: The wizard compiles the generated C and C++ code into object
files and libraries that will be linked with the Eiffel system.
Eiffel Compilation: For a client project the wizard compiles the generated
Eiffel code into a precompiled library that can be reused from other projects.
For a server project the wizard compiles the generated Eiffel code into a
standard project with the registration class as root class. If the location is
In-process then the project corresponds to a DLL whereas if the location of the
server is out-of-process then the project corresponds to a standard executable.
Finally, the wizard launches
EiffelBench and automatically opens the generated Eiffel system.
During processing, the name and
progress of each phase is displayed.
Wizard Progress Dialog
|
While processing the wizard displays information in real
time in the main window if this option was chosen in the Final Dialog. Displayed
information includes output of calls to external compilers (C, Eiffel and IDL)
and description of the current analyzed or generated Type Library item.
Eiffel Project Processing
Eiffel project processing has an additional phase: IDL
file generation. During the phase the wizard translates an Eiffel class into
an IDL file. Preferably, you should write a special class to serve as a Facade
to your system. After IDL generation the wizard proceeds as above.
Generated Files
The wizard generates code into the specified destination folder. The file
hierarchy is the following:
Generated Files Hierarchy
|
The root folder has two files and four subclusters.
The file generated.txt includes a list of all the files generated by the
wizard.
The file component.log contains a summary of the processing done
by the wizard. The name of the file is the name of the definition file appended
with log (so Figure 10 presumes that the definition file was e.g. component.idl)
The folder idl contains the generated IDL file.
The folders Client and Server include the files generated
respectively for reusing a component or creating a new component. Each consists
of three subdirectories: Include comprises header files needed to compile
the Eiffel code, CLib contains the generated C and C++ code as well as
the library files. Component holds Eiffel code that wraps or defines the
component. The Component subfolder of Server also includes the
registration class. This Eiffel class contains the code needed to activate the
component; it depends whether the component is in-process or out-of-process. You
do not need to read or edit the C and C++ code included in CLib because
the wizard automatically compiles it. It is given for information only and can
be deleted (you need to keep the library file though). The Client and Server
folders also have an Ace file used to compile the generated Eiffel code. In the
case of a client, the generated code is precompiled whereas in the case of a
server it is compiled in a normal system with the registration class being the
root class of the system. In the case of an in-process server the Eiffel system
is compiled into a DLL whereas in the case of an out-of-process server, it is
compiled in a standard executable.
The Common folder comprises code that is used for both the server and the
client parts. The Include and CLib directories contain
respectively the header files and the C and C++ code. The C and C++ sources are
not needed and can be deleted, only the library file needs to be kept for the
Eiffel system to compile. The Interfaces subdirectory include Eiffel
classes corresponding to the component interfaces and the Structures
subdirectory includes Eiffel classes wrapping data structures specified in the
definition file.
Class Hierarchy
The generated Eiffel code reflects the architecture of the component described
in the definition file. Each interface corresponds to a deferred Eiffel class
that includes one deferred feature per interface function. The deferred
features are implemented in the heir inheriting from all interfaces. This
central class will be referred to as Eiffel coclass in the rest of this
document.
EiffelCOM System Basic Architecture
|
In a client application the Eiffel coclass inherits from the class ECOM_QUERIABLE,
which is part of the EiffelCOM library. This class includes the feature make_from_other
to initialize the component from another instance of ECOM_INTERFACE.
EiffelCOM System Basic Architecture
|
In a server application the Eiffel coclass inherits from the class
ECOM_STUB,
which is part of the EiffelCOM library. This class includes the feature create_item
to initialize the component for COM.
The Interface_proxy folder includes Eiffel classes wrapping interfaces
that are sent to or received by the component. Such interfaces will be referred
to as client implemented interfaces in the rest of the document. These classes inherit
from both the deferred interface class and ECOM_QUERIABLE.
Client Implemented Interfaces
|
The Interface_stub folder includes Eiffel classes implementing interfaces
that are sent by the component. Such interfaces will be referred
to as server implemented interfaces in the rest of the document. These classes inherit
from both the deferred interface class and ECOM_STUB.
Server Implemented Interfaces
|
For both Eiffel coclass and Implemented interfaces,
the INTERFACE class contains no implementation; it only defines the
signatures of the functions that are part of the interface. The actual
implementation lies in the heir of that class.
How you should use the generated classes in your system depends on whether you
want to access an existing component (client) or build a new component in Eiffel
(server).
ACCESSING A COMPONENT
The wizard generates all the necessary code to access the
existing component. The plumbing is already done, so instantiating an Eiffel
coclass actually initializes COM internals.
Using the Generated Code
To access the component, you need to call features of the coclass. The data
types of function arguments are either Eiffel types defined in Eiffel data
structure libraries (EiffelBase), standard COM data types defined in the
EiffelCOM library, or component COM data types specified in the definition
file. For example, from the following IDL line
HRESULT InterfaceFunction ([in] int a, [out, retval] MyStruct * b)
|
The wizard generates the following feature in the Eiffel coclass:
interface_function (a: INTEGER): MY_STRUCT_RECORD
|
where MY_STRUCT_RECORD is a generated Eiffel class wrapping MyStruct.
Here is a more difficult example:
HRESULT a_function ([in] IsomeInterface * p_interface)
The wizard generates the following Eiffel feature:
a_function (p_interface: ISOME_INTERFACE_INTERFACE)
where ISOME_INTERFACE_INTERFACE is a generated
deferred class.
Where can you obtain an instance of the class? First, you may receive from other
function. Second, you can create an implemented server interface ISOME_INTERFACE_IMPL_STUB.
In the later case you should provide your own implementation.
Contracts
The wizard cannot generate fully specified contracts. Indeed, the tool has
no domain specific knowledge and can only generate contracts that are domain
independent. Such contracts, although useful, are not enough to describe
entirely the behavior of the component. Generated contracts include void Eiffel
objects as well as C pointer validity (for wrappers) checking. There might be
other conditions to allow calls to an Eiffel coclass feature. Invariants and
postconditions can be enforced in an heir of the generated Eiffel coclass.
Preconditions, however, cannot be strengthened. A workaround provided by the
wizard is to generate a precondition function for each feature in the interface.
The default implementation of these functions always returns True. They
should be redefined to implement the correct behavior:
interface_function (a: INTEGER): MY_STRUCT
is
require
interface_function_user_precondition: interface_function_user_precondition
do
...
ensure
non_void_my_struct:
Result
/=
Void
end
So the complete class hierarchy for an Eiffel client coclass is the following:
EiffelCOM Client System
|
Another advantage of the previous hierarchy is that it adds incrementality to
the EiffelCOM system. Indeed, should the definition file be modified and the
wizard run once more against it, your code would not be changed. Only the
generated Eiffel coclass would be, and it would suffice to adapt your heir
accordingly.
Exceptions
COM standard requires that any interface function
returns a status value (known as a HRESULT). This corresponds to side effect
features which the Eiffel methodology tends to avoid. The workaround used in
EiffelCOM systems is to map these return values into Eiffel exceptions. If the
server returns an error code, the EiffelCOM runtime raises an Eiffel exception
that your code should catch.
EiffelCOM Client System Exception Raising
|
As a result, any feature in the coclass client
making calls to the user defined Eiffel coclass should include a rescue clause.
The processing done in this clause might depend on the nature of the exception.
All the standard COM exceptions can be found in the library class ECOM_EXCEPTION_CODES,
which is inherited from by ECOM_EXCEPTION. The later also inherits
from the kernel class EXCEPTIONS and can consequently be used by the
coclass client to catch the exceptions.
The following code snippet illustrates how a client can process exceptions raised in the Eiffel coclass:
indexing
description: "Eiffel coclass client example"
class
COCLASS_CLIENT
inherit
ECOM_EXCEPTION
export
{NONE} all
end
create
make
feature
{NONE}
make
is
do
create
coclass.make
end
feature
coclass_feature_client
is
local
retried: BOOLEAN
coclass: EIFFEL_COCLASS_PROXY
do
create
coclass.make
if
not retried
then
coclass.coclass_feature
end
rescue
if
hresult = E_notimpl
then
retried := True
retry
elseif
hresult = E_invalidarg
then
retried := True
retry
else
end
end
end
Summary
There are a few rules to follow
when building an Eiffel coclass client, but they are straightforward and do not
add any constraints. First, inherit from the generated Eiffel coclass to
implement the preconditions. Second, any feature call to the Eiffel coclass
should include a rescue clause.
BUILDING A COMPONENT
The EiffelCOM wizard enables the development of COM
components in Eiffel. To build a COM component you can start either from an
IDL file or from an Eiffel project. In the later case the wizard generates
an IDL file for you.
Choosing an Eiffel Class
If you start building a COM component from an Eiffel system,
you need to give an Eiffel class to the wizard. The wizard translates the
Flat-Short form of the class into an IDL file, which is a formal specification
of the COM component. Clients of the component are able to access only features
specified in the IDL file. You need to supply the wizard with a Facade class
that provides clients with a higher-level, single interface (do not confuse with
COM interface) to the facilities of a system.
The Facade class should satisfy the following requirements:
- It should have a creation routine "make" without
arguments. The wizard does not enforce the rule, but breaking it
causes a runtime error.
- Features of the class may only have arguments and return values of
the following types: CHARACTER, INTEGER, REAL, DOUBLE,
BOOLEAN, INTEGER_REF, BOOLEAN_REF, REAL_REF,
CHARACTER_REF, DOUBLE_REF, STRING, DATE_TIME,
ECOM_CURRENCY, ECOM_DECIMAL, ECOM_UNKNOWN_INTERFACE,
ECOM_AUTOMATION_INTERFACE, and CELL and ECOM_ARRAY
of above types. Features with arguments or return values of other
types are excluded from the generated IDL file and are not accessible
to the COM runtime.
- Feature names should not be C++ keywords or names of standard
library functions such as min, max, etc. The wizard is able to handle
this correctly and to create a COM component; however, other languages
may not be able to access it.
- The class must belong to a compiled Eiffel project.
In most Eiffel systems functionality is spread out throughout many
classes. A system may not contain a class that can render all services of
the system and can serve as a Facade to the outside world. If you cannot
find such a class you should write one.
Using the Generated Code
If you start from an Eiffel
project, the wizard produces a ready-to-use component, and you do not need to
modify or to implement any generated code.
If you start from a COM definition file, you are able to
design a more flexible component that has more than one interface and/or coclass
and user defined types, but you have to implement features of the generated
coclass. The generated Eiffel coclass features are empty. You should redefine
them in heir to implement the intended behavior. Unlike client generated code,
the server generated code will differ whether you have chosen to implement an
in-process or an out-of-process component. The difference lies in the component
activation code in the class ECOM_<Name_of_system>_REGISTRATION. If the
component is in-process then this class includes the four functions that need to
be exported from an in-process COM component (DllRegisterServer, DllUnregisterServer,
DllGetClassObject, and DllCanUnloadNow). If the component is
out-of-process then the registration class includes a feature initializing the
component and its graphical user interface.
The architecture of generated code
for the server is similar to the one for the client: the generated Eiffel
coclass should be inherited from and the contract features redefined. The
default feature implementation of the generated Eiffel coclass is empty.
Features should be redefined to implement the intended behavior. These features
will be called by the EiffelCOM runtime whenever a client accesses an interface.
The architecture remains the same as when accessing a component: the generated Eiffel coclass should be inherited from and the contract features redefined. The default implementation for features from the generated Eiffel coclass are empty. They should also be redefined to implement the intended behavior. These features will be called by the EiffelCOM runtime whenever a client access an interface.
Note: For this first release, the name of the user defined coclass has to be <Name_of_generated_coclass>_IMP. So if the generated coclass name is MY_COCLASS then the user defined coclass name must be MY_COCLASS_IMP.
|
Component's GUI
In the case of an out-of-process server, you might want
to add a Graphical User Interface to your component. There are two different
scenarios in which the component can be activated: either its user launched it
explicitly (e.g. by double clicking the executable icon) or it was launched by
the COM runtime to satisfy a client request. The GUI should appear only in the
former case, when the user has explicitly launched the application. The
generated registration class for an out-of-process server includes the feature:
main_window: WEL_FRAME_WINDOW
|
This feature is a once function that can be
redefined in a child class to return the class corresponding to the component
window. This window is displayed only if COM does not start the component. When
COM loads an out-of-process component, it appends the option
"-embedding" to the executable. The generated registration class
looks for this option and if it is part of the process argument list then it
sets the default window appearance to hidden.
As a summary, when building a server from a COM definition you need to
implement classes that inherit from coclasses and implement interface functions.
The names of the children classes should be the names of the parent classes
appended with _IMP. You will also have to inherit from the registration
class in the case of an out-of-process component to provide the class that
implements the component GUI.
Exceptions
The COM provides error status to the client by returning
an HRESULT from the interface function. Such behavior is not acceptable in
Eiffel and is replaced with exceptions. In the case of accessing an existing
component, EiffelCOM runtime will raise exceptions with error code 24 (developer
exception) and your code should catch
them. When creating a component it will be your code that will raise exceptions
and the EiffelCOM runtime that will catch them. Here is what the Eiffel code for
a server should look like:
indexing
description: "Eiffel coclass server example"
class
ECOM_SERVER_COCLASS_IMP
inherit
ECOM_SERVER_COCLASS
ECOM_EXCEPTION
export
{NONE} all
end
feature
coclass_feature (an_argument: ARGUMENT_TYPE)
is
do
if
not is_valid (an_argument)
then
trigger (E_invalidargument)
else
end
end
feature
{NONE}
is_valid (an_argument: ARGUMENT_TYPE): BOOLEAN
is
do
end
end
This class inherits from the generated Eiffel coclass and from ECOM_EXCEPTION.
It redefines the feature coclass_feature from the generated coclass. This
feature is part of the interface functions that can be called by clients of the
component. Its implementation uses the feature trigger from ECOM_EXCEPTION to
raise exceptions in case the feature cannot be executed normally (invalid
argument e.g.). The EiffelCOM runtime catches the exception and maps it into an
HRESULT that is sent back to the client.
Summary
Implementing EiffelCOM
components consists in inheriting from the generated Eiffel coclasses and
implementing their features. The only specific rules to follow relate to the
redefinition of precondition features and the use of exceptions to return
error status to the client. In the case of an out-of-process server, the
registration class should be inherited from and the feature corresponding to
the component window redefined to return the correct class.
|