The Design of a COM-Oriented Module System

arXiv:cs/0405083v1 [] 23 May 2004

Riccardo Pucella
Department of Computer Science
Cornell University
riccardo@cs.cornell.edu

Abstract. We present in this paper the preliminary design of a module system
based on a notion of components such as they are found in COM. This module
system is inspired from that of Standard ML, and features first-class instances of
components, first-class interfaces, and interface-polymorphic functions, as well
as allowing components to be both imported from the environment and exported
to the environment using simple mechanisms. The module system automates the
memory management of interfaces and hides the IUnknown interface and QueryInterface mechanisms from the programmer, favoring instead a higher-level approach to handling interfaces.

1 Introduction
Components are becoming the principal way of organizing software and distributing
libraries on operating systems such as Windows NT. In fact, components offer a natural
improvement over classical distribution mechanism, in the areas of versioning, licensing
and overall robustness. Many languages are able to use such components directly and
even dynamically. On the other hand, relatively few languages are able to directly create
components usable from any language, aside from the major popular languages such as
C, C++ or Java.
Interfacing components in standard programming languages has some drawbacks
however. Since component models typically do not map directly to the large-scale programming mechanisms of a language, there is a paradigm shift between code using
external components and code using internal modular units. Similarly, the creation of
components in standard programming languages is not transparent to the programmer.
Specifically, converting a modular unit of the programming language into a component
often requires a reorganization of the code, especially when the large-scale programming mechanisms are wildly different from the component model targeted.
One direction currently pursued to handle the complexity and paradigm shift of using components in general languages is to avoid the problem altogether and focus on
scripting languages to “glue” components together and sometimes even create components in a lightweight fashion, by simple composition. This approach is useful for small
tasks and moderately simple programs, but does not scale well to large software projects
where the full capabilities of a general language supporting large-scale programming
structures is most useful.
A modern general language for programming in a component-based world should
diminish the paradigm shift required to use components versus using the language native large-scale programming mechanisms. Moreover, it should be possible to reason

about the code, by having a reasonable semantic description of the language that includes the interaction with components.
We explore in this paper the design of a language to address this issue. We tackle
the problem by specifying a language that uses a notion of components as its sole largescale programming mechanism, both external components imported from the environment and internal components written in the programming language. An internal component can be exported to the environment as is. The model of components on which
the system is based is the COM model. Our reasons for this were both pragmatic and
theoretical. Pragmatically, COM is widely used and easily accessible. Theoretically, it
is less object-oriented than say CORBA [22], and one of our goals is to explore issues
in component-based programming without worrying about object-oriented issues. Our
proposed module system subsumes both the IUnknown interface and the QueryInterface
mechanism through a higher-level mechanism based on signature matching.
We take as our starting point the language Standard ML (SML) [20]. SML is a
formally-defined mostly-functional language. One advantage of working with SML is
that there is a clear stratification between the module system and the core language. For
our purposes, this means that we can replace the existing module system with minor
rework of the semantics of the core language. Moreover, the SML module system will
be used as a model in our own proposal for a component-based module system. Note
that this is not simply a matter of implementing COM in SML, using the abstraction
mechanisms of the language. We seek to add specific module-level capabilities that
capture general COM-style abstractions.
This paper describes work in progress. The work is part of a general project whose
goals are to understand components as a mean of structuring programs, at the level of
our current understanding of module systems, and to provide appropriate support for
components in modern programming languages.

2 Preliminaries
In this section, we review the details necessary to understand the module system we are
proposing. We first describe the COM approach to component architectures, since our
module system is intended to model it. The description is sketchy, but good introductions include [27, 2] for COM-specific information, and [31] for general componentoriented information. We then describe the current module system of SML, since it
provides the inspiration and model for our own module system.
2.1 Components à la COM
COM is Microsoft’s component-based technology for code reuse and library distribution [27, 19]. COM is a binary specification, and relies on a small number of principles.
The underlying idea of COM is that of an interface to an object, where an object is
just an instance of a component. An interface is a view of a component. Given a COM
object, it is possible to query the object to see if it provides the given interface. If the
object indeed provides the interface, it returns a pointer to the interface, and through this
pointer it is possible to invoke the methods implemented by the interface. Specifically,
2

