// ============================================================================= // Class Implementation : COXService // ============================================================================= // // Source file : OXService.cpp // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "OXService.h" #include "UTBStrOp.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // Data members ------------------------------------------------------------- // protected: static const TCHAR szBar[] = _T("|"); const TCHAR COXService::m_cNull = _T('\0'); const TCHAR COXService::m_cBar = _T('|'); const LPCTSTR COXService::m_pszBar = szBar; SC_HANDLE COXService::c_hSCM = NULL; // handle of the service manager SC_LOCK COXService::c_Sclock = NULL; // handle of the service lock DWORD COXService::c_dwSCMAccess = 0; // last access when openning service manager // SC_HANDLE m_hSC; // handle of the service // DWORD m_dwErrCode; // last error code // CString m_sKeyName; // current service's key name // Member functions --------------------------------------------------------- COXService::COXService(LPCTSTR pszKeyName /* = NULL */) { m_hSC = NULL; m_dwErrCode = 0; if (pszKeyName) m_sKeyName = pszKeyName; } COXService::COXService(const COXService& service) { m_hSC = NULL; m_dwErrCode = 0; m_sKeyName = service.m_sKeyName; } COXService::~COXService() { Close(); } COXService& COXService::operator=(const COXService& service) { if(this==&service) return *this; m_hSC = NULL; m_dwErrCode = 0; m_sKeyName = service.m_sKeyName; return *this; } DWORD COXService::GetLastError() const { return m_dwErrCode; } void COXService::Empty() { Close(); ASSERT(m_hSC == NULL); m_dwErrCode = 0; m_sKeyName.Empty(); } BOOL COXService::IsEmpty() const { return m_sKeyName.IsEmpty(); } CString COXService::GetKeyName() const { return m_sKeyName; } void COXService::SetKeyName(LPCTSTR pszKeyName) { if (!IsEmpty()) { TRACE0("COXService::SetKeyName(): service was not empty.\r\n"); Empty(); } m_sKeyName = pszKeyName; } SC_HANDLE COXService::GetHandle() { if (m_hSC == NULL) Open(); return m_hSC; } BOOL COXService::Open(DWORD dwDesiredAccess /* = SERVICE_ALL_ACCESS */, LPCTSTR pszKeyName /* = NULL */) { if (m_hSC != NULL && !Close()) return FALSE; if (pszKeyName) m_sKeyName = pszKeyName; if (PrepareSCManager() && SetErrCode((m_hSC = ::OpenService(c_hSCM, m_sKeyName, dwDesiredAccess)) != NULL)) { TRACE1("COXService::Open(): service '%s' opened.\r\n", m_sKeyName); return TRUE; } return FALSE; } BOOL COXService::Create(LPCTSTR pszBinaryPathName, LPCTSTR pszDisplayName, LPCTSTR pszKeyName /* = NULL */, DWORD dwDesiredAccess /* = SERVICE_ALL_ACCESS */, DWORD dwServiceType /* = SERVICE_WIN32_OWN_PROCESS */, DWORD dwStartType /* = SERVICE_DEMAND_START */, DWORD dwErrorControl /* = SERVICE_ERROR_NORMAL */, LPCTSTR pszLoadOrderGroup /* = NULL */, LPDWORD pdwTagId /* = NULL */, LPCTSTR pszDependencies /* = NULL */, LPCTSTR pszServiceStartName /* = NULL */, LPCTSTR pszPassword /* = NULL */) { if (m_hSC != NULL && !Close()) return FALSE; if (pszKeyName) m_sKeyName = pszKeyName; if (PrepareSCManager() && SetErrCode((m_hSC = ::CreateService(c_hSCM, m_sKeyName, pszDisplayName, dwDesiredAccess, dwServiceType, dwStartType, dwErrorControl, pszBinaryPathName, pszLoadOrderGroup, pdwTagId, pszDependencies, pszServiceStartName, pszPassword)) != NULL)) { TRACE1("COXService::Create(): service '%s' created.\r\n", m_sKeyName); return TRUE; } return FALSE; } BOOL COXService::Close() { if (m_hSC == NULL) return TRUE; if (SetErrCode(::CloseServiceHandle(m_hSC))) { m_hSC = NULL; TRACE1("COXService::Close(): service '%s' closed.\r\n", m_sKeyName); return TRUE; } return FALSE; } BOOL COXService::Delete() { return PrepareSCHandle(DELETE) && SetErrCode(::DeleteService(m_hSC)); } BOOL COXService::Start(DWORD dwNumServiceArgs /* = 0 */, LPCTSTR* rgpszServiceArgVectors /* = NULL */) { return PrepareSCHandle(SERVICE_START) && SetErrCode(::StartService(m_hSC, dwNumServiceArgs, rgpszServiceArgVectors)); } BOOL COXService::Pause() { SERVICE_STATUS srvStatus; return PrepareSCHandle(SERVICE_PAUSE_CONTINUE) && SetErrCode(::ControlService(m_hSC, SERVICE_CONTROL_PAUSE, &srvStatus)); } BOOL COXService::Continue() { SERVICE_STATUS srvStatus; return PrepareSCHandle(SERVICE_PAUSE_CONTINUE) && SetErrCode(::ControlService(m_hSC, SERVICE_CONTROL_CONTINUE, &srvStatus)); } BOOL COXService::Stop() { SERVICE_STATUS srvStatus; return PrepareSCHandle(SERVICE_STOP) && SetErrCode(::ControlService(m_hSC, SERVICE_CONTROL_STOP, &srvStatus)); } BOOL COXService::Control(DWORD dwControl, LPSERVICE_STATUS pszServiceStatus /* = NULL */) { DWORD dwMinimumAccess; switch (dwControl) { case SERVICE_CONTROL_STOP: dwMinimumAccess = SERVICE_STOP; break; case SERVICE_CONTROL_PAUSE: case SERVICE_CONTROL_CONTINUE: dwMinimumAccess = SERVICE_PAUSE_CONTINUE; break; case SERVICE_CONTROL_INTERROGATE: dwMinimumAccess = SERVICE_INTERROGATE; break; case SERVICE_CONTROL_SHUTDOWN: return FALSE; default: if (dwControl >= 128 && dwControl <= 255) dwMinimumAccess = SERVICE_USER_DEFINED_CONTROL; else dwMinimumAccess = SERVICE_ALL_ACCESS; } // We noticed that if ::ControlService is called with an invalid controlcode // for that service, it tries to fill the SERVICE_STATUS structure parameter. // If this parameter is NULL, two exceptions are thrown and the true errorcode // ERROR_INVALID_SERVICE_CONTROL is never seen. So even if the user isn't // interested in the SERVICE_STATUS structure, we have to supply it. if (pszServiceStatus == NULL) { SERVICE_STATUS srvStatus; return PrepareSCHandle(dwMinimumAccess) && SetErrCode(::ControlService(m_hSC, dwControl, &srvStatus)); } else return PrepareSCHandle(dwMinimumAccess) && SetErrCode(::ControlService(m_hSC, dwControl, pszServiceStatus)); } ///////////////////////////////////////////////////////////////////////////// // BOOL ChangeServiceConfig(SC_HANDLE hService, // DWORD dwServiceType, // DWORD dwStartType, // DWORD dwErrorControl, // LPCTSTR lpBinaryPathName, // LPCTSTR lpLoadOrderGroup, // LPDWORD lpdwTagId, // LPCTSTR lpDependencies, // LPCTSTR lpServiceStartName, // LPCTSTR lpPassword, // LPCTSTR lpDisplayName) BOOL COXService::ChangeConfig(LPCTSTR pszDisplayName /* = NULL */, DWORD dwServiceType /* = SERVICE_NO_CHANGE */, DWORD dwStartType /* = SERVICE_NO_CHANGE */, DWORD dwErrorControl /* = SERVICE_NO_CHANGE */, LPCTSTR pszBinaryPathName /* = NULL */, LPCTSTR pszLoadOrderGroup /* = NULL */, LPDWORD pdwTagId /* = NULL */, LPCTSTR pszDependencies /* = NULL */, LPCTSTR pszServiceStartName /* = NULL */, LPCTSTR pszPassword /* = NULL */) { return PrepareSCHandle(SERVICE_CHANGE_CONFIG) && SetErrCode(::ChangeServiceConfig(m_hSC, dwServiceType, dwStartType, dwErrorControl, pszBinaryPathName, pszLoadOrderGroup, pdwTagId, pszDependencies, pszServiceStartName, pszPassword, pszDisplayName)); } BOOL COXService::ChangeDisplayName(LPCTSTR pszDisplayName) { return ChangeConfig(pszDisplayName); } BOOL COXService::ChangeServiceType(DWORD dwServiceType) { return ChangeConfig(NULL, dwServiceType); } BOOL COXService::ChangeStartType(DWORD dwStartType) { return ChangeConfig(NULL, SERVICE_NO_CHANGE, dwStartType); } BOOL COXService::ChangeErrorControl(DWORD dwErrorControl) { return ChangeConfig(NULL, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, dwErrorControl); } BOOL COXService::ChangeBinaryPathName(LPCTSTR pszBinaryPathName) { return ChangeConfig(NULL, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, pszBinaryPathName); } BOOL COXService::ChangeLoadOrderGroup(LPCTSTR pszLoadOrderGroup) { return ChangeConfig(NULL, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, NULL, pszLoadOrderGroup); } BOOL COXService::ChangeTagId(DWORD dwTagId) { return ChangeConfig(NULL, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, NULL, NULL, &dwTagId); } BOOL COXService::ChangeDependencies(LPCTSTR pszDependencies) { CString sDependencies = pszDependencies; int nMinBufLength = sDependencies.GetLength() + 1; BarToNullSeparator(sDependencies.GetBuffer(nMinBufLength)); return ChangeConfig(NULL, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, NULL, NULL, NULL, sDependencies); } BOOL COXService::ChangeStartName(LPCTSTR pszServiceStartName, LPCTSTR pszPassword /* = NULL */) { return ChangeConfig(NULL, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, pszServiceStartName, pszPassword); } ///////////////////////////////////////////////////////////////////////////// // BOOL QueryServiceConfig( SC_HANDLE hService, // LPQUERY_SERVICE_CONFIG lpServiceConfig, // DWORD cbBufSize, // LPDWORD pcbBytesNeeded) // typedef struct _QUERY_SERVICE_CONFIG { // qsc // DWORD dwServiceType; // DWORD dwStartType; // DWORD dwErrorControl; // LPTSTR lpBinaryPathName; // LPTSTR lpLoadOrderGroup; // DWORD dwTagId; // LPTSTR lpDependencies; // LPTSTR lpServiceStartName; // LPTSTR lpDisplayName; // } QUERY_SERVICE_CONFIG, LPQUERY_SERVICE_CONFIG; BOOL COXService::QueryConfig(LPQUERY_SERVICE_CONFIG lpServiceConfig, DWORD cbBufSize, LPDWORD pcbBytesNeeded) { return PrepareSCHandle(SERVICE_QUERY_CONFIG) && SetErrCode(::QueryServiceConfig(m_hSC, lpServiceConfig, cbBufSize, pcbBytesNeeded)); } #define OXSERVICE_QSC_BINARYPATHNAME 1 #define OXSERVICE_QSC_LOADORDERGROUP 2 #define OXSERVICE_QSC_DEPENDENCIES 3 #define OXSERVICE_QSC_SERVICESTARTNAME 4 #define OXSERVICE_QSC_DISPLAYNAME 5 #define OXSERVICE_QSC_SERVICETYPE 6 #define OXSERVICE_QSC_STARTTYPE 7 #define OXSERVICE_QSC_ERRORCONTROL 8 #define OXSERVICE_QSC_TAGID 9 CString COXService::QueryDisplayName() { return GetQSCData(OXSERVICE_QSC_DISPLAYNAME); } CString COXService::QueryBinaryPathName() { return GetQSCData(OXSERVICE_QSC_BINARYPATHNAME); } CString COXService::QueryLoadOrderGroup() { return GetQSCData(OXSERVICE_QSC_LOADORDERGROUP); } CString COXService::QueryDependencies() { return GetQSCData(OXSERVICE_QSC_DEPENDENCIES); } CString COXService::QueryStartName() { return GetQSCData(OXSERVICE_QSC_SERVICESTARTNAME); } DWORD COXService::QueryServiceType() { DWORD dwResult; GetQSCData(OXSERVICE_QSC_SERVICETYPE, &dwResult); return dwResult; } DWORD COXService::QueryStartType() { DWORD dwResult; GetQSCData(OXSERVICE_QSC_STARTTYPE, &dwResult); return dwResult; } DWORD COXService::QueryErrorControl() { DWORD dwResult; GetQSCData(OXSERVICE_QSC_ERRORCONTROL, &dwResult); return dwResult; } DWORD COXService::QueryTagId() { DWORD dwResult; GetQSCData(OXSERVICE_QSC_TAGID, &dwResult); return dwResult; } ///////////////////////////////////////////////////////////////////////////// BOOL COXService::QuerySecurity(SECURITY_INFORMATION dwSecurityInformation, PSECURITY_DESCRIPTOR lpSecurityDescriptor, DWORD cbBufSize, LPDWORD pcbBytesNeeded) { return PrepareSCHandle(READ_CONTROL) && SetErrCode(::QueryServiceObjectSecurity(m_hSC, dwSecurityInformation, lpSecurityDescriptor, cbBufSize, pcbBytesNeeded)); } BOOL COXService::ChangeSecurity(SECURITY_INFORMATION dwSecurityInformation, PSECURITY_DESCRIPTOR lpSecurityDescriptor) { DWORD dwMinimumAccess = 0; if (dwSecurityInformation & OWNER_SECURITY_INFORMATION || dwSecurityInformation & GROUP_SECURITY_INFORMATION) dwMinimumAccess |= WRITE_OWNER; if (dwSecurityInformation & DACL_SECURITY_INFORMATION) dwMinimumAccess |= WRITE_DAC; return PrepareSCHandle(dwMinimumAccess) && SetErrCode(::SetServiceObjectSecurity(m_hSC, dwSecurityInformation, lpSecurityDescriptor)); } BOOL COXService::QueryStatus(LPSERVICE_STATUS lpServiceStatus, BOOL bInterrogateNow /* = FALSE */) { if (bInterrogateNow) return Control(SERVICE_CONTROL_INTERROGATE, lpServiceStatus); return PrepareSCHandle(SERVICE_QUERY_STATUS) && SetErrCode(::QueryServiceStatus(m_hSC, lpServiceStatus)); } DWORD COXService::QueryCurrentState(BOOL bInterrogateNow /* = FALSE*/) { SERVICE_STATUS ss; VERIFY(QueryStatus(&ss, bInterrogateNow)); return ss.dwCurrentState; } CString COXService::QueryDisplayName(LPCTSTR pszKeyName) { if (!PrepareSCManager()) return _T(""); CString sResult; DWORD dwCharNeeded = 256; BOOL bResult = ::GetServiceDisplayName(c_hSCM, pszKeyName, sResult.GetBuffer(256), &dwCharNeeded); sResult.ReleaseBuffer(bResult ? -1 : 0); return sResult; } CString COXService::QueryKeyName(LPCTSTR pszDisplayName) { if (!PrepareSCManager()) return _T(""); CString sResult; DWORD dwCharNeeded = 256; BOOL bResult = ::GetServiceKeyName(c_hSCM, pszDisplayName, sResult.GetBuffer(256), &dwCharNeeded); sResult.ReleaseBuffer(bResult ? -1 : 0); return sResult; } ///////////////////////////////////////////////////////////////////////////// // Static SCManager Functions SC_HANDLE COXService::GetSCManagerHandle() { if (c_hSCM == NULL) OpenSCManager(); return c_hSCM; } BOOL COXService::OpenSCManager(LPCTSTR pszMachineName /* = NULL */, LPCTSTR pszDatabaseName /* = NULL */, DWORD dwDesiredAccess /* = SC_MANAGER_ALL_ACCESS */) { if (c_hSCM != NULL) { if (c_dwSCMAccess == dwDesiredAccess) { TRACE0("COXService::OpenSCManager(): desired SCManager was already opened previously.\r\n"); return TRUE; } if (!CloseSCManager()) return FALSE; TRACE0("COXService::OpenSCManager(): previously opened SCManager was closed by this function.\r\n"); } c_dwSCMAccess = dwDesiredAccess; return (c_hSCM = ::OpenSCManager(pszMachineName, pszDatabaseName, dwDesiredAccess)) != NULL; } BOOL COXService::LockDatabase() { if (c_hSCM != NULL && c_Sclock != NULL) { TRACE0("COXService::LockDatabase(): SCM database was already locked.\r\n"); return TRUE; } return PrepareSCManager(SC_MANAGER_LOCK) && ((c_Sclock = ::LockServiceDatabase(c_hSCM)) != NULL); } BOOL COXService::UnlockDatabase() { // ... SCM has to be opened expicitly or implicitly already ASSERT(c_hSCM); if (c_hSCM != NULL) { if (c_Sclock == NULL) { TRACE0("COXService::UnlockDatabase(): SCM database was not locked.\r\n"); return TRUE; } if (::UnlockServiceDatabase(c_Sclock)) { c_Sclock = NULL; return TRUE; } } return FALSE; } BOOL COXService::QueryDatabaseLockStatus(DWORD& /* bIsLocked */, CString& sLockOwner, DWORD& /* dwLockDuration */) { if (!PrepareSCManager(SC_MANAGER_QUERY_LOCK_STATUS)) return FALSE; QUERY_SERVICE_LOCK_STATUS qsls; DWORD dwBytesNeeded; qsls.lpLockOwner = sLockOwner.GetBuffer(256); BOOL bResult = ::QueryServiceLockStatus(c_hSCM, &qsls, 256, &dwBytesNeeded); if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { sLockOwner.ReleaseBuffer(0); qsls.lpLockOwner = sLockOwner.GetBuffer(dwBytesNeeded); bResult = ::QueryServiceLockStatus(c_hSCM, &qsls, dwBytesNeeded, &dwBytesNeeded); } sLockOwner.ReleaseBuffer(bResult ? -1 : 0); return bResult; } BOOL COXService::CloseSCManager() { if (c_hSCM == NULL) return TRUE; if (c_Sclock != NULL && !UnlockDatabase()) return FALSE; if (::CloseServiceHandle(c_hSCM)) { c_hSCM = NULL; return TRUE; } return FALSE; } // protected: BOOL COXService::SetErrCode(BOOL bFunctionSucceeded) // --- In : bFunctionSucceeded, function return BOOL value // --- Out : // --- Returns : same as bFunctionSucceeded (passing it through) // --- Effect : if last called function is failed, write down last error { m_dwErrCode = bFunctionSucceeded ? 0 : ::GetLastError(); #ifdef _DEBUG if (!bFunctionSucceeded) { LPVOID lpMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, m_dwErrCode, 0, (LPTSTR) &lpMsgBuf, 0, NULL); TRACE1("COXService Error: %s", lpMsgBuf); LocalFree(lpMsgBuf); } #endif return bFunctionSucceeded; } BOOL COXService::PrepareSCHandle(DWORD dwMinimumAccess /* = SERVICE_ALL_ACCESS */) // --- In : dwMinimumAccess, the minimum access right of the service handle // --- Out : // --- Returns : TRUE if successful; FALSE otherwise // --- Effect : if service handle is already opened, use it; if not, try to open // it with full access right (SERVICE_ALL_ACCESS); if failed, // try to open it with minimum access (dwMinimumAccess). { return m_hSC != NULL || Open(SERVICE_ALL_ACCESS) || (dwMinimumAccess != SERVICE_ALL_ACCESS && Open(dwMinimumAccess)); } CString COXService::GetQSCData(int QSC_Code, DWORD* pdwResult /* = NULL */) // --- In : QSC_Code, the code as above // --- Out : pdwResult, the DWORD field result // --- Returns : the string field result // --- Effect : obtain one field of the query service status { CString sResult; if (pdwResult != NULL) *pdwResult = 0; if (!PrepareSCHandle(SERVICE_QUERY_CONFIG)) return _T(""); DWORD dwBytesNeeded; if (::QueryServiceConfig(m_hSC, NULL, 0, &dwBytesNeeded) || ::GetLastError() != ERROR_INSUFFICIENT_BUFFER || dwBytesNeeded == 0) { SetLastError(FALSE); return _T(""); } LPQUERY_SERVICE_CONFIG pQSC = (LPQUERY_SERVICE_CONFIG)malloc(dwBytesNeeded); if (SetErrCode(::QueryServiceConfig(m_hSC, pQSC, dwBytesNeeded, &dwBytesNeeded))) { switch (QSC_Code) { case OXSERVICE_QSC_BINARYPATHNAME: sResult = pQSC->lpBinaryPathName; break; case OXSERVICE_QSC_LOADORDERGROUP: sResult = pQSC->lpLoadOrderGroup; break; case OXSERVICE_QSC_DEPENDENCIES: NullToBarSeparator(pQSC->lpDependencies); sResult = pQSC->lpDependencies; break; case OXSERVICE_QSC_SERVICESTARTNAME: sResult = pQSC->lpServiceStartName; break; case OXSERVICE_QSC_DISPLAYNAME: sResult = pQSC->lpDisplayName; break; case OXSERVICE_QSC_SERVICETYPE: *pdwResult = pQSC->dwServiceType; break; case OXSERVICE_QSC_STARTTYPE: *pdwResult = pQSC->dwStartType; break; case OXSERVICE_QSC_ERRORCONTROL: *pdwResult = pQSC->dwErrorControl; break; case OXSERVICE_QSC_TAGID: *pdwResult = pQSC->dwTagId; break; } } free(pQSC); return sResult; } BOOL COXService::PrepareSCManager(DWORD dwMinimumAccess /* = SC_MANAGER_ALL_ACCESS */) // --- In : dwMinimumAccess, the minimum access right of the SCManager // --- Out : // --- Returns : TRUE if successful; FALSE otherwise // --- Effect : if SCManager is already opened, use it; if not, try to open // it with full access right (SC_MANAGER_ALL_ACCESS); if failed, // try to open it with minimum access (dwMinimumAccess). { return c_hSCM != NULL || OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS) || (dwMinimumAccess != SC_MANAGER_ALL_ACCESS && OpenSCManager(NULL, NULL, dwMinimumAccess)); } void COXService::NullToBarSeparator(LPTSTR pStringData) // --- In : pStringData : Pointer to string with parts seperated by null chars // and concluded by two null chars // --- Out : pStringData : The same string but bars '|' used to seperate the parts // The string still end in a null char (without a bar) // --- Returns : // --- Effect : Converts the string { BOOL bPreviousWasNull = FALSE; BOOL bEnd = FALSE; BOOL bFirstSymbol=TRUE; while (!bEnd) { // ... String pointer should point to a valid string at all times ASSERT(AfxIsValidString(pStringData)); if (*pStringData == m_cNull) { if(bFirstSymbol) return; if (bPreviousWasNull) { // ... This char is NULL and so was the previous : // zero-terminate the string and end the loop *(pStringData - 1) = m_cNull; bEnd = TRUE; } else { // ... This char is NULL but the previous was not: // replace the zero-char by a bar *pStringData = m_cBar; bPreviousWasNull = TRUE; } } else // ... Encountered a normal (non-NULL-char) bPreviousWasNull = FALSE; pStringData++; bFirstSymbol=FALSE; } } void COXService::BarToNullSeparator(LPTSTR pStringData) // --- In : pStringData : A string with bars '|' used to seperate the parts // The string end in a null char (without a bar) // --- Out : pStringData : The same string but with parts seperated by null chars // and concluded by two null chars // --- Returns : // --- Effect : Converts the string // Note that the string will grow with one character. // Enough space should have been allocated before callinbg this function { // ... The pStringData should have enough space one additional (null) characters ASSERT(AfxIsValidAddress(pStringData, (_tcslen(pStringData) + 2) * sizeof(TCHAR))); // First add an extra NULL character at the end *(pStringData + _tcslen(pStringData) + 1) = m_cNull; // Now replace all bars by a NULL character (use _tcstok) TCHAR * p; LPCTSTR pNewStringData = UTBStr::tcstok(pStringData, m_pszBar, &p); while(pNewStringData != NULL) { pNewStringData = UTBStr::tcstok(NULL, m_pszBar, &p); } } // end of OXService.cpp