Click here to Skip to main content
15,881,810 members
Articles / Programming Languages / C++11

Implementing COM Interfaces with C++0x Variadic Templates

Rate me:
Please Sign up or sign in to vote.
4.92/5 (25 votes)
4 Sep 2011CPOL11 min read 42.9K   660   35   11
A step-by-step illustration of a minimalistic pattern for implementing a series of COM interfaces with little code

Introduction

It may be ridiculous to imagine that someone is still targeting COM, let alone writing an article on it. That's right, COM: Component Object Model, a Microsoft technology from the nineties that served as a first solid way to segregate software development tasks, allowing them to be distributed among teams that might not even be aware of each other's existence. Yet I would still like to share some of my experiences, even though I realize that the times when COM was at the height of its fame have irrevocably perished and the technology itself has given up the throne to more powerful players.

I develop a little utility named KO Approach whose main purpose is enhancing the Windows Shell. The main way to interact with the Shell is through the COM interfaces that it provides. So the subject of this article has been largely dictated by architectural conditions.

If you have tried to implement a COM interface by hand, you know how awkward it is. What drives me most crazy is the need to mention the interface twice:

  1. When specifying it as a class parent:
    C++
    class TestClass :
      public IClassFactory,
      public ILog,
      public IBindCtx,
      public IAccessControl
    {
      ...
    };
  2. When implementing the QueryInterface method:
    C++
    if (theIID == IID_IClassFactory)
      * theOut = static_cast<IClassFactory *>(this);
    
    else if (theIID == IID_ILog)
      * theOut = static_cast<ILog *>(this);
    
    else if (theIID == IID_IBindCtx)
      * theOut = static_cast<IBindCtx *>(this);
    
    else if (theIID == IID_IAccessControl)
      * theOut = static_cast<IAccessControl *>(this);
    
    else if (theIID == IID_IUnknown)
      * theOut = static_cast<IUnknown *>( static_cast<IClassFactory *>(this) );
    
    ...

Another element to bring frustration is reference-counting. Every time you implement reference-counting, you need to repeat the exact same code, such as the following:

C++
class TestClass
{
  LONG myRefs;

public:
  TestClass() : myRefs(0L) { }
};

Luckily, with little effort we can compact the code and re-use common functionality. Of course, you could use ATL, but in this article I wanted to introduce a minimalistic yet elegant pattern. Our code will be even more compact, and in addition we will get familiarized with the some cutting-edge features of the newest C++0x standard.

Prerequisites

  • Necessary: A C++ compiler that supports variadic templates. In this article, we will use GCC 4.6.
  • Optional: A Development Environment that can highlight text and visually build your project. For this article, I've chosen NetBeans because it provides a satisfactory level of integration with GCC.
  • Optional: doxygen to generate HTML documentation.

Step-by-Step Pattern Explanation

Basic Building Block

First, let's introduce a class that provides COM interface discovery information. That is, it provides a function block to inform the caller about a single interface that the class supports:

C++
template<typename tInterface, LPCGUID tIID = &__uuidof(tInterface)>
class ComEntryDiscoverable
{
public:
  typedef tInterface Interface;

protected:
  template<typename tObject>

  static HRESULT InternalQueryInterface(REFIID theIID, tObject * theObj, void ** theOut)
  {
    if (theIID == *tIID)
      *theOut = static_cast<tInterface *>(theObj);
    else
      return E_NOINTERFACE;

    return S_OK;
  }
}

Second, let's write a small class that actually implements a COM interface:

C++
template<typename tInterface, LPCGUID tIID = &__uuidof(tInterface)>
class ComEntry :
  public tInterface, public ComEntryDiscoverable<tInterface, tIID>
{ };

Why are we using templates? Because we want our code to run as fast as possible. To achieve that, our compiler will perform instantiation based on the template arguments that we will provide when writing a real class. Plus, the compiler may inline the call to InternalQueryInterface so that our object-oriented code will produce literally the same instructions as hand-crafted code does.

As you can see, the two classes use the same template arguments:

  1. The type of the interface that is being implemented.
  2. The GUID of this interface. Because we are using GCC that does not support the Microsoft __uuidof operator, we will have to use some tricks as a workaround which I'll explain at the end.

IUnknown Discovery

