Click here to Skip to main content
15,886,563 members
Articles / Mobile Apps

High Speed Graphics Library for WinCE

Rate me:
Please Sign up or sign in to vote.
4.81/5 (15 votes)
24 Jun 2002CPOL2 min read 265.6K   2.3K   91  
CEDraw is a high speed graphics library for WinCE.
/* ---------------------------------------------------------------------------------------------------
 *  
 *                          Windows CE Graphics Libary v1.00.0000
 *  
 *        
 *    Written by James.
 *    Bug report : jiet@msn.com
 *                                                             Copyright 2001
 */
// File : CEGDIObject.CPP
// Graphics Object define.
//-----------------------------------------------------------------------------------------------------
//										Update Information.
//-----------------------------------------------------------------------------------------------------

/*
 * Created by James D. 2001.
 * Date: 01/11/2001
 */
#include "StdAfx.h"
#include "CEGDIObject.h"
#include "CEDraw.h"

/*----------------------------------- Pen Class ------------------------------- */

//
// Function : CCEPen()
// Purpose  : Construction function.
//
CCEPen::CCEPen( UINT nPenStyle, UINT nWidth, unsigned short Color )
{
	m_dwObjectType = CEG_GDIOBJECT_TYPE_PEN;
	m_nPenStyle    = nPenStyle;
	m_nWidth       = nWidth;
	m_Color        = Color;
}

CCEPen::CCEPen()
{
	m_nPenStyle = 0;
	m_nWidth    = 1;
	m_Color     = 0;
	m_dwObjectType = CEG_GDIOBJECT_TYPE_PEN;
}

//
// Function : ~CCEPen()
// Purpose  : Destruction function.
//
CCEPen::~CCEPen()
{

}

//
// Function : CreatePen()
// Purpose  : Create the pen and set items' style
//
BOOL CCEPen::CreatePen( UINT nPenStyle, UINT nWidth, unsigned short Color )
{
	m_nPenStyle = nPenStyle;
	m_nWidth    = nWidth;
	m_Color     = Color;
	return TRUE;
}

/*----------------------------------- Brush Class ------------------------------- */

//
// Function : CCEBrush()
// Purpose  : Construction function.
//
CCEBrush::CCEBrush( UINT nBrushStyle, unsigned short Color, long lbHatch )
{
	m_dwObjectType = CEG_GDIOBJECT_TYPE_BRUSH;
	m_nBrushStyle  = nBrushStyle;
	m_lbHatch      = lbHatch;
	m_Color        = Color;
            
}
CCEBrush::CCEBrush()
{
	m_nBrushStyle  = 0;
	m_lbHatch      = 0;
	m_Color        = 0;
	m_dwObjectType = CEG_GDIOBJECT_TYPE_BRUSH;
}

//
// Function : ~CCEPen()
// Purpose  : Destruction function.
//
CCEBrush::~CCEBrush()
{

}

//
// Function : CreatePen()
// Purpose  : Create the pen and set items' style
//
BOOL CCEBrush::CreateBrush( UINT nBrushStyle, unsigned short Color, long lbHatch )
{
	m_dwObjectType = CEG_GDIOBJECT_TYPE_BRUSH;
	m_nBrushStyle  = nBrushStyle;
	m_lbHatch      = lbHatch;
	m_Color        = Color;
	return TRUE;
}

/*----------------------------------- Font Class ------------------------------- */


//
// Function : CCEFont()
// Purpose  : Construction function.
//
CCEFont::CCEFont()
{
	m_hFont = NULL;
}

//
// Function : ~CCEFont()
// Purpose  : Destruction function.
//
CCEFont::~CCEFont()
{
	if( NULL != m_hFont )
	{
		::DeleteObject( m_hFont );
	}
}


