Click here to Skip to main content
15,883,921 members
Articles / Desktop Programming / ATL

SP Numeric Edit Control

Rate me:
Please Sign up or sign in to vote.
4.78/5 (11 votes)
16 Nov 200511 min read 74.7K   3.6K   36  
Masked numeric edit ActiveX control.
/////////////////////////////////////////////////////////////////////////////////////////
//	Project:		SP Library 1.2
//
//	File:			spDynamicString.h	 interface for the SP::CDynamicString class 
//					template.
//
//	Developer(s):	Sergei Pavlovsky
//					sergei_vp@hotmail.com
//
//	Description:	SP::CDynamicString is a dynamic string. Preferably should be used 
//					for large strings.
//
//	Platforms:		Win32
//
/////////////////////////////////////////////////////////////////////////////////////////


#if !defined(__SP_SPDYNAMICSTRING_H__INCLUDED__)
#define __SP_SPDYNAMICSTRING_H__INCLUDED__

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "spVirtualMemory.h"

/*
#if defined(_DEBUG) && defined(_MFC_VER)
#include <afx.h>
#endif // defined(_DEBUG) && defined(_MFC_VER)
*/

namespace SP
{

/////////////////////////////////////////////////////////////////////////////////////////
// CDynamicString class template

template < class t_trates, class t_memory >
class CDynamicString : public t_trates
{
// Types
public:
	typedef t_memory								TMemory;
	typedef t_trates								TTrates;
	typedef typename t_trates::CTransStringTraits	TSTrates;

	typedef typename TTrates::CHAR_TYPE				CHAR_TYPE;
	typedef typename TSTrates::CHAR_TYPE			SCHAR_TYPE;

	typedef typename TTrates::SIZE_TYPE				SIZE_TYPE;
	typedef typename TTrates::INDEX_TYPE			INDEX_TYPE;
	typedef typename TTrates::LOCALEID_TYPE			LOCALEID_TYPE;

// Constants
public:
	enum
	{
		DEF_LOCALEID = TTrates::DEF_LOCALEID
	};

// Construction
public:
	CDynamicString()
	{
	}

	CDynamicString(const CDynamicString& src)
	{
		if ( !_Assign(src) )
			throw false;
	}

	CDynamicString(const CHAR_TYPE* lpcszSrc)
	{
		SIZE_TYPE cLength = TTrates::Length(lpcszSrc);

		if ( !_Assign(lpcszSrc, cLength, cLength, cLength) )   
			throw false;
	}

	CDynamicString(const CHAR_TYPE* lpcSrc, SIZE_TYPE cLength)
	{
		if ( !_Assign(lpcSrc, cLength, cLength, cLength) )   
			throw false;
	}

	CDynamicString(const CHAR_TYPE* lpcSrc, SIZE_TYPE cLength, LOCALEID_TYPE unCP)
	{
		if ( !_Assign(lpcszSrc, cLength, cLength, cLength) )
			throw false;	
	}

	CDynamicString(const SCHAR_TYPE* lpcszSrc)
	{
		if ( !_Assign(lpcszSrc, TSTrates::Length(lpcszSrc), DEF_LOCALEID) )
			throw false;
	}

	CDynamicString(const SCHAR_TYPE* lpcszSrc, SIZE_TYPE cLength)
	{
		if ( !_Assign(lpcszSrc, cLength, DEF_LOCALEID) )
			throw false;
	}

	CDynamicString(const SCHAR_TYPE* lpcSrc, SIZE_TYPE cLength, LOCALEID_TYPE unCP)
	{
		if ( !_Assign(lpcSrc, cLength, unCP) )
			throw false;
	}

private:
	CDynamicString(const CHAR_TYPE* lpcSrc1, SIZE_TYPE cLength1, 
				   const CHAR_TYPE* lpcSrc2, SIZE_TYPE cLength2)
	{
		_ASSERTE( cLength1 >= 0 && cLength2 >= 0 );
		_ASSERTE( !::IsBadReadPtr(lpcSrc1, sizeof(CHAR_TYPE) * cLength1) );
		_ASSERTE( !::IsBadReadPtr(lpcSrc2, sizeof(CHAR_TYPE) * cLength2) );

		SIZE_TYPE cCapacity = cLength1 + cLength2;

		if ( !m_memBuffer.Allocate(LengthToSize(cCapacity), 
								   LengthToSize(cCapacity)) )   
			throw false;

		Assign(lpcSrc1, cLength1);
		Append(lpcSrc2, cLength2);
	}

// Operators
public:
	CDynamicString& operator=(const CDynamicString& right)
	{
		if ( !Assign(right) )
			throw false;

		return *this;
	}

