Figure 1   Delegator Hook Interfaces

////////////////////////////////////////////////////////
// excerpt from delegate.idl
//

[
    uuid(9b8c32f1-249a-11d2-a7bb-006008d25ccf),
    object,
    local
]
interface IDelegatorHookMethods : IUnknown
{
    void DelegatorPreprocess( [in] DWORD nVtblIndex,
                              [in] void* pArgs,
                              [in, out] DWORD* pnCookie );
    HRESULT DelegatorPostprocess( [in] DWORD nVtblIndex,
                                  [in] HRESULT hrFromInner,
                                  [in] DWORD nCookie );
}

[
    uuid(9b8c32f9-249a-11d2-a7bb-006008d25ccf),
    object,
    local
]
interface IDelegatorHookQI : IUnknown
{
    typedef enum _DelegatorHookOptions
    {
        DHO_PREPROCESS_METHODS  = 0x00000001,
        DHO_POSTPROCESS_METHODS = 0x00000002
    } DelegatorHookOptions;

    HRESULT Init( [in] IUnknown* pUnkInner );
    HRESULT OnFirstDelegatorQIFor(
        [in] REFIID iid,
        [in, iid_is(iid)] IUnknown* pItfInner,
        [out] DWORD* pgrfDelegatorHookOptions,
        [in] REFIID iidMethodHook,
        [out, iid_is(iidMethodHook)] void** ppMethodHook );
}

Figure 2   CoDelegator::QueryInterface Implementation

////////////////////////////////////////////////////////
// excerpt from CoDelegator.cpp
//

STDMETHODIMP CoDelegator::QueryInterface( REFIID iid, void** ppv )
{
    // we subsume the identity of the inner
    if ( IID_IUnknown == iid )
    {
        ((IUnknown*)(*ppv = static_cast<IUnknown*>(this)))->AddRef();
        return S_OK;
    }
    else if ( ( IID_IMarshal == iid ) && ( DO_MBV_ALL & m_grf ) )
    {
        ((IUnknown*)(*ppv = static_cast<IMarshal*>(this)))->AddRef();
        return S_OK;
    }
    *ppv = 0;

    Lock lock( *this );

    // see if we've already assimilated the requested interface
    HRESULT hr = S_OK;
    {
        Delegator* pDelegator = 0;
        if ( _findDelegator( iid, pDelegator, hr ) )
        {
            if ( SUCCEEDED( hr ) )
                reinterpret_cast<IUnknown*>( *ppv = 
                    pDelegator )->AddRef();
            return hr;
        }
    }

    // go get the requested interface from the inner and assimilate it.
    IUnknown* pUnkInner = 0;
    hr = m_pUnkInner->QueryInterface( iid, (void**)&pUnkInner );
    if ( SUCCEEDED( hr ) )
    {
        DWORD grfOptions = 0;
        IDelegatorHookMethods* pHookMethods = 0;
        if ( m_pHook )
        {
            hr = m_pHook->OnFirstDelegatorQIFor( iid, pUnkInner,
                &grfOptions, IID_IDelegatorHookMethods,
                (void**)&pHookMethods );
                if ( SUCCEEDED( hr ) )
                {
                    // watch for invalid results from QI hook
                    grfOptions = grfOptions & 0x00000007;
                    if ( ( 0 == grfOptions ) && pHookMethods )
                    {
                        pHookMethods->Release();
                        pHookMethods = 0;
                    }
                }
                else if ( E_NOINTERFACE == hr )
                {
                    // we only give the QI hook *one* chance to say   
                    // this to a particular interface so that we 
                    // help maintain a correct implementation of QI.
                    grfOptions = Delegator::DONT_EXPOSE_FROM_QI;
                    hr = S_OK;
                }

                // if postprocessing is required,
                // make sure we've acquired a TLS slot
                if ( SUCCEEDED( hr )
                    && ( DHO_POSTPROCESS_METHODS & grfOptions )
                    && !_lazyAllocTLSIndex() )
                {
                    hr = E_OUTOFMEMORY;        // sort of :-)
                    if ( pHookMethods )
                    {
                        pHookMethods->Release();
                        pHookMethods = 0;
                    }
                }
        }

        if ( SUCCEEDED( hr ) )
        {

            // grow the array if necessary
            if ( m_last == m_end )
                hr = _growArray();
                  
            if ( SUCCEEDED( hr ) )
            {
                Delegator* pDelegator =
                    new Delegator( *this, pUnkInner, iid,
                        grfOptions, pHookMethods );
                if ( pDelegator )
                {
                    *m_last++ = pDelegator;
                    if ( Delegator::DONT_EXPOSE_FROM_QI &   
                        grfOptions )
                            hr = E_NOINTERFACE;
                    else reinterpret_cast<IUnknown*>
                        (*ppv = pDelegator)->AddRef();
                }
                    else hr = E_OUTOFMEMORY;
            }
            if ( pHookMethods )
                pHookMethods->Release();
        }
        pUnkInner->Release();
    }
    return hr;
}