//
// Function : CreateFont()
// Purpose  : Create the Font
//
BOOL CCEFont::CreateFont( 	int nHeight,
							int nWidth,
							int nEscapement,
							int nOrientation,
							int nWeight,
							BYTE bItalic,
							BYTE bUnderline,
							BYTE cStrikeOut,
							BYTE nCharSet,
							BYTE nOutPrecision,
							BYTE nClipPrecision,
							BYTE nQuality, 
							BYTE nPitchAndFamily,
							LPCTSTR lpszFacename )
{
	LOGFONT logFont;
	logFont.lfHeight		 = nHeight;
	logFont.lfWidth			 = nWidth;
	logFont.lfEscapement     = nEscapement;
	logFont.lfOrientation    = nOrientation;
	logFont.lfWeight         = nWeight;
	logFont.lfItalic         = bItalic;
	logFont.lfUnderline      = bUnderline;
	logFont.lfStrikeOut      = cStrikeOut;
	logFont.lfCharSet        = nCharSet;
	logFont.lfOutPrecision   = nOutPrecision;
	logFont.lfClipPrecision  = nClipPrecision;
	logFont.lfQuality        = nQuality;
	logFont.lfPitchAndFamily = nPitchAndFamily;
	memcpy( logFont.lfFaceName, lpszFacename, LF_FACESIZE );

	m_hFont = ::CreateFontIndirect( &logFont );
	if ( NULL == m_hFont ) return FALSE;

	return TRUE;
}

BOOL CCEFont::CreateFontIndirect( const LOGFONT *lpFont )
{
	m_hFont = ::CreateFontIndirect( lpFont );
	if ( NULL == m_hFont ) return FALSE;

	return TRUE;
}

/*----------------------------------- Bitmap Class ------------------------------- */


//
// Function : CCEBitmap()
// Purpose  : Construction function.
//
CCEBitmap::CCEBitmap()
{
	m_pBitmapBuffer = NULL;
	m_nType         = CEB_TYPE_BITMAP;
}

//
// Function : ~CCEFont()
// Purpose  : Destruction function.
//
CCEBitmap::~CCEBitmap()
{
	if( NULL != m_pBitmapBuffer )
	{
		free( m_pBitmapBuffer );
	}
}