What we are currently missing is discovery information about common parent interface – IUnknown. If a class implements at least one COM interface, it implicitly implements IUnknown and hence its QueryInterface method must return a correct pointer when supplied IID_IUnknown as the interface GUID. Let's add an auxiliary class to aid IUnknown discovery:

C++
template<typename tInterface, 
         typename tIntermediate, LPCGUID tIID = &__uuidof(tInterface)>
class ComEntry2StepDiscoverable
{
public:
  typedef tInterface Interface;

protected:
  template<typename tObject>
  static HRESULT InternalQueryInterface(REFIID theIID, tObject * theObj, void ** theOut)
  {
    if (theIID == *tIID)
      *theOut = static_cast<tInterface *>( static_cast<tIntermediate *>(theObj) );
    else
      return E_NOINTERFACE;

    return S_OK;
  }
};

Its only difference from ComEntryDiscoverable is the way its InternalQueryInterface works: the latter performs two casts, one to the intermediate type (denoted by the tIntermediate template argument) and the other to the final type. The intermediate type can be any interface that has exactly one tInterface in its inheritance chain, otherwise the compiler will complain on the ambiguous outer cast. Now let's embrace IUnknown:

C++
template <typename tIntermediate>
class ComEntryTerminator :
  public ComEntry2StepDiscoverable<IUnknown, tIntermediate>
{ };

We have called this class ComEntryTerminator because it will be the last entry in the COM interface discovery sequence…

Implementing Multiple Interfaces

"Wait a minute, what sequence?" So far we have only dealt with a rather crippled solution where we can implement one COM interface, plus provide discovery information about this interface and, mostly for the sake of code purity, its parent IUnknown. So let's move on and introduce the following class:

C++
template <typename tEntry1, 
          typename tEntry2 = ComEntryTerminator<typename tEntry1::Interface> >
class ComEntryCompound : public tEntry1, public tEntry2
{
public:
  template<typename tObject>
  static HRESULT InternalQueryInterface(REFIID theIID, tObject * theObj, void ** theOut)
  {
    HRESULT aRes = tEntry1::InternalQueryInterface(theIID, theObj, theOut);

    if ( FAILED(aRes) )
      aRes = tEntry2::InternalQueryInterface(theIID, theObj, theOut);

    return aRes;
  }
};

The ComEntryCompound class enables us to have a sequence of two interface discovery blocks, denoted by tEntry1 and tEntry2 template arguments. Should we wish to implement one COM interface, the code will look as follows:

C++
class TestClass : public ComEntryCompound< ComEntry<IClassFactory> >

{ };

But the real power comes when ComEntryCompound is used recursively. So for two interfaces, we can write something like this:

C++
class TestClass2 :
  public ComEntryCompound
  <
    ComEntry<IClassFactory>,
    ComEntryCompound< ComEntry<ILog> >
  >
{ };

To make our code look prettier, we are now going to grasp variadic templates, a new and for many, a long awaited feature of C++. Briefly, variadic templates are templates that can have an arbitrary number of arguments, analogously to how the printf function can have an arbitrary number of parameters. But the beauty of variadic templates is in the way they are implemented. While printf needs a runtime that will examine the stack and retrieve the actual number of arguments, C++ variadic templates are processed at compile time. Hence safer, faster, less error prone code!

But first let's introduce a helper class:

C++
template <typename tInterface1>
class ComEntry1 :
	public ComEntryCompound < ComEntry<tInterface1> >
{ };

Its only template argument is the type of the interface that can be implemented, such as:

C++
class TestClass : public ComEntry1<IClassFactory>
{ };

With the help of ComEntry1 we can finally write our variadic template versions. If you aren't familiar with C++0x, the syntax may at first seem a little weird, so I'll try to explain it step by step. First we declare a variadic template class:

C++
template<typename ... tInterfaces>
class ComEntryV;

Because we don't know exactly the number of template arguments and yet we need to put some meaningful implementation into the class, we provide another template that will be used recursively, as many times as dictated by the code that will instantiate the template. To achieve this, we divide the variable-length list of template arguments into two parts: the head consisting of exactly one argument, and a variable-length tail.

C++
template<typename tInterface, typename ... tTail>
class ComEntryV<tInterface, tTail...> :
  public ComEntryCompound
      <

        ComEntry<tInterface>,
        ComEntryV<tTail...>
      >
{ };