Figure 4   IDelegatorHook Methods


struct MethodHook : IDelegatorHookMethods
{
  const IID m_iid;
  MethodHook( REFIID iid ) : m_iid( iid ) {}

  // this could be some smart method that maps
  // method indices to names via type information
  // and dumps them to debug output or a logfile
  void LogMessage( const TCHAR* psz,
                   DWORD nMethod )
  {
    // implementation omitted for brevity
  }

  // IUnknown implementation omitted for brevity

  // IDelegatorHookMethods implementation
  void DelegatorPreprocess( DWORD nIndex,
                            void*, DWORD* )
  {
     LogMessage( __TEXT( "entering" ), nIndex );
  }
  HRESULT DelegatorPostprocess( DWORD nVtblIndex,
                                HRESULT, DWORD )
  {
    LogMessage( __TEXT( "leaving" ), nIndex );
    return S_OK;
  }
};

HRESULT CoAuditorHookQI::OnFirstDelegatorQIFor(
  REFIID iid, IUnknown*, DWORD* pgrfOptions,
  REFIID iidMethodHook, void** ppvMethodHook )
{
  HRESULT hr = S_OK;
  MethodHook* pHook = new MethodHook( iid );
  pHook->AddRef();
  hr = pHook->QueryInterface( iidMethodHook,
                              ppvMethodHook );
  pHook->Release();
  return hr;
}

Figure 6   Implementing Delegation


// delegation code
void __declspec(naked) delegate(void) {
  __asm {
    // stack: nVtblOffset/retaddr/this/args
    mov  eax, [esp+8] // eax = this (the shim)
    mov  eax, [eax+4] // eax = this->m_pUnkInner
    mov  [esp+8], eax // replace this ptr on stack
    mov  eax, [eax]   // eax = vptr
    add  eax, [esp]   // eax += nVtblOffset
    mov  eax, [eax]   // eax = vptr[nVtblOffset]
    add  esp, 4       // pop nVtblOffset
    jmp  eax          // delegate call
  }
}

// shim entry points
void __declspec(naked) method0(void) {
  __asm push 0 // zero byte offset into vtable
  __asm jmp  delegate
}
void __declspec(naked) method1(void) {
  __asm push 4 // four byte offset into vtable
  __asm jmp  delegate
}
// and so on, for all methods in the interface


Figure 7   Delegator Postprocessing


////////////////////////////////////////////////////////
// excerpt from Delegator.cpp
//

