Skip to content

Quick Intro to COM for Concord development

Gregg Miskelly edited this page May 14, 2020 · 5 revisions

NOTE: This section applies only when consuming the Concord API from native code.

The Concord API is partially based on COM, so if you have never used COM before, this page should hopefully give you an introduction to get you up to speed. COM is a fairly big concept in Windows with very large books written about various aspects of it. Fortunately, you need to know very little of it to implement Concord components or consume the Concord API.

What is COM? COM is short for the Microsoft Component Object Model. It is a system for exposing objects implemented in one dll/exe to another dll/exe in a version safe way based on interfaces (types containing only abstract virtual methods).

Here are a few things you should know -

1: IUnknown - IUnknown is the basis of all of COM. Every interface is COM derives from IUnknown and therefore all objects that implement COM interfaces implement IUnknown. IUnknown is a brilliant design because it very simple yet enables so much. IUnknown provides three methods:

-AddRef/Release: COM objects use reference counting to decide when no other object in the system is using the object, and therefore when the object can be deleted. AddRef increments this reference count, and Release decrements it. When the reference count reaches zero the object deletes itself. For more information about reference counting, see the AddRef Release Semantics page.

-QueryInterface: QueryInterface lets a caller ask a COM object if it implements another interface. So, for example, if I have an IFruit pointer, and I want to see if this object that I have a pointer to is a banana, I could call the QueryInterface method to find out if that object implements IBanana. To make this work each interface has an associated GUID (128-bit globally unique number).

The Microsoft C++ compiler provides a nice feature to make this easier - you can associate a GUID with a type using __declspec(uuid()) and then you can retrieve this GUID back with __uuidof. Example:

struct __declspec(uuid("09BFA72F-9E16-42FF-9957-AB59A5765AA1")) IBanana : public IFruit
{
    virtual HRESULT GetLength(_Out_ uint32_t* pLength) = 0;
};

bool IsBanana(_In_ IFruit* pFruit)
{
    CComPtr<IBanana> pBanana;
    if (pFruit->QueryInterface(__uuidof(IBanana), (void**)&pBanana) == S_OK)
    {
        return true;
    }

    return false;
}

2: Interface versioning - COM provides its versioning guarantees by saying that once an interface has shipped, it can never change. So if you want to extend an interface, rather than adding a new method/property to an existing interface, you introduce a new interface (with a new associated GUID). Typically this is done by adding a version number suffix to the end of the interface (example: IExample and IExample2). In the Visual Studio debugger we often use numbers that indicates what version of Visual Studio the interface was introduced in (example: IExample156 would mean that the interface was added in Visual Studio version 15.6).

3: A little bit about Apartments - Apartments in COM is a big topic, but here is what you need to know:

  • Objects in COM belong to something called an "Apartment" which roughly means the thread(s) that object's interfaces are called on.
  • Most of the UI code in Visual Studio expects to run on Visual Studio's "main" thread - the first thread in the Visual Studio process. In COM-speak, Visual Studio's main thread is a Single Threaded Apartment (STA).
  • All Concord components run on a background thread of Visual Studio. These background threads are part of the Visual Studio process's Multi-Threaded Apartment (MTA). It is safe to pass around interface pointers between threads in the MTA.
  • Some objects are special and can be called from any Apartment. All dispatcher objects (example: DkmProcess, anything class that starts with Dkm...) can be called from any apartment.
  • Most interfaces are only directly usable from the apartment they were created in. Some interface have an associated "marshaller" which means you can use the interface in another apartment if you ask COM to marshal the interface pointer for you. When this is done COM provides a proxy object that can be used in another apartment and COM will take care of switching threads on every call. Most of the interfaces you might want to use in a Concord component (examples: IDia*, ICorDebug*, ICorSym* interfaces) do NOT have an associated marshaller so they should only be used from the MTA. If you are working with other parts of Visual Studio, many of the IVs* and IDebug* interfaces to have a marshaller. For more information on marshalling between threads, see CoMarshalInterThreadInterfaceInStream in MSDN.

4: HRESULT - Almost all methods in COM return an HRESULT type. This is a typedef of a 32-bit integer. The top bit (signed bit) is used to indicate success or failure.

Any value >= 0 (signed bit cleared) mean that the operation was successful. S_OK (0) is by far the most common success code. Some methods can return other success codes. The only other common code is S_FALSE (1). In the Concord API this can be used to indicate that an optional out parameter could not be provided.

Any value < 0 (signed bit set) means the operation failed.

The middle 13-bits (((hr) >> 16) & 0x1fff) of the HRESULT indicate the 'facility' of the HRESULT. The 'facility' basically means the "family" of error code. See below for some of the common facility codes. The bottom 16-bits indicates the specific code within this facility.

  • 7: FACILITY_WIN32 -- a Windows error code. So for example, a file not found error would be 0x80070002. The 0x8 part means this is an error, the 0x007 part means this is a Windows error code, and the 0x0002 part means ERROR_FILE_NOT_FOUND. These error codes can be found in winerror.h.
  • 4: FACILITY_ITF -- indicates an interface-specific error. This is used by, among many other things, the IDebug* interfaces for their custom HRESULTs. For debugger interfaces, these can be found in msdbg.h.
  • 0x1233 and 0xede -- indicates a Concord HRESULT. These can be found in vsdbgeng.h.
  • 0 -- a few of the most common HRESULTs (example: E_FAIL) have a facility code of zero. These can also be found in winerror.h

5: CComPtr - Rather than manually calling AddRef/Release, it is highly encouraged to use a smart pointer class to do this automatically. Perhaps the most common example is CComPtr from the ATL. You can find an example usage in the IsBanana function above. The CComPtr will automatically take ownership of the results of QueryInterface, and in the CComPtr's destructor it will call Release. For more information about reference counting, see the AddRef Release Semantics page.

Additional resources

For more information on COM, here are some resources:

Clone this wiki locally