CComAptAffinityMeter class

 

Don Box says in his excellent Essential COM that "COM defines an HRESULT (RPC_E_WRONG_THREAD) that certain system-level objects will return when directly accessed from foreign apartments. It is legal for user-defined objects to return this HRESULT as well; however, few developers are willing to go to this length to ensure proper usage of their objects."  Well, not anymore :-) - the following class will let you do that with just a couple of lines of code .

Notes:
Every COM Apartment has a unique OXID. An OXID is assigned to every Apartment when it exports its first Interface pointer and is stored in the TLS of the thread which services the particular apartment. The following class extracts the OXID from a marshaled MEOW packet at the creation time and compares it with the caller's OXID during the time of a method call.

Usage:
Declare a member of the CComAptAffinityMeter say m_Meter, in your COM object class.
Then, in every method call where you are suspicious of a wrong thread acceessing your code call :

hr = m_Meter.IsCallerValid()
if(hr == RPC_E_WRONG_THREAD), you know you are in trouble brother !

Of course, the assumption here is that your object does not like its raw interface pointers being passed around apartments(does not aggregate FTM etc.)

Dependencies:  You should include the header file which I have created from the DCOM Wire Spec - objref.H 

Known Issues : Tested with NT4

Thanks to  :        Chris Sells for pointing out the bugs.. Thanks, Chris !


class CComAptAffinityMeter
{
private:
    struct MeUnknown : public IUnknown
    {
        MeUnknown():m_cRef(0){}
        STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
        {
            if (riid == IID_IUnknown)
                return reinterpret_cast<IUnknown*>(*ppv =                                          static_cast<IUnknown*>(this))->AddRef(), S_OK;
            else
                return (*ppv = 0), E_NOINTERFACE;
        }
        STDMETHODIMP_(ULONG) AddRef()
        {
            return InterlockedIncrement(&m_cRef);
        }
        STDMETHODIMP_(ULONG) Release()
        {
            long dw = InterlockedDecrement(&m_cRef);
            if(dw == 0){ delete this; }
            return dw;
        }
        long m_cRef;
    }*m_pUnk;


    OXID  m_CreaterOXID;
   
private:

    HRESULT GetOXID(/*[out]*/OXID *pOXID)
    {
        CComPtr<IStream> spStm = 0;
        HRESULT hr = E_FAIL;

        if(SUCCEEDED(::CreateStreamOnHGlobal(0, TRUE, &spStm)))
        {
            hr = ::CoMarshalInterface(spStm,IID_IUnknown,m_pUnk,
                    MSHCTX_INPROC,0,MSHLFLAGS_NORMAL);
       
            HGLOBAL hg = 0;
            hr = ::GetHGlobalFromStream(spStm,&hg);

            OBJREF *pObj = (OBJREF*)GlobalLock(hg);   
           
            if(!pObj)
            {
                GlobalUnlock(hg);
                LARGE_INTEGER l = { 0 };
                spStm->Seek(l, STREAM_SEEK_SET, NULL);
                hr = CoReleaseMarshalData(spStm);
                spStm.Release();
                return E_FAIL;
            }
                       
            if(pObj->objRefflags == 1) //STDOBJEREF
            {
                memcpy(pOXID,&(pObj->u_objref.u_standard.std.oxid), sizeof(OXID));
            }
            else if(pObj->objRefflags == 2)//Handler
            {
                memcpy(pOXID,&(pObj->u_objref.u_handler.std.oxid),sizeof(OXID));
            }
            else //Custom OBJREF
            {
                hr = E_FAIL;
            }
           
            GlobalUnlock(hg);
            LARGE_INTEGER l = { 0 };
            spStm->Seek(l, STREAM_SEEK_SET, NULL);
            spStm.Release();
        }
        return hr;
    }
   
public:   

    CComAptAffinityMeter() : m_pUnk(0)
    {
        m_pUnk = new MeUnknown();
        m_pUnk->AddRef();
        GetOXID(&m_CreaterOXID);
    }

    ~CComAptAffinityMeter()
    {
        if(m_pUnk)
        {
            m_pUnk->Release();
        }
    }

    //S_OK == wahoo ! Valid Apartment !

    //RPC_E_WRONG_THREAD == Trouble. Wrong caller !("The application called an interface that

    //was marshalled for a different thread..." Now pay the price !! ;-) )

    HRESULT IsCallerValid()
    {
       OXID oxidCaller;
       GetOXID(&oxidCaller);
       return ( (memcmp(&oxidCaller,&m_CreaterOXID,sizeof(OXID)) == 0) ? S_OK:RPC_E_WRONG_THREAD);
    }
};


This page and the source contained in(or referred to) by this page are Copyright (c) 1998, Dharma Shukla.
All rights reserved. No warrenties extended. Use at your own risk.
Do send Comments/Bugs to me :-)