Thread: Another weird error

  1. #1
    Master of Puppets rwmarsh's Avatar
    Join Date
    Feb 2006
    Location
    Texas
    Posts
    96

    Another weird error

    OK, Here we go again... I am now learning how to load and display bitmaps (DirectX). The code I have runs fine. The problem I am having is when I hit the ESC key to exit. The program shuts down just fine. I don't get the error message until after I get back to my desktop. Windows says that there is a problem and the program must be shut down (which it already is?!?) I assume it is either a memory allocation problem or something with one of the pointers, but I have not been able to find it.
    Code:
    #define WIN32_LEAN_AND_MEAN 
    
    #define INITGUID // make sure directX guids are included
    
    #include <windows.h>
    #include <windowsx.h> 
    #include <cstdio>
    #include <ddraw.h>
    
    #define WINDOW_CLASS_NAME "WINCLASS1"
    
    // default screen size
    #define SCREEN_WIDTH    640    // size of screen
    #define SCREEN_HEIGHT   480
    #define SCREEN_BPP      8      // bits per pixel
    #define MAX_COLORS      256    // maximum colors
    #define BITMAP_ID       0x4D42 // universal id for a bitmap
    
    // container structure for bitmaps .BMP file
    typedef struct BITMAP_FILE_TAG {
            BITMAPFILEHEADER bitmapfileheader;  // this contains the bitmapfile header
            BITMAPINFOHEADER bitmapinfoheader;  // this is all the info including the palette
            PALETTEENTRY     palette[256];      // we will store the palette here
            UCHAR            *buffer;           // this is a pointer to the data
    } BITMAPFILE, *PTR_BITMAPFILE;
    
    // MACROS //////////////////////////////////////////////////////////////////////
    
    #define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
    #define KEYUP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
    
    // initializes a direct draw struct
    #define DDRAW_INIT_STRUCT(ddstruct) { memset(&ddstruct,0,sizeof(ddstruct)); ddstruct.dwSize=sizeof(ddstruct); }
    
    // GLOBALS /////////////////////////////////////////////////////////////////////
    
    HWND      main_window_handle = NULL; // globally track main window
    HINSTANCE hinstance_app      = NULL; // globally track hinstance
    int       window_closed      = 0;
    
    // directdraw stuff
    LPDIRECTDRAW7         lpDD               = NULL; // dd object
    LPDIRECTDRAWSURFACE7  lpDDPrimarySurface = NULL; // dd primary surface
    LPDIRECTDRAWSURFACE7  lpDDBackSurface    = NULL; // dd back surface
    LPDIRECTDRAWPALETTE   lpDDPalette        = NULL; // a pointer to the created dd palette
    LPDIRECTDRAWCLIPPER   lpDDClipper        = NULL; // dd clipper
    PALETTEENTRY          palette[256];              // color palette
    PALETTEENTRY          save_palette[256];         // used to save palettes
    DDSURFACEDESC2        DDSurfaceDesc;             // a direct draw surface description struct
    DDBLTFX               DDbltfx;                   // used to fill
    DDSCAPS2              DDSurfaceCaps;             // a direct draw surface capabilities struct
    HRESULT               DDResultVal;               // result back from dd calls
    DWORD                 start_clock_count = 0;     // used for timing
    BITMAPFILE            bitmap;                    // holds the bitmap
     
    // FUNCTION PROTOTYPES /////////////////////////////////////////////////////////
    
    LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
    int Game_Init(void *parms = NULL, int num_parms = 0);
    int Game_Main(void *parms = NULL, int num_parms = 0);
    int Game_Shutdown(void *parms = NULL, int num_parms = 0);
    int Flip_Bitmap(UCHAR*, int, int);
    int Load_Bitmap_File(PTR_BITMAPFILE, char*); //
    int Unload_Bitmap_File(PTR_BITMAPFILE); //
    int DDraw_Fill_Surface(LPDIRECTDRAWSURFACE7,int); //
    
    // FUNCTION DEFINITIONS ////////////////////////////////////////////////////////
    
    ////////////////////////////////////////////////////////////////////////////////
    // WINMAIN
    int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance,
    					LPSTR lpcmdline, int ncmdshow) {
        WNDCLASSEX winclass; // this will hold the class we create
        HWND       hwnd;     // generic window handle
        MSG        msg;      // generic message
        HDC        hdc;      // graphics device context
    
        // first fill in the window class stucture
        winclass.cbSize         = sizeof(WNDCLASSEX);
        winclass.style	    = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
        winclass.lpfnWndProc    = WindowProc;
        winclass.cbClsExtra	    = 0;
        winclass.cbWndExtra	    = 0;
        winclass.hInstance      = hinstance;
        winclass.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
        winclass.hCursor        = LoadCursor(NULL, IDC_ARROW); 
        winclass.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
        winclass.lpszMenuName   = NULL;
        winclass.lpszClassName  = WINDOW_CLASS_NAME;
        winclass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);
    
        // save hinstance in global
        hinstance_app = hinstance;
    
        // register the window class
        if (!RegisterClassEx(&winclass))
    	    return 0;
    
        // create the window
        if (!(hwnd = CreateWindowEx(0,                     // extended style
                                    WINDOW_CLASS_NAME,     // class
                                    "DirectDraw Demo",     // title
                                    WS_POPUP,
                                    0,0,                   // initial x,y
                                    SCREEN_WIDTH, SCREEN_HEIGHT, // initial width, height
                                    NULL,                  // handle to parent 
                                    NULL,                  // handle to menu
                                    hinstance,             // instance of this application
                                    NULL) ) )              // extra creation parms
            return 0;
    
        // save main window handle
        main_window_handle = hwnd;
    
        // initialize game here
        Game_Init();
    
        // enter main event loop
        while(TRUE) {
            // test if there is a message in queue, if so get it
    	    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { 
        	   // test if this is a quit
               if (msg.message == WM_QUIT)
                   break;
    	
    	       // translate any accelerator keys
    	       TranslateMessage(&msg);
    
        	   // send the message to the window proc
    	       DispatchMessage(&msg);
            } // end if
        
            // main game processing goes here
            Game_Main();
       	} // end while
    
        // closedown game here
        Game_Shutdown();
    
        // return to Windows like this
        return(msg.wParam);
    } // end WinMain
    
    ////////////////////////////////////////////////////////////////////////////////
    // this is the main message handler of the system
    LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
        PAINTSTRUCT      ps;                         // used in WM_PAINT
        HDC              hdc;                        // handle to a device context
        char buffer[80];                             // used to print strings
    
        // what is the message 
        switch(msg) {	
    	    case WM_CREATE: {
    		    // do initialization stuff here
            
                // return success
    		    return 0;
    		} break;
       
        	case WM_PAINT: {
        		// simply validate the window 
       	        hdc = BeginPaint(hwnd,&ps);	 
            
                // end painting
                EndPaint(hwnd,&ps);
    
                // return success
    		    return 0;
       		} break;
    
        	case WM_DESTROY: {
        		// kill the application, this sends a WM_QUIT message 
    	    	PostQuitMessage(0);
    
                // return success
        		return 0;
    		} break;
    
    	    default: break;
    
        } // end switch
    
        // process any messages that we didn't take care of 
        return (DefWindowProc(hwnd, msg, wparam, lparam));
    } // end WinProc
    
    ////////////////////////////////////////////////////////////////////////////////
    // this function opens a bitmap file and loads the data into a bitmap
    int Load_Bitmap_File(PTR_BITMAPFILE bitmap, char *filename) {
        int      FileHandle,          // the file handle, duh...
                 index;               // looping index
        UCHAR    *pTempBuffer = NULL; // used to convert 24 bit images to 16 bit
        OFSTRUCT FileData;            // the file data information
        
        // open the file if it exists
        if ( (FileHandle = OpenFile(filename, &FileData, OF_READ) ) == -1)
            return 0;
        
        // now load the bitmap file header
        _lread(FileHandle, &bitmap->bitmapfileheader, sizeof(BITMAPFILEHEADER) );
        
        // test if this is a bitmap file
        if (bitmap->bitmapfileheader.bfType != BITMAP_ID) {
            // close the file
            _lclose(FileHandle);
            
            // return error
            return 0;
        } // end if
        
        // now we know this is a bitmap, so read in all the sections
        
        // first the bitmap info header
        _lread(FileHandle, &bitmap->bitmapinfoheader, sizeof(BITMAPINFOHEADER) );
        
        // now load the color palette if there is one
        if (bitmap->bitmapinfoheader.biBitCount == 8) {
            _lread(FileHandle, &bitmap->palette, MAX_COLORS * sizeof(PALETTEENTRY) );
            
            // now set all the flags in the palette correctly and fix
            // the reversed BRG RGBQUAD data format
            for (int index = 0; index < MAX_COLORS; index++) {
                // reverse the red and green fields
                int temp_color                = bitmap->palette[index].peRed;
                bitmap->palette[index].peRed  = bitmap->palette[index].peBlue;
                bitmap->palette[index].peBlue = temp_color;
            
                // always set the flags word to this
                bitmap->palette[index].peFlags =PC_NOCOLLAPSE;
            } // end for index
        } // end if
        
        // finally, find the image data
        _llseek(FileHandle, -(int)(bitmap->bitmapinfoheader.biSizeImage), SEEK_END);
        
        // and read in the image
        if (bitmap->bitmapinfoheader.biBitCount == 8  ||
                bitmap->bitmapinfoheader.biBitCount == 16 ||
                bitmap->bitmapinfoheader.biBitCount == 24) {
            // delete the last image if there was one
            if (bitmap->buffer)
                free(bitmap->buffer);
            
            // allocate the memory for the image
            if (!(bitmap->buffer = (UCHAR*)malloc(bitmap->bitmapinfoheader.biSizeImage) ) ) {
                // close the file
                _lclose(FileHandle);
                
                // return error
                return 0;
            } // end if
            
            // now read it in
            _lread(FileHandle, bitmap->buffer, bitmap->bitmapinfoheader.biSizeImage);
        } else {
            // serious problem
            return 0;
        } // end if / else
        
        // close the file
        _lclose(FileHandle);
        
        // flip the bitmap
        Flip_Bitmap(bitmap->buffer,
                    bitmap->bitmapinfoheader.biWidth * (bitmap->bitmapinfoheader.biBitCount / 8),
                    bitmap->bitmapinfoheader.biHeight);
        
        // return success
        return 1;
    } // end Load_Bitmap_File
    
    ////////////////////////////////////////////////////////////////////////////////
    // this function releases all memory associated with the bitmap
    int Unload_Bitmap_File(PTR_BITMAPFILE bitmap) {
        if (bitmap->buffer) {
            // release the memory
            free(bitmap->buffer);
            
            // reset the pointer
            bitmap->buffer = NULL;
        } // end if
        
        // return success
        return 1;
    } // end Unload_Bitmap_File
    
    ////////////////////////////////////////////////////////////////////////////////
    // this function is used to flip bottom-up .BMP images
    int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height) {
        UCHAR *buffer; // used to perform the image processing
        int index;     // looping index
    
        // allocate the temporary buffer
        if (!(buffer = (UCHAR*)malloc(bytes_per_line * height) ) )
            return(0);
    
        // copy image to work area
        memcpy(buffer, image, bytes_per_line * height);
    
        // flip vertically
        for (index = 0; index < height; index++)
            memcpy(&image[( (height-1) - index) * bytes_per_line],
                   &buffer[index * bytes_per_line], bytes_per_line);
    
        // release the memory
        free(buffer);
    
        // return success
        return 1;
    } // end Flip_Bitmap
    
    ////////////////////////////////////////////////////////////////////////////////
    //
    int DDraw_Fill_Surface(LPDIRECTDRAWSURFACE7 lpDDSurface, int color) {
        DDBLTFX DDBltFx; // this contains the DDBLTFX structure
    
        // clear out the structure and set the size field 
        DDRAW_INIT_STRUCT(DDBltFx);
    
        // set the dwfillcolor field to the desired color
        DDBltFx.dwFillColor = color; 
    
        // ready to blt to surface
        lpDDSurface->Blt(NULL,                        // ptr to dest rectangle
                         NULL,                        // ptr to source surface, NA            
                         NULL,                        // ptr to source rectangle, NA
                         DDBLT_COLORFILL | DDBLT_WAIT,// fill and wait                   
                         &DDBltFx);                   // ptr to DDBLTFX structure
    
        // return success
        return 1;
    } // end DDraw_Fill_Surface
    
    ////////////////////////////////////////////////////////////////////////////////
    // this is called once after the initial window is created and
    // before the main event loop is entered, do all your initialization here
    int Game_Init(void *parms, int num_parms) {
        // create IDirectDraw interface 7.0 object and test for error
        if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpDD, IID_IDirectDraw7, NULL) ) ) {
            return 0;
        }
    
        // set cooperation to fullscreen
        if (FAILED(lpDD->SetCooperativeLevel(main_window_handle, DDSCL_FULLSCREEN | 
                                                                 DDSCL_ALLOWMODEX |
                                                                 DDSCL_EXCLUSIVE  |
                                                                 DDSCL_ALLOWREBOOT) ) ) {
            return 0;
        }
    
        // set display mode to 640x480x8
        if (FAILED(lpDD->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,
                                        0, 0) ) ) {
            return 0;
        }
    
        // clear ddsd and set size
        DDRAW_INIT_STRUCT(DDSurfaceDesc);
        
        // enable valid fields
        DDSurfaceDesc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    
        // set the backbuffer count field to 1, use 2 for triple buffering
        DDSurfaceDesc.dwBackBufferCount = 1;
    
        // request a complex, flippable
        DDSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
    
        // create the primary surface
        if (FAILED(lpDD->CreateSurface(&DDSurfaceDesc, &lpDDPrimarySurface, NULL) ) ) {
            return 0;
        }
    
        // now query for attached surface from the primary surface
    
        // this line is needed by the call
        DDSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    
        // get the attached back buffer surface
        if (FAILED(lpDDPrimarySurface->GetAttachedSurface(&DDSurfaceDesc.ddsCaps, &lpDDBackSurface) ) ) {
            return 0;
        }
    
        // build up the palette data array
        for (int color = 1; color < 255; color++) {
            // fill with random RGB values
            palette[color].peRed   = rand()%256;
            palette[color].peGreen = rand()%256;
            palette[color].peBlue  = rand()%256;
    
            // set flags field to PC_NOCOLLAPSE
            palette[color].peFlags = PC_NOCOLLAPSE;
        } // end for color
    
        // now fill in entry 0 and 255 with black and white
        palette[0].peRed     = 0;
        palette[0].peGreen   = 0;
        palette[0].peBlue    = 0;
        palette[0].peFlags   = PC_NOCOLLAPSE;
    
        palette[255].peRed   = 255;
        palette[255].peGreen = 255;
        palette[255].peBlue  = 255;
        palette[255].peFlags = PC_NOCOLLAPSE;
    
        // create the palette object
        if (FAILED(lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256 | DDPCAPS_INITIALIZE, 
                                       palette,&lpDDPalette, NULL) ) ) {
            return 0;
        }
    
        // finally attach the palette to the primary surface
        if (FAILED(lpDDPrimarySurface->SetPalette(lpDDPalette) ) ) {
            return 0;
        }
    
        // load the 8-bit image
        if (!Load_Bitmap_File(&bitmap, "MyBitMap.bmp") ) {
            return 0;
        }
    
        // load it's palette into directdraw
        if (FAILED(lpDDPalette->SetEntries(0, 0, MAX_COLORS, bitmap.palette) ) ) {
            return 0;
        }
    
        // clean the surface
        DDraw_Fill_Surface(lpDDPrimarySurface, 0);
    
    
        // return success or failure or your own return code here
        return 1;
    } // end Game_Init
    
    /////////////////////////////////////////////////////////////////////////////////
    // this is called after the game is exited and the main event
    // loop while is exited, do all you cleanup and shutdown here
    int Game_Shutdown(void *parms, int num_parms) {
        // this is called after the game is exited and the main event
        // loop while is exited, do all you cleanup and shutdown here
    
        // first the palette
        if (lpDDPalette) {
            lpDDPalette->Release();
            lpDDPalette = NULL;
        } // end if
    
        // now the back buffer surface
        if (lpDDBackSurface) {
            lpDDBackSurface->Release();
            lpDDBackSurface = NULL;
        } // end if
    
        // now the primary surface
        if (lpDDPrimarySurface) {
            lpDDPrimarySurface->Release();
            lpDDPrimarySurface = NULL;
        } // end if
    
        // now blow away the IDirectDraw4 interface
        if (lpDD) {
            lpDD->Release();
            lpDD = NULL;
        } // end if
    
        // return success or failure or your own return code here
        return 1;
    } // end Game_Shutdown
    
    ////////////////////////////////////////////////////////////////////////////////
    // this is the main loop of the game, do all your processing here
    int Game_Main(void *parms, int num_parms) {
        // make sure this isn't executed again
        if (window_closed)
            return 0;
    
        // for now test if user is hitting ESC and send WM_CLOSE
        if (KEYDOWN(VK_ESCAPE) ) {
            window_closed = 1;
            SendMessage(main_window_handle, WM_CLOSE, 0, 0);
        }
    
        // copy the bitmap image to the primary buffer line by line
        // note this is a good candidate operation to make into a function - hint!
    
        // lock the primary surface
        lpDDPrimarySurface->Lock(NULL, &DDSurfaceDesc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);
    
        // get video pointer to primary surfce
        UCHAR *primary_buffer = (UCHAR*)DDSurfaceDesc.lpSurface;       
    
        // test if memory is linear
        if (DDSurfaceDesc.lPitch == SCREEN_WIDTH) {
            // copy memory from double buffer to primary buffer
            memcpy( (void*)primary_buffer, (void*)bitmap.buffer, SCREEN_WIDTH * SCREEN_HEIGHT);
        } else { 
            // non-linear
            // make copy of source and destination addresses
            UCHAR *dest_ptr = primary_buffer;
            UCHAR *src_ptr  = bitmap.buffer;
    
            // memory is non-linear, copy line by line
            for (int y = 0; y < SCREEN_HEIGHT; y++) {
                // copy line
                memcpy( (void*)dest_ptr, (void*)src_ptr, SCREEN_WIDTH);
    
                // advance pointers to next line
                dest_ptr += DDSurfaceDesc.lPitch;
                src_ptr  += SCREEN_WIDTH;
            } // end for
    
        } // end if / else
    
        // now unlock the primary surface
        if (FAILED(lpDDPrimarySurface->Unlock(NULL) ) ) {
            return 0;
        }
    
    
        // do nothing -- look at pretty picture
    
    
        // wait a sec so everything does not fly by so quick
        //Sleep(30);
    
        // return success or failure or your own return code here
        return 1;
    } // end Game_Main
    Using DEV-C++ Under Windows XP
    +------------------------------+

    "No! Do, or Do Not. There is no Try..."

  2. #2
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Looks to me like an example from LaMothe's book. Are you sure you've copied it correctly? If you've modified it, would you mind pointing out the modified places? I'm pretty sure I've compiled the bitmap loading example in my time, and it worked.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  3. #3
    Master of Puppets rwmarsh's Avatar
    Join Date
    Feb 2006
    Location
    Texas
    Posts
    96
    I think I have traced the problem down to one of these two segments of code
    from Function Game_Init:
    Code:
        // load the 8-bit image
        if (!Load_Bitmap_File(&bitmap, "bitmap8.bmp") ) {
            return 0;
        }
    
        // load it's palette into directdraw
        if (FAILED(lpDDPalette->SetEntries(0, 0, MAX_COLORS, bitmap.palette) ) ) {
            return 0;
        }
    
        // clean the surface
        DDraw_Fill_Surface(lpDDPrimarySurface, 0);
    and from function Game_Main:
    Code:
        // copy the bitmap image to the primary buffer line by line
        // note this is a good candidate operation to make into a function - hint!
    
        // lock the primary surface
        lpDDPrimarySurface->Lock(NULL, &DDSurfaceDesc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);
    
        // get video pointer to primary surfce
        UCHAR *primary_buffer = (UCHAR*)DDSurfaceDesc.lpSurface;       
    
        // test if memory is linear
        if (DDSurfaceDesc.lPitch == SCREEN_WIDTH) {
            // copy memory from double buffer to primary buffer
            memcpy( (void*)primary_buffer, (void*)bitmap.buffer, SCREEN_WIDTH * SCREEN_HEIGHT);
        } else { 
            // non-linear
            // make copy of source and destination addresses
            UCHAR *dest_ptr = primary_buffer;
            UCHAR *src_ptr  = bitmap.buffer;
    
            // memory is non-linear, copy line by line
            for (int y = 0; y < SCREEN_HEIGHT; y++) {
                // copy line
                memcpy( (void*)dest_ptr, (void*)src_ptr, SCREEN_WIDTH);
    
                // advance pointers to next line
                dest_ptr += DDSurfaceDesc.lPitch;
                src_ptr  += SCREEN_WIDTH;
            } // end for
    
        } // end if / else
    
        // now unlock the primary surface
        if (FAILED(lpDDPrimarySurface->Unlock(NULL) ) ) {
            return 0;
        }
    still hav not figured it out yet, any ideas?
    Using DEV-C++ Under Windows XP
    +------------------------------+

    "No! Do, or Do Not. There is no Try..."

  4. #4
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Yeah. Use a debugger to find the exact source line it crashes at.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  5. #5
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Many of the reviews from amazon and other sources like gamedev state that the samples in the book rarely run as is on modern compilers. Some of the samples I've been told are completely broken and must be altered significantly in order to run.

    In Lamothe's favor, however, this book is very old and DirectDraw 7 is definitely deprecated and unsupported. New drivers do not guarantee compatibility with DirectDraw interfaces.

    Direct3D is now on top of DirectDraw or moreso merged with it. You can still gain access to DirectDraw surfaces but it is done through a completely different mechanism.

    I highly recommend moving to Direct3D or OpenGL and doing 2D there. It will help you much more than dabbling in DirectDraw.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. how do you resolve this error?
    By -EquinoX- in forum C Programming
    Replies: 32
    Last Post: 11-05-2008, 04:35 PM
  2. Errors including <windows.h>
    By jw232 in forum Windows Programming
    Replies: 4
    Last Post: 07-29-2008, 01:29 PM
  3. Quantum Random Bit Generator
    By shawnt in forum C++ Programming
    Replies: 62
    Last Post: 06-18-2008, 10:17 AM
  4. Connecting to a mysql server and querying problem
    By Diod in forum C++ Programming
    Replies: 8
    Last Post: 02-13-2006, 10:33 AM
  5. very weird .h problem
    By royuco77 in forum C++ Programming
    Replies: 1
    Last Post: 09-11-2005, 07:55 AM