	CDynamicString& operator=(const CHAR_TYPE* lpcszSrc)
	{
		if ( !Assign(lpcszSrc) )
			throw false;

		return *this;
	}

	CDynamicString& operator=(const SCHAR_TYPE* lpcszSrc)
	{
		if ( !Assign(lpcszSrc) )
			throw false;

		return *this;
	}

	operator const CHAR_TYPE*() const 
	{
		return GetBuffer();
	}

	operator CHAR_TYPE*()
	{
		return GetBuffer();
	}

	const CHAR_TYPE& operator[](INDEX_TYPE index) const
	{
		_ASSERTE( index >= 0 && (SIZE_TYPE)index <= GetCapacity() );

		return GetBuffer()[index];
	}

	CHAR_TYPE& operator[](INDEX_TYPE index)
	{
		_ASSERTE( index >= 0 && (SIZE_TYPE)index <= GetCapacity() );

		return GetBuffer()[index];
	}

	bool operator==(const CDynamicString& right) const
	{
		return TTrates::Compare(GetBuffer(), right.GetBuffer()) == 0;
	}

	bool operator==(const CHAR_TYPE* lpcszRight) const
	{
		return TTrates::Compare(GetBuffer(), lpcszRight) == 0;
	}

	bool operator!=(const CDynamicString& right) const
	{
		return TTrates::Compare(GetBuffer(), right.GetBuffer()) != 0;
	}

	bool operator!=(const CHAR_TYPE* lpcszRight) const
	{
		return TTrates::Compare(GetBuffer(), lpcszRight) != 0;
	}

	bool operator>(const CDynamicString& right) const
	{
		return TTrates::Compare(GetBuffer(), right.GetBuffer()) > 0;
	}

	bool operator>(const CHAR_TYPE* lpcszRight) const
	{
		return TTrates::Compare(GetBuffer(), lpcszRight) > 0;
	}

	bool operator<(const CDynamicString& right) const
	{
		return TTrates::Compare(GetBuffer(), right.GetBuffer()) < 0;
	}

	bool operator<(const CHAR_TYPE* lpcszRight) const
	{
		return TTrates::Compare(GetBuffer(), lpcszRight) < 0;
	}

	bool operator>=(const CDynamicString& right) const
	{
		return TTrates::Compare(GetBuffer(), right.GetBuffer()) >= 0;
	}

	bool operator>=(const CHAR_TYPE* lpcszRight) const
	{
		return TTrates::Compare(GetBuffer(), lpcszRight) >= 0;
	}

	bool operator<=(const CDynamicString& right) const
	{
		return TTrates::Compare(GetBuffer(), right.GetBuffer()) <= 0;
	}

	bool operator<=(const CHAR_TYPE* lpcszRight) const
	{
		return TTrates::Compare(GetBuffer(), lpcszRight) <= 0;
	}


	CDynamicString& operator+=(const CDynamicString& right)
	{
		_ASSERTE( this != &right );

		if ( !Append(right.GetBuffer()) )
			throw false;

		return *this;
	}

	CDynamicString& operator+=(const CHAR_TYPE* lpcszRight)
	{
		_ASSERTE( lpcszRight != GetBuffer() );

		if ( !Append(lpcszRight) )
			throw false;

		return *this;
	}

	CDynamicString operator+(const CDynamicString& right) const
	{
		return CDynamicString(GetBuffer(), GetLength(),
							  right.GetBuffer(), right.GetLength());
	}

	CDynamicString operator+(const CHAR_TYPE* lpcszRight) const
	{
		return CDynamicString(GetBuffer(), GetLength(),
							  lpcszRight, TTrates::Length(lpcszRight));
	}

