Academic Company Events NI Developer Zone Support Solutions Products & Services Contact NI MyNI

Document Type: Tutorial
NI Supported: Yes
Publish Date: Sep 6, 2006


Feedback


Yes No

Related Categories

Related Links - Developer Zone

Related Links - Products and Services

ActiveX and COM -- Part II

38 ratings | 4.42 out of 5
Print

Overview

Getting Objects and Interfaces
In Part I, we discussed some basic concepts in component object model (COM). In Part II, we discuss the implementation of COM and why it was implemented the way it is.

GUIDs

First, we need an identifier for the object type (or class). Second, we need an identifier for each interface. Here is the problem -- the identifiers have to be unique across all machines, because you cannot know on which machines you want to install a component. Your objects and interfaces need the same identifier on all machines so that any client can use your component. Further, no other object or interface may use that identifier. In other words, these identifiers must be globally unique.

Fortunately, algorithms and data formats exist for creating such identifiers. By using your machine's unique network card ID, the current time, and other data, a program called GUIDGEN.EXE identifies these globally unique identifiers (GUIDs). GUIDs are stored in 16-byte (128-bit) structures, giving 2,128 possible GUIDs.

In C++, COM header files define data types for GUID, class identifier GUID (CLSID), and interface identifier GUID (IID). Because these 16-byte structures are rather large to be passing around by value, you usually use REFCLSID and REFIID as the data types of parameters when passing GUIDs. You must create a CLSID for each object type you write and an IID for each custom interface you create.

The "Unknown" Standard Interface


COM defines a large set of standard interfaces and their associated IIDs. For instance, the mother of all interfaces, IUnknown, has an IID of "00000000-0000-0000-c000-000000000046". COM defines this IID, and you refer to it using the symbol IID_IUnknown, which is defined for you in the headers.

The IUnknown interface has three functions:

HRESULT QueryInterface(REFIID riid, void **ppvObject);
ULONG AddRef();
ULONG Release();

Your Interfaces


Custom interfaces are interfaces you create. You create your own IID for these interfaces, and define your own list of functions.

Suppose we want a simple class declaration of the Animal from Part I:

class IAnimal {
   virtual void Eat(void) = 0;
   virtual void Sleep(int Hours) = 0;
   virtual void Walk(double Distance) = 0;
};

We modify this to be COM-compliant:

interface IAnimal : IUnknown {
virtual HRESULT STDMETHODCALLTYPE Eat(void) = 0;
virtual HRESULT STDMETHODCALLTYPE Sleep(int Hours) = 0;
virtual HRESULT STDMETHODCALLTYPE Walk(double Distance) = 0;
};

All COM functions return an HRESULT error code. This HRESULT is a 32-bit number that uses the sign bit to represent success or failure and the remaining 31 bits to indicate the "facility" and the error code. Normally you return the success code S_OK, but you can return an error code if you encounter a problem in your method.

Finally, note that IAnimal is derived from the standard COM interface IUnknown. That means that any class that implements IAnimal also needs to implement the three functions AddRef, Release, and QueryInterface. All COM interfaces are derived from IUnknown, so all COM interfaces contain these three functions in addition to any other functions.

More Code?


Don’t worry; you do not actually write the declaration above yourself, the MIDL compiler generates it for you -- making your life easier. Also, most programming languages, including C++, cannot express all things in an interface. Recall that COM objects can be DLLs used in-process, or in the same address space. So if you pass a pointer to some data as an in-process server, the server can dereference the pointer directly.

But, COM objects can be a local server in separate address spaces, or even accessed remotely through distributed component object model (DCOM). Pointers are meaningless in any other address space. The meaningful data is that to which the pointer targets. This data has to be copied into another address space. This process of copying the right data is called marshalling. Thankfully, most of the time, COM does the marshalling for you. Therefore, you just need to tell COM how the pointer is used. Does the pointer point to an array? A double? Is the parameter an input parameter only? An output parameter only? Or both an input and output?

Interface definition language (IDL) is the language that defines the interface. IDL looks like C++, but it has “attributes” in square brackets. MIDL.EXE compiles the IDL you or Visual Studio, in most cases, writes to produce a variety of outputs