an interface is simply a pointer to a table of function pointers (called a vtable), one for
each method of the interface.
The identification of components and interfaces is done via globally unique identifiers: A component is identified by a class identifier (CLSID), and an interface by an
interface identifier (IID). It is important to note that the CLSID of a component is part
of its formal description. When an application registers a component with the system
(so that other applications can use it), it adds the CLSID of the component to a system
database. Similarly, an interface identifier is associated formally and permanently with
a given interface. To use a COM component, one need the CLSID of the component,
and the IID of an interface of the component. For example, the Win32 function CoCreateInstance expects a CLSID and an IID to create an instance of the component with
that CLSID, and returns a pointer to the specified interface (it fails if no such interface
is defined).
An interface can inherit from another interface. An interface A that inherits from
interface B simply specifies that B’s methods appear before the method specified by A
in the vtable of the interface. It really is interface inheritance — not a word is said about
implementation, which need not be shared by A and B.
A special interface is defined by the COM standard. This interface, IUnknown, is
required to be inherited by every other interface. The interface (simplified for our purposes) is defined as follows in IDL1 :
interface IUnknown {
HRESULT QueryInterface ([in] const IID& iid,
[out] void **ppv);
unsigned long AddRef ();
unsigned long Release ();
}

Since IUnknown is inherited by every interface, every interface must defines those functions. They are the heart of the COM technology. The idea behind QueryInterface is that
the programmer, having created an instance of a component and obtained a given interface A, can use the method QueryInterface of A to obtain another interface to the given
instance. Various requirements are made of QueryInterface, summarized as follows:
1. Querying for IUnknown form any interface of a given component always returns
the same pointer.
2. From any interface on a component, it is possible to query for any other interface
provided by the component.
Point 1 is important because it defines the notion of object identity. The requirement
is that no matter which interface to a given instance one is working with, querying that
interface for the IUnknown interface is guaranteed to return a specific pointer, always
the same no matter what interface was used to call QueryInterface. Therefore, if querying for IUnknown from two distinct interface yields the same pointer, one is sure that the
two interfaces are actually to the same instance. Point 2 ensures that all the interfaces
of an instance are accessible from any interface of the instance.
1

IDL is an interface definition language, a notation used to describe interfaces. It essentially
uses the C notation for types, augmented with attributes.

3

signature PEANO_SIG = sig
type N
val zero : N
val succ : N -> N
end
structure Peano : PEANO_SIG = struct
type N = int
val zero = 0
fun succ (n) = n+1
end
Fig. 1. Peano arithmetic

The two final methods in IUnknown, AddRef and Release, are used for memory
management of interfaces. COM implements a reference-counting scheme to manage
components. AddRef is called whenever a new pointer to an interface is created, incrementing the reference count of the interface. Release is called when a pointer to an
interface is not to be used anymore (for example, before the pointer variable goes out
of scope), and simply decrements the reference count. When the count goes to 0, the
memory associated with the interface can be freed by the system. Although greatly simplifying memory management, correctly using AddRef and Release to prevent memory
leaks and dangling pointers to interfaces is not easy, and the burden of safety is put on
the programmer.
Containment and aggregation are two ways of combining and reusing components.
Containment is straightforward: a component C1 (outer) is said to contain a component
C2 (inner) if C1 uses C2 in its implementation. In other words, C1 is a client of C2 . The
only requirement for containment is that upon initialization, the outer component should
initialize the inner component. Aggregation is specific to COM, and can best be seen
as an optimization of containment. Suppose the outer component C1 wants to expose
an interface actually implemented by the inner component C2 . Using containment, C1
would need to define the interface and implement every method call by calling the inner
component’s interface. The inefficiency introduced by such indirection is slight, but if
many such interface get redirected, the inefficiency accumulates. Aggregation is a mean
of directly exposing the interfaces of inner components through to the outer component.
An important property of aggregation concerns object identity: the inner component
should not be recognizable as a distinct component. Therefore, both the inner and the
outer component must return the same pointer when a query is made for IUnknown.
2.2 Modules à la SML
Having presented the COM framework, and delineated the target of our proposed module system, let us review the basics of the SML module system [16], our underlying
model. It is not necessary for the reader to have a deep knowledge of SML to understand
this presentation. It is sufficient to know that SML is a mostly-functional language,
with first-class functions and a polymorphic type system which is statically checked:
4

