Kirjautuminen

Haku

Tehtävät

Koodit: Template-luokka dynaamisen muistin käsittelyyn (C++)

Kirjoittaja: Asko Telinen

Kirjoitettu: 02.12.2004 – 02.12.2004

Tagit: koodi näytille, vinkki

Tulipa sitten kirjoitettua sellainen muun ohella kun kyllästyin kirjoittamaan aina malloc/calloc varaaman muistin käsittelyyn liittyvää koodia.

// Tiedosto : DynamicBuffer.h

#ifndef ___DYNAMIC_BUFFER__H__D31DFCFB_5BCE_4556_A1B8_FE2D98523A1F__DEFINED__
#define ___DYNAMIC_BUFFER__H__D31DFCFB_5BCE_4556_A1B8_FE2D98523A1F__DEFINED__

#include <stdlib.h>
#include <memory.h>
#include <string.h>

namespace atlib {


// Error messages
static const char *BUFFERERROR_INDEX_OUT_OF_BOUNDS	= "Invalid index !!!";
static const char *BUFFERERROR_OUT_OF_MEMORY		= "Not enough memory to complete requested operation !!!";
static const char *BUFFERERROR_INVALID_BUFFER		= "Buffer is not allocated yet !!!";
static const char *BUFFERERROR_INVALID_PARAMETER	= "The parameter is incorrect !!!";

// block size
#ifndef BLOCK
#define BLOCK unsigned char
#endif

#define CHECK_BUFFER(buffer)	{ if ( !buffer )			{ throw BUFFERERROR_INVALID_BUFFER;			} }

#define CHECK_INDEX(Index)		{ if ( Index >= getSize() ) { throw BUFFERERROR_INDEX_OUT_OF_BOUNDS;	} }

typedef unsigned long INDEX_TYPE;


// error index
static const INDEX_TYPE npos = (INDEX_TYPE)(-1);

template<class __DataType>
class DynamicBuffer {

public:
	typedef __DataType			DATA;
	typedef __DataType*			LPDATA;
	typedef const __DataType*	LPCDATA;

protected:
	LPDATA m_pBuffer;		// Actual data
	INDEX_TYPE m_Size;	// Allocated size (in block size)

	static unsigned short BLOCKSIZE();

	bool DoInsert(LPCDATA lpSourceData, INDEX_TYPE ulSourceSize, INDEX_TYPE ulOffset);

	virtual void Copy(INDEX_TYPE ulStartIndex, LPCDATA lpSourceData, INDEX_TYPE ulSourceSize);
	virtual void Move(INDEX_TYPE ulDestinationIndex, INDEX_TYPE ulSourceIndex, INDEX_TYPE ulCount);

public:
	DynamicBuffer(INDEX_TYPE ulSize = 0);
	DynamicBuffer(const DynamicBuffer<__DataType> &db); // copy constructor

	virtual ~DynamicBuffer();

	void				Fill(__DataType Value);
	void				Fill(__DataType Value, INDEX_TYPE ulOffset);
	void				Fill(__DataType Value, INDEX_TYPE ulOffset, INDEX_TYPE ulCount);

	virtual bool		Allocate(INDEX_TYPE ulSize);
	void				Release();

	void				setAt(INDEX_TYPE ulIndex, __DataType NewValue);
	__DataType			getAt(INDEX_TYPE ulIndex) const;

	bool				Append(const DynamicBuffer<__DataType> &Source);
	bool				Append(LPCDATA lpSource, INDEX_TYPE lpDataSize);
	bool				Append(__DataType Data);

	bool				Insert(const DynamicBuffer<__DataType> &Source, INDEX_TYPE ulOffset);
	bool				Insert(const DynamicBuffer<__DataType> &Source, LPCDATA lpBeginPtr);
	bool				Insert(LPCDATA lpSource, INDEX_TYPE ulDataSize, INDEX_TYPE ulOffset);
	bool				Insert(LPCDATA lpSource, INDEX_TYPE ulDataSize, LPCDATA lpBeginPtr);
	bool				Insert(__DataType Data, INDEX_TYPE ulOffset);
	bool				Insert(__DataType Data, LPCDATA lpBeginPtr);