If we created a new interface IDog that adds a method Bark(int *) to the other three methods, the IDL would look something like this:

[ uuid(E314789F-A9C7-11D1-A49E-0198A5D980C7) ]
interface IDog : IUnknown
{
   HRESULT Eat();
   HRESULT Sleep(int in_only);
   HRESULT Walk(double in_only);
   HRESULT Bark([in, out] int *inout);
};

Notice the attributes in the IDL, enclosed in square brackets. The attributes always apply immediately after, so the GUID attribute above applies to the interface. The [in, out] attribute applies to the pointer and tells COM that it must marshal a single int both in and out when it calls Bark. Also, there is IDL code to define the object. For instance, the code fragment to define our object might look like this:

[ uuid(E314789F-A9C7-11D1-A49E-0198A5D980C7)]
coclass Dog
{
   [default] interface IDog;
};

The Creation


Once our CLSID is associated with an object type, we can create an object. This is very simple:

IDog *pDog = NULL;
HRESULT hr = CoCreateInstance(CLSID_Dog, NULL, CLSCTX_ALL,
               IID_IDog, (void **)&pDog);

If CoCreateInstance succeeds, it creates an instance of the object identified by the CLSID GUID, CLSID_Dog. You always refer to the object through an interface pointer. So we have to specify which interface we want, IID_Idog, and pass a pointer to a place for CoCreateInstance to store the interface pointer.

CoCreateInstance returns an HRESULT to indicate whether it succeeded or not. Use the SUCCEEDED macro to check the result because nonnegative values mean success. Once the object has been created successfully, you can use the interface pointer to call the methods of the interface, as shown below.

if (SUCCEEDED(hr)) {
   pDog->Eat();  
   pDog->Sleep(5);
   pDog->Release();  
}
else // Failure...

You must release the interface pointer when you are done with it by calling Release. All interfaces work with Release, because all interfaces are derived from IUnknown. Your COM object is responsible for freeing itself once the program tells it you have finished. If we forgot to call Release, the object would be locked in memory until our application closed or you reboot the system. This mistake is very common, difficult to find, and the cause of most memory leaks in COM programming. Also, notice that we only release the interface if we could actually create it.

The figure below is a diagram of our object. IUnknown is not labeled and is always drawn attached to the upper right hand corner of the object with all of the other interfaces drawn on the left.


Using the Registry

When a COM component is installed, it has to have entries made in the registry. For our Dog class, the entry might look something like this:

HKEY_CLASSES_ROOT
  CLSID
    { E314789F-A9C7-11D1-A49E-0198A5D980C7}="Dog Class"
      InprocServer32="C:\\Examples\\Dog\\Debug\\Dog.dll"

Most objects have some additional entries, but we are not going to include those in this discussion.

At HKEY_CLASSES_ROOT\CLSID, there is an entry for the CLSID of our class. That is how CoCreateInstance looks up the DLL name for the component.

COM Modules


One module, the basic unit that you build and install, can implement one or more components. Each component has its own CLSID and entry in the registry that points to the module's file name. And each component implements at least two interfaces -- IUnknown, and an interface that exposes the component's functionality.

What Does IUnknown do?

Let's say that we have a new and improved Guard Dog object that implements three custom interfaces: IAnimal, IDog, and IGuard. We already know how to create such an object using CoCreateInstance and how to get a pointer to one of its four interfaces, including IUnknown.

So how do we get an interface pointer to one of the object's other interfaces?

Not by calling CoCreateInstance again, that would create another object. IUnknown::QueryInterface solves this. Because all interfaces inherit from IUnknown, all interfaces implement the QueryInterface method. You just use the first interface pointer to call QueryInterface to get the second interface pointer.

IDog *pDog = NULL;
HRESULT hr = CoCreateInstance(CLSID_GDog, NULL, CLSCTX_ALL,
               IID_IDog, (void **)&pDog);
if (SUCCEEDED(hr)) {
   pDog->Eat();  
   IGuard *pGuard = NULL;
   hr = pDog->QueryInterface(IID_IGuard, (void **)&pGuard);
   if (SUCCEEDED(hr)) {
      int inoutval = 5;
      pGuard->Attack(&inoutval);  
      pGuard->Release();
   }
   pDog->Release();
}