programs that do not type-check at compile-time are flagged as such and rejected. Excellent introductions to SML are available [23, 32, 8].
The basic elements of the SML module system are structures, signatures and functors. A structure is a package of possibly both types and values into a single unit. A
signature is the “type” of a structure. Consider the example in Figure 1, a simple structure defining Peano arithmetic with its corresponding signature. The structure defines
a type N of Peano integers, a value for zero and a function succ. The structure defines
a Peano integer to simply be an integer, and the zero and successor to be simply 0 and
+1. The signature PEANO explicitly specifies the types and values that are visible outside the structure. A signature matches a structure if the signature consistently denotes
a subset of the types and values of the structure. Matching a structure with a signature
declaring less information than the structure is called signature ascription. Suppose one
wanted to define a structure like Peano but that did not have a successor function. One
could use signature ascription to control the visibility, as in
structure Peano2 : sig
type N
val zero : N
end = Peano

This example also illustrate signature matching by inlining a signature description instead of using a named signature. In SML, signature matching is by default transparent:
although signature ascription can weed out declarations, it does not hide the representation of the types. For example, the implementation of Peano uses integers to represent
the type N. Although the signature does not specify the representation type, the system
will still accept
3 + (Peano.zero)

as well-typed. In effect, the type N is viewed as an abbreviation for the type integer. In
contrast, opaque matching (using the matching symbol :>) completely hides whatever
information is not specified in the signature, including representation types. The above
sample would then fail to type-check.
A functor is a parametrized structure. Suppose one wanted to write a structure defining elementary algebraic operations on the integers using Peano arithmetic. Since one
may have multiple implementation of Peano arithmetic, the simplest way would be to
parameterize the structure as follows:
functor AlgOpFun (structure P : PEANO_SIG) : sig
...
end = struct
...
end

which declares the functor AlgOpFun to take a structure P matching signature PEANO SIG
as parameter and creating a new structure using structure P in its body. Instantiating a
functor is simple:
structure AlgOp = AlgOpFun (structure P = Peano)

5

3 Design of the module system
After reading about the SML module system, one recognizes a strong similarity between the notion of a structure and the notion of the instance of a component2. A functor with no argument can be seen as a component, with the generated structure corresponding to an instance, and the notion of containment and aggregation bear a strong
resemblance to functors with parameters. Of course, this preliminary intuition does not
take into account interfaces and their behavior under the QueryInterface mechanism.
In this section, we introduce a module system based on the SML module system,
and providing the notions of components and interfaces. We impose the following design criteria on our design of the system:
1. Component instances provide interfaces.
2. Interfaces provide both types and values.
3. Component instances and interfaces are first-class, that is they can be passed to and
returned from functions, and stored in data structures.
4. Memory management of interfaces is hidden from the programmer.
5. The QueryInterface mechanism is subsumed by syntax.
6. Syntactically and operationally, there is no distinction between internal and imported components.
7. Exportable components are easily characterized and general mechanisms are used
to make a component exportable.
Criteria 1–2 define the “architecture” of the module system, the relationship between components, interfaces and the core language. Criterion 3 is required if we want
to emulate pointer-based interface manipulation. Criteria 4–5 are important to ease the
use of the system: memory management under COM, although easier than it could be,
is still fragile in that the user is responsible for managing reference counts explicitly
(in practice, languages like C++ encourage the use of smart pointers to alleviate most of
the burden). The QueryInterface mechanism is powerful, but very low-level. We can
take advantage of patterns of use and provide a high-level mechanisms for accessing
interfaces. Finally, criteria 6–7 are mandated by the fact that the module system will be
used as the large-scale programming mechanism of the language. There should be no
difference between code using an internal component versus an imported component. It
is clear that not every Core SML type is exportable (since the interfaces must at the very
least be expressible in IDL to be exportable), so restricting the notion of component to
what can be meaningfully exported is too restrictive for components that we don’t want
to export, that are only used internally. A simple and elegant way to support exportable
components and unrestricted internally-used components is a must for a truly usable
system. We use signature ascription to achieve this.
3.1 Components and interfaces
Let us give a quick overview of the basic elements of the module system. A component
is defined as providing a set of interfaces. A component has a signature, which assigns
2