	virtual __DataType	Replace(__DataType Data, INDEX_TYPE ulOffset);
	virtual __DataType	Replace(__DataType Data, LPCDATA lpBeginPtr);
	virtual bool		Replace(const DynamicBuffer<__DataType> &Source, INDEX_TYPE ulOffset, INDEX_TYPE ulCount);
	virtual bool		Replace(const DynamicBuffer<__DataType> &Source, LPCDATA lpBeginPtr, LPCDATA lpEndPtr);
	virtual bool		Replace(LPCDATA lpSource, INDEX_TYPE ulDataSize, INDEX_TYPE ulOffset, INDEX_TYPE ulCount);
	virtual bool		Replace(LPCDATA lpSource, INDEX_TYPE ulDataSize, LPCDATA lpBeginPtr, LPCDATA lpEndPtr);

	bool				Erase(INDEX_TYPE ulOffset, INDEX_TYPE ulCount);
	bool				Erase(LPCDATA lpBeginPtr, LPCDATA lpEndPtr);

	virtual void		setBuffer(LPDATA lpNewBuffer, INDEX_TYPE ulBufferSize);
	virtual LPDATA		getBuffer() { return m_pBuffer; }

	virtual INDEX_TYPE  ptrToOffset(LPCDATA lpPtr) const;

	virtual bool		getSlice(LPCDATA lpBeginPtr, LPCDATA lpEndPtr, DynamicBuffer<__DataType> &Result) const;
	virtual bool		getSlice(INDEX_TYPE ulOffset, INDEX_TYPE ulCount, DynamicBuffer<__DataType> &Result) const;

	// size
	virtual INDEX_TYPE  getSize() const			{ return m_Size;					}
	virtual INDEX_TYPE  getByteSize() const		{ return getSize() * BLOCKSIZE();	}
	virtual bool		setSize(INDEX_TYPE ulNewSize);

	///////////////////////////////////////////////////////////////////////////////////////////////////

	// operator overloading
	__DataType					operator[](INDEX_TYPE ulIndex) const { return getAt(ulIndex); }

	//DynamicBuffer<__DataType> & operator+(DynamicBuffer<__DataType> &db);
	virtual DynamicBuffer<__DataType> &	operator=(const DynamicBuffer<__DataType> &db);
	virtual DynamicBuffer<__DataType> &	operator+=(const DynamicBuffer<__DataType> &db);
	virtual bool						operator==(const DynamicBuffer<__DataType> &db);
	virtual bool						operator!=(const DynamicBuffer<__DataType> &db);
	virtual bool						operator!() const { return (m_pBuffer == NULL); }

