// 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);
}