Especially if one adheres to the Edinburgh school, which advocates the creation of structures
exclusively through the application of functors with no arguments.

6

interface_sig X_SIG = {
val fooX : unit -> unit
}
interface_sig Y_SIG = {
val fooY : unit -> unit
}
component_sig FOO_SIG = {
interface X : X_SIG
interface Y : Y_SIG
}
component FooComp () : FOO_SIG = {
interface X = {
fun fooX () = print "fooX"
}
interface Y = {
fun fooY () = print "fooY"
}
}
Fig. 2. Simple component example

interface signatures to its interfaces. An interface defines types and values (including
functions). An interface signature is simply the “type” of an interface. Signature ascription can be used to thin out interfaces from components or types or values from
interfaces. At the present time, we require signatures to be named. Component definitions are generative: one needs to instantiate a component to use it.
Let us illustrate with a simple example, presented in Figure 2. To use component
FooComp, one first instantiates it by
val Foo = FooComp ()

and accessing its elements is done using the dot notation, so that Foo.X.fooX ()
prints fooX. Interfaces are first-class, so it is possible to bind an interface, as in
val FooX = Foo.X

which corresponds to accessing the X interface of Foo. The type of an interface is simply the name of its signature, surrounded by || · · · ||, so that Foo.X has type ||X SIG||.
Similarly, component instances are first-class, and their type again is simply the name
of their signature, surrounded by | · · · |, so that Foo has type |FOO SIG|.
As a last remark, we mention that signature matching is opaque. If one wants to
carry representation types through a signature, one needs to explicitly give the representation types in the signature, as in [9, 12].
7

3.2 Parametrized components
As the notation for component declarations suggests, every component is parametrized.
In Figure 2, FooComp was a nullary component, a component with no parameters
(hence the () in the declaration of FooComp). Here is a sample parametrized component:
component BarComp (val X : ||X_SIG||
val Y : ||Y_SIG||) : BAR_SIG = {
...
}

where BarComp is parametrized over interfaces matching X SIG and Y SIG. A simple
instantiation would be
val Bar = BarComp (val X = Foo.X
val Y = Foo.Y)

passing in the corresponding interfaces from the Foo instance of FooComp.
3.3 Importing and exporting components
One key aspect of the COM framework is the possibility of accessing components written in different languages, and conversely, of providing components that can be accessed by different languages. Let us first see how to import a component in our system. We need a way to define an interface that is imported from the environment. This
is done through an interface signature as in the previous cases, except that we need to
specify the IID of every interface being imported.
interface_sig IMPORTED_IFC_SIG = {
...
} with_iid 00000000-0000-0000-0000-000000000000

The requirement being that the signature of an imported component must specify an
IID for each interface.
Once all the interfaces that are part of the component to be imported are specified
with their IID, we can import the component from the environment:
import ImportedFooComp : FOO_SIG = clsid
00000000-0000-0000-0000-000000000000

