Here are my crib notes for "Effective COM: 50 Ways to Improve your COM and MTS-based Applications" by Don Box, Keith Brown, Tim Ewald, and Chris Sells, from Addison Wesley, 1999.
Remember how Meyer's "Effective C++" book stopped you from using about two-thirds of the gimmicks explained in earlier books? "Effective COM" will stop you from imitating at least 90% of the COM you see elsewhere. Only after reading this book did I feel I had any hope of achieving good style with COM.
Here are some lessons I learned, although not necessarily as the authors intended.
E_NOTIMPL
from
unimplemented methods. Use smaller
interfaces that can be implemented
completely. (Item 7.)
IDataObject
.
For type safety, define a unique interface
for each type of data. To avoid race
conditions, return related pieces of data
together in a structure or from a single
method call. (Item 8.)
Implement IUnknown
exclusively to share
components with other C++ or VB developers.
Implement IDispatch
very selectively for
customers who can't manage anything other
than VB scripting. The book's Epilogue says
"Curse the Visual Basic team for the
abomination that is IDispatch
."
Remember "Visual Basic programmers can still implement externally defined pure vtable and pure dispatch interfaces" although they can only define new interfaces as dual interfaces. It's not your problem.
size_is
when passing arrays, but
don't use first_is
or length_is
.
Instead, pass a shifted array pointer. (Item
13)
IUnknown
object references because proxies and stubs
won't always give you the one you want. Use
iid_is
to specify the exact interface.
(Item 14.)
[in,out]
parameters that
contain pointers because you won't know what
resources to release after a failure. Use
two separate [in]
and [out]
arguments. Rethink how you pass structures
that contain pointers. (Item 15.)
AddRef
) won't
work across apartment boundaries. The proxy
will disappear. (Noticing a pattern here?)
(Item 16.)
AddRef
's. One will be AddRef
'd by
third parties, and one will call AddRef
on those same third parties. (Item 16.)
wire_marshal
,
transmit_as
, call_as
, and
cpp_quote
to alias data types or methods.
They aren't handled properly by type
libraries. (Item 17.)
[out]
parameter pointers to null if
you return an error. (Set *ppThing=0
.)
Stubs will ignore your HRESULT
and
marshal non-null [out]
parameters anyway.
(Item 18.)
malloc
and
free
. A method that safely copies a
string should take a page of code. Check
that callers provide non-null [out]
pointers to pointers. Before returning an
error, free the memory of [out]
parameters. If allocation fails, return
E_OUTOFMEMORY
. (Item 19.)
null
immediately after releasing them. Use a
macro if necessary. (Item 20.)
AddRef
and Release
immediately
before and after a QueryInterface
to get
an interface pointer that you return from a
method. Otherwise, if QueryInterface
fails, the implementer of Release
will
have no opportunity to free resources. (Item
20.)
CoCreateInstance
to create a member with
this
as the controlling IUnknown*
.
Since you can't call Release
in a
constructor, you must increment the internal
counter by hand at the beginning of the
constructor. (Item 20.)
QueryInterface
or Invoke
, always call
static_cast
to the correct type, then
call reinterpret_cast
to the void**
or void*
. (Item 21.)
auto_ptr
showed that heap memory cannot be managed by
scoping rules as if it were stack memory.
You must have read and understood the
implementation of your smart pointer, to know
the limitations. If you are likely to
confuse maintainers of your code, then don't
use smart pointers. (Item 22.)
OLECHAR*
arrays and
BSTR
s. The C++ compiler can't distinguish
their types. (Item 27.)
QueryInterface
, return
the interface with a getSomeInterface()
method. (Item 28.)
getSomeInterface
method or put the global
into a CreateStreamOnHGlobal
with a
CoMarshalInterface
and extract with
CoUnmarshalInterface
. (Item 29.)
AddRef
on passed interface pointers, and
the new thread should call Release
.
(Item 30.)
IMessageFilter
to postpone other calls
and messages. All outward calls are placed
in a new thread so that the message pump can
keep the GUI alive. Unfortunately the
response may arrive after other calls that
want to change the state of your object.
(Item 35.)
CoCreateInstance
with
CLSCTX_LOCAL_SERVER
instead of
CLSCTX_INPROC_SERVER
. (Item 36.)
CoDisconnectObject
to inform the stub to
release references to the object. Proxies
will then return the HRESULT
of
RPC_E_DISCONNECTED
to calls by clients.
(Item 37.)
This book confirms the long-standing belief that MS development tools encourage fragile and unmaintainable code. The code may still compile, but it won't be easy to upgrade or integrate with next year's code. MS will constantly change their recommended patterns, frameworks, and developer tools. The book's epilogue says "Distrust your tools. The COM group produces COM, and various tool groups at Microsoft proceed to butcher it." Remember, clean maintainable code is not necessarily in Microsoft's interest.
You should be able to justify every line typed into your COM code, without a wizard's guidance. Find a safe core subset of COM and don't use clever tricks just because they are explained in every other COM book. If you stick to the safe subset, you may not have to rewrite current code every time you reuse it.
This book, and Don Box's previous book "Essential COM", unfortunately maintain the pretense that COM is a binary standard, independent of language or platform. COM is is none of these. COM is a C++ specification for Microsoft compilers on Microsoft operating systems. With any language other than C++ you are incompatible with the design of COM. With any other compiler or platform, you can forget about binary compatibility. The binary layout of C++ vtables is not a standard. You can use parts of COM in other Microsoft languages, but those languages introduce hacks that look horrible in C++.
Stick to C++ when implementing interfaces for other Microsoft developers, and stick to the cleanest possible C++ style. If you want Java, GNU C++, or other non-Microsoft languages, then use portable connections like sockets or JNI to talk to COM proxies and clients. (J++ isn't Java, and it isn't meant to be maintainable.)
The purpose of COM is to introduce components to each other, not to dictate how they interact or exchange data. The COM specification emphasizes, "COM, like a traditional system service API, provides the operations through which a client of some service can connect to multiple providers of that service in a polymorphic fashion. But once a connection is established, COM drops out of the picture. COM serves to connect a client and an object, but once that connection is established, the client and the object communicate directly without having to suffer overhead of being forced through a central piece of API code..."
Bill Harlan, March 1999
Return to parent directory.