We pass QueryInterface the IID of the interface we want and a pointer to the place for QueryInterface to store the new interface pointer. If successful, we just use the interface pointer to call that interface's function.

Notice now that we must release both interface pointers when we are done with them. Failure to release either of them leaks the object. In order for the object as a whole to be released, we have to release each and every interface pointer we received.

IUnknown has two other functions, AddRef and Release.

Most COM objects keep track of how many interface pointers to the object are in use. When the reference count for all of the object's interfaces goes to zero, it is destroyed. So when programming you do not explicitly free the object, just release all of the object's interface pointers and the object frees itself.

AddRef increments the reference count, and Release decrements it. So in our example above if we did not call AddRef, why do we have to call Release?

Whenever QueryInterface hands out a new pointer to an object it calls AddRef before returning the pointer. That is why we did not have to call AddRef for the pointers we received. Notice that you are required to call Release on the same interface pointer on which you called AddRef. You need to call AddRef if you are making a copy of an interface pointer so that the reference count for the interface is correct.

Conclusion


After explaining all of this, it is nice to know that there are smart pointer classes that can make dealing with the great IUnknown much easier. There are several such classes in Visual C++ 5.0 or later, and if you are using another language such as Visual Basic, the language's implementation of COM deals with the IUnknown methods correctly for you.
38 ratings | 4.42 out of 5
Print

Reader Comments | Submit a comment »

LabVIEW??!
I thought we were all LabVIEW programmers here! This information is of no use if it's not in the LabVIEW context!
- Sep 28, 2008

Very Good Article
The article is easy to understand and good. Hope u explain more such topics of COM in your feature articles. Thank U.
- Kranti, HCL Technologies Ltd, Chennai. k_madineni@rediffmail.com - Mar 24, 2008

part II not for Labview programmers
Part II seems to assume that the reader is not a Labview programmer but a text programmer. It just launches into code, and into the language of inheritance, objects, etc., that may have little meaning to people who have only programmed in Labview.
- Jan 9, 2006

Great detailed information! Part3, Part3, Part3 please! More! Bravo!
- Oct 5, 2005

Very nice! Tips for Part3
Hi. Thanks for a very nice article. I would like for part 3 to explain to me how to query an interface from another library/DLL. Also the difference between LPUnknown and IUnknown. Thanks for a very helpful article. It is a work of art.
- Hallgrímur, TS. sp99hl@ts.spar.is - Mar 30, 2005

COM II
The topic was good. Thanks for information about ActiveX COM Hope additional information will be posted on the Site.
- Nitin Majgaonkar, Websolutions. nitin@india.com - Apr 15, 2003

Answered a lot of my questions about COM...excellent
- paracha3@hotmail.com - Jan 3, 2001

More COM ans ATL using ComponentWorks++
I am new to VC++ and I am eager for examples using ComponentWorks++ and ATL for creating Instrument ActiveX controls and components. Currently, I am using ComponentWork GPIB control extensively in VB. I want to build custom VC++ activeX controls and components for test and measurement.
- hui_meng@star-quest.com - Dec 28, 2000

Very useful article, can't wait for part 3.....
I'm very anxiuos to here more about COM from an NI person, as I intend on writing COM objects for use in NI products, specificly Labview, to replace the native C++ objects I've been using. I hope that you cover automation and IDispatch in some upcoming articles. I have been experimenting with COM in measurement studio by writing some simple "test" components using ATL( I presume that You'll be covering ATL in the future ). It appears that Labview only uses the IDispatch interface, which makes the use of multiple interfaces difficult, if not impossible, when using VC++ and ATL to create the COM object.
- Dean Smith, Corvis Corp.. dnsmith@corvis.com - Nov 25, 2000

 

Legal
This tutorial (this "tutorial") was developed by National Instruments ("NI"). Although technical support of this tutorial may be made available by National Instruments, the content in this tutorial may not be completely tested and verified, and NI does not guarantee its quality in any way or that NI will continue to support this content with each new revision of related products and drivers. THIS TUTORIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND AND SUBJECT TO CERTAIN RESTRICTIONS AS MORE SPECIFICALLY SET FORTH IN NI.COM'S TERMS OF USE (http://ni.com/legal/termsofuse/unitedstates/us/).