![]() |
|
It should be no secret to anyone that carrying out good (D)COM development is significantly more difficult than programming C++ and Windows applications the classic way. The game gets even more complicated when MTS (Microsoft Transaction Server) comes into play, with its additional burden of intricacies related to the rules and the constraints imposed by the transactional context. This complexity should not be ascribed to the intrinsic difficulty of some esoteric COM aspects; after all, most of them are shared by the other paradigms for distributed and component-based paradigms currently on the cutting edge of the technological wave, namely CORBA and (Enterprise) JavaBeans. The true reason why it all appears so prone to errors is the higher degree of discipline required to benefit from the positive aspects of the paradigm, both in terms of the thought on every design decision made and in the great care required during the coding phase (the latter gains particular importance if you work in C++). I find this necessary addition of thought in even the apparently innocuous and basic tasks beneficial in the long run to the mindset of many badly educated (or lazy, depending on the case) programmers and object-oriented design (OOD) specialists. The dark side of the medal is represented by the steep learning curve that keeps many from really getting familiar with the COM way of thinking and programming in short timeframes.
It is arguable that the tardiness showed by the industry in endorsing the COM message and investing in it has at least part of its motivations in the lack of explanatory and comprehensive documentation on the foundations (conceptual and implementative) of the model. Now that the primary hole has been more or less filled by a decent amount of quality literature, including most notably Essential COM, authored by one of the coauthors of this text and reviewed by DDJs ERCB some months ago, many engineers in the industry are struggling to apply the newly digested paradigm to everyday software projects, often facing unexpected difficulties and uncertainties. The problem lies in the point I made earlier: Many developers have got in touch with COM/MTS and know its theory reasonably well, but they are stuck in the second part of the learning curve -- the one that extends from the theoretical knowledge up to the actual hands-on expertise, the one required to effectively build COM-based systems of nontrivial dimension.
The COM universe is so extremely vast and the paradigm shift so big that it is often very daunting to get acquainted and secure working with it, either as architectural designers or hardcore implementers.
When you find yourself in this situation, any reliable source of suggestions, proven guidelines, and exhaustive answers to recurring doubts would greatly help understand and overcome the many nontrivial issues. Thats where Effective COM fits right in. The book can be thought of as a distilled dispenser of 50 rules-of-thumb and clearly explained guidelines stemming from the combined wisdom amassed by the four coauthors in many years of real-world experience and research.
The structure, not only the title, clearly resembles that of Scott Meyerss most excellent Effective C++. Each numbered rule is first stated and then explained and motivated, making frequent recourses to short and focused code snippets rigorously in pure C++, either to demonstrate the rule at work or to present the alternative solutions and drawbacks that made the authors opt for other possibilities. I have mixed feelings about whether this subdivision of the book in 50 seemingly independent aphorisms really fits this topic as well as it did for the C++ language in Meyerss case -- but it immensely helped the authors split what they had to say in short, manageable blocks. In this respect, the book is more digestible than its predecessor Essential COM, as one can blissfully read it in chunks of limited length, a few pages at a time, and even follow an order different from that proposed by the numeration.
The writing shows the same clean, direct style that characterized the aforementioned precursor. Considering the essential design shared by most Addison-Wesley texts of this type, with little or no graphical elements or screenshots and many copious pages of written text and short code fragments, the reader might at first get the impression of an overly academic manual. But as s/he proceeds with the reading, this sentiment quickly turns into appreciation for the directness of the concepts illustrated. When the topics get tough, the last thing you desire is a bunch of disturbing elements and side notes steering you away from the center of the analysis.
In conclusion, this is neither an introductory text, nor a tutorial. If your daily job is that of a manager, or if you are still familiarizing yourself with the COM, youd better save the money for the moment. But if you are an experienced developer spending hours a day with COM/MTS and C++, or simply a COM programmer with much more theoretical knowledge than practical experience and strong intentions to avoid the typical errors and general misconceptions , you will do yourself a big favor purchasing a copy.
-- Davide Marcato (marcato@programmers.net and http://www.DavideMarcato.com)
Note: Davide Marcato was a technical reviewer of the draft manuscript for Addison-Wesley.
From "Implementations" -- "28. COM aggregation and COM containment are for identity tricks, not code reuse", page 120
"COM supports interface inheritance; it does not support implementation inheritance. Interface inheritance, or subtyping, enables polymorphism, the ability to treat an object of some specific type as if it were an object of some less specific type (e.g., to treat an Apple as a Fruit). From a clients point of view, this is the only type of inheritance that is important. Implementation inheritance, or subclassing, is only a way to avoid rewriting code. Unfortunately, a bad thing happened when COM debuted (underneath OLE) in 1993. At that time, most developers didnt distinguish between the two types of inheritance (partly because most of them worked in C++, which blurs the distinction). Worse, most developers saw implementation inheritance as the preferred way to reuse objects. Since reuse was one of the basic benefits the industry was hoping to derive from objects, the lack of implementation inheritance (and by illogical extension, the lack of object reuse) in COM was seen by many as a major weakness.
In an attempt to overcome this obstacle, many early COM proponents legitimately criticized the tendency of implementation inheritance to introduce overly tight coupling between base and derived classes (the fragile base class problem). Unfortunately, they also evangelized two alternative reuse techniques, COM aggregation and COM containment. These techniques allow a COM object to expose interfaces whose implementation is shunted directly (aggregation) or indirectly (containment) to one or more collaborating COM objects.
Both these mechanisms were criticized as poor replacements for real implementation inheritance, and the result was a long, harrowing, and tedious debate among the object intelligentsia and their partisan hackers about the validity of COM as an object-oriented technology. All this furor could have been avoided if someone had just pointed out that object reuse is not synonymous with implementation inheritance and can be easily achieved through other means.
Consider the task of modeling a bicycle with objects. Assume that you have two existing classes, Handlebar and Wheel, that youd like to reuse. [ ]"
From "Transactions" -- "47. Beware of committing a transaction implicitly", page 194
"Because the lifetime of a transactional object is scoped to its transaction, MTS tries to commit the transaction when the root object is deactivated. Normally, this means that the root object has returned from a method in SetComplete state. However, if the client releases all of its references to the objects context wrapper prior to the objects entering the SetComplete state, MTS will attempt to commit the transaction anyway. Since the object will be deactivated when the context wrapper goes away, this is consistent with the relationship between the transactions and the objects. When the context wrapper attempts to commit the transaction in this way, it is said that the transaction has committed implicitly (i.e., the transactional object does not initiate the commit explicitly).
Implicit commits are actually a reasonable idea. They allow the client to create an object, submit some method requests, and release the object, all within the scope of a single transaction. The primary problem with implicit commits is that, unlike an explicit commit (or abort), the client cannot easily discover the outcome of the transaction. Recall that when a root transactional object returns from a method in SetComplete state, it is telling the context wrapper to attempt to commit the transaction prior to returning control to the caller. If the transaction commits successfully, the client will receive the successful HRESULT from the object. If, however, the commit attempt fails, the context wrapper will modify the HRESULT to indicate that the transaction has been aborted. This allows the client to deal with the failed transaction in whatever manner it deems appropriate
In the case of an implicit commit, however, the commit attempt is not made until the clients final call to IUnknown::Release. Unfortunately, there is no way for the context wrapper to communicate the outcome of the transaction through this method call. [ ]"
Preface
Shifting from C++ to COM
1. Define your interfaces before you define your classes (and do it in IDL).2. Design with distribution in mind.
3. Objects should not have their own user interface.
4. Beware the COM singleton.
5. Don't allow C++ exceptions to cross method boundaries.
Interfaces
6. Interfaces are syntax and loose semantics. Both are immutable.7. Avoid E_NOTIMPL.
8. Prefer typed data to opaque data.
9. Avoid connection points.
10. Don't provide more than one implementation of the same interface on a single object.
11. Typeless languages lose the benefits of COM.
12. Dual interfaces are a hack. Don't require people to implement them.
13. Choose the right array type (avoid open and varying arrays).
14. Avoid passing IUnknown as a statically typed object reference (use iid_is).
15. Avoid [in,out] parameters that contain pointers.
16. Be conscious of cyclic references (and the problems they cause).
17. Avoid wire_marshal,transmit_as, call_as, and cpp_quote.
Implementations
18. Code defensively.19. Always initialize [out] parameters.
20. Don't use interface pointers that have not been AddRef'ed.
21. Use static_cast when bridging between the C++ type system and the COM type system.
22. Smart interface pointers add at least as much complexity as they remove.
23. Don't hand-optimize reference counting.
24. Implement enumerators using lazy evaluation.
25. Use flyweights where appropriate.
26. Avoid using tearoffs across apartment boundaries.
27. Be especially careful with BSTRs.
28. COM aggregation and COM containment are for identity tricks, not code reuse.
Apartments
29. Don't access raw interface pointers across apartment boundaries.30. When passing an interface pointer between one MTA thread and another, use AddRef.
31. User-interface threads and objects must run in single-threaded apartments (STAs).
32. Avoid creating threads from an in-process server.
33. Beware the Free-Threaded Marshaler (FTM).
34. Beware physical locks in the MTA.
35. STAs may need locks too.
36. Avoid extant marshals on in-process objects.
37. Use CoDisconnectObject to inform the stub when you go away prematurely.
Security
38. CoInitializeSecurity is your friend. Learn it, love it, call it.39. Avoid As-Activator activation.
40. Avoid impersonation.
41. Use fine-grained authentication.
42. Use fine-grained access control.
Transactions
43. Keep transactions as short as possible.44. Always use SafeRef when handing out pointers to your own object.
45. Don't share object references across activity boundaries.
46. Beware of exposing object references from the middle of a transaction hierarchy.
47. Beware of committing a transaction implicitly.
48. Use nontransactional objects where appropriate.
49. Move nontrivial initialization to IObjectControl::Activate.
50. Don't rely on JIT activation and ASAP deactivation to achieve scalability.
Epilogue
About the Authors
Index
| Readability |
|
| Originality |
|
| Organization |
|
| Accuracy |
|
| Consistency |
|
| Depth |
|
| Timeliness |
|
| Editing |
|
| Design |
|
| Overall Value |
|
Explanation of ERCB rating scale: No stars = unacceptable, 1 Star = marginal, 2 Stars = average, 3 Stars = above average, 4 Stars = exceptional.