# Effective COM 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. * Lessons * __ Here are some lessons I learned, although not necessarily as the authors intended. o Implement a singleton with a static class member. Provide interface methods that use this static member. Do not attempt to return interfaces as singleton instances. (Item 4.) o Forget about using exceptions. Every known emulation is worse than simply checking error codes. You can't throw an exception more than a few lines within a single method, and you'll find it harder to know which resources to release. (Item 5.) o Don't return ``E_NOTIMPL'' from unimplemented methods. Use smaller interfaces that can be implemented completely. (Item 7.) o Don't pass data through ``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.) o Feel free to use a COM interface to negotiate a separate non-COM connection, such as a socket. (Item 8.) o Don't use connection points. Define your own observer and observable interfaces. (Item 9.) o Don't use tear-off interfaces. Clients may get new implementations of an interface when sharing an apartment with your object, but they may get a previous stub if they live in a different apartment. (Item 10, 26.) o Don't use per-interface reference counting. Proxies will not necessarily notify you when individual interfaces are released. (Item 10.) Instead use multiple COM identities with shared state in the implementation. (Item 26.) o With VB script, you're stuck with one interface per object. Forget about polymorphism or type safety. Don't let this environment degrade the rest of your framework design. Only expose the highest-level user functionality for customers who demand scripting. (Item 11.) o Avoid dual interfaces. Don't require them in callbacks (observer interfaces) because you'll call only one of the two implementations anyway. (Item 12.) 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. o Use ``size_is'' when passing arrays, but don't use ``first_is'' or ``length_is''. Instead, pass a shifted array pointer. (Item 13) o Don't pass statically-typed ``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.) o Don't use ``[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.) o Weak references (holding an interface pointer without calling ``AddRef'') won't work across apartment boundaries. The proxy will disappear. (Noticing a pattern here?) (Item 16.) o To avoid circular references, you can instantiate two COM entities in the same apartment. These two communicate without ``AddRef'''s. One will be ``AddRef'''d by third parties, and one will call ``AddRef'' on those same third parties. (Item 16.) o Don't use IDL commands ``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.) o Set ``[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.) o Rediscover how to manage memory robustly in plain C, with MS versions of ``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.) o Set interface pointers to ``null'' immediately after releasing them. Use a macro if necessary. (Item 20.) o Remember that COM interfaces use non-virtual destructors. Before deleting a C++ object that implements a COM interface, you must recast to the original most-derived class. (Item 20.) o Call ``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.) o A constructor for an aggregation might call ``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.) o When passing interface pointers to ``QueryInterface'' or ``Invoke'', always call ``static_cast'' to the correct type, then call ``reinterpret_cast'' to the ``void**'' or ``void*''. (Item 21.) o The long struggle to define ``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.) o Don't implement every object as a COM object, with the unnecessary constraints and overhead. Expose COM interfaces only when "crossing boundaries in your system: distribution boundaries (apartments, processes, and hosts), language boundaries, product boundaries (including third-party products), and logical boundaries encapsulating subsystems and their extensibility points." (Item 25.) o Consider lazy wrapping of numerous C++ objects as COM objects only when requested. (Item 25.) o Avoid mixing ``OLECHAR*'' arrays and ``BSTR''s. The C++ compiler can't distinguish their types. (Item 27.) o You can easily live without COM aggregation and COM containment, although you'll have to deal with bugs in others' implementations. As usual, prefer containment to inheritance. Rather than make a contained interface accessible through ``QueryInterface'', return the interface with a ``getSomeInterface()'' method. (Item 28.) o Keep your life as simple as possible when marshaling objects between apartments. (Avoid tricky optimization.) Expect this part of the COM environment to change soon. o Don't put interface pointers in global variables because they won't be marshaled when accessed from another apartment. Use a ``getSomeInterface'' method or put the global into a ``CreateStreamOnHGlobal'' with a ``CoMarshalInterface'' and extract with ``CoUnmarshalInterface''. (Item 29.) o The creator of a new thread should call ``AddRef'' on passed interface pointers, and the new thread should call ``Release''. (Item 30.) o UI code should run in a STA. The code isn't thread-safe, and it requires an event loop (message pump) to avoid locking the UI during time-consuming callbacks. Worker threads should run in a separate MTA. Messages to the UI should go to MTA objects, which update the UI with messages to the STA when necessary. (Item 31.) o Use an executable server instead of an in-process server if you need to create new threads. Otherwise you will be unable to manage simultaneously the lifetime of your threads and in-process references to your objects. (Item 32.) o The Free-Threaded Marshaler is a difficult optimization that will probably be obsolete by the time you understand how to use it safely. Go ahead and pay for the overhead of proxies between STA and MTA components. (Item 33.) o You still have to think of synchronization issues in a STA. If you call out of a STA and want to receive a reply first, then use a ``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.) o Don't export pointers to in-process objects implemented in different DLL's. You will be unable to prevent the DLL from being unloaded while others hold references to the object. Call ``CoCreateInstance'' with ``CLSCTX_LOCAL_SERVER'' instead of ``CLSCTX_INPROC_SERVER''. (Item 36.) o When an object must shut down abruptly and abandon clients, then it should call ``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.) _ * Editorial * 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