//
// Function : LoadBitmap()
// Purpose  : Load the bitmap and translate to the GAPI recoginzlia memory buffer...
//
BOOL CCEBitmap::LoadBitmap( CCEDraw* pCEDraw, LPCTSTR lpszBitmapName )
{
	HBITMAP hBitmap;
	BITMAP  Bitmap;

	// Free the buffer, if it exist...
	if( NULL != m_pBitmapBuffer ) 
	{
		free( m_pBitmapBuffer );
		m_pBitmapBuffer = NULL;
	}

	// If the lpszBitmapName == NULL, that means we create a screen bitmap...
	if( NULL == lpszBitmapName )
	{
		m_sizeBitmap.cx = pCEDraw->GetDisplayProperties().cxWidth;
		m_sizeBitmap.cy = pCEDraw->GetDisplayProperties().cyHeight;
		m_pBitmapBuffer = (unsigned char*)malloc( m_sizeBitmap.cx * m_sizeBitmap.cy * pCEDraw->GetDisplayProperties().cBPP / 8 );
		memcpy( m_pBitmapBuffer, pCEDraw->GetBuffer(), m_sizeBitmap.cx * m_sizeBitmap.cy * pCEDraw->GetDisplayProperties().cBPP / 8 );
		m_nType = CEB_TYPE_SCREENBUFFER;
		return TRUE;
	}

	// First to load the bitmap from file...
	hBitmap = (HBITMAP) LoadFile( lpszBitmapName );
	//hBitmap =(HBITMAP)::LoadBitmap( GetModuleHandle( NULL ), MAKEINTRESOURCE(IDB_BITMAP1));
	if( hBitmap == NULL ) 
	{
		// Failed to load the bitmap...
		return FALSE;
	}
	GetObject( hBitmap, sizeof(BITMAP), &Bitmap );
	
	// Creating new bitmap and receive pointer to it's bits.
	HBITMAP hTargetBitmap;
	unsigned char *pBuffer;

	// Initilize DIBINFO structure
	BITMAPINFO  dibInfo;
	dibInfo.bmiHeader.biBitCount = 24;
	dibInfo.bmiHeader.biClrImportant = 0;
	dibInfo.bmiHeader.biClrUsed = 0;
	dibInfo.bmiHeader.biCompression = 0;
	dibInfo.bmiHeader.biHeight = Bitmap.bmHeight;
	dibInfo.bmiHeader.biPlanes = 1;
	dibInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
	dibInfo.bmiHeader.biSizeImage = Bitmap.bmWidth*Bitmap.bmHeight*3;
	dibInfo.bmiHeader.biWidth = Bitmap.bmWidth;
	dibInfo.bmiHeader.biXPelsPerMeter = 3780;
	dibInfo.bmiHeader.biYPelsPerMeter = 3780;
	dibInfo.bmiColors[0].rgbBlue = 0;
	dibInfo.bmiColors[0].rgbGreen = 0;
	dibInfo.bmiColors[0].rgbRed = 0;
	dibInfo.bmiColors[0].rgbReserved = 0;

	// Create bitmap and receive pointer to points into pBuffer
	HDC hDC = ::GetDC(NULL);
	ASSERT(hDC);
	hTargetBitmap = CreateDIBSection( hDC,
									  (const BITMAPINFO*)&dibInfo,
									  DIB_RGB_COLORS,
									  (void**)&pBuffer,
									  NULL,
									  0
									 );

	::ReleaseDC(NULL, hDC);

	// Copy source bitmap into the target bitmap.

	// Create 2 device contexts 
	HDC memDc;
	if ( !( memDc = CreateCompatibleDC(NULL) ) ) 
	{
		DeleteObject( hBitmap );
		DeleteObject( hTargetBitmap );
		return FALSE;
	}

	HDC targetDc;
	if ( !( targetDc = CreateCompatibleDC(NULL) ) ) 
	{
		DeleteDC( memDc );
		DeleteObject( hBitmap );
		DeleteObject( hTargetBitmap );
		return FALSE;
	}

	// Select source bitmap into one DC, target into another
	HBITMAP hOldBitmap1 = (HBITMAP)::SelectObject( memDc, hBitmap);
	HBITMAP hOldBitmap2 = (HBITMAP)::SelectObject( targetDc, hTargetBitmap );

	// Copy source bitmap into the target one
	::BitBlt( targetDc, 0, 0, Bitmap.bmWidth, Bitmap.bmHeight, memDc, 0, 0, SRCCOPY );

	// Create my own bitmap buffer use the GAPI recognized style...
	// Free the memory if it exist...

	int nX, nY;
	unsigned short nColor;
	int nBitmapYPitch = ((Bitmap.bmWidth*3)+3) & 0xfffc;

	BOOL   bReturn = TRUE;
	if( NULL != m_pBitmapBuffer ) 
	{
		free( m_pBitmapBuffer );
		m_pBitmapBuffer = NULL;
	}

	long cByte = pCEDraw->GetDisplayProperties().cBPP / 8;
	m_sizeBitmap.cx = Bitmap.bmWidth;
	m_sizeBitmap.cy = Bitmap.bmHeight;
	m_pBitmapBuffer = (unsigned char*)malloc( Bitmap.bmWidth * Bitmap.bmHeight * cByte );
	if( !m_pBitmapBuffer ) 
	{
		bReturn = FALSE;
		goto Exit;
	}

    /* Warning : Does not support 8 byte color model in here */
	for( nY = 0; nY < Bitmap.bmHeight ; nY ++ )
	{
		for( nX = 0; nX < Bitmap.bmWidth ; nX ++ )
		{

			nColor = pCEDraw->GetBitmapPointColor( ( pBuffer +  ( nX ) * 3 +
											               ( Bitmap.bmHeight - nY - 1 ) * 
											                 nBitmapYPitch ) );
			if( cByte == 2 )
				*(unsigned short*)( m_pBitmapBuffer + ( nX + nY * Bitmap.bmWidth ) * cByte ) = nColor;
			else *( m_pBitmapBuffer + ( nX + nY * Bitmap.bmWidth ) * cByte ) = (BYTE)nColor;
				
		}
	}

	// Restore device contexts
Exit:
	::SelectObject( memDc, hOldBitmap1 );
	::SelectObject( targetDc, hOldBitmap2 );
	DeleteDC( memDc );
	DeleteDC( targetDc );
	DeleteObject( hBitmap );
	DeleteObject( hTargetBitmap );
	return bReturn;
}

