Figure 1    Using the Free Threading Model

// constant _WIN32_DCOM defined in project settings

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow)
{


/*
Check for presence of the function CoCreateInstanceEx() in the operating
system DLL Ole32.dll. If not found, then the free threading model is
not supported. Inform user and exit.
*/

    HANDLE hMod = GetModuleHandle ("ole32.dll") ;

    FARPROC fp = GetProcAddress (hMod, "CoCreateInstanceEx") ;

    if (fp == NULL)
    {
        MessageBox (NULL, "This version of the operating system does not\
        support the free threading model", "Error", MB_OK) ;

        return -1 ;
    }

/* 
Initialize COM for free threading model.
*/

    CoInitializeEx (NULL, COINIT_MULTITHREADED) ;

    <rest of WinMain>
}

Figure 3    IDataObject::GetData


extern UINT cfThreadId, cfTimeData,;

STDMETHODIMP CTimeData::GetData (LPFORMATETC lpfe, LPSTGMEDIUM lpstg) 
{

/*
We support a private clipboard format identified by the UINT cfThreadId. 
The caller is asking for the ID of the thread on which we receive the 
call. Allocate a new global, copy our thread ID into it, and fill in the 
elements of the STGMEDIUM structure that we were passed.
*/

    if (lpfe->cfFormat == cfThreadId
        && lpfe->tymed == TYMED_HGLOBAL
        && lpfe->dwAspect == DVASPECT_CONTENT)
    {
        DWORD *pdw ;
        pdw = (DWORD *)GlobalAlloc(GMEM_FIXED, sizeof (DWORD));

        if (!pdw)
        {
            return E_OUTOFMEMORY ;
        }                                           
        
        *pdw = GetCurrentThreadId ( ) ;
        
        lpstg->tymed = TYMED_HGLOBAL ;
        lpstg->hGlobal = (HGLOBAL) pdw ;
        lpstg->pUnkForRelease = NULL ; 
        
        return  S_OK ;

    }

/*
We support a private clipboard format, identified by the UINT 
cfTimeData. This is actually a structure of type SYSTEMTIME. Allocate a 
new global, copy our global system time into it, and fill in the 
elements of the STGMEDIUM structure that we were passed.
*/
    if (lpfe->cfFormat == cfTimeData
        && lpfe->tymed == TYMED_HGLOBAL
        && lpfe->dwAspect == DVASPECT_CONTENT)
    {
        SYSTEMTIME *pSt ;
            
/*
If lindex == -1, then we are simply transferring a SYSTEMTIME structure.
Any other value means that we are doing a performance test.  In this
case, the value of lindex is the amount of memory to allocate, while
the calling app measures response time.
*/
        if (lpfe->lindex == -1)
        {
            pSt = (SYSTEMTIME *)GlobalAlloc(GMEM_FIXED, 
                sizeof (SYSTEMTIME));
        }
        else
        {
            pSt = (SYSTEMTIME *)GlobalAlloc(GMEM_FIXED, lpfe->lindex);
        }

        if (!pSt)
        {
            return E_OUTOFMEMORY ;
        }                                           
        
        GetLocalTime (pSt) ; 
        
        lpstg->tymed = TYMED_HGLOBAL ;
        lpstg->hGlobal = (HGLOBAL) pSt ;
        lpstg->pUnkForRelease = NULL ; ;
        
        return  S_OK ;
    }

/*
User asked for a format that we couldn't supply.  Return error code.
*/
    return DATA_E_FORMATETC ;
}