As you can see, ComEntryV implicitly derives from its own specialization whose number of template arguments is reduced by one. The argument in question is used to implement a COM interface and provide its discovery information. The only thing that we need is a class specialization that will terminate the recursion. Since it implements only one interface, we will utilize the helper ComEntry1 class introduced earlier:

C++
template <typename tInterface>
class ComEntryV<tInterface> :
  public ComEntry1<tInterface>
{ };

With ComEntryV in place, we can write real pretty code for as many interfaces as we need:

C++
class TestClass :
  public ComEntryV<IClassFactory, ILog, IBindCtx, IAccessControl>
{
  // TODO: implement IClassFactory
  // TODO: implement ILog
  // TODO: implement IBindCtx
  // TODO: implement IAccessControl
};

Instantiation

As you can see from the previous listing, our classes do not implement IUnkonwn, even though they declare support for it via interface discovery. As a result, our TestClass cannot be instantiated directly. Instead, we will introduce another template that will take care of instantiation via the following functionality:

  • Providing a common mechanism for reference counting
  • Providing a means to invoke an arbitrary constructor of the class being instantiated
  • Implementing IUnknown

First, let's introduce a class to be responsible for reference counting:

C++
template<bool tThreadSafe = false>
struct ComRefCount
{
  LONG myNumRefs;

  ComRefCount() : myNumRefs(1L) { }

  ULONG STDMETHODCALLTYPE AddRef()
  {
    if (!tThreadSafe)
      return ++myNumRefs;
    else
      return InterlockedIncrement(&myNumRefs);
  }

  template<typename tObj>
  ULONG STDMETHODCALLTYPE Release(tObj * theObj)
  {
    ULONG aNumRefs;

    if (!tThreadSafe)
      aNumRefs = --myNumRefs;
    else
      aNumRefs = InterlockedDecrement(&myNumRefs);

    if (aNumRefs <= 0L)
      delete theObj;

    return aNumRefs;
  }
};

Depending on the threading model, this class can be either thread safe or thread unsafe. Please note that the number of references is automatically set to one because there is at least one reference to the object, held by the caller where the object was created. Now, let's complete the instantiation pattern:

C++
template<typename tImpl, bool tThreadSafe = false>
class ComInstance : public tImpl
{
protected:
  ComRefCount<tThreadSafe> myRefs;

public:
  ComInstance()
  { }

  template<typename ... tParams>
  ComInstance(tParams && ... theParams)
    : tImpl( std::forward<tParams>(theParams)... )
  { }

  ULONG STDMETHODCALLTYPE AddRef()
  { return myRefs.AddRef(); }

  ULONG STDMETHODCALLTYPE Release()
  { return myRefs.Release(this); }

  STDMETHODIMP QueryInterface(REFIID theIID,  void ** theOut)
  {
    HRESULT aRes = tImpl::InternalQueryInterface(theIID, this, theOut);

    if ( SUCCEEDED(aRes) )
      AddRef();

    return aRes;
  }
};

In addition to the thread safety argument already discussed, this class expects the tImpl template argument identifying the type that contains real functionality. This is the class that implements COM interfaces, such as the TestClass mentioned in the previous listings.

Here we see the variadic template constructor that allows us to automatically invoke any constructor contained in the implementation class, including the so called move constructor introduced in C++0x.

This is it. Let's just see how instances can be created:

C++
ComInstance<TestClass> aStackObj( /*args for TestClass::TestClass*/ );

TestClass * aHeapObj = new ComInstance<TestClass>( /*args*/ );

auto aHeapObj1 = new ComInstance<TestClass>( /*args*/ );  // C++0x only

More Complex Interface Hierarchies

It is not rare for COM to have certain interface derived for something other than IUnknown. A typical use case is the so called interface versioning where new functionality is contained in an interface whose name is suffixed with a version number. The interface itself would derive from its previous version. The longest chain I have found so far is ITaskbarListITaskbarList2ITaskbarList3ITaskbarList4. By the way, it is a good way to trace how the Windows Shell was progressing. But for the sake of brevity, let's deal with some home-made interfaces, rather than these bulky ones:

C++
class IInterface : public IUnknown
{
public:   STDMETHOD (InterfaceMethod) () = 0;
};

