Component Oriented Software Development

To simply put it, it means building components and assembling components into software applications.  

This scheme has been used in other industries for centuries.  A good analogy is PC manufacturing.

Components in PC AssemblyComponents in Software Development
Components: CPU, video card, case, keyboard, monitor, etc.Components: buttons, controls, computations by using certain algorithms, etc.    
More than one vendor can manufacture the same component. e.g. CPU vendors: Intel, AMD. More than one software vendor can develop the same component. e.g. many companies can offer components for playing MPEG (movie) files. 
Each component may have more than one interfaces.  e.g. a sound card has microphone in, line in, line out connectors to interface with microphone, audio source, speakers respectively. A component may have more than one interfaces. e.g. a media player may have an audio interface and a video interface. 
Standards: IBM PC and Apple are two different standards that require different components even they have the same functions.Component Standards: Microsoft COM (Component Object Model), CORBA (Common Object Request Broker Architecture), Java Beans. 

Component oriented software development is gradually becoming a norm in software development just like object oriented design and programming.  It helps software development in the following aspects:

  1. Code reuse.
  2. Application customizability
  3. Easy maintenance
  4. Merge non-distributed and distributed application development.

By definition, a component technology is not directly related to the 4th aspect above.  However in reality  component technologies do address it.  Addressing the above issues enhances software robustness, reduces the development cost and increases productivity.  Most current software development work now is probably worse than  making the first airplane Wright Brothers in terms of utilizing components.  For large software project, such as MS Windows, using component has become a necessity instead of a better option.

Why are these notes here?

Why should anything like this be written here while there are dozens of books about this topic.  It is exactly because of this, Hong thinks there should be a better and fast way to help people understand the fundamental ideas of COM and start developing COM components and using these components quickly, or specifically, in hours instead of in weeks.

This writing does not replace books or Microsoft online documents.  It emphasizes on fundamental conceptual understanding and fast practical start.  Deep understanding of COM comes from using it while reading more. 

COM is a technology itself.  It is worthwhile to keep in mind that almost all people trying to understand COM except some at Microsoft are users of this technology, not the developers of this technology.  Users only need to know the concepts and the best ways to use this technology in their work.  The usefulness of understanding the concepts may last for a long time or for ever, but the knowledge of specific means to use them may be short-lived.  For example, many people including the author here started programming Windows before Visual C++ came out.  We had to develop GUI in very tedious ways (e.g. specifying the actual coordinates of dialog boxes or controls, dealing with message loops) in early 90s.  The experience of doing those things is almost useless nowadays. However the decent understanding of how Windows works (especially the message driven mechanism) has always benefited us and will do for a long time. This is true of COM.  The principles of COM have remained the same since its inception in 1993 under the guise of OLE, but the way to use it has been changing all the time. If there is anything that is tedious and repetitive, but is very useful widely, someone will come up with tools to do it for you.  This is exactly what Microsoft has been doing for COM.  They have been pushing COM because it is for Windows, so they have been making tools to make using COM easier and easier.  It is just like they have made tools (Visual Studio) that make programming Windows at least 10 times more productive now than 10 years.  

To summarize Hong's approach to COM from a user's point of view,

1.  Understand the fundamental concepts.