Figure 5   WM_PAINT Message Handler

 extern IDataObject *pFreeObj ;
 extern UINT cfThreadId ;    DWORD FreeTransferTime ;
 
 case WM_PAINT:
 {
         PAINTSTRUCT ps ;    HDC hDC ;
         char out [256] ;    int length ;
         hDC = BeginPaint (hWnd, &ps) ;
 
 /*
 If no object present, so inform user.
 */
         if (pFreeObj == NULL)
         {
             length = wsprintf (out, "<no object>") ;
             TextOut (hDC, 0, 0, out, length) ;
         }
         else
         {
 /*
 Call IDataObject::GetData() to make the object report the ID of the
 thread on which it receives calls. Draw on screen. 
 */
             FORMATETC fe ;     STGMEDIUM stm ;
             DWORD ThreadId = 0xFFFFFFFF, *pdw ;
 
             fe.cfFormat = cfThreadId ;
             fe.ptd = NULL ;
             fe.dwAspect = DVASPECT_CONTENT ;
             fe.lindex = -1 ;
             fe.tymed = TYMED_HGLOBAL ;
 
             if (pFreeObj->GetData (&fe, &stm) == S_OK)
             {
                 pdw = (DWORD *)GlobalLock (stm.hGlobal) ;
                 ThreadId = *pdw ;
                 GlobalUnlock (stm.hGlobal) ;
                 ReleaseStgMedium (&stm) ;
              }
 
              length = wsprintf (out, "Object received last call from this " 
                     "window on thread ID == 0x%x", ThreadId) ;
              TextOut (hDC, 0, 0, out, length) ;
              length = wsprintf (out, "Transfer Time == %d ms", 
                      FreeTransferTime) ;
              TextOut (hDC, 0, 20, out, length) ;
     }
     EndPaint (hWnd, &ps) ;
     return 0 ;
 }

Figure 8   Control Server IUnknown Interface

 /*
 AddRef() and Release() methods use InterlockedIncrement() and
 InterlockedDecrement() to serialize access to the member variable m_RefCount,
 thereby making themselves thread safe.
 */
 STDMETHODIMP_(ULONG) CTimeData::AddRef(void)
 {
     return InterlockedIncrement (&m_RefCount) ;
 }
 
 /*
 The important point here is that you can not touch any member variables of the
 object after the InterlockedDecrement, even if the return in non-zero, since
 your thread could get preempted and another thread could call the final Release 
 that deletes the object. When the first thread runs again, the object is gone and AVs.
 */
 STDMETHODIMP_(ULONG) CTimeData::Release(void)
 {
     ULONG cRefs = InterlockedDecrement (&m_RefCount);
     if (cRefs == 0)
     {
         delete this;
         return 0 ;
     }
     return cRefs ;
 }
 
 /*
 The QueryInterface() method does everything on the stack. It is thread safe as is.
 */
 
 HRESULT CTimeData::QueryInterface(REFIID riid, LPVOID FAR *ppv)
 {
     if (riid == IID_IUnknown || riid == IID_IDataObject)
     {
         *ppv = (LPVOID)this;  
         AddRef();
         return S_OK ;
     }
     else
     {
         *ppv = NULL;       
         return  E_NOINTERFACE ;
     }
 }

Figure 9   Serializing IDataObject::DAdvise and Dunadvise

 STDMETHODIMP CTimeData::DAdvise (FORMATETC FAR* lpfe, DWORD dw, 
                                  LPADVISESINK lpas, DWORD *lpdw) 
 {
         HRESULT hr, retval = S_OK ;
 
 /*
 Wait on the critical section so that no other threads making this method call can
 interrupt this operation.
 */
         EnterCriticalSection (&m_csDAdvise) ;
 
         if (m_pAdvSink == NULL)
         {
 /*
 Perform operations necessary for advise callback from a different thread.
 Ignore this code, from here up to but not including the call to LeaveCriticalSection(),
 until the discussion of servers that support both threading models.
 */
 
             m_pAdvSink = lpas ;
             m_pAdvSink->AddRef () ;
 /*
 Marshal the advise sink interface pointer into a stream.
 */
             IStream *pStream ;
             hr = CoMarshalInterThreadInterfaceInStream (IID_IAdviseSink, 
                                                         m_pAdvSink, &pStream) ;
 
             if (hr != S_OK)
             {
                    char out [128] ;
                    wsprintf (out, "Marshal failed %x", hr) ;
                    MessageBox (NULL, out, "", 0) ;
             }
 /*
 Spin off a new thread to do timer notifications. Pass the
 stream pointer to the new thread as its startup parameter.
 */
             g_ThreadShutdownFlag = FALSE ;
 
             m_hThread = CreateThread (NULL, 0, TimerProc, pStream, 
                                       0, &m_ThreadID) ;
         }
         else
         {
             retval = E_FAIL ;
         }
 
 
 /*
 If you are following my comment above to ignore the code, stop now. 
 
 Leave the critical section.
 */
 
     LeaveCriticalSection (&m_csDAdvise) ;
 
     return  hr ;
 }
 
 STDMETHODIMP CTimeData::DUnadvise  (DWORD dw)
 {
 
 /*
 Wait on the critical section so that no other threads making this method call can
 interrupt this operation.
 */
 
     EnterCriticalSection (&m_csDAdvise) ;
 
 /*
 Perform cleanup.
 */
     if (m_pAdvSink)
     {
        m_pAdvSink->Release() ;
        m_pAdvSink = NULL ;
        g_ThreadShutdownFlag = TRUE ;
     }
 
 /*
 Leave the critical section.
 */
     LeaveCriticalSection (&m_csDAdvise) ;
 
     return S_OK ;
}

