1140 lines
36 KiB
C
1140 lines
36 KiB
C
/************************************************************************/
|
|
/* SISCO SOFTWARE MODULE HEADER *****************************************/
|
|
/************************************************************************/
|
|
/* (c) Copyright Systems Integration Specialists Company, Inc., */
|
|
/* 1997-2008, All Rights Reserved */
|
|
/* */
|
|
/* MODULE NAME : glbsem_w32.c */
|
|
/* PRODUCT(S) : */
|
|
/* */
|
|
/* MODULE DESCRIPTION : Multi-thread support for Windows ONLY. */
|
|
/* */
|
|
/* GLOBAL FUNCTIONS DEFINED IN THIS MODULE : */
|
|
/* */
|
|
/* MODIFICATION LOG : */
|
|
/* Date Who Rev Comments */
|
|
/* -------- --- ------ ------------------------------------------- */
|
|
/* 06/10/08 EJV 21 Ported to Windows Vista. */
|
|
/* 03/27/08 EJV 20 Use S_MAX_PATH instead of MAX_PATH. */
|
|
/* 01/29/08 EJV 19 Use S_FMT_* macros to log pointers & handles.*/
|
|
/* 05/23/07 DSF 18 Added dependency on advapi32.dll */
|
|
/* 06/29/06 MDE,EJV 17 Added UNICODE support (gs_translate_name). */
|
|
/* Del <process.h>, see sysincs.h. */
|
|
/* 04/12/05 DSF 16 Added refCount member to GS_MUTEX (Windows) */
|
|
/* 01/06/05 DSF 15 Added owner member to GS_MUTEX (Windows) */
|
|
/* 01/14/05 EJV 14 Reversed change. */
|
|
/* 07/21/04 DWL 13 Added ifdef so non debug configs would build.*/
|
|
/* 07/02/04 DSF 12 Always link in winmm.lib */
|
|
/* 05/21/04 DSF 11 Timeout log is now FLOW instead of NERR */
|
|
/* 04/19/04 DSF 10 gs_wait_thread () now returns SD_TIMEOUT on */
|
|
/* timeouts */
|
|
/* 12/29/03 ASK 09 enable logLastError, pass gle to logLastError*/
|
|
/* 12/03/03 EJV 08 gs_is_win_ver(): chg return ST_RET to ST_INT;*/
|
|
/* Add SD_WIN_VER_2003_AND_LATER, replaced */
|
|
/* SD_WIN_VER_NET with SD_WIN_VER_2003; */
|
|
/* 12/01/03 EJV 07 gs_is_win_ver: chk for corr ret 1, 0, -1. */
|
|
/* Named mutex, event: for 2000S w/terminal serv*/
|
|
/* chg from XP to SD_WIN_VER_2K_AND_LATER. */
|
|
/* 11/24/03 DSF 06 Spelling */
|
|
/* 11/04/03 JRB 05 Del gs_sleep (use sMsSleep). */
|
|
/* 06/20/03 EJV 04 Renamed gs_mutex_get to gs_mutex_get_tm. */
|
|
/* 06/12/03 EJV 03 Redesigned mutex sems implementation. */
|
|
/* 06/10/03 EJV 02 Added gs_named_mutex_xxx functions. */
|
|
/* 06/06/03 JRB 01 NEW. Code taken out of glbsem.c */
|
|
/************************************************************************/
|
|
#include "glbtypes.h"
|
|
#include "sysincs.h"
|
|
#include "mem_chk.h"
|
|
#include "slog.h"
|
|
#include "glbsem.h"
|
|
|
|
#ifdef WIN32
|
|
|
|
#pragma comment(linker, "/defaultlib:winmm.lib")
|
|
#pragma comment(linker, "/defaultlib:advapi32.lib")
|
|
|
|
/************************************************************************/
|
|
/* For debug version, use a static pointer to avoid duplication of */
|
|
/* __FILE__ strings. */
|
|
|
|
|
|
#ifdef DEBUG_SISCO
|
|
SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
|
|
#endif
|
|
|
|
static ST_VOID logLastError (DWORD errCode);
|
|
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
/* */
|
|
/* MUTEX SEMAPHORE FUNCTIONS for _WIN32 */
|
|
/* */
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
|
|
/************************************************************************/
|
|
/* gs_mutex_create */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Initialize the mutex semaphore ms. */
|
|
/* CRITICAL_SECTION is used for mutex semaphore for faster operation. */
|
|
/* Parameters: */
|
|
/* ms pointer to mutex object */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_mutex_create (ST_MUTEX_SEM *ms)
|
|
{
|
|
ms->mutexType = GS_MUTEX_UNNAMED;
|
|
ms->owner = 0;
|
|
ms->refCount = 0;
|
|
InitializeCriticalSection (&ms->u.cs);
|
|
|
|
return (SD_SUCCESS);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_translate_name */
|
|
/*----------------------------------------------------------------------*/
|
|
/* This function, if necessary, will prepend "Global\\" to the name and */
|
|
/* convert the name to Unicode. */
|
|
/* name ptr to name str (named mutex or sem). */
|
|
/* newName, wideName ptr to output buffer. */
|
|
/* bufSize output buffer size. */
|
|
/* */
|
|
/* !NOTE: The UNICODE was not tested. */
|
|
/************************************************************************/
|
|
#if defined (UNICODE)
|
|
ST_RET gs_translate_name (ST_CHAR *name, WCHAR *wideName, ST_UINT bufSize)
|
|
#else
|
|
ST_RET gs_translate_name (ST_CHAR *name, ST_CHAR *newName, ST_UINT bufSize)
|
|
#endif
|
|
{
|
|
#if defined (UNICODE)
|
|
ST_CHAR tmpName[S_MAX_PATH]; /* max size that a mutex/sem name can have */
|
|
#endif
|
|
ST_UINT maxLen;
|
|
ST_BOOLEAN bPrepend = SD_FALSE;
|
|
|
|
/* NOTE: do not put any logging to this function. It is used in DLLs. */
|
|
|
|
/* compute the max name size that can be handled & set bPrepend flag */
|
|
#if defined (UNICODE)
|
|
maxLen = sizeof(tmpName) - 1;
|
|
#else
|
|
maxLen = bufSize - 1;
|
|
#endif
|
|
if (gs_is_win_ver (SD_WIN_VER_2K_AND_LATER) == 1)
|
|
if (memcmp (name, "Global\\", strlen ("Global\\")) != 0)
|
|
{
|
|
/* need to prepend "Global\\" */
|
|
maxLen = maxLen - strlen ("Global\\");
|
|
bPrepend = SD_TRUE;
|
|
}
|
|
|
|
/* make sure we got buffer big enough for the resulting prepended name */
|
|
if (strlen(name) > maxLen)
|
|
return (SD_FAILURE);
|
|
|
|
#if defined (UNICODE)
|
|
{
|
|
/* copy the name to temporary buffer and prepend "Global\\" if necessary */
|
|
if (bPrepend)
|
|
sprintf (tmpName, "Global\\%s", name);
|
|
else
|
|
strcpy (tmpName, name);
|
|
|
|
/* Convert ANSI name to Unicode (error will be set if bufSize is too small) */
|
|
if (MultiByteToWideChar (CP_ACP, 0, tmpName, strlen (tmpName)+1, wideName, bufSize) == 0)
|
|
return (SD_FAILURE);
|
|
}
|
|
#else /* !defined (UNICODE) */
|
|
if (bPrepend)
|
|
sprintf (newName, "Global\\%s", name);
|
|
else
|
|
strcpy (newName, name);
|
|
#endif /* !defined (UNICODE) */
|
|
|
|
return (SD_SUCCESS);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_named_mutex_create */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Create/Open NAMED mutex. */
|
|
/* On Windows XP this function prepends 'Global\\' to the mutex name */
|
|
/* and sets the security descriptor in the way that the mutex can */
|
|
/* be accessed by processes in different user spaces (in Windows XP Fast*/
|
|
/* User Switching). */
|
|
/* For each call to this function the gs_named_mutex_destroy must be */
|
|
/* called. */
|
|
/* Parameters: */
|
|
/* ms pointer where to return handle to named mutex */
|
|
/* name pointer to mutex name. */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_named_mutex_create (ST_MUTEX_SEM *ms, ST_CHAR *name)
|
|
{
|
|
ST_RET ret = SD_SUCCESS;
|
|
HANDLE hMutex = NULL;
|
|
#if defined (UNICODE)
|
|
WCHAR mutexName[S_MAX_PATH];
|
|
#else
|
|
ST_CHAR mutexName[S_MAX_PATH];
|
|
#endif
|
|
|
|
if (ms == NULL || name == NULL)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
GLBSEM_LOG_ERR0 ("GLBSEM error: gs_named_mutex_create() failed ms=NULL or name=NULL");
|
|
#endif
|
|
return (SD_FAILURE);
|
|
}
|
|
|
|
ret = gs_translate_name (name, mutexName, (ST_UINT) (sizeof(mutexName)/sizeof(mutexName[0])));
|
|
if (ret != SD_SUCCESS)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: gs_translate_name () failed, name='%s'.", name);
|
|
#endif
|
|
return (ret);
|
|
}
|
|
|
|
/* set the mutex type */
|
|
ms->mutexType = GS_MUTEX_NAMED;
|
|
ms->owner = 0;
|
|
ms->refCount = 0;
|
|
|
|
if (gs_is_win_ver (SD_WIN_VER_2K_AND_LATER) == 1)
|
|
{
|
|
PSECURITY_DESCRIPTOR psd;
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
/* This code allows the mutex to be accessed from different */
|
|
/* applications in different users (Windows XP Fast User Switching). */
|
|
psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
if (psd == NULL)
|
|
return (SD_FAILURE);
|
|
if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
|
|
{
|
|
LocalFree (psd);
|
|
return (SD_FAILURE);
|
|
}
|
|
if (!SetSecurityDescriptorDacl(psd, TRUE, (PACL) NULL, FALSE))
|
|
{
|
|
LocalFree (psd);
|
|
return (SD_FAILURE);
|
|
}
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = psd;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
hMutex = CreateMutex (&sa, /* security attributes */
|
|
SD_FALSE, /* FALSE is initially not owned */
|
|
mutexName); /* object name */
|
|
if (hMutex == NULL)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle = GetLastError ();
|
|
GLBSEM_LOG_ERR2 ("GLBSEM error: CreateMutex failed GLE=%d for %s",
|
|
(unsigned int) gle, mutexName);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
LocalFree (psd);
|
|
}
|
|
else
|
|
{
|
|
/* this is Win 98, NT or 2000 */
|
|
hMutex = CreateMutex (NULL, /* no security attributes */
|
|
SD_FALSE, /* FALSE is initially not owned */
|
|
mutexName); /* object name */
|
|
if (hMutex == NULL)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle = GetLastError ();
|
|
GLBSEM_LOG_ERR2 ("GLBSEM error: CreateMutex failed GLE=%d for %s",
|
|
(unsigned int) gle, mutexName);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
}
|
|
|
|
ms->u.hMutex = hMutex; /* return the handle to user */
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_mutex_get_tm */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Lock the mutex semaphore ms. */
|
|
/* This function uses the gs_wait_event_sem to wait for ownership of */
|
|
/* the named mutex. The named mutex object can be also passed to the */
|
|
/* gs_wait_mult_event_sem function when waiting for multiple objects is */
|
|
/* required. */
|
|
/* */
|
|
/* Parameters: */
|
|
/* ms pointer to mutex object */
|
|
/* timeout Interval in milliseconds to wait for the named */
|
|
/* mutex to be owned, if -1L then the function will*/
|
|
/* be blocked indefinitely until the named mutex */
|
|
/* is owned. */
|
|
/* The timeout parameter is ignored for unnamed */
|
|
/* mutex. The function will wait indefinetely until*/
|
|
/* the mutex is owned. */
|
|
/* Return values: */
|
|
/* none */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/* SD_TIMEOUT timeout */
|
|
/* */
|
|
/* CRITICAL: gs_mutex_get_tm must NOT call any slog, stime, or mem_chk */
|
|
/* functions. These functions call gs_mutex_get_tm causing */
|
|
/* an infinite loop. Logging in the named mutex code is OK. */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_mutex_get_tm (ST_MUTEX_SEM *ms, ST_LONG timeout)
|
|
{
|
|
ST_RET ret = SD_SUCCESS;
|
|
|
|
if (!gs_already_inited) /* Make sure gs is initialized. */
|
|
gs_init ();
|
|
|
|
if (ms->mutexType == GS_MUTEX_UNNAMED)
|
|
/* ! do not put logging here to avoid infinite loop */
|
|
EnterCriticalSection (&ms->u.cs);
|
|
else
|
|
/* named mutex */
|
|
ret = gs_wait_event_sem ((ST_EVENT_SEM) ms->u.hMutex, timeout);
|
|
if (ret == SD_SUCCESS)
|
|
{
|
|
ms->owner = GET_THREAD_ID ();
|
|
++ms->refCount;
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_mutex_free */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Unlock the mutex semaphore ms. */
|
|
/* Parameters: */
|
|
/* ms pointer to mutex object */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/* CRITICAL: gs_mutex_free must NOT call any slog, stime, or mem_chk */
|
|
/* functions. These functions call ge_mutex_free which would cause */
|
|
/* an infinite loop. Logging in the named mutex code is OK. */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_mutex_free (ST_MUTEX_SEM *ms)
|
|
{
|
|
ST_RET ret = SD_SUCCESS;
|
|
|
|
--ms->refCount;
|
|
if (ms->refCount == 0)
|
|
ms->owner = 0;
|
|
if (ms->mutexType == GS_MUTEX_UNNAMED)
|
|
/* ! do not put logging here to avoid infinite loop */
|
|
LeaveCriticalSection (&ms->u.cs);
|
|
else
|
|
{
|
|
/* named mutex */
|
|
if (ReleaseMutex (ms->u.hMutex) == SD_FALSE)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle = GetLastError ();
|
|
GLBSEM_LOG_ERR2 ("GLBSEM error: ReleaseMutex failed for hMutex=" S_FMT_HANDLE " (GLE=%d)",
|
|
ms->u.hMutex, (unsigned int) gle);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_mutex_destroy */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Free all resources allocated for the MUTEX semaphore ms. */
|
|
/* Parameters: */
|
|
/* ms pointer to mutex object */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_mutex_destroy (ST_MUTEX_SEM *ms)
|
|
{
|
|
ST_RET ret = SD_SUCCESS;
|
|
|
|
if (ms->mutexType == GS_MUTEX_UNNAMED)
|
|
DeleteCriticalSection (&ms->u.cs);
|
|
else
|
|
{
|
|
/* named mutex */
|
|
if (CloseHandle (ms->u.hMutex) == SD_FALSE)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle = GetLastError ();
|
|
GLBSEM_LOG_ERR2 ("GLBSEM error: CloseHandle failed for hMutex=" S_FMT_HANDLE " (GLE=%d)",
|
|
ms->u.hMutex, (unsigned int) gle);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
/* */
|
|
/* USER's EVENT SEMAPHORE FUNCTIONS for _WIN32 */
|
|
/* */
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
|
|
/************************************************************************/
|
|
/* gs_get_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Create a "manual-reset" or "auto-reset" event semaphore. */
|
|
/* Parameters: */
|
|
/* manualReset SD_TRUE or SD_FALSE */
|
|
/* Return values: */
|
|
/* ST_EVENT_SEM handle to event semaphore object */
|
|
/************************************************************************/
|
|
|
|
ST_EVENT_SEM gs_get_event_sem (ST_BOOLEAN manualReset)
|
|
{
|
|
ST_EVENT_SEM retEventSem;
|
|
|
|
retEventSem = CreateEvent(NULL, // no security attributes
|
|
manualReset, // manual-reset event
|
|
FALSE, // initial state is signaled
|
|
NULL // object name
|
|
);
|
|
if (retEventSem == NULL)
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: CreateEvent failed for event semaphore, rc = %d", GetLastError ());
|
|
|
|
return (retEventSem);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_get_named_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Create a "manual-reset" or "auto-reset" NAMED event semaphore. */
|
|
/* On Windows XP this function prepends 'Global\\' to the semaphore name*/
|
|
/* and sets the security descriptor in the way that the semaphore can */
|
|
/* be accessed by processes in different user spaces (in Windows XP Fast*/
|
|
/* User Switching). */
|
|
/* Parameters: */
|
|
/* name pointer to semaphore name. */
|
|
/* manualReset SD_TRUE or SD_FALSE */
|
|
/* Return values: */
|
|
/* ST_EVENT_SEM handle to named event semaphore object */
|
|
/************************************************************************/
|
|
|
|
ST_EVENT_SEM gs_get_named_event_sem (ST_CHAR *name, ST_BOOLEAN manualReset)
|
|
{
|
|
ST_EVENT_SEM retEventSem = NULL;
|
|
#if defined (UNICODE)
|
|
WCHAR semName[S_MAX_PATH];
|
|
#else
|
|
ST_CHAR semName[S_MAX_PATH];
|
|
#endif
|
|
|
|
/* NOTE: do not put any logging to this function. It is used in DLLs. */
|
|
|
|
if (gs_translate_name (name, semName, (ST_UINT) (sizeof(semName)/sizeof(semName[0])))
|
|
!= SD_SUCCESS)
|
|
return (NULL);
|
|
|
|
if (gs_is_win_ver (SD_WIN_VER_2K_AND_LATER) == 1)
|
|
{
|
|
PSECURITY_DESCRIPTOR psd;
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
/* This code allows the event semaphore to be accessed from different */
|
|
/* applications in different users (Windows XP Fast User Switching). */
|
|
psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
if (psd == NULL)
|
|
return (NULL);
|
|
if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
|
|
{
|
|
LocalFree (psd);
|
|
return (NULL);
|
|
}
|
|
if (!SetSecurityDescriptorDacl(psd, TRUE, (PACL) NULL, FALSE))
|
|
{
|
|
LocalFree (psd);
|
|
return (NULL);
|
|
}
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = psd;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
retEventSem = CreateEvent(&sa, /* security attributes */
|
|
manualReset, /* manual-reset event */
|
|
SD_FALSE, /* initial state is non-signaled */
|
|
semName); /* object name */
|
|
LocalFree (psd);
|
|
}
|
|
else
|
|
{
|
|
/* this is Win 98, NT or 2000 */
|
|
retEventSem = CreateEvent (NULL, manualReset, SD_FALSE, semName);
|
|
}
|
|
|
|
return (retEventSem);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_wait_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Wait for event semaphore es until it becomes signaled or timeout */
|
|
/* occurrs. */
|
|
/* If compiled with DEBUG_SISCO the gs_track variable need to be set to */
|
|
/* value >0 to enable the timing code. This way logging can be used */
|
|
/* by an application without degradation of the EVENT sem performance. */
|
|
/* Parameters: */
|
|
/* es event semaphore object */
|
|
/* timeout interval in milliseconds to wait for the es to */
|
|
/* be signaled, if -1L then the function will be */
|
|
/* blocked indefinitely until the event semaphore */
|
|
/* is signaled. */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/* SD_TIMEOUT timeout */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_wait_event_sem (ST_EVENT_SEM es, ST_LONG timeout)
|
|
{
|
|
ST_RET ret;
|
|
DWORD rc;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
ST_DOUBLE startTime;
|
|
ST_DOUBLE endTime;
|
|
ST_DOUBLE elapsedTime;
|
|
#endif
|
|
ST_THREAD_ID thisThreadId;
|
|
|
|
/* Let's see just who we are */
|
|
thisThreadId = GET_THREAD_ID();
|
|
|
|
#if defined(DEBUG_SISCO)
|
|
if (gs_track)
|
|
startTime = sGetMsTime ();
|
|
#endif
|
|
|
|
rc = WaitForSingleObject (es, timeout);
|
|
|
|
#if defined(DEBUG_SISCO)
|
|
if (gs_track)
|
|
{
|
|
endTime = sGetMsTime ();
|
|
elapsedTime = endTime - startTime;
|
|
if (elapsedTime > gs_hwEventTime)
|
|
gs_hwEventTime = elapsedTime;
|
|
}
|
|
#endif
|
|
|
|
if (rc == WAIT_FAILED)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR3 ("GLBSEM error: WaitEvent rc=0x%04x, GLE=0x%04x, ThreadId=0x%08x",
|
|
(unsigned int) rc, (unsigned int)gle, thisThreadId);
|
|
logLastError (gle);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
else if (rc == WAIT_TIMEOUT)
|
|
ret = SD_TIMEOUT;
|
|
else
|
|
ret = SD_SUCCESS;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_wait_mult_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Wait for event semaphores in table esTable until one of them becomes */
|
|
/* signaled or timeout occurrs. */
|
|
/* If compiled with DEBUG_SISCO the gs_track variable need to be set to */
|
|
/* value >0 to enable the timing code. This way logging can be used */
|
|
/* by an application without degradation of the EVENT sem performance. */
|
|
/* Parameters: */
|
|
/* numEvents number of event semaphores to wait for */
|
|
/* esTable pointer to table of event semaphore objects */
|
|
/* activity pointer to table where this function will mark */
|
|
/* proper index entry with SD_TRUE for the event */
|
|
/* semaphore that have been signaled */
|
|
/* timeout interval in milliseconds to wait for the es to */
|
|
/* be signaled, if -1L then the function will be */
|
|
/* blocked indefinitely until the event semaphore */
|
|
/* is signaled. */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/* SD_TIMEOUT timeout */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_wait_mult_event_sem (ST_INT numEvents, ST_EVENT_SEM *esTable,
|
|
ST_BOOLEAN *activity, ST_LONG timeout)
|
|
{
|
|
ST_RET ret;
|
|
DWORD rc;
|
|
ST_INT i;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
ST_DOUBLE startTime;
|
|
ST_DOUBLE endTime;
|
|
ST_DOUBLE elapsedTime;
|
|
#endif
|
|
ST_THREAD_ID thisThreadId;
|
|
|
|
/* Let's see just who we are */
|
|
thisThreadId = GET_THREAD_ID();
|
|
|
|
#if defined(DEBUG_SISCO)
|
|
if (gs_track)
|
|
startTime = sGetMsTime ();
|
|
#endif
|
|
|
|
rc = WaitForMultipleObjects (numEvents, esTable, FALSE, timeout);
|
|
|
|
#if defined(DEBUG_SISCO)
|
|
if (gs_track)
|
|
{
|
|
endTime = sGetMsTime ();
|
|
elapsedTime = endTime - startTime;
|
|
if (elapsedTime > gs_hwEventTime)
|
|
gs_hwEventTime = elapsedTime;
|
|
}
|
|
#endif
|
|
|
|
/* make sure activity array is zero'd out */
|
|
memset (activity, '\x0', sizeof (ST_BOOLEAN) * numEvents);
|
|
|
|
i = rc - WAIT_OBJECT_0;
|
|
if ((i >= 0) && (i < numEvents))
|
|
{
|
|
activity[i] = SD_TRUE;
|
|
ret = SD_SUCCESS;
|
|
}
|
|
else if (rc == WAIT_FAILED)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR3 ("GLBSEM error: WaitEvent rc=0x%04x, GLE=0x%04x, ThreadId=0x%08x",
|
|
(unsigned int) rc, (unsigned int)gle, thisThreadId);
|
|
logLastError (gle);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
else if (rc == WAIT_TIMEOUT)
|
|
ret = SD_TIMEOUT;
|
|
else
|
|
ret = SD_SUCCESS;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_signal_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Signal event semaphore es. */
|
|
/* Manual-Reset: all waiting threads are released, es remains signaled */
|
|
/* until reset explicitly by call to ResetEvent. */
|
|
/* Auto-Reset: first waiting thread is released and es will be reset */
|
|
/* to non-signaled state. If no thread is waiting the es */
|
|
/* remains in signaled state unless ResetEvent is called. */
|
|
/* Parameters: */
|
|
/* es event semaphore object */
|
|
/* Return values: */
|
|
/* none */
|
|
/************************************************************************/
|
|
|
|
ST_VOID gs_signal_event_sem (ST_EVENT_SEM es)
|
|
{
|
|
BOOL rc;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
#endif
|
|
|
|
rc = SetEvent(es);
|
|
#if defined(DEBUG_SISCO)
|
|
if (rc == FALSE)
|
|
{
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: SetEvent GLE=0x%04x", (unsigned int) gle);
|
|
logLastError (gle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_pulse_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Uses PulseEvent to signal the event semaphore es. */
|
|
/* Manual-Reset: all waiting threads are released, es state changes to */
|
|
/* non-signaled. */
|
|
/* Auto-Reset: first waiting thread is released and es will be reset */
|
|
/* to non-signaled state even if no thread is waiting. */
|
|
/* Parameters: */
|
|
/* es event semaphore object */
|
|
/* Return values: */
|
|
/* none */
|
|
/************************************************************************/
|
|
|
|
ST_VOID gs_pulse_event_sem (ST_EVENT_SEM es)
|
|
{
|
|
BOOL rc;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
#endif
|
|
|
|
rc = PulseEvent(es);
|
|
#if defined(DEBUG_SISCO)
|
|
if (rc == FALSE)
|
|
{
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: PulseEvent GLE=0x%04x", (unsigned int) gle);
|
|
logLastError (gle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_reset_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Reset event semaphore es. */
|
|
/* This function should be called after function gs_wait_event_sem or */
|
|
/* gs_wait_mult_event_sem return SD_SUCCESS and the signaled event */
|
|
/* semaphore is a "manual-reset" semaphore. */
|
|
/* Parameters: */
|
|
/* es event semaphore object */
|
|
/* Return values: */
|
|
/* none */
|
|
/************************************************************************/
|
|
|
|
ST_VOID gs_reset_event_sem (ST_EVENT_SEM es)
|
|
{
|
|
BOOL rc;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
#endif
|
|
|
|
rc = ResetEvent (es);
|
|
#if defined(DEBUG_SISCO)
|
|
if (rc == FALSE)
|
|
{
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: ResetEvent gle=0x%04x", (unsigned int)gle);
|
|
logLastError (gle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* gs_free_event_sem */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Release resources taken be event semaphore es. */
|
|
/* Parameters: */
|
|
/* es event semaphore object */
|
|
/* Return values: */
|
|
/* none */
|
|
/************************************************************************/
|
|
|
|
ST_VOID gs_free_event_sem (ST_EVENT_SEM es)
|
|
{
|
|
BOOL rc;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
#endif
|
|
|
|
rc = CloseHandle (es);
|
|
#if defined(DEBUG_SISCO)
|
|
if (rc == FALSE)
|
|
{
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: CloseHandle GLE=0x%04x", (unsigned int)gle);
|
|
logLastError (gle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
/* */
|
|
/* USER's THREAD FUNCTIONS for _WIN32 */
|
|
/* */
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
|
|
/************************************************************************/
|
|
/* gs_start_thread */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Start a new thread. The thread handle should be closed after the */
|
|
/* thread terminates by calling the gs_close_thread(). The function */
|
|
/* gs_wait_thread can be used to wait until a thread terminates. */
|
|
/* Parameters: */
|
|
/* threadFunc pointer to thread function to run */
|
|
/* threadArg thread function argument list */
|
|
/* threadHandleOut pointer where to return thread handle */
|
|
/* threadIdOut pointer where to return thread ID */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_start_thread (ST_THREAD_RET (ST_THREAD_CALL_CONV *threadFunc) (ST_THREAD_ARG),
|
|
ST_THREAD_ARG threadArg,
|
|
ST_THREAD_HANDLE *threadHandleOut,
|
|
ST_THREAD_ID *threadIdOut)
|
|
{
|
|
ST_RET ret;
|
|
ST_THREAD_HANDLE threadHandle;
|
|
ST_THREAD_ID threadId;
|
|
|
|
threadHandle = _beginthreadex (NULL, /* No security */
|
|
0, /* Use def stack size */
|
|
threadFunc,
|
|
threadArg, /* Argument for thread */
|
|
0, /* Creation flags, 0 for running initial state */
|
|
&threadId); /* Thread identifier */
|
|
|
|
if (threadHandle == 0)
|
|
{
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: gs_start_thread _beginthreadex failed errno=%d",
|
|
errno);
|
|
ret = SD_FAILURE;
|
|
}
|
|
else
|
|
{
|
|
ret = SD_SUCCESS;
|
|
|
|
if (threadHandleOut != NULL)
|
|
*threadHandleOut = threadHandle;
|
|
|
|
if (threadIdOut != NULL)
|
|
*threadIdOut = threadId;
|
|
|
|
GLBSEM_LOG_FLOW2 ("GLBSEM: gs_start_thread created thread threadHandle=" S_FMT_THREAD_HANDLE
|
|
" threadId=0x%08x", threadHandle, threadId);
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* gs_wait_thread */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Wait until thread with threadHandle terminates or timeout occurrs. */
|
|
/* Parameters: */
|
|
/* threadHandle thread handle returned from gs_start_thread */
|
|
/* threadId thread ID returned from gs_start_thread */
|
|
/* timeout max time in milliseconds to wait for thread to */
|
|
/* terminate. */
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_wait_thread (ST_THREAD_HANDLE threadHandle,
|
|
ST_THREAD_ID threadId, ST_LONG timeout)
|
|
{
|
|
ST_RET ret;
|
|
DWORD rc;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
#endif
|
|
|
|
rc = WaitForSingleObject ((HANDLE)threadHandle, timeout);
|
|
|
|
if (rc != WAIT_OBJECT_0)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
gle = GetLastError ();
|
|
#endif
|
|
if (rc == WAIT_TIMEOUT)
|
|
{
|
|
ret = SD_TIMEOUT;
|
|
/* WAIT_FAILED or WAIT_TIMEOUT */
|
|
#if defined(DEBUG_SISCO)
|
|
GLBSEM_LOG_FLOW3 ("GLBSEM: Timeout=%d ms in Wait Thread threadHandle=" S_FMT_THREAD_HANDLE ", "
|
|
"threadId=0x%08x", timeout, threadHandle, threadId);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
GLBSEM_LOG_ERR3 ("GLBSEM error: Wait Thread threadHandle=" S_FMT_THREAD_HANDLE ", rc=0x%04x, GLE=0x%04x",
|
|
threadHandle, (unsigned int)rc, (unsigned int)gle);
|
|
logLastError (gle);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
}
|
|
else
|
|
ret = SD_SUCCESS;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* gs_close_thread */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Cleanup after thread terminated. */
|
|
/* Parameters: */
|
|
/* threadHandle thread handle ret from gs_start_thread()*/
|
|
/* Return values: */
|
|
/* SD_SUCCESS function successful */
|
|
/* SD_FAILURE error occurred */
|
|
/************************************************************************/
|
|
|
|
ST_RET gs_close_thread (ST_THREAD_HANDLE threadHandle)
|
|
{
|
|
ST_RET ret = SD_SUCCESS;
|
|
#if defined(DEBUG_SISCO)
|
|
DWORD gle;
|
|
#endif
|
|
|
|
if (threadHandle)
|
|
{
|
|
if (CloseHandle ((HANDLE)threadHandle) == 0)
|
|
{
|
|
#if defined(DEBUG_SISCO)
|
|
gle = GetLastError ();
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: Close Thread GLE=0x%04x", (unsigned int)gle);
|
|
#endif
|
|
ret = SD_FAILURE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GLBSEM_LOG_ERR1 ("GLBSEM error: Close Thread invalid threadHandle=" S_FMT_THREAD_HANDLE ".",
|
|
threadHandle);
|
|
ret = SD_FAILURE;
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* gs_set_thread_name */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Start a new thread. */
|
|
/* Parameters: */
|
|
/* threadId ID of Thread whose name is to be set */
|
|
/* -1 if name is to be set for calling */
|
|
/* thread */
|
|
/* pThreadName Name to set */
|
|
/************************************************************************/
|
|
|
|
#define THREADNAME_EXCEPT_ID 0x406D1388
|
|
#define THREADNAME_INFO_TYPE 0x1000
|
|
|
|
typedef struct tagTHREADNAME_INFO
|
|
{
|
|
ST_LONG type;
|
|
ST_CHAR *pName;
|
|
ST_THREAD_ID threadId;
|
|
LONG flags;
|
|
} THREADNAME_INFO;
|
|
|
|
ST_VOID gs_set_thread_name (ST_THREAD_ID threadId, ST_CHAR *pThreadName)
|
|
{
|
|
THREADNAME_INFO info;
|
|
|
|
info.type = THREADNAME_INFO_TYPE;
|
|
info.pName = pThreadName;
|
|
info.threadId = threadId;
|
|
info.flags = 0;
|
|
|
|
__try
|
|
{
|
|
#if (_MSC_VER >= 1300)
|
|
RaiseException (THREADNAME_EXCEPT_ID, 0,
|
|
sizeof (info) / sizeof (ST_LONG), (ULONG_PTR *) &info);
|
|
#else
|
|
RaiseException (THREADNAME_EXCEPT_ID, 0,
|
|
sizeof (info) / sizeof (ST_LONG), (DWORD *) &info);
|
|
#endif
|
|
}
|
|
__except (EXCEPTION_CONTINUE_EXECUTION)
|
|
{
|
|
}
|
|
}
|
|
|
|
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
/* */
|
|
/* Misc. FUNCTIONS for _WIN32 */
|
|
/* */
|
|
/*---*---*---*---*---*---*---*---*---*---*---*---*---*--*/
|
|
|
|
/************************************************************************/
|
|
/* gs_is_win_ver */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Accesses the OS version of the running system and checks if the */
|
|
/* version is ver. */
|
|
/* This function is compatible with Windows 95 and above. */
|
|
/* Parameters: */
|
|
/* ST_UINT ver see SD_WIN_* choices in glbsem.h */
|
|
/* Return: */
|
|
/* 1 If OS version is ver */
|
|
/* 0 If OS version is not ver */
|
|
/* -1 If could not access OS version info or ver invalid */
|
|
/************************************************************************/
|
|
ST_INT gs_is_win_ver (ST_UINT ver)
|
|
{
|
|
ST_INT ret = 0;
|
|
OSVERSIONINFO OsVerInfo; /* supported on Win 95 and up */
|
|
|
|
/* NOTE: do not put any logging to this function. It is used in DLLs. */
|
|
|
|
OsVerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
|
|
if (GetVersionEx (&OsVerInfo) == 0)
|
|
return (-1);
|
|
|
|
switch (ver)
|
|
{
|
|
case SD_WIN_VER_95_98_Me: /* 95, 98, Me */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_NT_AND_LATER: /* NT 3.51, NT 4.0 and above */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_2K_AND_LATER: /* 2000 and above */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
((OsVerInfo.dwMajorVersion == 5 && OsVerInfo.dwMinorVersion >= 0) ||
|
|
OsVerInfo.dwMajorVersion > 5))
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_XP_AND_LATER: /* XP and above */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
((OsVerInfo.dwMajorVersion == 5 && OsVerInfo.dwMinorVersion >= 1) ||
|
|
OsVerInfo.dwMajorVersion > 5))
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_2003_AND_LATER: /* 2003 and above */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
((OsVerInfo.dwMajorVersion == 5 && OsVerInfo.dwMinorVersion >= 2) ||
|
|
OsVerInfo.dwMajorVersion > 5))
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_VISTA_AND_LATER: /* Vista and above */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
((OsVerInfo.dwMajorVersion == 6 && OsVerInfo.dwMinorVersion >= 0) ||
|
|
OsVerInfo.dwMajorVersion > 6))
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_95:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
|
|
OsVerInfo.dwMajorVersion == 4 && OsVerInfo.dwMinorVersion == 0)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_98:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
|
|
OsVerInfo.dwMajorVersion == 4 && OsVerInfo.dwMinorVersion == 10)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_Me:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
|
|
OsVerInfo.dwMajorVersion == 4 && OsVerInfo.dwMinorVersion == 90)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_NT_351:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
OsVerInfo.dwMajorVersion == 3 && OsVerInfo.dwMinorVersion == 51)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_NT_40:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
OsVerInfo.dwMajorVersion == 4 && OsVerInfo.dwMinorVersion == 0)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_2000:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
OsVerInfo.dwMajorVersion == 5 && OsVerInfo.dwMinorVersion == 0)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_XP:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
OsVerInfo.dwMajorVersion == 5 && OsVerInfo.dwMinorVersion == 1)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_2003:
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
OsVerInfo.dwMajorVersion == 5 && OsVerInfo.dwMinorVersion == 2)
|
|
ret = 1;
|
|
break;
|
|
|
|
case SD_WIN_VER_VISTA: /* Vista workstation or Longhorn server */
|
|
if (OsVerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
OsVerInfo.dwMajorVersion == 6 && OsVerInfo.dwMinorVersion == 0)
|
|
ret = 1;
|
|
break;
|
|
|
|
default:
|
|
ret = -1; /* invalid ver */
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* logLastError */
|
|
/*----------------------------------------------------------------------*/
|
|
/* Parameters: */
|
|
/* Return values: */
|
|
/************************************************************************/
|
|
|
|
static ST_VOID logLastError (DWORD errCode)
|
|
{
|
|
LPVOID lpMsgBuf;
|
|
DWORD len;
|
|
|
|
len = FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
errCode,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPTSTR) &lpMsgBuf,
|
|
0,
|
|
NULL);
|
|
|
|
if(len == 0) /* FormatMessage failed... */
|
|
return;
|
|
|
|
GLBSEM_LOG_CERR1 (" -> %s", lpMsgBuf);
|
|
|
|
// Free the buffer.
|
|
LocalFree( lpMsgBuf );
|
|
}
|
|
#endif
|