2.  Use the latest, best tools to  develop components. (Hong's preference: VC++ plus ATL)

3. Learn what you need while practicing.  

 

COM (Component Object Model)

- a brief introduction 

For the other two component standards, please check the following two sites:

CORBA home,     Jave Beans home

The definition of COM  by Webopedia:

A model for binary code developed by Microsoft. The Component Object Model (COM) enables programmers to develop objects that can be accessed by any COM-compliant application. Both OLE and ActiveX are based on COM.

If you look at the details of COM, it is not a simple standard.  It is very easy to get into details and forget the big picture.  There are dozens of books and probably thousands of articles on COM.  The most important thing to learn COM is to gain a good understanding of its concepts.  Learning COM for programmers is very similar to learning C++ with C background.  The semantics is not the difficult part of learning C++.  True understanding and appreciation of concepts such as class, inheritance, etc. is the most important and challenging.

To learn COM, we have to always keep the grand view in mind and try to understand why some procedures are necessary to build COM components and applications instead of trying to remember the details.

The grandest view is very simple:

There are two stages of COM application development: 1. making components; 2. assembling components into applications.

You can do both or just one of them.  The only additional work to conventional programming is that we have to follow some standard procedures to obey certain rules so the components we make can be used by other people following the same rules and/or we can use the components made by other people following the same rules.

Tools such as Microsoft's ATL (Active Template Library) that makes following the rules easier and easier.  With COM getting more and more ubiquitous, tools will become more and more powerful and this makes remembering the details of the procedures specific to COM development less and less important.  It is like the progress of Windows application development.  Before MS Visual C++ and other similar IDE's came out, we had to know a lot of details specific to programming Windows.  We even had to edit .def file to define GUI (i.e. buttons, edit controls) that very few of the present Widnows application developers have ever heard of. 

How to make COM components and use them 

- a one-paragraph description

Let's try to have a highly abstracted understanding on how COM works without using any jargons. First we build components following COM rules and put them into files (.dll or .exe).  Then we register them so the information about them is stored in Windows registry.  By now we have finished the component (or server) building job. To use certain functions, we need to know their unique ID's and the function description (or declaration) of each of them.  We can get this information by checking the registry using some tools or use some files from the component vendors that store this information.  Then we get the pointers of these functions by making calls to them following the COM rules. The Windows will take care of the rest of of the work such as locating the file storing the functions and load/unload them as needed.  Now we can enjoy the functions just like using any conventional functions to get our tasks done.

Let's have a little bit clarification:  A component is a group of functions.  A .exe or .dll file is not component.  They are just the means to store components.  A file can have multiple components.  Each component has an ID (CLSID) and each interface of a component also has an ID (IID).  All the ID's are globally unique.  In other worlds, no two functions or components in the world have the same ID.  Each component can have more than one interface.  Each interface is a group of functions.

How to make COM components 

- a bit more detail for using ATL

COM is a standard, or a set of rules.  It is not a language.  Theoretically you can use any language to build COM components ( or servers) or assemble COM applications (clients).  The standard dictates the binary (compiled code) compatibility.  It does not care how that binary code is generated.  

Following COM rules may sound easy, but it involves many tedious grunge jobs such as defining interfaces, object creation, loading and unloading objects properly, component registration, interface navigation, etc.  Since these jobs are almost the same for every component, having a tool to do these repetitive jobs is logical.  This is exactly what ATL (Active Template Library) does. 

ATL is a part of Microsoft Visual C++.  Again, we do not have to use C++ to make component. If we use C++, we do not have to use Microsoft Visual C++.  Even if we use MS VC++, we can make components without using ATL.  However, it is commonly accepted that ATL is the most powerful tool to make high performance, small footprint (size) components. There may be reasons for using other tools to develop certain components, but there is little reason to make components without using any tools.   ATL can accomplish all the work needed to make any components.    

The following is a step-by-step explanation of making an actual component with ATL emphasizing the major concepts.  It is not a complete description of all the procedures. It complements or helps understanding the entire process.  MS VC++ Wizard guides the component creation with friendly user-interface that can be complemented with online documents.

1. Creating an ATL project

Use VC++'s ATL Wizard to create an ATL project.  Name the project Truck.  Choose Dynamic Link Library (DLL) option and check the check boxes for"Allow merging of proxy/stub code" and "Support MFC".  An ATL project corresponds to a .exe or .dll file.  As explained above, It is just a means to store and transport components.  After creating an ATL project, we do not have any components yet.  The project is like a truck without any goods at this stage.  

Table 1 shows the files created and you can see in your VC++ Workspace panel.  Usually these files do not need to be bothered at all in the process of building components.  They do all the tedious work that are about the same for all components.  

Table 1 Initial files created by ATL Wizard that are visible in workspace. 

Truck.cppThe main file of the DLL file. This file satisfies calls to the well-known functions by delegating to a CComModule class that's part of the project.
Truck.defIt contains exports for the four functions COM DLLs must export
Trruck.idlThis IDL (Interface Description Language) file will be compiled by MIDL to create a type library file that will be used the user of this component.
Truck.rcIt includes any resources you want to include in your COM-based DLL.
Stdafx.hThe has general #include statements.
Stdafx.cppIt compiles stadafx.h

There are two more files that are specifically for the option of "Allow merging of proxy/stub code" : dlldatax.c, dlldatax.h.  For this writing, we do not use them at all.  To use them, take a look at the comment in file truck.cpp.  We generate them here to leave more room for further development of the component.

If we build this project at this point of time, some additional files will be created as shown in Table 2. 

Table 2 Files created by during project inital building 

Truck.hIt is generated by Microsoft Interface Definition Language (MIDL) from the truck.idl describe in the above table.  it contains the declarations of the interfaces and components.
Truck_i.cIt is also generated by Microsoft Interface Definition Language (MIDL) from the truck.idl describe in the above table. It contains the definitions of the GUIDs we're using.
Truck.tlbThe type library file that can be used the component users.  It is also generated by Microsoft Interface Definition Language (MIDL) from the truck.idl describe in the above table.
bastsd.hType definitions for the basic sized types

 Again, we usually do not need to bother these files at all. Leave them alone.

Again, at this point of time, the created DLL is jus an empty shell that does not have any components.

2. Add an ATL object

Use ATL Object Wizard (New ATL Object... under Insert menu) to create an object named Beeper.  Use all the default options of the wizard. The two new files associated with the newly created class "CBeeper" are Beeper.cpp and Beeper.h.  

Now the project has a component, or the truck has a good.  The component by default has only one interface that is the set of all the methods of the newly created class.  With the help of ATL, MSVC++ has already created a bunch of methods (e.g. the famous IUnknow()) for us. Here is a point that could be confusing. To use a component, we need to get an interface.  In C++, getting an interface means getting a pointer to an object of the component class mentioned (in our case a pointer to an object of CBeeper).  ATL does not make creating more than one interface for a component easy.  Most of the time we do not need multiple interfaces.  One interface with plenty of functions usually is all we need for a component.  If we need multiple interfaces for one component, we can always achieve the same goal by spreading the interfaces to different individual components to the class added.

If we build this project now, the component Beeper will be registered and hence be available for users (clients) to use.  However it is a useless component at this point of time because it has no useful methods except some standard ones that every component has.

Let's do one more thing to complete the building of our component.  Right click IBeeper interface connector in ClassView and choose "Add Method..." to add the function STDMETHODIMP CBeeper::Beep(int iLength). You can do whatever you want in the function of beep.  The simplest thing may be using one statement:

 ::Beep(1000, iLength);

This makes the computer beep at 1000 Hz for iLength milliseconds.  Let's take this opportunity to clarify one more confusing point - methods vs. functions.  You can see from the above example, a method is a function of a class, but not every function is a method of component that is exposed to users.  You can add as many functions as you want to CBeeper class just like you add class member functions in typical C++ programming and use them inside your component in any way you like.   As long as they are not methods in COM sense, the users can never see them.  Expose a function as a COM interface function needs some work.  This work is done by VC++.  We just need to use out mouse to click the right place and input required information properly.    

This is it.  Now you can see, we can complete the whole process of building this component in a few minutes.  It is supposed to be this easy.  We software developers who use component technology should spend most of our time on core business logic, algorithms, other important and interesting things instead of tedious repetitive work following certain rules that does not need any kind of creativeness or analytical thinking.

How to use COM components 

- a bit more detail.

Using components should be much easier than building them.  It is just like that using an IC chip such as Pentium III CPU should be much easier than designing and building it.  Using a component involves the following two steps

1. Register the component.

If you build the component on the same computer where you are going to use the component, VC++ registers it for you during building.  If you use this component on another computer, you just need to get the DLL file - Truck.dll and run the following command that is exactly how VC++ register the component:

regsvr32 /s /c Truck.dll

As usual, you can read Microsoft online documents to gain more understanding about the registering process.

2. Use the component in the code.

Include the following two files in your code: Truck.h, Truck_i.c.  More specifically, add the following two lines at the header of your source code where you consume this component:

#include "truck.h"
#include "truck_i.c"

The following code is all you need to make the beeper to beep for you:

::CoInitializeEx(NULL, OINIT_APARTMENTTHREADED);
IBeeper *pBeeper = NULL;
::CoCreateInstance( CLSID_Beeper, NULL, CLSCTX_ALL, IID_IBeeper, (LPVOID *)&pBeeper)
pBeeper->Beep(300);
pBeeper->Release();
::CoUninitialize( );

If you have any problem using CoInitializeEx(), just use CoInitialize(). 

Of course, we programmers always like to have some comments and debugging features in our code.  If you are interested in the actual code that the author wrote, you can check the code at the end.

You can see, we need three files to use the component: Tuck.dll, Truck.h, Truck_i.c.  Sure, you can try the author's version of these three files by downloading the file truck.zip

There are other ways to use the components.  You may ask the question: what is the use of the type library file: Truck.tlb.  It is used by Visual Basic, Java, and some other language or development tool that supports COM.  These languages obviously cannot use Truck.h and Truck_i.c. Remember our component IBeeper can be used by any of them although we use C++ to consume it here.

We checked the option of supporting MFC to have the freedom of using MFC classes in our component such as the most frequently used class: CString.  The caveat here is that we incur the penalty of depending on MFC runtime DLL by doing this.  Since MFC runtime DLL is installed on almost every machine that runs Windows,  this is usually not a problem or an extra burden in using the component.  There may be some performance penalty in some situations.  

We cannot finish this writing without mentioning ActiveX.  In fact, ATL was born out of the need to provide developers with a way to write COM classes and ActiveX controls with a smaller footprint.   A very good way to explain ActiveX control is by George Shepherd in his Visual Programmer column on 1997 Oct. issue of Microsoft System Journal: ActiveX controls are just COM classes with a bunch of interfaces hanging off them.  

 

void CTesterDlg::OnBeep()

{

            // TODO: Add your control notification handler code here

            bool bSuccess;

           

            HRESULT hr =  ::CoInitializeEx(

                                                NULL,                                                     // void * pvReserved,  Reserved

                                                COINIT_APARTMENTTHREADED            // DWORD dwCoInit     COINIT value

                                                ) ;

            switch(hr)

            {

            case S_OK:

                        bSuccess = true;

                        break;

            case S_FALSE:

                        bSuccess = true;                      // it has already been initialized

                        break;

            case RPC_E_CHANGED_MODE :

                        bSuccess = false;

                        ::AfxMessageBox("A previous call to CoInitializeEx specified a different concurrency model for the calling thread");

                        break;

            default:

                        ::AfxMessageBox("Unrecognized return from Error CoInitializeEx()");

            }

 

            if(bSuccess)

            {

 

                        IBeeper           *pBeeper = NULL;

                        HRESULT stdReturn = ::CoCreateInstance(

                                                               CLSID_Beeper,         //REFCLSID rclsid,     Class identifier (CLSID) of the object

                                                                NULL,                     // LPUNKNOWN pUnkOuter, Pointer to controlling IUnknown, NULL = the
                                                                                              // object is not being created as part of an aggregate

                                                               CLSCTX_ALL,          // DWORD dwClsContext,  Context for running executable code

                                                               IID_IBeeper,            // REFIID riid,        Reference to the identifier of the interface

                                                               (LPVOID *)&pBeeper            // LPVOID * ppv         Address of output variable that receives

                                                                                                         // the interface pointer requested in riid

                                                                );

                        if(stdReturn == S_OK)

                        {

                                    pBeeper->Beep(100);

                                    pBeeper->Release();

                        }

                        else

                        {

                                    ::AfxMessageBox("CoCreateInstance() error");

                        }

            }

            else

            {

                        ::AfxMessageBox("CoInitializeEx() error");

            }

 

            ::CoUninitialize( );

}

(originally writton in June, 2001)