	friend CDynamicString operator+(const CHAR_TYPE* lpcszSrc1, 
									const CDynamicString& src2)
	{
		return CDynamicString(lpcszSrc1, t_trates::Length(lpcszSrc1),
							  src2, src2.GetLength());
	}

// Helpers
private:
	static typename TMemory::SIZE_TYPE LengthToSize(SIZE_TYPE cLength)
	{
		_ASSERTE( cLength >= 0 );

		return TMemory::SIZE_TYPE((cLength + 1) * sizeof(CHAR_TYPE));
	}

	static SIZE_TYPE SizeToLength(typename TMemory::SIZE_TYPE cbSize)
	{
		_ASSERTE( !cbSize || cbSize >= sizeof(CHAR_TYPE) );

		return (cbSize) ? cbSize/sizeof(CHAR_TYPE) - 1 : 0;
	}

// Operations: Memory allocation
public:
	bool Allocate(SIZE_TYPE cLengthToAllocate, SIZE_TYPE cLengthToReserve = 0)
	{
		_ASSERTE( cLengthToAllocate >= 0 && cLengthToReserve >= 0 );
		_ASSERTE( GetBuffer() == NULL );

		bool bResult = m_memBuffer.Allocate(LengthToSize(cLengthToAllocate), 
							cLengthToReserve ? LengthToSize(cLengthToReserve) : 0);
		_ASSERTE( bResult );
		if ( !bResult )   return false;

		if ( cLengthToAllocate )
			GetBuffer()[0] = CHAR_TYPE(0);			

		return true;
	}

	bool Reallocate(SIZE_TYPE cLengthToAllocate, SIZE_TYPE cLengthToReserve = 0, 
					bool bKeepContents = true)
	{
		_ASSERTE( cLengthToAllocate >= 0 && cLengthToReserve >= 0 );
//		_ASSERTE( (bKeepContents && GetBuffer()) || !bKeepContents );

		bool bResult = m_memBuffer.Reallocate(LengthToSize(cLengthToAllocate), 
							cLengthToReserve ? LengthToSize(cLengthToReserve) : 0, 
							bKeepContents);
		_ASSERTE( bResult );
		if ( !bResult )   return false;

		if ( !bKeepContents )
			GetBuffer()[0] = CHAR_TYPE(0);			

		return true;
	}

	void Free()
	{
		m_memBuffer.Free();
	}

// Helpers
private:
	bool _Assign(const CHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength, 
				 SIZE_TYPE cAllocLength, SIZE_TYPE cReserveLength)
	{
		_ASSERTE( cSrcLength >= 0 );
		_ASSERTE( cAllocLength >= cSrcLength && cReserveLength >= cAllocLength );
		_ASSERTE( !::IsBadReadPtr(lpcSrc, sizeof(CHAR_TYPE) * cSrcLength) );
		_ASSERTE( !lpcSrc || GetBuffer() != lpcSrc );
		
		if ( lpcSrc )
		{
			TMemory::SIZE_TYPE cbSrc = cSrcLength * sizeof(CHAR_TYPE);

			if 	( m_memBuffer.IsOverlappedWith(lpcSrc, cbSrc) )
			{
				TMemory::Move(GetBuffer(), lpcSrc, cbSrc);
			}
			else
			{
				TMemory::SIZE_TYPE cbAllocate = LengthToSize(cAllocLength);
				TMemory::SIZE_TYPE cbReserve = LengthToSize(cReserveLength);

				bool bResult = m_memBuffer.Reallocate(cbAllocate, cbReserve, false);
				ATLASSERT( bResult );
				if ( !bResult )   return false;

				TMemory::Copy(GetBuffer(), lpcSrc, cbSrc);
			}

			GetBuffer()[cSrcLength] = CHAR_TYPE(0);
		}
		else
		{
			Free();
		}

		return true;
	}

	bool _Assign(const CDynamicString& src)
	{
		SIZE_TYPE cSrcLength = src.GetLength();

		return _Assign(src.GetBuffer(), cSrcLength, cSrcLength, cSrcLength);
	}