//
// Function : BitBlt()
// Transfers pixels from a specified source rectangle to a specified destination rectangle
//
BOOL CCEBitmap::BitBlt( CCEDraw* pCEDraw, int nXDest, int nYDest, int nWidth, int nHeight, int nXSrc, int nYSrc, DWORD dwRop, float fAlpha )
{
	if( NULL == pCEDraw || NULL == m_pBitmapBuffer ) return FALSE;
	unsigned char* pBuffer = pCEDraw->GetBuffer();
	unsigned char* pBitmap = m_pBitmapBuffer;
	long cbxPitch = pCEDraw->m_cbxPitch;
	long cbyPitch = pCEDraw->m_cbyPitch;
	long cbxBmpPitch = pCEDraw->GetDisplayProperties().cBPP / 8;
	long cbyBmpPitch = cbxBmpPitch * m_sizeBitmap.cx;
	long cBPP = pCEDraw->GetDisplayProperties().cBPP;
	
	// The buffer type is screenbuffer, so...
	if( m_nType == CEB_TYPE_SCREENBUFFER  )
	{
		//cbxBmpPitch = cbxPitch;
		//cbyBmpPitch = cbyPitch;
		if( dwRop == SRCCOPY )
			memcpy( pCEDraw->GetBuffer(), m_pBitmapBuffer, m_sizeBitmap.cx * m_sizeBitmap.cy * pCEDraw->GetDisplayProperties().cBPP / 8 );
		else return FALSE;
	}

	nWidth == 0 ? nWidth = m_sizeBitmap.cx : nWidth;
	nHeight == 0 ? nHeight = m_sizeBitmap.cy : nHeight;

    if( ( nXSrc + nWidth ) > m_sizeBitmap.cx ) nWidth = m_sizeBitmap.cx - nXSrc;
	if( ( nYSrc + nHeight ) > m_sizeBitmap.cy ) nHeight = m_sizeBitmap.cy - nYSrc;
	
	if( ( nXDest + nWidth ) > (LONG)pCEDraw->GetDisplayProperties().cxWidth )
		nWidth = pCEDraw->GetDisplayProperties().cxWidth - nXDest;
	if( ( nYDest + nHeight ) > (LONG)pCEDraw->GetDisplayProperties().cyHeight )
		nHeight = pCEDraw->GetDisplayProperties().cyHeight - nYDest;

	if( pCEDraw->IsPointOutside( nXDest, nYDest ) && 
		pCEDraw->IsPointOutside( nXDest + nWidth, nYDest + nHeight ) &&
		pCEDraw->IsPointOutside( nXDest + nWidth, nYDest ) &&
		pCEDraw->IsPointOutside( nXDest, nYDest + nHeight ) ) return TRUE; // Not need to draw the bitmap;

	// Prepare the buffer pointer...
	pBuffer += nXDest * cbxPitch + nYDest * cbyPitch;
	pBitmap += nXSrc * cbxBmpPitch + nYSrc * cbyBmpPitch;

	// Copy the buffer data ...
	
	unsigned char* pHeadBuffer = pBuffer;
	unsigned char* pHeadBmpBuffer = pBitmap;
	unsigned short ColorR, ColorG, ColorB, Color;
	if( dwRop == SRCCOPY )
	{
		for( int y = 0; y < nHeight; y ++ ) 
		{
			pBuffer = pHeadBuffer;
			pBitmap = pHeadBmpBuffer;
			for( int x = 0; x < nWidth; x ++ ) 
			{
				
				if( cBPP == 8 ) 
				{
					*pBuffer = *pBitmap;
				}
				else 
				{
					*(unsigned short*)pBuffer = *(unsigned short*)pBitmap;
				}
				pBuffer += cbxPitch;
				pBitmap += cbxBmpPitch;
			}
			pHeadBmpBuffer += cbyBmpPitch;
			pHeadBuffer += cbyPitch;
		}
	}
	else if( dwRop == SRCCOLORKEY )
	{	
		// Get the up left color as the colorkey
		unsigned short ColorKey;
		if( cBPP == 8 ) ColorKey = *pHeadBmpBuffer;
		else ColorKey = *(unsigned short*)pHeadBmpBuffer;

		for( int y = 0; y < nHeight; y ++ ) 
		{
			pBuffer = pHeadBuffer;
			pBitmap = pHeadBmpBuffer;
			for( int x = 0; x < nWidth; x ++ ) 
			{
				
				if( cBPP == 8 ) 
				{
					if( *pBitmap != ColorKey ) *pBuffer = *pBitmap;
				}
				else 
				{
					if( *(unsigned short*)pBitmap != ColorKey )
						*(unsigned short*)pBuffer = *(unsigned short*)pBitmap;
				}
				pBuffer += cbxPitch;
				pBitmap += cbxBmpPitch;
			}
			pHeadBmpBuffer += cbyBmpPitch;
			pHeadBuffer += cbyPitch;
		}
	}
	else if ( dwRop == SRCALPHA && fAlpha != 0.5f )
	{
		for( int y = 0; y < nHeight; y ++ ) 
		{
			pBuffer = pHeadBuffer;
			pBitmap = pHeadBmpBuffer;
			for( int x = 0; x < nWidth; x ++ ) 
			{
				
				if( cBPP == 8 ) 
				{
					*pBuffer = *pBitmap;
				}
				else 
				{
					if( pCEDraw->GetDisplayProperties().ffFormat & kfDirect565 )
					{
						/* n% ALPHA */
						Color = *(unsigned short*)pBuffer;
						ColorR = (unsigned short)((Color >> 11) & 0x1f) * ( 1 - fAlpha );
						ColorG = (unsigned short)((Color >> 5) & 0x3f) * ( 1 - fAlpha );
						ColorB = (unsigned short)(Color & 0x1f) * ( 1 - fAlpha );
						Color = *(unsigned short*)pBitmap;
						ColorR = (unsigned short)(((Color >> 11)&0x1f) * fAlpha + ColorR);
						ColorG = (unsigned short)(((Color >> 5)&0x3f) * fAlpha + ColorG);
						ColorB = (unsigned short)((Color & 0x1f)* fAlpha + ColorB);
						Color = (unsigned short)( ( ColorR & 0xff ) << 11 | 
										  ( ColorG & 0xff ) << 5 | 
										  ( ColorB & 0xff ) );
						*(unsigned short*)pBuffer = Color;
					}
					else if( pCEDraw->GetDisplayProperties().ffFormat & kfDirect555 )
					{
							/* n% ALPHA */
						Color = *(unsigned short*)pBuffer;
						ColorR = (unsigned short)((Color >> 10) & 0x1f) * ( 1 - fAlpha );
						ColorG = (unsigned short)((Color >> 5) & 0x1f) * ( 1 - fAlpha );
						ColorB = (unsigned short)(Color & 0x1f) * ( 1 - fAlpha );
						Color = *(unsigned short*)pBitmap;
						ColorR = (unsigned short)(((Color >> 10)&0x1f) * fAlpha + ColorR);
						ColorG = (unsigned short)(((Color >> 5)&0x1f) * fAlpha + ColorG);
						ColorB = (unsigned short)((Color & 0x1f)* fAlpha + ColorB);
						Color = (unsigned short)( ( ColorR & 0xff ) << 10 | 
										  ( ColorG & 0xff ) << 5 | 
										  ( ColorB & 0xff ) );
						*(unsigned short*)pBuffer = Color;
					
					}
					else *(unsigned short*)pBuffer = Color;
				}
				pBuffer += cbxPitch;
				pBitmap += cbxBmpPitch;
			}
			pHeadBmpBuffer += cbyBmpPitch;
			pHeadBuffer += cbyPitch;
		}
	}
	else if ( dwRop == SRCALPHA )
	{
		for( int y = 0; y < nHeight; y ++ ) 
		{
			pBuffer = pHeadBuffer;
			pBitmap = pHeadBmpBuffer;
			for( int x = 0; x < nWidth; x ++ ) 
			{
				
				if( cBPP == 8 ) 
				{
					*pBuffer = *pBitmap;
				}
				else 
				{
					if( pCEDraw->GetDisplayProperties().ffFormat & kfDirect565 )
					{
						Color = *(unsigned short*)pBuffer;
						ColorR = ((Color >> 11)&0x1f)>>1;
						ColorG = ((Color >> 5)&0x3f)>>1;
						ColorB = (Color & 0x1f)>>1;
						Color = *(unsigned short*)pBitmap;
						ColorR = ((Color >> 11)&0x1f)>>1 + ColorR;
						ColorG = ((Color >> 5)&0x3f)>>1 + ColorG;
						ColorB = ((Color & 0x1f))>>1 + ColorB;
						Color = (unsigned short)( ( ColorR & 0xff ) << 11 | 
										  ( ColorG & 0xff ) << 5 | 
										  ( ColorB & 0xff ) );
						*(unsigned short*)pBuffer = Color;
					}
					else *(unsigned short*)pBuffer = Color;
				}
				pBuffer += cbxPitch;
				pBitmap += cbxBmpPitch;
			}
			pHeadBmpBuffer += cbyBmpPitch;
			pHeadBuffer += cbyPitch;
		}
	}

	return TRUE;
}