where the component signature FOO SIG specifies the interface signatures of the imported component. The component is imported through its class identifier (CLSID).
The component so imported can be instantiated just like a native nullary component.
Note that interface negotiation is done up-front: when a component is instantiated, it is
checked that all the interface specified in the signature are present.
The converse of importing a component is to export a component. When exporting
a component a program becomes a component server from which clients can create
and instantiate components. Given a component BarComp, one exports it using the
declaration:
export BarComp : BAR_SIG with_clsid
00000000-0000-0000-0000-000000000000

8

The class identifier specified must be a new GUID, as is the rule in COM programming3 . The component to be exported must be a nullary component. The component
signature must again specify interface signatures with interface identifiers.
In order for the exported component to be a valid COM component, its interface
must at least be expressible in IDL. As we are using Core SML as our core language, we
characterize the SML types that can be naturally represented in IDL via a suitable mapping. One possible definition follows: we say that a type τ is IDL-expressible if either
of the following holds: τ is int, bool or real; τ is a record type with all field types IDLexpressible; τ is an algebraic datatype with the alternative types all IDL-expressible; τ
is a list with an IDL-expressible element type; τ is a component signature; or τ is an
interface signature. An interface signature I is IDL-expressible if every type it defines
is IDL-expressible and if for every value v of type τ , either of the following holds: τ is
int,bool or string; or τ is a function type of the form τ1 → τ2 with τ1 and τ2 either unit,
IDL-expressible or tuples of IDL-expressible types.
A key feature of the design is that at export time, one can use signature ascription
to keep only the portions of a component which are IDL-expressible. The component
itself is fully usable from within the language, while the restricted version is usable
from without. This still requires the programmer to possibly partition the interfaces into
those that are intended to be exported and those that are not, but at least the underlying
framework is the same, and moreover the implementation can be shared across the
interfaces.

3.4 Dynamic interface negotiation
The mechanism of section 3.3 for importing components assumes that the interface
negotiation is done up-front, when the component is instantiated. Clearly, this cannot
cover all cases of interest: one may want to use a component that can be either of two
versions, both containing an interface A, but one containing an “optimized” version
of the interface, called A′ . Clearly, one should try to use interface A′ if it is available,
otherwise downgrade to using A. To do this, we introduce a notion of dynamic interface
negotiation to handle components in a way compatible with other languages.
We provide an interface case construct to dynamically query a component instance
for a given interface:
ifc_case x
of FOO => ...
| BAR => ...
else => ...

This form evaluates to the appropriate expression if instance x supports any of FOO
or BAR, or if none of the cases apply. To fully benefit from this construct, it is useful
to introduce a primitive operation instanceOf. Given any interface A, instanceOf A
returns the underlying component instance of the interface.
3

We do not specify how such GUIDs are created. Presumably through the programming environment.

9

4 Discussion
The main question with respect to the preliminary design of our module system, as
described in the previous section, is the appropriateness of the model to capture COMstyle component-based programming as it exists.
First, note that the system subsumes, at least at this point, a module system like
SML’s, modulo an extra level of indirection. A structure can be seen as an instance with
a single interface, and functors are just components. Although the design forces us to
write all structures as functor applications, and we need to access the code indirectly
through the interface, one could imagine syntactic sugar that would give modules an
SML flavor.
Having first-class component instances and interfaces provides most of the flexibility needed to handle common COM-style programming in a type-safe manner. For
example, first-class interfaces allow the definition of interface-polymorphic functions,
functions which are polymorphic over a given interface type. One can for example define a function of type
val foo : ||FOO_INTERFACE|| -> int