	operator LPCDATA() const { return (LPCDATA)m_pBuffer; }
	operator LPDATA()		 { return (LPDATA)m_pBuffer;  }

/*------------------------------------------------------------------------------------------------*/

};

///////////////////////////////////////////////////////////////////////////////////////////////////
// Static members

// Retuns __DataType size in bytes
template<class __DataType>
unsigned short DynamicBuffer<__DataType>::BLOCKSIZE()
{
	return sizeof(__DataType) * sizeof(BLOCK);
}
///////////////////////////////////////////////////////////////////////////////////////////////////


// Copy constructor
template<class __DataType>
DynamicBuffer<__DataType>::DynamicBuffer(const DynamicBuffer<__DataType> &db) : m_pBuffer(NULL), m_Size(0)
{
	*this = db;
}

template<class __DataType>
DynamicBuffer<__DataType>::DynamicBuffer(INDEX_TYPE ulSize) : m_pBuffer(NULL), m_Size(0)
{
	if ( !Allocate(ulSize) ) {
		throw BUFFERERROR_OUT_OF_MEMORY;
	}
}

template<class __DataType>
DynamicBuffer<__DataType>::~DynamicBuffer()
{
	Release();
}


///////////////////////////////////////////////////////////////////////////////////////////////////

// Utility methods

template<class __DataType>
void DynamicBuffer<__DataType>::Copy(INDEX_TYPE ulStartIndex, LPCDATA lpSourceData, INDEX_TYPE ulSourceSize)
{
	memcpy(m_pBuffer + ulStartIndex, lpSourceData, ulSourceSize * BLOCKSIZE());
}

template<class __DataType>
void DynamicBuffer<__DataType>::Move(INDEX_TYPE ulDestinationIndex, INDEX_TYPE ulSourceIndex, INDEX_TYPE ulCount)
{
	memmove(m_pBuffer + ulDestinationIndex, m_pBuffer + ulSourceIndex, ulCount * BLOCKSIZE());
}

template<class __DataType>
INDEX_TYPE DynamicBuffer<__DataType>::ptrToOffset(LPCDATA lpPtr) const
{
	CHECK_BUFFER(m_pBuffer);

	if ( !lpPtr || lpPtr > m_pBuffer + getSize() || lpPtr < m_pBuffer ) {
		return npos;
	}

	return (INDEX_TYPE)(lpPtr - m_pBuffer);
}

template<class __DataType>
void DynamicBuffer<__DataType>::setAt(INDEX_TYPE ulIndex, __DataType NewValue)
{
	CHECK_BUFFER(m_pBuffer);

	CHECK_INDEX(ulIndex);

	(DATA)m_pBuffer[ulIndex] = NewValue;
}

template<class __DataType>
__DataType DynamicBuffer<__DataType>::getAt(INDEX_TYPE ulIndex) const
{
	CHECK_INDEX(ulIndex);

	return (__DataType)m_pBuffer[ulIndex];
}


// Set the allocated memory size. New size is in block size
template<class __DataType>
bool DynamicBuffer<__DataType>::setSize(INDEX_TYPE ulNewSize)
{
	if ( ulNewSize == getSize() ) {
		return true;
	}

	if ( ulNewSize == 0 ) {
		Release();
	}
	else {

		if ( !m_pBuffer ) {
			return Allocate(ulNewSize);
		}
		else {

			m_pBuffer = (LPDATA)realloc(m_pBuffer, ulNewSize * BLOCKSIZE());

			if ( !m_pBuffer ) {
				return false;
			}

			if ( ulNewSize > getSize() ) {

				// Pad with zeros
				memset(m_pBuffer + getSize(), 0, (ulNewSize - getSize()) * BLOCKSIZE());
			}
		}
	}

	m_Size = ulNewSize;

	return true;
}




///////////////////////////////////////////////////////////////////////////////////////////////////
//
// MODIFY
//
///////////////////////////////////////////////////////////////////////////////////////////////////



template<class __DataType>
void DynamicBuffer<__DataType>::setBuffer(LPDATA lpNewBuffer, INDEX_TYPE ulBufferSize)
{
	Release();

	if ( lpNewBuffer && ulBufferSize ) {
		m_pBuffer = lpNewBuffer;
		m_Size = ulBufferSize;
	}
	else {
		throw BUFFERERROR_INVALID_PARAMETER;
	}
}

/*------------------------------------------------------------------------------------------------*/
// Append

template<class __DataType>
bool DynamicBuffer<__DataType>::Append(const DynamicBuffer<__DataType> &Source)
{
	return Insert(Source, getSize());
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Append(__DataType Data)
{
	return Insert(Data, getSize());
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Append(LPCDATA lpSource, INDEX_TYPE lpDataSize)
{
	return Insert(lpSource, lpDataSize, getSize());
}

/*------------------------------------------------------------------------------------------------*/
// Insert

template<class __DataType>
bool DynamicBuffer<__DataType>::DoInsert(LPCDATA lpSourceData, INDEX_TYPE ulSourceSize, INDEX_TYPE ulOffset)
{
	if ( !lpSourceData ) {
		return true;
	}

	// We need to check it before setting new size because the m_pBuffer pointer might change...
	bool bDuplicate = (lpSourceData == m_pBuffer);

	LPDATA tmp_buffer = NULL;

	if ( bDuplicate ) { // same object, so we duplicate it

		// Create copy of current data
		tmp_buffer = (LPDATA)calloc(ulSourceSize, BLOCKSIZE());

		if ( !tmp_buffer ) {
			return false;
		}

		memcpy(tmp_buffer, m_pBuffer, ulSourceSize * BLOCKSIZE());
	}

	if ( !setSize(getSize() + ulSourceSize) ) {

		if ( tmp_buffer ) {
			free((void *)tmp_buffer);
		}

		return false;
	}

	// Define source data
	LPDATA lpSource = (LPDATA)lpSourceData;

	if ( bDuplicate ) { // same object
		lpSource = tmp_buffer;
	}


	if ( ulOffset >= getSize() - ulSourceSize ) {

		// Append
		Copy(getSize() - ulSourceSize, lpSource, ulSourceSize);
	}
	else {

		// Insert
		Move(ulOffset + ulSourceSize, ulOffset, getSize() - ulOffset - ulSourceSize);
		Copy(ulOffset, lpSource, ulSourceSize);
	}

	if ( tmp_buffer ) {
		free((void *)tmp_buffer);
	}

	return true;
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Insert(LPCDATA lpSource, INDEX_TYPE ulDataSize, INDEX_TYPE ulOffset)
{
	return DoInsert(lpSource, ulDataSize, ulOffset);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Insert(const DynamicBuffer<__DataType> &Source, INDEX_TYPE ulOffset)
{
	return DoInsert((LPCDATA)Source, Source.getSize(), ulOffset);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Insert(__DataType Data, INDEX_TYPE ulOffset)
{
	CHECK_BUFFER(m_pBuffer);

	if ( !setSize(getSize() + 1) ) {
		return false;
	}

	if ( ulOffset >= getSize() - 1 ) { // new size is one block bigger
		// Append
		setAt(getSize() - 1, Data); // set last block
	}
	else {

		// Insert
		memmove(m_pBuffer + ulOffset + 1, m_pBuffer + ulOffset, (getSize() - ulOffset - 1) * BLOCKSIZE());
		setAt(ulOffset, Data);
	}

	return true;
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Insert(LPCDATA lpSource, INDEX_TYPE ulDataSize, LPCDATA lpBeginPtr)
{
	INDEX_TYPE ulOffset = PtrToOffset(lpBeginPtr);

	if ( ulOffset == npos ) {
		return false;
	}

	return Insert(lpSource, ulDataSize, ulOffset);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Insert(__DataType Data, LPCDATA lpBeginPtr)
{
	INDEX_TYPE ulOffset = PtrToOffset(lpBeginPtr);

	if ( ulOffset == npos ) {
		return false;
	}

	return Insert(Data, ulOffset);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Insert(const DynamicBuffer<__DataType> &Source, LPCDATA lpBeginPtr)
{
	INDEX_TYPE ulOffset = PtrToOffset(lpBeginPtr);

	if ( ulOffset == npos ) {
		return false;
	}

	return Insert(Source, ulOffset);
}

/*------------------------------------------------------------------------------------------------*/
// Erase

template<class __DataType>
bool DynamicBuffer<__DataType>::Erase(INDEX_TYPE ulOffset, INDEX_TYPE ulCount)
{
	CHECK_BUFFER(m_pBuffer);

	if ( ulOffset + ulCount > getSize() ) {
		throw BUFFERERROR_INVALID_PARAMETER;
	}

	// Check special cases
	if ( ulOffset + ulCount == getSize() ) { // Delete from end of the buffer ?

		if ( ulOffset == 0 ) { // Delete whole buffer?
			Release();
			return true;
		}

		return setSize(ulOffset - 1);
	}

	// move the memory after the block we´re erasing
	memmove(m_pBuffer + ulOffset, m_pBuffer + ulOffset + ulCount, (getSize() - (ulOffset + ulCount)) * BLOCKSIZE());

	// set new size
	return setSize(getSize() - ulCount);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Erase(LPCDATA lpBeginPtr, LPCDATA lpEndPtr)
{
	INDEX_TYPE ulOffset = PtrToOffset(lpBeginPtr);
	INDEX_TYPE ulCount = PtrToOffset(lpEndPtr);

	if ( ulOffset == npos || ulCount == npos || ulCount < ulOffset ) {
		return false;
	}

	// Calculate count
	ulCount = ulCount - ulOffset + 1;

	return Erase(ulOffset, ulCount);
}

/*------------------------------------------------------------------------------------------------*/
// Replace

template<class __DataType>
__DataType	DynamicBuffer<__DataType>::Replace(__DataType Data, INDEX_TYPE ulOffset)
{
	__DataType retval = getAt(ulOffset);
	setAt(ulOffset, Data);
	return retval;
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Replace(const DynamicBuffer<__DataType> &Source, INDEX_TYPE ulOffset, INDEX_TYPE ulCount)
{
	if ( &Source == this ) {
		throw BUFFERERROR_INVALID_PARAMETER;
	}

	if ( Erase(ulOffset, ulCount) ) {
		return Insert(Source, ulOffset);
	}

	return false;
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Replace(LPCDATA lpSource, INDEX_TYPE ulDataSize, INDEX_TYPE ulOffset, INDEX_TYPE ulCount)
{
	if ( m_pBuffer && (m_pBuffer == lpSource) ) {
		throw BUFFERERROR_INVALID_PARAMETER;
	}

	if ( Erase(ulOffset, ulCount) ) {
		return DoInsert(lpSource, ulDataSize, ulOffset);
	}

	return false;
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Replace(LPCDATA lpSource, INDEX_TYPE ulDataSize, LPCDATA lpBeginPtr, LPCDATA lpEndPtr)
{
	INDEX_TYPE ulOffset = ptrToOffset(lpBeginPtr);
	INDEX_TYPE ulCount = ptrToOffset(lpEndPtr);

	if ( ulOffset == npos || ulCount == npos || ulCount < ulOffset ) {
		return false;
	}

	// Calculate count
	ulCount = ulCount - ulOffset + 1;

	return Replace(lpSource, ulDataSize, ulOffset, ulCount);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::Replace(const DynamicBuffer<__DataType> &Source, LPCDATA lpBeginPtr, LPCDATA lpEndPtr)
{
	INDEX_TYPE ulOffset = ptrToOffset(lpBeginPtr);
	INDEX_TYPE ulCount = ptrToOffset(lpEndPtr);

	if ( ulOffset == npos || ulCount == npos || ulCount < ulOffset ) {
		return false;
	}

	// Calculate count
	ulCount = ulCount - ulOffset + 1;

	return Replace(Source, ulOffset, ulCount);
}

template<class __DataType>
__DataType DynamicBuffer<__DataType>::Replace(__DataType Data, LPCDATA lpBeginPtr)
{
	INDEX_TYPE ulOffset = ptrToOffset(lpBeginPtr);

	if ( ulOffset == npos ) {
		return false;
	}

	return Replace(Data, ulOffset);
}

/*------------------------------------------------------------------------------------------------*/
// Slice

template<class __DataType>
bool DynamicBuffer<__DataType>::getSlice(LPCDATA lpBeginPtr, LPCDATA lpEndPtr, DynamicBuffer<__DataType> &Result) const
{
	Result.Release();

	INDEX_TYPE ulOffset = ptrToOffset(lpBeginPtr);
	INDEX_TYPE ulCount = ptrToOffset(lpEndPtr);

	if ( ulOffset == npos || ulCount == npos || ulCount < ulOffset ) {
		return false;
	}

	ulCount = ulCount - ulOffset + 1;

	return getSlice(ulOffset, ulCount, Result);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::getSlice(INDEX_TYPE ulOffset, INDEX_TYPE ulCount, DynamicBuffer<__DataType> &Result) const
{
	CHECK_BUFFER(m_pBuffer);

	Result.Release();

	if ( ulOffset + ulCount > getSize() ) {
		return false;
	}

	if ( ulCount == 0 ) {
		return true;
	}

	if ( !Result.setSize(ulCount) ) {
		return false;
	}

	memcpy(Result.getBuffer(), m_pBuffer + ulOffset, ulCount * BLOCKSIZE());

	return true;
}

/*------------------------------------------------------------------------------------------------*/
// Fill

template<class __DataType>
void DynamicBuffer<__DataType>::Fill(__DataType Value)
{
	Fill(Value, 0, getSize());
}

template<class __DataType>
void DynamicBuffer<__DataType>::Fill(__DataType Value, INDEX_TYPE ulOffset)
{
	Fill(Value, ulOffset, getSize() - ulOffset);
}

template<class __DataType>
void DynamicBuffer<__DataType>::Fill(__DataType Value, INDEX_TYPE ulOffset, INDEX_TYPE ulCount)
{
	if ( ulOffset + ulCount > getSize() ) {
		throw BUFFERERROR_INVALID_INDEX;
	}

	for ( INDEX_TYPE m = 0; m < ulCount / 2; m++ ) {
		setAt(ulOffset + m, Value);
		setAt(ulOffset + ulCount - 1 - m, Value);
	}

	if ( ulCount % 2 != 0 ) { // not even
		setAt(ulOffset + ulCount / 2, Value);
	}
}




///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Operator overloading
//
///////////////////////////////////////////////////////////////////////////////////////////////////



template<class __DataType>
DynamicBuffer<__DataType> & DynamicBuffer<__DataType>::operator+=(const DynamicBuffer<__DataType> &db)
{
	Append(db);
	return *this;
}

template<class __DataType>
DynamicBuffer<__DataType> & DynamicBuffer<__DataType>::operator=(const DynamicBuffer<__DataType> &db)
{
	if ( &db == this ) {
		return *this;
	}

	if ( db.getSize() != getSize() ) {

		if ( db.getSize() == 0 ) {
			Release();
		}
		else if ( !setSize(db.getSize()) ) {
			throw BUFFERERROR_OUT_OF_MEMORY;
		}
	}

	if ( getSize() > 0 ) {
		Copy(0, db.m_pBuffer, getSize());
	}

	return *this;
}

template<class __DataType>
bool DynamicBuffer<__DataType>::operator==(const DynamicBuffer<__DataType> &db)
{
	if ( &db == this ) {
		return true;
	}

	if ( !m_pBuffer || !db.m_pBuffer || getSize() != db.getSize() ) {
		return false;
	}

	return (memcmp(m_pBuffer, db.m_pBuffer, getSize()) == 0);
}

template<class __DataType>
bool DynamicBuffer<__DataType>::operator!=(const DynamicBuffer<__DataType> &db)
{
	return !(*this == db);
}


///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Data allocation and release
//
///////////////////////////////////////////////////////////////////////////////////////////////////



// Allocates memory. Releases already allocated memory, if any.
// Size is in block size
template<class __DataType>
bool DynamicBuffer<__DataType>::Allocate(INDEX_TYPE ulSize)
{
	Release();

	if ( ulSize > 0 ) {

		// Allocate moemory
		m_pBuffer = (LPDATA)calloc(ulSize, BLOCKSIZE());
		if ( !m_pBuffer ) {
			return false;
		}

		m_Size = ulSize;
	}

	return true;
}

template<class __DataType>
void DynamicBuffer<__DataType>::Release()
{
	if ( m_pBuffer ) {
		free((void *)m_pBuffer);
		m_pBuffer = NULL;
		m_Size = 0;
	}
}


} // namespace

#endif // ___DYNAMIC_BUFFER__H__D31DFCFB_5BCE_4556_A1B8_FE2D98523A1F__DEFINED__

Esimerkki:

DynamicBuffer<long> buff(80); // varaa dynaamista muistia 80 long tyyppiselle luvulle

buff.setAt(15) = 27;

long luku16 = buff[15];

DynamicBuffer<long buff2;

//Haetaan 10 lukua luvusta 16 alkaen.
buff.getSlice(15, 10, buff2);

//jne...aika suoraviivaista...

Kommentit

BlueByte [03.12.2004 23:30:19]

#

Okein nätti.

kala [08.12.2004 00:43:02]

#

"__":n sisältävät symbolit on määrittelyn mukaan varattu kääntäjän/ympäristön määrittelyihin.

ZcMander [13.12.2004 16:36:30]

#

Eikö auto_prt riitä?

Ceez [25.05.2005 11:15:06]

#

Se on auto_ptr. *nus nus* :)

Meitsi [13.09.2005 18:25:03]

#

Tosta taitaa puuttua BUFFERERROR_INVALID_INDEX-ilmoitus, mutta sen nyt laittaa sinne nopeasti.

Saakos tuota muuten suoraan käyttää omissa ohjelmissa jos laittaa sinun nimesi tekijäluetteloon?

EDIT:
Ja onko tuossa esimerkissä ihan kaikki funktiot?

Tupla EDIT: Eikun joo, nuo onkin aika suoraviivaiset niinkuin sanoit :)

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta