// RotObj.cpp : Implementation of CContentRotator class, which does all the // interesting work // George V. Reilly a-georgr@microsoft.com georger@microcrafts.com Nov/Dec 96 // Shortcomings: this works fine for small-to-medium sized tip files // (under 2000 lines), but it's not very efficient for large ones. #include "stdafx.h" #include #include "ContRot.h" #include "RotObj.h" #include "debug.h" #include #include "Monitor.h" #define MAX_WEIGHT 10000 #define INVALID_WEIGHT 0xFFFFFFFF extern CMonitor* g_pMonitor; // // forward declaration of some utility functions // LPTSTR TcsDup(LPCTSTR ptsz); LPTSTR GetLine(LPTSTR& rptsz); BOOL IsBlankString(LPCTSTR ptsz); UINT GetWeight(LPTSTR& rptsz); LPTSTR GetTipText(LPTSTR& rptsz); HRESULT ReportError(DWORD dwErr); HRESULT ReportError(HRESULT hr); #if DBG #define ASSERT_VALID(pObj) \ do {ASSERT(pObj != NULL); pObj->AssertValid();} while (0) #else #define ASSERT_VALID(pObj) ((void)0) #endif class CTipNotify : public CMonitorNotify { public: CTipNotify(); virtual void Notify(); bool IsNotified(); private: long m_isNotified; }; DECLARE_REFPTR( CTipNotify,CMonitorNotify ) CTipNotify::CTipNotify() : m_isNotified(0) { } void CTipNotify::Notify() { ::InterlockedExchange( &m_isNotified, 1 ); } bool CTipNotify::IsNotified() { return ( ::InterlockedExchange( &m_isNotified, 0 ) ? true : false ); } // // "Tip", as in tip of the day // class CTip { public: CTip( LPCTSTR ptszTip, UINT uWeight) : m_ptsz(ptszTip), m_uWeight(uWeight), m_cServingsLeft(uWeight), m_pPrev(NULL), m_pNext(NULL) { ASSERT_VALID(this); } ~CTip() { ASSERT_VALID(this); if (m_pPrev != NULL) m_pPrev->m_pNext = NULL; if (m_pNext != NULL) m_pNext->m_pPrev = NULL; } #if DBG void AssertValid() const; #endif LPCTSTR m_ptsz; // data string UINT m_uWeight; // weight of this tip, 1 <= m_uWeight <= MAX_WEIGHT UINT m_cServingsLeft;// how many servings left: no more than m_uWeight CTip* m_pPrev; // Previous in tips list CTip* m_pNext; // Next in tips list }; // // A list of CTips, which are read from a datafile // class CTipList { public: CTipList() : m_ptszFilename(NULL), m_ptszData(NULL), m_cTips(0), m_uTotalWeight(0), m_pTipsListHead(NULL), m_pTipsListTail(NULL), m_fUTF8(false) { m_pNotify = new CTipNotify; ASSERT_VALID(this); } ~CTipList() { ASSERT_VALID(this); // check for both a valid Filename ptr as well as a valid Monitor ptr. // If the ContRotModule::Unlock is called prior to this destructor,then // the Monitor object has already been cleaned up and deleted. DeleteTips(); ASSERT_VALID(this); } HRESULT ReadDataFile( LPCTSTR ptszFilename); HRESULT SameAsCachedFile( LPCTSTR ptszFilename, BOOL& rfIsSame); UINT Rand() const; void AppendTip( CTip* pTip); void RemoveTip( CTip* pTip); HRESULT DeleteTips(); #if DBG void AssertValid() const; #endif LPTSTR m_ptszFilename; // Name of tips file LPTSTR m_ptszData; // Buffer containing contents of file UINT m_cTips; // # tips UINT m_uTotalWeight; // sum of all weights CTip* m_pTipsListHead; // Head of list of tips CTip* m_pTipsListTail; // Tail of list of tips CTipNotifyPtr m_pNotify; bool m_fUTF8; }; // // A class that allows you to enter a critical section and automatically // leave when the object of this class goes out of scope. Also provides // the means to leave and re-enter as needed while protecting against // entering or leaving out of sync. // class CAutoLeaveCritSec { public: CAutoLeaveCritSec( CRITICAL_SECTION* pCS) : m_pCS(pCS), m_fInCritSec(FALSE) {Enter();} ~CAutoLeaveCritSec() {Leave();} // Use this function to re-enter the critical section. void Enter() {if (!m_fInCritSec) {EnterCriticalSection(m_pCS); m_fInCritSec = TRUE;}} // Use this function to leave the critical section before going out // of scope. void Leave() {if (m_fInCritSec) {LeaveCriticalSection(m_pCS); m_fInCritSec = FALSE;}} protected: CRITICAL_SECTION* m_pCS; BOOL m_fInCritSec; }; // // Wrapper class for handles to files opened for reading // class CHFile { public: CHFile(LPCTSTR ptszFilename) { m_hFile = ::CreateFile(ptszFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); } ~CHFile() { if (m_hFile != INVALID_HANDLE_VALUE) ::CloseHandle(m_hFile); } operator HANDLE() const {return m_hFile;} BOOL operator!() const {return (m_hFile == INVALID_HANDLE_VALUE);} private: // private, unimplemented default ctor, copy ctor, and op= to prevent // compiler synthesizing them CHFile(); CHFile(const CHFile&); CHFile& operator=(const CHFile&); HANDLE m_hFile; }; ///////////////////////////////////////////////////////////////////////////// // CContentRotator Public Methods // // ctor // CContentRotator::CContentRotator() : m_ptl(NULL), m_ptlUsed(NULL) { TRACE0("CContentRotator::CContentRotator\n"); InitializeCriticalSection(&m_CS); #if (_WIN32_WINNT >= 0x0403) SetCriticalSectionSpinCount(&m_CS, 1000); #endif // Seed the random-number generator with the current time so that // the numbers will be different each time that we run ::srand((unsigned) time(NULL)); ATLTRY(m_ptl = new CTipList); ATLTRY(m_ptlUsed = new CTipList); } // // dtor // CContentRotator::~CContentRotator() { TRACE0("CContentRotator::~CContentRotator\n"); DeleteCriticalSection(&m_CS); delete m_ptl; delete m_ptlUsed; } // // ATL Wizard generates this // STDMETHODIMP CContentRotator::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IContentRotator, }; for (int i=0; iMapPath(bstrDataFile, &bstrPhysicalDataFile); if (SUCCEEDED(hr)) hr = _ChooseContent(bstrPhysicalDataFile, pbstrRetVal); } else { hr = ::ReportError(E_OUTOFMEMORY); } } else { hr = ::ReportError(E_NOINTERFACE); } } catch ( std::bad_alloc& ) { hr = ::ReportError(E_OUTOFMEMORY); } catch ( ... ) { hr = E_FAIL; } return hr; } // // Writes all of the entries in the tip file, each separated by an
, back // to the user's browser. This can be used to proofread all of the entries. // STDMETHODIMP CContentRotator::GetAllContent( BSTR bstrDataFile) { HRESULT hr = E_FAIL; try { if (bstrDataFile == NULL) return ::ReportError(E_POINTER); CContext cxt; hr = cxt.Init( CContext::get_Server | CContext::get_Response ); // Do we have valid Server and Response objects? if ( !FAILED( hr ) ) { // Do we have valid CTipLists? if ( (m_ptl != NULL) && (m_ptlUsed != NULL)) { // Map bstrDataFile (a logical name such as /IISSamples/tips.txt) to // a physical filesystem name such as d:\inetpub\IISSamples\tips.txt. CComBSTR bstrPhysicalDataFile; hr = cxt.Server()->MapPath(bstrDataFile, &bstrPhysicalDataFile); // See note below about critical sections CAutoLeaveCritSec alcs(&m_CS); if (SUCCEEDED(hr)) hr = _ReadDataFile(bstrPhysicalDataFile, TRUE); if (SUCCEEDED(hr)) { const CComVariant cvHR(OLESTR("\n
\n\n")); BOOL bFirstTip = TRUE; for (CTip* pTip = m_ptl->m_pTipsListHead; pTip != NULL; pTip = pTip->m_pNext) { // Write the leading HR only on the first pass if (bFirstTip == TRUE) { cxt.Response()->Write(cvHR); bFirstTip = FALSE; } // Write back to the user's browser, one tip at a time. // This is more efficient than concatenating all of the // tips into a potentially huge string and returning that. CMBCSToWChar convStr; BSTR pbstrTip; // need to convert the string to Wide based on the UTF8 flag if (hr = convStr.Init(pTip->m_ptsz, m_ptl->m_fUTF8 ? 65001 : CP_ACP)) { break; } // make a proper BSTR out of the wide version if (!(pbstrTip = ::SysAllocString(convStr.GetString()))) { hr = ::ReportError( E_OUTOFMEMORY ); break; } cxt.Response()->Write(CComVariant(pbstrTip)); cxt.Response()->Write(cvHR); ::SysFreeString(pbstrTip); } } } else { hr = ::ReportError(E_OUTOFMEMORY); } } else { hr = ::ReportError(E_NOINTERFACE); } } catch ( std::bad_alloc& ) { hr = ::ReportError( E_OUTOFMEMORY ); } catch ( ... ) { hr = E_FAIL; } return hr; } ///////////////////////////////////////////////////////////////////////////// // CContentRotator Private Methods // // Do the work of ChooseContent, but with a real filename, not with a // virtual filename // HRESULT CContentRotator::_ChooseContent( BSTR bstrPhysicalDataFile, BSTR* pbstrRetVal) { ASSERT(bstrPhysicalDataFile != NULL && pbstrRetVal != NULL); // The critical section ensures that the remaining code in this // function is executing on only one thread at a time. This ensures // that the cached contents of the tip list are consistent for the // duration of a call. // Actually, the critical section is not needed at all. Because we // need to call Server.MapPath to map the virtual path of // bstrDataFile to a physical filesystem path, the OnStartPage method // must be called, as this is the only way that we can get access to // the ScriptingContext object and thereby the Server object. // However, the OnStartPage method is only called for page-level // objects (object is created and destroyed in a single page) and for // session-level objects. Page-level objects don't have to worry // about protecting their data from multiple access (unless it's // global data shared between several objects) and neither do // session-level objects. Only application-level objects need worry // about protecting their private data, but application-level objects // don't give us any way to map the virtual path. // The Content Rotator might be more useful it it were an // application-level object. We would get better distribution of the // tips (see below) and do a lot less rereading of the data file. The // trivial changes necessary to accept a filesystem path, such as // "D:\ContRot\tips.txt", instead of a virtual path, such as // "/IISSamples/tips.txt",are left as an exercise for the reader. CAutoLeaveCritSec alcs(&m_CS); HRESULT hr = _ReadDataFile(bstrPhysicalDataFile, FALSE); if (SUCCEEDED(hr)) { const UINT uRand = m_ptl->Rand(); UINT uCumulativeWeight = 0; CTip* pTip = m_ptl->m_pTipsListHead; LPCTSTR ptszTip = NULL; for ( ; ; ) { ASSERT_VALID(pTip); ptszTip = pTip->m_ptsz; uCumulativeWeight += pTip->m_uWeight; if (uCumulativeWeight <= uRand) pTip = pTip->m_pNext; else { // Found the tip. Now we go through a bit of work to make // sure that each tip is served up with the correct // probability. If the tip has already been served up as // many times as it's allowed (i.e., m_uWeight times), then // it's moved to the Used List. Otherwise, it's (probably) // moved to the end of the Tips List, to reduce the // likelihood of it turning up too soon again and to // randomize the order of the tips in the list. When all // tips have been moved to the Used List, we start afresh. // If the object is created, used, and destroyed in a // single page (i.e., it's not a session-level object), // then none of this does us any good. The list is in // exactly the same order as it is in the data file and // we just have to hope that Rand() does give us // well-distributed random numbers. // If you expect a single user to see more than one tip, // you should use a session-level object, to benefit from // the better distribution of tips. This would be the case // if you're serving up tips from the same file on multiple // pages, or if you have a page that automatically // refreshes itself, such as the one included in the // Samples directory. if (--pTip->m_cServingsLeft > 0) { // Move it to the end of the list some of the time. // If we move it there all of the time, then a heavily // weighted tip is more likely to turn up a lot // as the main list nears exhaustion. if (rand() % 3 == 0) { // TRACE1("Move to End\n%s\n", ptszTip); m_ptl->RemoveTip(pTip); m_ptl->AppendTip(pTip); } } else { // TRACE1("Move to Used\n%s\n", ptszTip); pTip->m_cServingsLeft = pTip->m_uWeight; // reset m_ptl->RemoveTip(pTip); m_ptlUsed->AppendTip(pTip); if (m_ptl->m_cTips == 0) { TRACE0("List exhausted; swapping\n"); CTipList* const ptlTemp = m_ptl; m_ptl = m_ptlUsed; m_ptlUsed = ptlTemp; } } break; } } // TRACE2("total weight = %u, rand = %u\n", // m_ptl->m_uTotalWeight, uRand); // TRACE1("tip = `%s'\n", ptszTip); CMBCSToWChar convStr; if (hr = convStr.Init(ptszTip, m_ptl->m_fUTF8 ? 65001 : CP_ACP)); else { *pbstrRetVal = ::SysAllocString(convStr.GetString()); } } return hr; } HRESULT CContentRotator::_ReadDataFile( BSTR bstrPhysicalDataFile, BOOL fForceReread) { USES_CONVERSION; // needed for OLE2T LPCTSTR ptszFilename = OLE2T(bstrPhysicalDataFile); HRESULT hr = S_OK; if (ptszFilename == NULL) { return E_OUTOFMEMORY; } // Have we cached this tips file already? if (!fForceReread) { BOOL fIsSame; HRESULT hr = m_ptl->SameAsCachedFile(ptszFilename, fIsSame); TRACE(_T("%same file\n"), fIsSame ? _T("S") : _T("Not s")); if (FAILED(hr) || fIsSame) return hr; } // destroy any old tips m_ptl->DeleteTips(); m_ptlUsed->DeleteTips(); hr = m_ptl->ReadDataFile(ptszFilename); if (FAILED(hr)) { m_ptl->DeleteTips(); m_ptlUsed->DeleteTips(); } return hr; } ///////////////////////////////////////////////////////////////////////////// // CTipList Public Methods // // Read a collection of tips from ptszDataFile // // The file format is zero or more copies of the following: // One or more lines starting with "%%" // Each %% line contains zero or more directives: // # (positive integer, 1 <= weight <= MAX_WEIGHT) // // (a comment that runs to the end of the line) // The tip text follows, spread out over several lines // HRESULT CTipList::ReadDataFile( LPCTSTR ptszFilename) { TRACE1("ReadDataFile(%s)\n", ptszFilename); UINT weightSum = 0; if ( m_ptszFilename != NULL ) { g_pMonitor->StopMonitoringFile( m_ptszFilename ); delete [] m_ptszFilename; } m_ptszFilename = TcsDup(ptszFilename); if (m_ptszFilename == NULL) return ::ReportError(E_OUTOFMEMORY); // Open the file CHFile hFile(m_ptszFilename); if (!hFile) return ::ReportError(::GetLastError()); // Get the last-write-time and the filesize BY_HANDLE_FILE_INFORMATION bhfi; if (!::GetFileInformationByHandle(hFile, &bhfi)) return ::ReportError(::GetLastError()); // If it's more than 4GB, let's not even think about it! if (bhfi.nFileSizeHigh != 0) return ::ReportError(E_OUTOFMEMORY); // Calculate the number of TCHARs in the file const DWORD cbFile = bhfi.nFileSizeLow; const DWORD ctc = cbFile / sizeof(TCHAR); // Allocate a buffer for the file's contents m_ptszData = NULL; ATLTRY(m_ptszData = new TCHAR [ctc + 2]); if (m_ptszData == NULL) return ::ReportError(E_OUTOFMEMORY); // Read the file into the memory buffer. Let's be paranoid and // not assume that ReadFile gives us the whole file in one chunk. DWORD cbSeen = 0; do { DWORD cbToRead = cbFile - cbSeen; DWORD cbRead = 0; if (!::ReadFile(hFile, ((LPBYTE) m_ptszData) + cbSeen, cbToRead, &cbRead, NULL)) return ::ReportError(::GetLastError()); cbSeen += cbRead; } while (cbSeen < cbFile); m_ptszData[ctc] = _T('\0'); // Nul-terminate the string LPTSTR ptsz = m_ptszData; #ifdef _UNICODE #error "This file should NOT be compiled with _UNICODE defined!!! // Check for byte-order mark if (*ptsz == 0xFFFE) { // Byte-reversed Unicode file. Swap the hi- and lo-bytes in each wchar for ( ; ptsz < m_ptszData + ctc; ++ptsz) { BYTE* pb = (BYTE*) ptsz; const BYTE bHi = pb[1]; pb[1] = pb[0]; pb[0] = bHi; } ptsz = m_ptszData; } if (*ptsz == 0xFEFF) ++ptsz; // skip the byte-order mark #endif // check for the UTF-8 BOM if ((ctc > 3) && (ptsz[0] == (TCHAR)0xef) && (ptsz[1] == (TCHAR)0xbb) && (ptsz[2] == (TCHAR)0xbf)) { // note its presence and advance the file pointer past it. m_fUTF8 = true; ptsz += 3; } // Finally, parse the file while (ptsz < m_ptszData + ctc) { UINT uWeight = GetWeight(ptsz); // a value of INVALID_WEIGHT for weight indicates that no weight was found, // i.e. an invalid data file, or the value was not valid. if (uWeight == INVALID_WEIGHT) { return ::ReportError((DWORD)ERROR_INVALID_DATA); } weightSum += uWeight; if (weightSum > MAX_WEIGHT) { return ::ReportError((DWORD)ERROR_INVALID_DATA); } LPTSTR ptszTipText = GetTipText(ptsz); if (!IsBlankString(ptszTipText) && uWeight > 0) { CTip* pTip = NULL; ATLTRY(pTip = new CTip(ptszTipText, uWeight)); if (pTip == NULL) return ::ReportError(E_OUTOFMEMORY); AppendTip(pTip); } else if (ptsz < m_ptszData + ctc) { // not at a terminating "%%" line at the end of the data file TRACE2("bad tip: tip = `%s', weight = %u\n", ptszTipText, uWeight); } } g_pMonitor->MonitorFile( m_ptszFilename, m_pNotify ); if (m_uTotalWeight == 0 || m_cTips == 0) return ::ReportError((DWORD)ERROR_INVALID_DATA); return S_OK; } // // Is ptszFilename the same file as m_ptszFilename in both its name // and timestamp? // HRESULT CTipList::SameAsCachedFile( LPCTSTR ptszFilename, BOOL& rfIsSame) { rfIsSame = FALSE; // Have we cached a file at all? if (m_ptszFilename == NULL) return S_OK; // Are the names the same? if (_tcsicmp(ptszFilename, m_ptszFilename) != 0) return S_OK; #if 1 // FILETIME ftLastWriteTime; // CHFile hFile(ptszFilename); // // if (!hFile) // return ::ReportError(::GetLastError()); // // if (!::GetFileTime(hFile, NULL, NULL, &ftLastWriteTime)) // return ::ReportError(::GetLastError()); // // rfIsSame = (::CompareFileTime(&ftLastWriteTime, &m_ftLastWriteTime) == 0); if ( !m_pNotify->IsNotified() ) { rfIsSame = TRUE; } #else // The following is more efficient, but it won't work on Win95 with // Personal Web Server because GetFileAttributesEx is new to NT 4.0. WIN32_FILE_ATTRIBUTE_DATA wfad; if (!::GetFileAttributesEx(ptszFilename, GetFileExInfoStandard, (LPVOID) &wfad)) return ::ReportError(::GetLastError()); rfIsSame = (::CompareFileTime(&wfad.ftLastWriteTime, &m_ftLastWriteTime) == 0); #endif return S_OK; } // // Generate a random number in the range 0..m_uTotalWeight-1 // UINT CTipList::Rand() const { UINT u; ASSERT(m_uTotalWeight > 0); if (m_uTotalWeight == 1) return 0; else if (m_uTotalWeight <= RAND_MAX + 1) u = rand() % m_uTotalWeight; else { // RAND_MAX is only 32,767. This gives us a bigger range // of random numbers if the weights are large. u = ((rand() << 15) | rand()) % m_uTotalWeight; } ASSERT(0 <= u && u < m_uTotalWeight); return u; } // // Append a tip to the list // void CTipList::AppendTip( CTip* pTip) { ASSERT_VALID(this); pTip->m_pPrev = pTip->m_pNext = NULL; ASSERT_VALID(pTip); pTip->m_pPrev = m_pTipsListTail; if (m_pTipsListTail == NULL) m_pTipsListHead = pTip; else m_pTipsListTail->m_pNext = pTip; m_pTipsListTail = pTip; ++m_cTips; m_uTotalWeight += pTip->m_uWeight; ASSERT_VALID(this); } // // Remove a tip from somewhere in the list // void CTipList::RemoveTip( CTip* pTip) { ASSERT_VALID(this); ASSERT_VALID(pTip); ASSERT(m_cTips > 0); if (m_cTips == 1) { ASSERT(m_pTipsListHead == pTip && pTip == m_pTipsListTail); m_pTipsListHead = m_pTipsListTail = NULL; } else if (pTip == m_pTipsListHead) { ASSERT(m_pTipsListHead->m_pNext != NULL); m_pTipsListHead = m_pTipsListHead->m_pNext; m_pTipsListHead->m_pPrev = NULL; } else if (pTip == m_pTipsListTail) { ASSERT(m_pTipsListTail->m_pPrev != NULL); m_pTipsListTail = m_pTipsListTail->m_pPrev; m_pTipsListTail->m_pNext = NULL; } else { ASSERT(m_cTips >= 3); pTip->m_pPrev->m_pNext = pTip->m_pNext; pTip->m_pNext->m_pPrev = pTip->m_pPrev; } pTip->m_pPrev = pTip->m_pNext = NULL; --m_cTips; m_uTotalWeight -= pTip->m_uWeight; ASSERT_VALID(this); } // // Destroy the list of tips and reset all member variables // HRESULT CTipList::DeleteTips() { ASSERT_VALID(this); CTip* pTip = m_pTipsListHead; for (UINT i = 0; i < m_cTips; ++i) { pTip = pTip->m_pNext; delete m_pTipsListHead; m_pTipsListHead = pTip; } ASSERT(pTip == NULL && m_pTipsListHead == NULL); // check for both a valid Filename ptr as well as a valid Monitor ptr. // If the ContRotModule::Unlock is called prior to this destructor,then // the Monitor object has already been cleaned up and deleted. if ( (m_ptszFilename != NULL) && (g_pMonitor != NULL) ) { g_pMonitor->StopMonitoringFile( m_ptszFilename ); } delete [] m_ptszFilename; delete [] m_ptszData; m_ptszFilename = m_ptszData = NULL; // m_ftLastWriteTime.dwLowDateTime = m_ftLastWriteTime.dwHighDateTime = 0; m_cTips = m_uTotalWeight = 0; m_pTipsListHead = m_pTipsListTail = NULL; ASSERT_VALID(this); return S_OK; } #if DBG // Paranoia: check that Tips and TipLists are internally consistent. // Very useful in catching bugs. void CTip::AssertValid() const { ASSERT(m_ptsz != NULL && m_uWeight > 0); ASSERT(0 < m_cServingsLeft && m_cServingsLeft <= m_uWeight); ASSERT(m_pPrev == NULL || m_pPrev->m_pNext == this); ASSERT(m_pNext == NULL || m_pNext->m_pPrev == this); } void CTipList::AssertValid() const { if (m_cTips == 0) { ASSERT(m_pTipsListHead == NULL && m_pTipsListTail == NULL); ASSERT(m_uTotalWeight == 0); } else { ASSERT(m_pTipsListHead != NULL && m_pTipsListTail != NULL); ASSERT(m_pTipsListHead->m_pPrev == NULL); ASSERT(m_pTipsListTail->m_pNext == NULL); ASSERT(m_uTotalWeight > 0); if (m_cTips == 1) ASSERT(m_pTipsListHead == m_pTipsListTail); else ASSERT(m_pTipsListHead != m_pTipsListTail); } UINT uWeight = 0; CTip* pTip = m_pTipsListHead; UINT i; for (i = 0; i < m_cTips; ++i) { ASSERT_VALID(pTip); uWeight += pTip->m_uWeight; if (i < m_cTips - 1) pTip = pTip->m_pNext; } ASSERT(uWeight == m_uTotalWeight); ASSERT(pTip == m_pTipsListTail); } #endif ///////////////////////////////////////////////////////////////////////////// // Utility functions // // Make a copy of a TSTR that can be deleted with operator delete[] // static LPTSTR TcsDup( LPCTSTR ptsz) { LPTSTR ptszNew = NULL; ATLTRY(ptszNew = new TCHAR [_tcslen(ptsz) + 1]); if (ptszNew != NULL) _tcscpy(ptszNew, ptsz); return ptszNew; } // // reads a \n-terminated string from rptsz and modifies rptsz to // point after the end of that string // static LPTSTR GetLine( LPTSTR& rptsz) { LPTSTR ptszOrig = rptsz; LPTSTR ptszEol = _tcspbrk(rptsz, _T("\n")); if (ptszEol != NULL) { // is it "\r\n"? if (ptszEol > ptszOrig && ptszEol[-1] == _T('\r')) ptszEol[-1] = _T('\0'); else ptszEol[0] = _T('\0'); rptsz = ptszEol + 1; } else { // no newline, so point past the end of the string rptsz += _tcslen(rptsz); } // TRACE1("GetLine: `%s'\n", ptszOrig); return ptszOrig; } // // Is the string blank? // static BOOL IsBlankString( LPCTSTR ptsz) { if (ptsz == NULL) return TRUE; while (*ptsz != _T('\0')) if (!_istspace(*ptsz)) return FALSE; else ptsz++; return TRUE; } // // Read a weight line from rptsz and update rptsz to point after the // end of any %% lines. // static UINT GetWeight( LPTSTR& rptsz) { UINT u = INVALID_WEIGHT; // default to invalid weight while (*rptsz == _T('%')) { LPTSTR ptsz = GetLine(rptsz); if (ptsz[1] == _T('%')) { u = 1; // now that the format is correct, default to 1 ptsz +=2; // Skip "%%" while (*ptsz != _T('\0')) { while (_istspace(*ptsz)) ptsz++; if (*ptsz == _T('/') && ptsz[1] == _T('/')) { // TRACE1("// `%s'\n", ptsz+2); break; // a comment: ignore the rest of the line } else if (*ptsz == _T('#')) { ptsz++; if (_T('0') <= *ptsz && *ptsz <= _T('9')) { LPTSTR ptsz2; u = _tcstoul(ptsz, &ptsz2, 10); ptsz = ptsz2; // TRACE1("#%u\n", u); if (u > MAX_WEIGHT) u = MAX_WEIGHT; // clamp } else // ignore word { while (*ptsz != _T('\0') && !_istspace(*ptsz)) ptsz++; } } else // ignore word { while (*ptsz != _T('\0') && !_istspace(*ptsz)) ptsz++; } } } } return u; } // // Read the multiline tip text. Updates rptsz to point past the end of it. // static LPTSTR GetTipText( LPTSTR& rptsz) { LPTSTR ptszOrig = rptsz; LPTSTR ptszEol = _tcsstr(rptsz, _T("\n%%")); if (ptszEol != NULL) { // is it "\r\n"? if (ptszEol > rptsz && ptszEol[-1] == _T('\r')) ptszEol[-1] = _T('\0'); else ptszEol[0] = _T('\0'); rptsz = ptszEol + 1; } else { // no "\n%%", so point past the end of the string rptsz += _tcslen(rptsz); } // TRACE1("GetTipText: `%s'\n", ptszOrig); return ptszOrig; } // // Set the Error Info. It's up to the calling application to // decide what to do with it. By default, Denali/VBScript will // print the error number (and message, if there is one) and // abort the page. // static HRESULT ReportError( HRESULT hr, DWORD dwErr) { HLOCAL pMsgBuf = NULL; // If there's a message associated with this error, report that if (::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &pMsgBuf, 0, NULL) > 0) { AtlReportError(CLSID_ContentRotator, (LPCTSTR) pMsgBuf, IID_IContentRotator, hr); } // TODO: add some error messages to the string resources and // return those, if FormatMessage doesn't return anything (not // all system errors have associated error messages). // Free the buffer, which was allocated by FormatMessage if (pMsgBuf != NULL) ::LocalFree(pMsgBuf); return hr; } // // Report a Win32 error code // static HRESULT ReportError( DWORD dwErr) { return ::ReportError(HRESULT_FROM_WIN32(dwErr), dwErr); } // // Report an HRESULT error // static HRESULT ReportError( HRESULT hr) { return ::ReportError(hr, (DWORD) hr); }