that can be applied to any interface matching the FOO INTERFACE interface signature.
Any instance of a component with such an interface can be used with that function (by
passing in the interface). Since it is always possible to extract the underlying instance
of an interface, one can also return interfaces while keeping a handle on the underlying
instance. One could imagine a more advanced type system that would record not only
the type of interface required by such a function, but also the set of interfaces that are
accessed from that interface. We leave this investigation for the future. We can similarly
define component-polymorphic functions, where one can moreover use the subtyping
relation on components induced by signature ascription to define say functions to act
on any component providing interface FOO and BAR.
Regarding the suitability to interact with the COM framework through imported and
exported component, the basic notions are by design compatible. The hiding of IUnknown and of QueryInterface greatly simplifies both the design and the code. Memory
management is hidden from the user, using a combination of automatically generated
underlying calls to AddRef and Release. and reliance on garbage collection and finalization, in the style of the Direct-to-COM compiler [7].
We have not yet carefully investigated the issues of containment and aggregation. As
we mentionned earlier, those composition mechanisms have a flavor of functor application, but the match is not exact. One can write a parametrized component, but the parameterization cannot be over another component (component instances are first-class,
but not components themselves). A component can be parametrized over an instance,
but this then implies that a component has to be instantiated before being used as a
parameter to another component. One could solve this problem by making components
higher-order, allowing them to be used as arguments to parametrized components, or
returned from such. Higher-order components correspond to higher-order functors in a
SML-style module system, which greatly complicate the module system theory, especially in regard to the interaction with types [17, 9, 13]. With higher-order components,
10

one could provide syntactic sugar for convenient aggregation and containment mechanisms. However, when such components are exported, the issues raised by Sullivan et
al [30, 29] with regard to the rules required to ensure the legality of COM components
arise, and need to be addressed. This is another area of future work.
A word about implementation is in order. We use two different implementation
mechanisms, one for internal components, one for imported components, along with
specific handling for exported components. Internal components are handled in more or
less the same way modules are handled in current SML implementation, modulo firstclass interfaces and component instances. Imported components rely on the underlying
CoCreateInstance for creation, and QueryInterface for access to interfaces. Instances
of imported components are represented by their IUnknown interface pointers, allowing for equality checking. For exported components, the generation of the appropriate
layout of the vtables can be done on the fly, at export time.

5 Related work
A lot of work has been done independently on both module systems and component systems, but none quite taking our approach. Most work in programming language support
for components consists of providing access to components from within a language’s
abstraction mechanism. For example, Java [6] and C++ [28] both map components onto
classes. Similarly, functional approaches pioneered by Haskell [24, 25, 3, 4] and then
used in OCaml [15, 14] and SML [26] rely on a combination of abstract types and
functions to interface to components. One can write classes implementing components
in Java and C++, and using the functional approach in Haskell, but the notions do not
match exactly. Our approach to studying the problem is to express components with a
language explicitely geared towards talking about components and interfaces.
The closest language in spirit to our effort is Component Pascal [21], a language
providing extensible records that has been used to implement COM components. However, the problem is again that there is a paradigm mismatch between the structuring
mechanisms of the language and components. Component Pascal is a modular language
for writing components, but components themselves are not the modularity mechanism
of the language.
COMEL [10, 11] was our original inspiration for this work. The mechanism of hiding IUnknown and QueryInterface are similar, but the difference is that COMEL is a
small language meant for a study of the formal properties of the COM framework, while
our proposal is for an extension of an existing language to support component-based
code structuring mechanisms.

6 Conclusion
We have presented in this paper a preliminary design for a module system that directly
incorporates the notions of components and interfaces as defined in COM. The design
is rough, but the basic idea is clear. The system can subsume a powerful module system
such as SML’s, and is directly compatible with COM’s model of the world. Work is
now needed to complete and evaluate the design. Aside from the issues raised in the
11

text concerning aggregation and higher-order components, we are working on a formal
semantics for the module system, both a static semantics (types and their propagation)
and a dynamic semantics (the execution model and interaction with the COM runtime
system). The implementation has to be completed and systems built using it.
Finally, and this is once again future work, it is of great interest to investigate the
relationship between our approach and the approach supported by structuring mechanisms such as units [5] and mixins à la Jigsaw [1].
Acknowledgments. Thanks to Greg Morrisett for many discussions on module systems and COM. Thanks to Peter Fröhlich for a thorough reading of the paper and many
comments that helped shape the presentation. Thanks to Dino Oliva, Erik Meijer and
Daan Leijen for early discussions about various aspects of COM. Thanks as well to
John Reppy for general inspiration.