Figure 10   Registering and Resuming a Suspended Class Factory

 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
 	         LPSTR lpCmdLine, int nCmdShow)
 {
     MSG msg;   WNDCLASS  wc; 
     HRESULT  hr ;
 
 /*
 Check which threading model the client wants us to support. This is my own
 addition to the LocalServer32 key for convenience in the demonstration app,
 not part of COM. Initialize COM accordingly. 
 */
 
     BOOL bApartment ;
 
     if (strstr (lpCmdLine, "-Apt"))
     {
       bApartment = TRUE ;
       ClsidToRegister = GUID_ApartmentThreadedTimeDataLocal ;
       hr = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED) ;
     }
     else
     {
       ClsidToRegister = GUID_FreeThreadedTimeDataLocal ;
       bApartment = FALSE ;
       hr = CoInitializeEx (NULL, COINIT_MULTITHREADED) ;
     }
 /*
 Check command line to see if launched as an OLE server.  If so, register
 the class factory for this app.
 */
 
     DWORD dwRegister ;   BOOL bRegister = FALSE ;
 
     if (strstr (lpCmdLine, "-Embedding"))
     {
       LPCLASSFACTORY pCF = new CClassFactory ( ) ;
 
       hr = CoRegisterClassObject(
             ClsidToRegister, pCF, 
             CLSCTX_LOCAL_SERVER,
             REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED,
             &dwRegister) ;
     }
 /*
 Resume class objects, telling server that it's open for business.
 */
     CoResumeClassObjects ( ) ;
 
     < rest of WinMain >
 }

Figure 11   CoAddRefServerProcess and CoReleaseServerProcess

 CTimeData::CTimeData()
 {
     m_RefCount = 1 ; 
     InterlockedIncrement (&g_ObjectCount) ;
 /*
 New object created, increment the server process's internal reference count. 
 */
     CoAddRefServerProcess( ) ;
 }
 
 
 CTimeData::~CTimeData(void)
 {
     InterlockedDecrement (&g_ObjectCount) ;
     InvalidateRect (hMainWnd, NULL, TRUE) ;
 
 /*
 Object destroyed. Decrement the server process's internal reference count.
 If it reaches zero, begin server shutdown. 
 */
     int count = CoReleaseServerProcess( ) ;
 
     if (count == 0)
     {
       ShutDownServer ( ) ;
     }
 }
 
 STDMETHODIMP CClassFactory::LockServer(BOOL bLock)
 {
 
 /*
 LockServer called with value of TRUE. Increment server process reference count.
 */
     if (bLock == TRUE)
     {
       CoAddRefServerProcess( ) ;
     }
 
 /*
 LockServer called with value of FALSE. Decrement server process reference count.
 If it reaches zero, begin shutdown.
 */
 
     else
     {
       int count = CoReleaseServerProcess( ) ;
 
       if (count == 0)
       {
           ShutDownServer ( ) ;
       }
     }
 
     return S_OK ;
}

Figure 12   Apartment Thread Procedure in a Free Threaded Client

 DWORD WINAPI AptThreadProc (LPVOID lpv)
 {
 
 /*
 Initialize COM on this thread as using the apartment model. The free threads in the app
 omit this call, as the main thread has already made it to use the free threading model. 
 */
 
     CoInitializeEx (NULL, COINIT_APARTMENTTHREADED) ;
 
 /*
 Create MDI Child window, and perform other initializations.
 
 Code omitted for clarity. 
 */
 
 
 /*
 Service the message loop, as required for the apartment threading model.
 */
 
     MSG msg ;
 
     while (GetMessage(&msg, NULL, NULL, NULL)) 
     {
       TranslateMessage(&msg); 
       DispatchMessage(&msg); 
     }
 
 
 /*
 Shut down COM in this thread. The free threads omit this call as well. 
 */
 
     CoUninitialize( ) ;
 
     return 0 ;
}