	bool _Assign(const SCHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength, LOCALEID_TYPE unCP)
	{
		_ASSERTE( cSrcLength >= 0 );
		_ASSERTE( !::IsBadReadPtr(lpcSrc, sizeof(SCHAR_TYPE) * cSrcLength) );
		_ASSERTE( !m_memBuffer.IsOverlappedWith(lpcSrc, sizeof(SCHAR_TYPE) * cSrcLength) );

		if ( lpcSrc )
		{
			SIZE_TYPE cSLength = TSTrates::TranscodedLength(lpcSrc, cSrcLength, unCP);
			TMemory::SIZE_TYPE cSize = LengthToSize(cSLength);

			// Reallocate buffer of required new size
			bool bResult = m_memBuffer.Reallocate(cSize, cSize, false);
			ATLASSERT( bResult );
			if ( !bResult )   return false;

			// Translation and copy data in our buffer
			bResult = TSTrates::Transcode(GetBuffer(), cSrcLength, lpcSrc, cSrcLength,
										  unCP) == cSLength;
			_ASSERTE( bResult );
			GetBuffer()[cSLength] = CHAR_TYPE(0);

			return bResult;
		}
		else
		{
			Free();
		}

		return true;
	}

	SIZE_TYPE _TrimRight(const CHAR_TYPE* lpcszCharsToTrim, SIZE_TYPE cLength)
	{
		_ASSERT( lpcszCharsToTrim );
		_ASSERT( GetBuffer() );

		CHAR_TYPE* lpcszBuffer = GetBuffer() + cLength - 1;
				
		while ( *lpcszBuffer && lpcszBuffer >= GetBuffer() )
		{
			bool bTrim = false;
			const CHAR_TYPE* lpcszToTrim = lpcszCharsToTrim;

			while ( *lpcszToTrim && !bTrim )
			{
				bTrim = ( *lpcszBuffer == *lpcszToTrim );

				lpcszToTrim++;
			}

			if ( bTrim )
			{
				*lpcszBuffer = CHAR_TYPE(0);
				cLength--;
			}
			else
				break;

			lpcszBuffer--;
		}

		return cLength;
	}

	SIZE_TYPE _TrimLeft(const CHAR_TYPE* lpcszCharsToTrim, SIZE_TYPE cLength)
	{
		_ASSERT( lpcszCharsToTrim );
		_ASSERT( GetBuffer() );

		const CHAR_TYPE* lpcszBuffer = GetBuffer();
				
		while ( *lpcszBuffer )
		{
			bool bTrim = false;
			const CHAR_TYPE* lpcszToTrim = lpcszCharsToTrim;

			while ( *lpcszToTrim && !bTrim )
			{
				bTrim = ( *lpcszBuffer == *lpcszToTrim );

				lpcszToTrim++;
			}

			if ( bTrim )
				lpcszBuffer++;
			else
				break;
		}

		if ( lpcszBuffer != GetBuffer() )
		{
			cLength = cLength - (lpcszBuffer - GetBuffer());

			TMemory::Move(GetBuffer(), lpcszBuffer, (cLength + 1)* sizeof(CHAR_TYPE));
		}

		return cLength;
	}

// Operations: Data assignment
public:
	bool Assign(const CDynamicString& src)
	{
		return ( this != &src ) ? _Assign(src) : true;
	}


	bool Assign(const CHAR_TYPE* lpcszSrc)
	{
		return Assign(lpcszSrc, TTrates::Length(lpcszSrc));
	}

	bool Assign(const CHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength)
	{
		return ( lpcSrc != GetBuffer() ) 
					? _Assign(lpcSrc, cSrcLength, cSrcLength, cSrcLength) 
					: true;
	}

	bool Assign(const CHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength, LOCALEID_TYPE unCP)
	{
		return Assign(lpcSrc, cSrcLength);
	}


	bool Assign(const SCHAR_TYPE* lpcszSrc)
	{
		return _Assign(lpcszSrc, TSTrates::Length(lpcszSrc), DEF_LOCALEID);
	}

	bool Assign(const SCHAR_TYPE* lpcszSrc, SIZE_TYPE cSrcLength)
	{
		return _Assign(lpcSrc, cSrcLength, DEF_LOCALEID);
	}

	bool Assign(const SCHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength, LOCALEID_TYPE unCP)
	{
		return _Assign(lpcSrc, cSrcLength, unCP);
	}

// Operations: Data manipulation
public:
	bool Append(const CHAR_TYPE* lpcszSrc)
	{
		return Append(lpcszSrc, TTrates::Length(lpcszSrc));
	}