References
1. G. Bracha. The Programming Language Jigsaw: Mixins, Modularity and Multiple Inheritance. PhD thesis, Department of Computer Science, University of Utah, 1992.
2. D. Chappell. Understanding ActiveX and OLE. Microsoft Press, 1996.
3. S. Finne, D. Leijen, E. Meijer, and S. Peyton Jones. H/Direct: A binary foreign language
interface for Haskell. In Proceedings of the 1998 ACM SIGPLAN International Conference
on Functional Programming, pages 153–162. ACM Press, 1998.
4. S. Finne, D. Leijen, E. Meijer, and S. Peyton Jones. Calling hell from heaven and heaven
from hell. In Proceedings of the 1999 ACM SIGPLAN International Conference on Functional Programming, pages 114–125. ACM Press, 1999.
5. M. Flatt. Programming Languages for Reusable Software Components. PhD thesis, Rice
University, 1999.
6. J. Gosling, B. Joy, and G. Steele. The Java Language Specification. Addison Wesley, 1996.
7. D. Gruntz and B. Heeb. Direct-To-COM compiler provides garbage collection for COM
objects. In Proceedings of the 2nd Component User’s Conference (CUC), July 1997.
8. R. Harper.
Programming in Standard ML.
Online tutorial notes available from
http://www.cs.cmu.edu/∼rwh/introsml/index.html, 1998.
9. R. Harper and M. Lillibridge. A type-theoretic approach to higher-order modules and sharing. In Conference Record of the Twenty-First Annual ACM Symposium on Principles of
Programming Languages. ACM Press, 1994.
10. R. Ibrahim and C. Szyperski. The COMEL language. Technical Report FIT-TR-97-06, Faculty of Information Technology, Queensland University of Technology, Brisbane, Australia,
1997.
11. R. Ibrahim and C. Szyperski. Can Component Object Model (COM) be formalized? — the
formal semantics of the COMEL language. Work-In-Progress, IRW/FMP’98. Also appears
as Technical Report TR-CS-98-09, The Australian National University, 1998.
12. X. Leroy. Manifest types, modules, and separate compilation. In Conference Record of
the Twenty-First Annual ACM Symposium on Principles of Programming Languages, pages
109–122. ACM Press, 1994.
13. X. Leroy. Applicative functors and fully transparent higher-order modules. In Conference
Record of the Twenty-Second Annual ACM Symposium on Principles of Programming Languages, pages 142–153. ACM Press, 1995.
14. X. Leroy.
Camlidl user’s manual.
Online manual available from
http://caml.inria.fr/camlidl/, 1999.

12

15. X. Leroy, J. Vouillon, and D. Doligez. The Objective Caml system. Software and documentation available from http://pauillac.inria.fr/ocaml/, 1996.
16. D. MacQueen. Modules for Standard ML. In Conference Record of the 1984 ACM Symposium on Lisp and Functional Programming, pages 198–207. ACM Press, 1984.
17. D. B. MacQueen and M. Tofte. A semantics for higher-order functors. In Fifth European
Symposium on Programming, volume 788 of Lecture Notes in Computer Science, pages 409–
423. Springer, 1994.
18. Microsoft Corporation. Developing for Microsoft Agent. Microsoft Press, 1997.
19. Microsoft Corporation and Digital Equipment Corporation. The Component Object Model
Specification. Draft version 0.9, available from http://www.microsoft.com/com,
1995.
20. R. Milner, M. Tofte, R. Harper, and D. MacQueen. The Definition of Standard ML (Revised).
The MIT Press, Cambridge, Mass., 1997.
21. Oberon Microsystems Inc.
Component Pascal language report.
Available from
http://www.oberon.ch, 1997.
22. Object Management Group. The Common Object Request Broker: Architecture and Specifications (revision 1.2). OMG document 93.12.43, Object Management Group, 1993.
23. L. C. Paulson. ML for the Working Programmer. Cambridge University Press, second edition, 1996.
24. J. Peterson and K. Hammond (Eds). Report on the programming language Haskell, version
1.4. Technical report, Department of Computer Science, Yale University, 1997. Available
from http://www.haskell.org.
25. S. Peyton Jones, E. Meijer, and D. Leijen. Scripting COM components from Haskell. In Proceedings of the Fifth International Conference on Software Reuse. IEEE Computer Society
Press, 1998.
26. R. Pucella, E. Meijer, and D. Oliva. Aspects de la programmation d’applications Win32 avec
un langage fonctionnel. In Actes des Journées Francophones des Languages Applicatifs,
pages 267–291. INRIA, 1999.
27. D. Rogerson. Inside COM. Microsoft Press, 1997.
28. B. Stroustrup. The C++ Programming Language. Addison Wesley, Reading, MA, third
edition, 1997.
29. K. J. Sullivan and M. Marchukov. Interface negotiation and efficient reuse: A relaxed theory
of the component object model. Technical Report 97-11, Department of Computer Science,
University of Virginia, 1997.
30. K. J. Sullivan, J. Socha, and M. Marchukov. Using formal methods to reason about architectural standards. In Proceedings of the International Conference on Software Engineering,
pages 503–513. ACM Press, 1997.
31. C. Szyperski. Component Software. Addison Wesley, 1997.
32. J. D. Ullman. Elements of ML Programming. Prentice-Hall, ML97 edition, 1998.