class IInterface2 : public IInterface
{
public:   STDMETHOD (InterfaceMethod2) () = 0;
};

class IInterface3 : public IInterface2
{
public:   STDMETHOD (InterfaceMethod3) () = 0;
};

class IInterface4 : public IInterface3
{
public:   STDMETHOD (InterfaceMethod4) () = 0;
};

class Impl : public ComEntryV<IInterface4>
{
protected:
  STDMETHODIMP InterfaceMethod ()
    { cout << "InterfaceMethod" << endl; }

  STDMETHODIMP InterfaceMethod2 ()
    { cout << "InterfaceMethod2" << endl; }

  STDMETHODIMP InterfaceMethod3 ()
    { cout << "InterfaceMethod3" << endl; }

  STDMETHODIMP InterfaceMethod4 ()
    { cout << "InterfaceMethod4" << endl; }
};

For a class deriving from IInterface4 a hand-crafted QueryInterface implementation would declare support for all parent interfaces, as well. But since we want to avoid having to explicitly write the method, we should address the issue in a different way.

There can be several ways to assure that. First, we can provide a partially hand-written implementation for theInternalQueryInterface method:

C++
static HRESULT InternalQueryInterface(
    REFIID theIID, Impl * theObj, void ** theOut)
{
  HRESULT aRes = ComEntryV<IInterface4>::InternalQueryInterface(
    theIID, theObj, theOut);

  if ( FAILED(aRes) )
  {
    aRes = S_OK;

    if      (theIID == IID_IInterface)
      *theOut = static_cast<IInterface  *>(theObj);

    else if (theIID == IID_IInterface2)
      *theOut = static_cast<IInterface2 *>(theObj);

    else if (theIID == IID_IInterface3)
      *theOut = static_cast<IInterface3 *>(theObj);

    else
      aRes = E_NOINTERFACE;
  }

  return aRes;
}

But the actual benefit of our infrastructure has been pretty much neglected. After all, why bother with all these classes if all we need now is adding similar hand-written blocks for two more interfaces: our top-level IInterface4 and IUnknown?

This is why we have another, a lot more elegant solution where we won't even have to sneak in the interface discovery process. The elegance again comes from C++0x and template specializations. If we analyze the inheritance chain the Impl class, we'll notice that it essentially derives from ComEntry<IInterface4>. What if we could somehow give compiler a hint about the additional interfaces that ComEntry<IInterface4> supports? Yes we can.

C++
template<>
class ComEntry<IInterface4> :
  public ComEntryWithParentDiscoveryV
    <IInterface4, IInterface3, IInterface2, IInterface>
{ };

The above listing is a template specialization that will do the job for us. With this specialization in place, our Impl class will have a modified logic of its interface discovery routine, simply because we have explicitly told the compiler how to do the override. No need to change a single line of code for our Impl class! All we need is to see what the ComEntryWithParentDiscoveryV class is all about. We will use already familiar concepts of the C++ variadic templates so that an arbitrary number of parent interfaces could be auto-discovered. First, let's define the logic for the case of one parent interface. This is very similar to discovery of IUnknown. The only difference is that we now discover two interfaces: the one being implemented and its parent:

C++
template<typename tInterface, typename tParent>
class ComEntryWithParentDiscovery1 :
  public tInterface,
  public ComEntryCompound
    <
      ComEntryDiscoverable<tInterface>,
      ComEntry2StepDiscoverable<tParent, tInterface>
    >
{
public:
  typedef tInterface Interface;
};

Now let's define the variadic template versions:

C++
template <typename tChild, typename ... tParents>
class ComEntryWithParentDiscoveryV;

template <typename tChild, typename tParent, typename ... tTail>
class ComEntryWithParentDiscoveryV<tChild, tParent, tTail...> :
  public ComEntryCompound
    <
      ComEntry2StepDiscoverable<tParent, tChild>,
      ComEntryWithParentDiscoveryV<tChild, tTail...>
    >
{
public:
  typedef tChild Interface;
};

template <typename tChild, typename tParent>
class ComEntryWithParentDiscoveryV<tChild, tParent> :
  public ComEntryWithParentDiscovery1<tChild, tParent>
{ };