Figure 13   IAdviseSink::OnDataChange

 STDMETHODIMP_(void) CAdviseSink::OnDataChange(LPFORMATETC pFEIn,
                                               LPSTGMEDIUM pSTM)
 {
 
 /*
 Get ID of the thread from which this call originated. This is
 in the data sent by the object.
 */
     DWORD *pdw ;
     UINT CallerThreadId ;
 
     if (pFEIn->cfFormat == cfThreadId)
     {
       pdw = (DWORD *)GlobalLock (pSTM->hGlobal) ;
       CallerThreadId = *pdw ;
       GlobalUnlock (pSTM->hGlobal) ;
     }
 
 /*
 Get the current thread ID, which is the ID of the thread on which
 this call is received.
 */
 
     DWORD CurrentThreadID  = GetCurrentThreadId( ) ;
 
 /*
 Draw both on the screen for the user to see.
 */
 
     MessageBeep (0) ; 
 
     HDC hDC = GetDC (m_hWnd) ;
     char out [128] ; int length ;
 
     length = wsprintf (out, "Callback originated on thread ID == 0x%x", 
                        CallerThreadId) ;
     TextOut (hDC, 0, 50, out, length) ;
 
     length = wsprintf (out, "Callback received on thread ID == 0x%x", 
                        CurrentThreadID) ;
     TextOut (hDC, 0, 70, out, length) ;
 
     ReleaseDC (m_hWnd, hDC) ;
     return;
}

Figure 16   Performing Callbacks

 extern UINT cfThreadId ;
 extern CTimeData *g_pTimeDataObj ;
 extern BOOL g_ThreadShutdownFlag ;
 
 unsigned long __stdcall TimerProc (void *pStream)
 {
     IAdviseSink *pAdvSink ;
 
 /*
 Unmarshal the interface pointer.
 */
 
     HRESULT hr = CoGetInterfaceAndReleaseStream (
       (IStream *)pStream, 
       IID_IAdviseSink, 
       (void **)&pAdvSink) ;
 
     if (hr != S_OK)
     {
       MessageBox (NULL, "Get Interface fail", "", 0) ;
     }
 
 
     for (;;)
     {
 /*
 Check shutdown conditions. Quit when signaled to do so.
 */
 
       if (g_ThreadShutdownFlag == TRUE || g_pTimeDataObj == NULL)
       {
           pAdvSink->Release() ;
       }
 
 /*
 Send data change to waiting client app.
 */
 
       FORMATETC fe ;    STGMEDIUM stg ;
 
       fe.cfFormat = cfThreadId ;
       fe.tymed = TYMED_HGLOBAL ;
       fe.dwAspect = DVASPECT_CONTENT ;
       fe.lindex = -1 ;
       fe.ptd = NULL ;
 
       DWORD *pdw ;
       pdw = (DWORD *)GlobalAlloc(GMEM_FIXED, sizeof (DWORD));		
       *pdw = GetCurrentThreadId ( ) ;
 
       stg.tymed = TYMED_HGLOBAL ;
       stg.hGlobal = (HGLOBAL) pdw ;
       stg.pUnkForRelease = NULL ; 
 
       pAdvSink->OnDataChange (&fe, &stg) ;
 
 /*
 Sleep for a second, then do it again.
 */
       Sleep (1000) ;
     }
 
     return 0 ;
}

Figure 18   Using Critical Sections with ATL

 void CComTypeInfoHolder::AddRef()
 {
     EnterCriticalSection(&_Module.m_csTypeInfoHolder);
     m_dwRef++;
     LeaveCriticalSection(&_Module.m_csTypeInfoHolder);
 }
 
 void CComTypeInfoHolder::Release()
 {
     EnterCriticalSection(&_Module.m_csTypeInfoHolder);
     if (--m_dwRef == 0)
     {
       if (m_pInfo != NULL)
             m_pInfo->Release();
       m_pInfo = NULL;
     }
     LeaveCriticalSection(&_Module.m_csTypeInfoHolder);
}