A

Microsoft Agent

A popular example in the literature concerned with interacting with COM is Microsoft
Agent [18]. It is an interesting example because it is simple yet non-trivial, and allows
for nice demonstrations. Microsoft Agent provides a server in charge of loading little
animated characters that can interact on the screen. The Agent Server is a component
with a single interface, IAgent. Here is first the Agent Server component signature, and
import from the environment.
13

component_sig AGENT_SERVER = {
interface IAgent : I_AGENT
}
import AgentServer () : AGENT_SERVER = clsid
A7B93C92-7B81-11D0-AC5F-00C04FD97575

Here is an extract of the I AGENT interface signature. Note that the method getCharacter returns an AGENT CHARACTER which is as we shall see a component instance
that represent a character that can be animated.
interface_sig I_AGENT = {
val load : string -> (int,int)
val unload : int -> unit
val register : |AGENT_NOTIFY_SINK| -> sinkID
val unregister : sinkID -> unit
val getCharacter : int -> |AGENT_CHARACTER|
...
} with_iid A7B93C91-7B81-11D0-AC5F-00C04FD97575

A character is implemented by its own component, with the following signature. We
concentrate on the IAgentCharacter interface. Other interfaces are available to intercept
and process commands to the characters, but we will not be considering those in this
example.
component_sig AGENT_CHARACTER = {
interface IAgentCharacter : I_AGENT_CHARACTER
}

We do not need to import the corresponding component from the environment, since
the creation of characters is restricted to the getCharacter function of the Agent Server
component. Indeed, the AgentCharacter component does not explicitly exist in Microsoft Agent.
The IAgentCharacter interface is used to control a character, to make it appear,
move about the screen, speak and animate.
interface_sig I_AGENT_CHARACTER = {
val setPosition : int * int -> unit
val getPosition : unit -> (int * int)
val play : string -> int
val stop : int -> unit
val show : bool -> int
val speak : string * string -> int
....
} with_iid A7B93C8F-7B81-11D0-AC5F-00C04FD97575

The simplest example of code using such an interface is the following, which simply
displays an agent for 10 seconds.
14

fun test () = let
val AS = AgentServer ()
val (charId,_) = AS.IAgent.load ("merlin")
val Char = AS.IAgent.getCharacter (charId)
in
Char.IAgentCharacter.show (0);
Char.IAgentCharacter.speak ("Hello world","");
sleep (10000); (* wait for 10000 milliseconds *)
AS.IAgent.unload (charId)
end

We leave the task of defining abstraction and combinators to help dealing with characters in a sane way, in the style of [25], as an exercise to the reader.

15

