-
Notifications
You must be signed in to change notification settings - Fork 51
Quick Intro to COM for Concord development
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).
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
. The0x8
part means this is an error, the0x007
part means this is a Windows error code, and the0x0002
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.
For more information on COM, here are some resources:
- MSDN has detailed COM documentation. Most of this is much more than you need to know: https://msdn.microsoft.com/en-us/library/windows/desktop/ff637359(v=vs.85).aspx
- The book "Inside COM" by Dan Rogerson is a good reference
Concord Documentation:
- Overview
- Visual Studio 2022 support
- Concord Architecture
- Getting troubleshooting logs
- Tips for debugging extensions
- Component Discovery and Configuration
- Component Levels
- Navigating the Concord API
- Obtaining the Concord API headers, libraries, etc
- Concord Threading Model
- Data Container API
- Creating and Closing Objects
- Expression Evaluators (EEs)
- .NET language EEs
- Native language EEs
- Installing Extensions
- Cross Platform and VS Code scenarios:
- Implementing components in native code:
- Worker Process Remoting
Samples: