Sometimes it's difficult to see the wood for the trees. You know that using COM is good, but how can you use it? How do you write an app that uses COM? How do you get from
IApe and all the text book examples to something in the real world? This article is intended to be a bit of a beginner's guide to a simple use of COM. Something to try once you understand the basics and now want to write a real, working application.
By writing key pieces of code as COM objects and then allowing the user to configure which objects to use at runtime using component categories you can write applications that are easy to extend. I've tried to make this article a little different from most of the 'look at this cool way to do such and such with ATL' articles, they're fine if you're desperately seeking the answers they give but they don't do much to help journeyman COM programmer's write their first real world applications...
You don't need to learn all of these bloated, complex, Microsoft interfaces inside out to write good COM apps. You need to think about what you want, and use COM to achieve it.
Requirements change. It's a fact of life. You're just about to ship your application when you suddenly need to support more new functionality. Without this functionality your app is dead in the water as competitors have already leap-frogged your release candidate... Unfortunately this kind of thing happens all the time. No matter how rapid, or should that be rabid, your development process, requirements change at the least opportune of moments. Being able to incorporate changes in requirements at a very late stage in development seems to be the number one requirement these days.
Luckily COM can help with just this kind of problem. You have to put in a little extra work up front, but if you do it can be well worthwhile. If aspects of key functionality that have been identified as most likely to change are provided by simple in-proc COM servers then you can simply plug in different functionality when business requirements deem it necessary. What's more, since COM interfaces are immutable, if you got your initial design right, the new functionality can be added without changing the rest of the application. That localises the changes behind the COM interface firewall - which means less re-testing. If you do all of this using in-proc servers you hardly take a performance hit at all, plus you can always rely on COM to allow you to remote pieces of your application if necessary.
You can even take it one step further and allow the users of your application to configure the components they require themselves - you could even publish a detailed spec of your interfaces and let others develop replacement components for you!
Start with a clean design
Of course, to reap all of these rewards you have to put the work in at the start. You need to analyse your problem domain and decide on what areas are most likely to fall victim to requirements creep. Next you need to determine a clean way to break the risky pieces of code out of the main app and present them as clean and extensible interfaces. This takes practice. Sometimes, quite a lot of practice...
I find that a lot of these potential components fall out of the object model you build up as you work on your design. Look for large chunks of functionality that communicate with the rest of the application through clean interfaces. For example, if you have code that drives pieces of external hardware but that don't require the complexity of a real device driver, then these are a prime candidates for pulling out of the application and making them into device drivers implemented as COM objects. If you have an area where you're currently using a range of polymorphic objects through an abstract base class, then chances are you could make each into a separate COM object and access them through their common interface. Perhaps your application supports spell checking, image rendering, compression, encryption? Perhaps you know that time is short and you aren't going to be able to deliver 100% functionality by ship date. Turn the risky code into COM objects and ship the stuff you can with release 1 and then ship the extra components when they're done!
Don't stop at the first level of component that you find. Often the application could simply serve as the glue that joins components together. In a credit card production system I worked on the device drivers were COM objects which could present interfaces which represented their functionality (encode a magnetic stripe, emboss a card, print on a card, encode a chip card). The type of card was also a COM object (standard magnetic stripe credit card, proprietary format magnetic stripe, chip card, etc). Both card and device presented fixed interfaces to the application, but the device could present any interface it liked to the card. It was up to the card to see if the device that the application was trying to partner it with supported the interfaces that it required. The card type knew how to make the card through the interfaces that were required, it also knew how to find out if a device supported those interfaces and make intelligent decisions about downgrading functionality on less able devices; a magnetic stripe card could be made on a machine that could encode a stripe, or one that could print and encode, etc. A chip card needed chip card functionality. The application itself didn't know anything about the interfaces used between the card object and the device object. This allowed us to add new types of cards needing new types of machine with new interfaces very late in the day without needing to change the application at all... Do you suddenly have a requirement for a photo card, printed on a new machine that you've never seen before? No problem: write a device driver for the new machine, develop a new interface for the new picture printing functionality, write a card that knows how to print pictures through the new interface and plug the whole lot into the rest of the app without needing to change anything...
Let your objects talk to other objects and let the application introduce them and then get out of the way!
Use component categories
It's all very well writing components but when you then tie everything together so tightly that you might as well have permanently linked the components together at compile time you lose much of the advantage of component based development. Beware binding components together using hard coded GUIDs. Use a more flexible method (even if it's just reading the GUID from a registry key...) Far better, use component categories. Allow your user to select from any of the objects that implement the categories that your application requires.
Using a simple wrapper around the standard COM category manager interfaces you can write code that makes it easy to list all the objects in your required category. Put those in a list box, let the user decide and store the decision in the registry. When you provide improved functionality, ship a new object and once it's registered it will appear in the list and allow for an easy functionality upgrade.
Complex objects that are user configurable should be able to persist themselves so that the user doesn't have to continually repeat their configuration. You don't have to wade through the documentation trying to work out which
IPersist interface to use though! Just make one up, one that works for your application and your situation. You could have the object persist directly to the registry, but this could cause problems if suddenly the application decides to allow the user to configure two of the object. To make the object as flexible as possible it's probably best to persist to something simple, a byte buffer that gets returned to the application but that is in a format understood only by the object is fairly safe. The object can always save and restore itself to the buffer, the app can persist the bytes wherever and however it wants. At the end of the day, IPersistStream is a pretty safe bet. It's easy to use from both component and application ends and it gives the application the ability to change where it is storing the resulting data stream to without affecting how the component writes out its configuration.
Build a framework for your components
COM's great for programmers, it's complex, takes ages to learn, and is always changing - that means we get paid lots for being able to do it. Long may this continue, but sometimes you have to take the pragmatic view.
Whilst it's all very well writing objects where you need to be fully conversant with Inside OLE to be able to adjust them, it's far better to build a framework that allows people with limited, or no, knowledge of COM to create new components for you. This is especially important if you're the current COM Guru of your work place. Just think, do you REALLY want to be the ONLY person capable of writing the all of the rest of the components for your system?
Where possible, write one or two of the particular class of component, then generalise as much of the code as you can and factor it into boilerplate or library code that others can use just by writing a normal C++ object. Then when the time comes to write the 10th really boring serial card embossing device driver you can give it to anyone who knows C++. Unfortunately I find that ATL and all of the wizards makes it far harder to do this kind of thing. Unless of course, you write a wizard that generates 90% of your component for you... The problem is the wizards don't encourage you to think about how you'll go about writing the 10th driver that's almost exactly the same except it talks a slightly different protocol down the wire. Cut and paste doesn't cut it.
Using COM doesn't have to be difficult. Lots of projects can benefit from just adding some flexibility to the system by making some elements replaceable. You don't have to dive right in and try and use every COM trick in the book to get some benefit. Simple uses have their places, and help you to get used to working with the technology.