//
// Function : LoadFile()
// Use the IMGDECMP.DLL to load the image data file...
//
HBITMAP CCEBitmap::LoadFile( LPCTSTR lpszBitmapName )
{
	HBITMAP hBitmap = 0;

	HDC hdc = CreateCompatibleDC(NULL);

	HRESULT hr;
	BYTE    szBuffer[1024] = {0};
	HANDLE hFile = INVALID_HANDLE_VALUE;

	DecompressImageInfo	dii;

	hFile = CreateFile(lpszBitmapName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
	if (hFile == INVALID_HANDLE_VALUE)
	{
		DeleteDC( hdc );
		return FALSE;
	}

	// Fill in the 'DecompressImageInfo' structure
	dii.dwSize = sizeof( DecompressImageInfo );		// Size of this structure
	dii.pbBuffer = szBuffer;						// Pointer to the buffer to use for data
	dii.dwBufferMax = 1024;							// Size of the buffer
	dii.dwBufferCurrent = 0;						// The amount of data which is current in the buffer
	dii.phBM = &hBitmap;							// Pointer to the bitmap returned (can be NULL)
	dii.ppImageRender = NULL;						// Pointer to an IImageRender object (can be NULL)
	dii.iBitDepth = GetDeviceCaps(hdc,BITSPIXEL);	// Bit depth of the output image
	dii.lParam = ( LPARAM ) hFile;					// User parameter for callback functions
	dii.hdc = hdc;							    	// HDC to use for retrieving palettes
	dii.iScale = 100;   							// Scale factor (1 - 100)
	dii.iMaxWidth = 10000;		        			// Maximum width of the output image
	dii.iMaxHeight = 10000;     					// Maxumum height of the output image
	dii.pfnGetData = GetImageData;					// Callback function to get image data
	dii.pfnImageProgress = ImageProgress;			// Callback function to notify caller of progress decoding the image
	dii.crTransparentOverride = ( UINT ) -1;		// If this color is not (UINT)-1, it will override the
													// transparent color in the image with this color. (GIF ONLY)

	// Process and decompress the image data
	typedef HRESULT (CALLBACK* ULPRET)( DecompressImageInfo*	pParams );

	HINSTANCE       hLib;
    ULPRET          lpfnDLLProc;

    hLib = LoadLibrary ( L"Imgdecmp.dll" );
    if ( hLib )
    {
       lpfnDLLProc = (ULPRET) GetProcAddress ( hLib, L"DecompressImageIndirect" );
       hr = (*lpfnDLLProc) ( &dii );
       FreeLibrary ( hLib );
    }

	// Clean up 
	DeleteDC( hdc );
	CloseHandle( hFile );
	return hBitmap;
}

//
// Function : GetImageData()
// Callback function to load image data...
//
DWORD CALLBACK CCEBitmap::GetImageData(LPSTR szBuffer, DWORD dwBufferMax, LPARAM lParam )
{
	DWORD dwNumberOfBytesRead;

	if ( (HANDLE)lParam == INVALID_HANDLE_VALUE )
		return 0;

	ReadFile( (HANDLE)lParam, szBuffer, dwBufferMax, &dwNumberOfBytesRead, NULL );

	// Return number of bytes read
	return dwNumberOfBytesRead;
}

void CALLBACK CCEBitmap::ImageProgress(IImageRender *pRender, BOOL bComplete, LPARAM lParam )
{
	if( bComplete )
	{
		// (Optional) add code here for completion processing
	}
}


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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
China China
8 Years Development experience from MS-DOS v3.0 to WindowsXP in C/C++. Profession in Visual C++, Native Win32 and MFC programming, Embedded development.Computer Graphics(Computer Games),Network Program.

Living in China now.

Comments and Discussions