Again, the concept must be already familiar. First we define that the class can have an arbitrary number of parent interfaces for which we want to add discovery information. Second, we pick one class off the variable-length list of parent interfaces and define the actual discovery information logic in a recursive manner. And finally, we terminate the recursion with a class that only has one immediate parent.

The __uuidof Operator

In our build environment (GCC), special tricks need to be performed in order to emulate the __uuidof operator. A series of preprocessor macros illustrates the approach:

C++
#define DEFINE_UUIDOF_ID(Q, IID) template<> GUID hold_uuidof<Q>::__IID = IID;
#define DEFINE_UUIDOF   (Q)      DEFINE_UUIDOF_ID(Q, IID_##Q)
#define __uuidof        (Q)      hold_uuidof<Q>::__IID

These macros rely on the fact that most of the interfaces not only declare their GUID as an attribute, but also specify an explicit GUID constant, named IID_InterfaceNameGoesHere. However, the developer will still need to map the interfaces used in a project to their GUIDs by placing these macros somewhere in a .CPP file:

C++
DEFINE_UUIDOF(IUnknown)
DEFINE_UUIDOF(IClassFactory)
DEFINE_UUIDOF(ILog)
DEFINE_UUIDOF(IBindCtx)
DEFINE_UUIDOF(IAccessControl)

Sample Application

The sample provided is a basic console application that uses the Microsoft XML parser to extract the version number of the before mentioned KO Approach from a PAD file hosted on the KO Software website. PAD files are XML files of standardized structure that allow software sites to extract various information about a software product.

We will be using SAX for parsing. Firstly, because it's simple. But the most important reason is that with SAX we will have to implement a COM interface without the need to have a full-fledged COM server. Just in case you don't know, SAX is an event passed paradigm of XML parsing. As the parser detects various entities in the XML document, it raises events passing the data about found entities. It is up to the developer how these entities will be further processed. For this task, we will wait for the inner text inside a predefined XML element identifying the product version. Here's the principal code:

C++
int main(int argc, char** argv)
{
  CoInitialize(NULL);

  // Create a SAX XML reader through COM, use the earliest version possible
  // (version 3.0) in this case.

  ISAXXMLReader * aRdr = 0;
  HRESULT aRes = CoCreateInstance
  (
    CLSID_SAXXMLReader30,
    NULL,
    CLSCTX_ALL,
    IID_ISAXXMLReader,
    (void **)&aRdr
  );

  if ( SUCCEEDED(aRes) && aRdr != 0)
  {
    // Create an instance of the content handler that will respond to various
    // SAX events.

    ComInstance<XmlHandler> aH;
    aRes = aRdr->putContentHandler(&aH);

    if ( SUCCEEDED(aRes) )
    {
      aRes = aRdr->parseURL(L"http://www.ko-sw.com/pad/approach.xml");

      if ( SUCCEEDED(aRes) )
        wcout << L"The current version of KO Approach is "
              << aH.GetParsedCurrentVersion() << endl;
      else
        wcerr << L"Unable to parse the URL: " << aRes << endl;
    }

    else
      wcerr << L"Unable to set the SAX content handler: " << aRes << endl;

    if (aRdr != 0)
      aRdr->Release();
  }
  else
    wcerr << L"Unable to create XML reader: " << aRes << endl;


  CoUninitialize();

  return 0;
}

The class responsible for parsing is XmlHandler. As it receives events, it accumulates information about the product version that we are interested in:

C++
class XmlHandler : public ComEntryV<ISAXContentHandler>
{
private:
  std::vector<std::wstring> myStack;
  std::wstring myCurVersion;


// Interface
public:
  const std::wstring & GetParsedCurrentVersion() const
    { return myCurVersion; }


// ISAXContentHandler members
protected:

  STDMETHODIMP startElement (LPCWSTR theLocalName, int theNumCharsLocalName)
  {
    std::wstring aName(theLocalName, theLocalName + theNumCharsLocalName);
    myStack.push_back(aName);

    return S_OK;
  }

  STDMETHODIMP endElement (LPCWSTR theLocalName, int theNumCharsLocalName)
  {
    std::wstring aName(theLocalName, theLocalName + theNumCharsLocalName);

    if (myStack.back() != aName)
      return E_FAIL;

    myStack.pop_back();
    return S_OK;
  }