	bool Append(const CHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength)
	{
		_ASSERTE( cSrcLength >= 0 );
		_ASSERTE( !::IsBadReadPtr(lpcSrc, cSrcLength * sizeof(CHAR_TYPE)) );

		if ( lpcSrc )
		{
			SIZE_TYPE cCurLength = GetLength();
			SIZE_TYPE cNewLength = cCurLength + cSrcLength;
			TMemory::SIZE_TYPE cbSrcSize = cSrcLength * sizeof(CHAR_TYPE);
			TMemory::SIZE_TYPE cbNewSize = lpcSrc ? LengthToSize(cNewLength) : 0;

			CDynamicString dsTemp;
			bool bResult;
			if ( m_memBuffer.IsOverlappedWith(lpcSrc, cbSrcSize) )
			{
				bResult = dsTemp.Assign(lpcSrc, cSrcLength);
				_ASSERTE( bResult );
				if ( !bResult )   return false;

				lpcSrc = dsTemp;
			}

			bResult = m_memBuffer.Reallocate(cbNewSize, cbNewSize, true);
			_ASSERTE( bResult );
			if ( !bResult )   return false;


			TMemory::Copy(GetBuffer() + cCurLength, lpcSrc, cbSrcSize);		

			GetBuffer()[cNewLength] = CHAR_TYPE(0);
		}
		
		return true;
	}

	bool Insert(INDEX_TYPE index, const CHAR_TYPE* lpcszSrc)
	{
		return Insert(index, lpcszSrc, TTrates::Length(lpcszSrc));
	}

	bool Insert(INDEX_TYPE index, const CHAR_TYPE* lpcSrc, SIZE_TYPE cSrcLength)
	{
		_ASSERTE( index >= 0 && cSrcLength >= 0 );
		_ASSERTE( index <= GetLength() );
		_ASSERTE( !::IsBadReadPtr(lpcSrc, cSrcLength * sizeof(CHAR_TYPE)) );

		SIZE_TYPE cCurLength = GetLength();
		SIZE_TYPE cNewLength = cCurLength + cSrcLength;
		TMemory::SIZE_TYPE cbSrcSize = cSrcLength * sizeof(CHAR_TYPE);
		TMemory::SIZE_TYPE cbNewSize = LengthToSize(cNewLength);		

		// If the text to be inserted is in our buffer we have to  store it in 
		// temporal buffer.
		bool bResult;
		CDynamicString dsTemp;
		if ( m_memBuffer.IsOverlappedWith(lpcSrc, cbSrcSize) )
		{
			bResult = dsTemp.Assign(lpcSrc, cSrcLength);
			_ASSERTE( bResult );
			if ( !bResult )   return false;

			lpcSrc = dsTemp;
		}

		// Reallocate buffer
		bResult = m_memBuffer.Reallocate(cbNewSize, cbNewSize, true);
		_ASSERTE( bResult );
		if ( !bResult )   return false;

		// Move tralling part over insertion point
		CHAR_TYPE* pIns = GetBuffer() + index;
		SIZE_TYPE cTrlLength = cNewLength - cCurLength;
		if ( cTrlLength )
		{			
			TMemory::Move(pIns + cSrcLength, pIns, cTrlLength * sizeof(CHAR_TYPE));
		}

		// Copy the text to be inserted
		TMemory::Copy(pIns, lpcSrc, cbSrcSize);

		// Set null terminator
		GetBuffer()[cNewLength] = CHAR_TYPE(0);
		
		return true;
	}

	void Delete(INDEX_TYPE index, SIZE_TYPE cLength = 1)
	{
		_ASSERTE( index >= 0 && cLength >= 0 );
		_ASSERTE( index + cLength <= GetLength() );

		// Call destructors for elements
		SIZE_TYPE cCurLength = GetLength();
		SIZE_TYPE cNewLength = cCurLength - cLength;
		SIZE_TYPE cTrlLength = cNewLength - index;

		// Move tralling part over deletion
		if ( cTrlLength )
		{
			CHAR_TYPE* pIns = GetBuffer() + index;
			CHAR_TYPE* pTrl = pIns + cLength;

			TMemory::Move(pIns, pTrl, cTrlLength * sizeof(CHAR_TYPE));
		}

		// Set null terminator
		GetBuffer()[cNewLength] = CHAR_TYPE(0);
	}