// this method is called if we need to postprocess as well
DWORD __stdcall preprocess2( Delegator& d, const void* pReturnAddr,
                             DWORD nVtblOffset, void* pArgs )
{
    // first try to acquire a buffer - if this fails,
    // we cannot do any delegation at all - just shunt the method directly
    // to the inner object and forget it.
    CallContext* pcc = CoDelegator::PushNewCallContext( d, pReturnAddr,
                                                        nVtblOffset );
    if ( !pcc )
        return 0;

    if ( DHO_PREPROCESS_METHODS & d.m_grf )
        d.m_pHook->DelegatorPreprocess( nVtblOffset / sizeof( void* ),
                                        pArgs, &pcc->m_nCookie );

    return 1;
}

HRESULT __stdcall postprocess( HRESULT hrFromInner,
                                const void** ppReturnAddr )
{
    // get the call context back from TLS
    CallContext* const pcc = CoDelegator::PopCallContext();

    HRESULT hr = pcc->m_pDelegator->m_pHook->
            DelegatorPostprocess( pcc->m_nVtblOffset / sizeof( void* ),
                                  hrFromInner, pcc->m_nCookie );

    *ppReturnAddr = pcc->m_pReturnAddr;
    CoDelegator::DeleteCallContext( pcc );

    return hr;
}

static __declspec(naked) void delegateAndPostprocess(void)
{
    __asm
    {
        // get the vtbl index
        pop  eax            // eax = vtbl index (in bytes)
        sub  esp, 8
        push eax
        push ebp            // set up simple stack frame
        mov  ebp, esp

        // ebp+4  = local variable: vtbl offset (in bytes)
        // ebp+8  = local variable: result of context allocation
        // ebp+12 = local variable: address of inner's method
        // ebp+16 = retaddr
        // ebp+20 = this
        // ebp+24 = args

        lea  eax, [ebp+24]  // eax = preprocess( this, pReturnAddr,
        push eax            //                   nVtblOffset, pArgs );
        push [ebp+4]
        push [ebp+16]
        push [ebp+20]
        call preprocess2
        mov  [ebp+8], eax   // store result of context allocation

        mov  eax, [ebp+20]  // this = eax = pInner
        mov  eax, [eax+4]
        mov  [ebp+20], eax  

        mov  eax, [eax]     // store address of inner's virtual function
        add  eax, [ebp+4]
        mov  eax, [eax]     
        mov  [ebp+12], eax

        pop  ebp            // tear down stack frame
        pop  eax            // discard vtbl offset

        pop  eax            // was context alloc successful?
        test eax, 1
        jnz allocSuccessful

        pop  eax            // delegate without postprocessing
        jmp eax

    allocSuccessful:
        pop  eax
        add  esp, 4         // remove caller's return addr from stack
        call eax            // and call inner

        sub  esp, 4         // make room for original return addr
        push esp            // eax = postprocess( eax, ppReturnAddr )
        push eax
        call postprocess

        ret
    }
}

Figure 8   aclsample.idl


/////////////////////////////////////////////////////////////////////
// aclsample.idl
//
// Sample IDL demonstrating declarative security
// using custom attributes and the access control hook

#include "ahattr.h"
import "unknwn.idl";
[
    object,
    uuid(69934612-7F39-11D2-A7FF-006008D25CCF),
    AH_GRANT_ACCESS( "CouchPotatoes" )
]
interface ISnack : IUnknown
{
    HRESULT Eat();

    [AH_DENY_ACCESS( "HeavyDrinkers" )]
    HRESULT Drink();
}

[
    object,
    uuid(69934613-7F39-11D2-A7FF-006008D25CCF)
]
interface IPretzel : IUnknown
{
    [AH_GRANT_ACCESS( "FolksWithHighBloodPressure" )]
    HRESULT ScrapeOffSalt();

    HRESULT ChewSlowly();
}

[uuid(69934606-7F39-11D2-A7FF-006008D25CCF), version(1.0)]
library SnackLib
{
    importlib("stdole32.tlb");

    [
    uuid(69934614-7F39-11D2-A7FF-006008D25CCF),
    AH_GRANT_ACCESS( "PretzelEaters" )
    ]
    coclass Pretzel
    {
        [default] interface IPretzel;
        interface ISnack;
    }
}