  STDMETHODIMP characters (LPCWSTR theString, int theNumChars)
  {
    if (myStack.size() == 3)
      if ( myStack[0] == L"XML_DIZ_INFO" &&
           myStack[1] == L"Program_Info" &&

           myStack[2] == L"Program_Version" )
      {
        myCurVersion += std::wstring(theString, theString + theNumChars);
      }

    return S_OK;
  }
};

Here's what the app prints at the time of writing this article:

Demo Application Screenshot

Conclusion

The concepts described here are an illustration of how a language feature that may seem highly theoretical, experimental, meaningful to C++ purists only, can help in a practical task of implementing an arbitrary number of COM interfaces with less code and yet very little (if no) performance overhead. These concepts are in no way a replacement for professional solutions for COM development, such as ATL. The latter has some really advanced features that many COM developers use, such as tear-off entries, class factory utilities, IDispatch support, and many more.

It is also highly unlikely that the build environment chosen for this article will be used in an ATL project. For Visual Studio however, the code will still compile, with limitations caused by the former's limited support for C++0x:

  • Instead of variadic-template ComEntryV, fixed-length template versions must be used: ComEntry1 .. ComEntry5, with support for up to five interfaces. Implementing the concept for six and more interfaces is a matter of simple copy-paste.
  • Instead of variadic-template ComEntryWithParentDiscoveryV, fixed-length template versions must be used: ComEntryWithParentDiscovery1 .. ComEntryWithParentDiscovery5, with support for up to five parent interfaces. Implementing the concept for six and more parent interfaces is a matter of simple copy-paste.
  • Instead of variadic-template constructors, the ComInstance class will have fixed-length versions. Up to five arguments are supported. Adding more arguments is even simpler than above.

I hope that with newer releases of Visual Studio 2010, support for C++0x will be more consistent and developers will be able to utilize the code in a more convenient build environment also.

History

  • 2011-09-03: Initial release
  • 2011-09-04: Minor code listing and text corrections

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Russian Federation Russian Federation
Kirill Osipov constantly lives in Petrozavodsk, Russia. He enjoys listening to music, playing the guitar, learning German, and, occasionally, drinking beer and whisky.

He is extremely passionate about software development and has recently realized he can be most creative in this particular field.

He also maintains his part time business at http://www.ko-sw.com.

Comments and Discussions

 
GeneralHm.. Pin
Natko.Kalisnik21-Aug-17 8:47
Natko.Kalisnik21-Aug-17 8:47 
QuestionNow the Variadic temlates fully supported in C++ VS 2013. Pin
oleg636-Mar-14 6:52
professionaloleg636-Mar-14 6:52 
Question"It may be ridiculous to imagine that someone is still targeting COM" Pin
Michael Chourdakis25-Jul-12 22:58
mvaMichael Chourdakis25-Jul-12 22:58 
AnswerRe: "It may be ridiculous to imagine that someone is still targeting COM" Pin
Evan Lang14-Mar-14 7:25
Evan Lang14-Mar-14 7:25 
GeneralMy vote of 5 Pin
Pablo Aliskevicius18-Sep-11 22:56
Pablo Aliskevicius18-Sep-11 22:56 
GeneralMy vote of 5 Pin
yafan8-Sep-11 3:36
yafan8-Sep-11 3:36 
QuestionYou have my 5!!!! Pin
Member 11753135-Sep-11 19:05
Member 11753135-Sep-11 19:05 
Question[My vote of 2] COM is alive Pin
Sergey Podobry5-Sep-11 4:56
professionalSergey Podobry5-Sep-11 4:56 
AnswerRe: [My vote of 2] COM is alive Pin
Jim Crafton6-Sep-11 5:46
Jim Crafton6-Sep-11 5:46 
GeneralRe: [My vote of 2] COM is alive Pin
Kirill Osipov6-Sep-11 8:31
Kirill Osipov6-Sep-11 8:31 
GeneralRe: [My vote of 2] COM is alive Pin
Pablo Aliskevicius18-Sep-11 22:57
Pablo Aliskevicius18-Sep-11 22:57 
About the COM 2 spec: COM was originally called OLE 2...
Smile | :)
Pablo.

"Accident: An inevitable occurrence due to the action of immutable natural laws." (Ambrose Bierce, circa 1899).

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.