	bool Replace(INDEX_TYPE iDst, SIZE_TYPE cDstLength, const CHAR_TYPE* lpcszSrc)
	{
		return Replace(iDst, cDstLength, lpcszSrc, TTrates::Length(lpcszSrc));
	}

	bool Replace(INDEX_TYPE iDst, SIZE_TYPE cDstLength, const CHAR_TYPE* lpcSrc, 
				 SIZE_TYPE cSrcLength)
	{
		_ASSERTE( iDst >= 0 && cDstLength >= 0 && cSrcLength >= 0 );
		_ASSERTE( !::IsBadReadPtr(lpcSrc, cSrcLength * sizeof(CHAR_TYPE)) );

		SIZEDIF_TYPE cDifLength = cSrcLength - cDstLength;
		SIZE_TYPE cRplLength = 0;

		// If the text to be inserted is in our buffer we have to  store it in 
		// temporal buffer.
		SIZE_TYPE cbSrcSize = cSrcLength * sizeof(CHAR_TYPE);

		bool bResult;
		CDynamicString dsTemp;
		if ( m_memBuffer.IsOverlappedWith(lpcSrc, cbSrcSize) )
		{
			bResult = dsTemp.Assign(lpcSrc, cSrcLength);
			_ASSERTE( bResult );
			if ( !bResult )   return false;

			lpcSrc = dsTemp;
		}

		if ( cDifLength > 0 )
		{
			cRplLength = cSrcLength - cDifLength;
			
			if ( !Insert(iDst + cRplLength, lpcSrc + cRplLength, cDifLength) )   
				return false;
		}
		else if ( cDifLength < 0 )
		{
			cRplLength = cDstLength + cDifLength;

			Delete( iDst + cRplLength, -cDifLength);
		}

		// Copy overlapped elements
		TMemory::Copy(GetBuffer() + iDst, lpcSrc, cRplLength * sizeof(CHAR_TYPE));

		return true;
	}

	void TrimLeft(const CHAR_TYPE* lpcszCharsToTrim)
	{
		_TrimLeft(lpcszCharsToTrim, GetLength());
	}

	void TrimRight(const CHAR_TYPE* lpcszCharsToTrim)
	{
		_TrimRight(lpcszCharsToTrim, GetLength());
	}

	void Trim(const CHAR_TYPE* lpcszCharsToTrim)
	{
		_TrimLeft(lpcszCharsToTrim, _TrimRight(lpcszCharsToTrim, GetLength()));
	}

//ToLower
	//CharLowerBuff
//ToUpper

// Accessors & mutators
public:
	const CHAR_TYPE* GetBuffer() const
	{
		return reinterpret_cast<const CHAR_TYPE*>(m_memBuffer.GetBuffer());
	}

	CHAR_TYPE* GetBuffer()
	{
		return reinterpret_cast<CHAR_TYPE*>(m_memBuffer.GetBuffer());
	}

	SIZE_TYPE GetLength() const
	{
		return TTrates::Length(GetBuffer());
	}

	bool IsEmpty() const
	{
		return ( !GetBuffer() || !(*GetBuffer()) );
	}

	SIZE_TYPE GetCapacity() const
	{
		return SizeToLength(m_memBuffer.GetAllocatedSize());
	}

	SIZE_TYPE GetReservedCapacity() const
	{
		return SizeToLength(m_memBuffer.GetReservedSize());
	}

// Attributes
private:
	TMemory	m_memBuffer;
};


/*
#if defined(_DEBUG) && defined(_MFC_VER)
template < class t_trates, class t_memory, class CHAR_TYPE >
static CDumpContext& AFXAPI operator<<(CDumpContext& dc, 
									   CDynamicString<t_trates, t_memory>& vs)
{
	dc << static_cast<const CDynamicString<t_trates, t_memory>::CHAR_TYPE*>(vs);
	return dc;
}
#endif // defined(_DEBUG) && defined(_MFC_VER)
*/

}; // namespace SP;

#endif // !defined(__SP_SPDYNAMICSTRING_H__INCLUDED__)

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions