0

I'm getting these errors:

1>------ Build started: Project: TankSample, Configuration: Debug Win32 ------

1>Debug\BackBuffer.obj : warning LNK4042: object specified more than once; extras ignored

1>Source.obj : error LNK2019: unresolved external symbol "public: thiscall BackBuffer::BackBuffer(struct HWND *,int,int)" (??0BackBuffer@@QAE@PAUHWND__@@HH@Z) referenced in function "long stdcall WndProc(struct HWND *,unsigned int,unsigned int,long)" (?WndProc@@YGJPAUHWND__@@IIJ@Z)

1>Source.obj : error LNK2019: unresolved external symbol "public: __thiscall BackBuffer::~BackBuffer(void)" (??1BackBuffer@@QAE@XZ) referenced in function "public: void * __thiscall BackBuffer::`scalar deleting destructor'(unsigned int)" (??_GBackBuffer@@QAEPAXI@Z)

1>Source.obj : error LNK2019: unresolved external symbol "public: struct HDC__ * thiscall BackBuffer::getDC(void)" (?getDC@BackBuffer@@QAEPAUHDC@@XZ) referenced in function "int __cdecl Run(void)" (?Run@@YAHXZ)

1>Source.obj : error LNK2019: unresolved external symbol "public: void __thiscall BackBuffer::present(void)" (?present@BackBuffer@@QAEXXZ) referenced in function "int __cdecl Run(void)" (?Run@@YAHXZ)

1>C:\Users\Josh\Documents\Game Institute\C++\Module Two\Exercises\Chapter 17\TankSample\Debug\TankSample.exe : fatal error LNK1120: 4 unresolved externals ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

When I try to compile this code:

Backbuffer.h

#pragma once

#include <Windows.h>

class BackBuffer
{
public:
    BackBuffer( HWND hWnd, int width, int height );
    ~BackBuffer();

    HDC getDC();

    int width();
    int height();

    void present();

//private:
    // Make copy constructor and assignment operator private so
    // client cannot copy BackBuffers. We do this because this
    // class is not designed to be copied because it is not
    // efficient--copying bitmaps is slow. (Lots of memory).
    //BackBuffer( const BackBuffer& rhs );
    //BackBuffer& operator=( const BackBuffer& rhs );

private:
    HWND    mhWnd;       // A handle to the main window. We need this to obtain a device context associated to the main window.
    HDC     mhDC;        // A handle to the system memoy DC.
    HBITMAP mhSurface;   // A handle to the bitmap that serves as our backbuffer.
    HBITMAP mhOldObject; // A handle to the previous bitmap.
    int     mWidth;      // The width of the bitmap matrix.
    int     mHeight;     // The height of the bitmap matrix.

};

Backbuffer.cpp

#include "BackBuffer.h"

BackBuffer::BackBuffer( HWND hWnd, int width, int height ) : mhWnd( hWnd ), mWidth( width ), mHeight ( height )
{

    // Get a handle to the device context associated with the window.
    HDC hWndDC = GetDC( hWnd );

    // Create system memory DC that is compatible with the window one.
    mhDC = CreateCompatibleDC( hWndDC );

    // Create the backbuffer surface bitmap that is compatible with the window
    // DC bitmap format. That is the surface we will render onto.
    mhSurface = CreateCompatibleBitmap( mhDC, mWidth, mHeight );

    // Done with window DC.
    ReleaseDC( hWnd, hWndDC );

    // At this point, the backbuffer surface is uninitialized, so lets clear it
    // to some non-zero value. Note that it needs to be non-zero. If it is zero
    // then it will mess up our sprite blending logic.

    // Select the backbuffer bitmap into the DC.
    mhOldObject = (HBITMAP)SelectObject( hWndDC, mhSurface );

    // Select a white brush.
    HBRUSH white    = (HBRUSH)GetStockObject( WHITE_BRUSH );
    HBRUSH oldBrush = (HBRUSH)SelectObject( mhDC, white );

    // Clear the backbuffer rectangle.
    Rectangle( hWndDC, 0, 0, mWidth, mHeight );

    // Restore the original brush.
    SelectObject( mhDC, oldBrush );

}
/*
BackBuffer::BackBuffer( const BackBuffer& rhs )
{
    *this = rhs;
}

BackBuffer& BackBuffer::operator=( const BackBuffer& rhs )
{
    if( this == &rhs )
        return *this;

    mhWnd       = rhs.mhWnd;       
    mhDC        = rhs.mhDC;
    mhSurface   = rhs.mhSurface;
    mhOldObject = rhs.mhOldObject;
    mWidth      = rhs.mWidth;
    mHeight     = rhs.mHeight;

    return *this;
}*/


BackBuffer::~BackBuffer()
{
    // Restore the original selected bitmap before deleting anything.
    SelectObject( mhDC, mhOldObject );

    // Delete the backbuffer bitmap.
    DeleteObject( mhSurface );

    // Delete the DC.
    DeleteDC( mhDC );
}

HDC BackBuffer::getDC()
{
    return mhDC;
}

int BackBuffer::width()
{
    return mWidth;
}

int BackBuffer::height()
{
    return mHeight;
}

void BackBuffer::present()
{
    // Get a handle to the DC associated with the window.
    HDC hWndDC = GetDC( mhWnd );

    // Copy the backbuffer contents over to the windows client area.
    BitBlt( hWndDC, 0, 0, mWidth, mHeight, mhDC, 0, 0, SRCCOPY );

    // Always free a window DC when done.
    ReleaseDC( mhWnd, hWndDC );
}

Vec2.h

#pragma once

#include <cmath>

class Vec2
{
public:
    // Constructors
    Vec2() : x( 0.0f ), y( 0.0f ){};
    Vec2( float coords[2] ) : x( coords[0] ), y( coords[1] ){};
    Vec2( const POINT& p ) : x( p.x ), y( p.y ){};
    Vec2( float v1, float v2 ) : x( v1 ), y( v2 ){};

    Vec2( const Vec2& rhs )
    {
        *this = rhs;            
    }

    Vec2& operator=( const Vec2& rhs )
    {
        if( this == &rhs )
            return *this;

        x = rhs.x;
        y = rhs.y;

        return *this;
    }

    ~Vec2(){};

    Vec2 operator+( const Vec2& rhs ) const
    {
        Vec2 sum;

        sum.x = x + rhs.x;
        sum.y = y + rhs.y;

        return sum;
    }

    Vec2 operator-( const Vec2& rhs ) const
    {
        Vec2 result;

        result.x = x - rhs.x;
        result.y = y - rhs.y;

        return result;
    }

    Vec2 operator-()
    {
        Vec2 result;
        x = -x;
        y = -y;

        return result;
    }

    void operator*=( float scalar )
    {
        x *= scalar;
        y *= scalar;
    }

    void operator+=( const Vec2& rhs )
    {
        x += rhs.x;
        y += rhs.y;
    }

    void operator-=( const Vec2& rhs )
    {
        x -= rhs.x;
        y -= rhs.y;
    }

    void operator/=( const Vec2& rhs )
    {
        // Assumes rhs != 0
        x /= rhs.x;
        y /= rhs.y;
    }

    // Convert to point.
    operator POINT()
    {
        POINT p = {(int)x, (int)y};
        return p;
    };

    // Return length.
    float length()
    {
        return sqrtf( x * y + y * y );
    }

    float dot( const Vec2& rhs )
    {
        return x * rhs.x + y * rhs.y;
    }

    Vec2& rotate( float t )
    {
        float newX;
        newX = x * cosf(t) - y * sinf(t);
        y    = y * cosf(t) + x * sinf(t);

        this->x = newX;
        return *this;
    }

    // Data members.
    float x;
    float y;

};

Vec2 operator*( const Vec2& rhs, float s )
{
    Vec2 result;

    result.x = rhs.x * s;
    result.y = rhs.y * s;

    return result;
}

Vec2 operator/( const Vec2& rhs, float s )
{
    Vec2 result;

    result.x = rhs.x / s;
    result.y = rhs.y / s;

    return result;
}

Source.cpp

#include <Windows.h>
#include <string>
#include <list>
#include "BackBuffer.h"
#include "Vec2.h"
#include "resource.h"

using namespace std;

// WINDOWS HANDLES
HWND      ghMainWnd  = 0;
HINSTANCE ghAppInst  = 0;
HMENU     ghMainMenu = 0;

// The backbuffer we will render onto.
BackBuffer* gBackBuffer = 0;

// The text that will appear in the main window's caption bar.
string gWndCaption = "Tank Sample";

// Client rectangle dimensions we will use.
const int gClientWidth  = 800;
const int gClientHeight = 600;

const POINT gClientCenter =
{
    gClientWidth / 2,
    gClientHeight / 2
};

// Pad window dimensions so that there is room for window
// borders, caption bar, and menu.
const int gWindowWidth  = gClientWidth + 6;
const int gWindowHeight = gClientHeight + 52;

// Client area rectangle.
RECT gMapRect = {0, 0, 800, 600};

// Vector to store the center position of the tank
// relative to the client area rectangle.
Vec2 gTankPos( 400.0f, 300.0f );

// Handle to the pen we will use to draw the tanks' gun.
HPEN gGunPen;

// A vector describing the direction of the tanks' gun
// is aimed in. The vectors' magnitude denotes the 
// length of the gun.
Vec2 gGunDir( 0.0f, -120.0f );

// A list, where we will add bullets to as they are fired.
// The list stores the bullets positions, so that we can draw
// an ellipse at the positino of each bullet.
list<Vec2> gBulletList;

//==============================================
// Function Prototypes
//==============================================

bool InitMainWindow();
int Run();
void DrawFramesPerSecond( float deltaTime );

LRESULT CALLBACK
WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

//==============================================
// Name: WinMain
// Desc: Program execution starts here.
//==============================================
int WINAPI
WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd )
{

    ghAppInst = hInstance;

    if( !InitMainWindow() )
    {
        MessageBox( 0, "Window Creation Failed", "Error", MB_OK );
        return 0;
    }


    return Run();

}

//==============================================
// Name: InitMainWindow
// Desc: Creates the main window upon which
//       we will draw onto.
//==============================================

bool InitMainWindow()
{
    WNDCLASS wc; 
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = ghAppInst;
    wc.hIcon         = ::LoadIcon(0, IDI_APPLICATION);
    wc.hCursor       = ::LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)::GetStockObject(NULL_BRUSH);
    wc.lpszMenuName  = 0;
    wc.lpszClassName = "MyWndClassName";

    RegisterClass( &wc );

    // WS_OVERLAPPED | WS_SYSMENU: Window cannot be resized
    // and does not have a min/max button.  
    ghMainMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU1));

    ghMainWnd = CreateWindow("MyWndClassName", gWndCaption.c_str(), WS_OVERLAPPED | WS_SYSMENU, 200, 200, gWindowWidth, gWindowHeight, 0, ghMainMenu, ghAppInst, 0);

    if(ghMainWnd == 0)
    {
        ::MessageBox(0, "CreateWindow - Failed", 0, 0);
        return 0;
    }

    ShowWindow(ghMainWnd, SW_NORMAL);
    UpdateWindow(ghMainWnd);

    return true;
}

//=========================================================
// Name: Run
// Desc: Encapsulates the message loop.
//=========================================================
int Run()
{
    MSG msg;
    ZeroMemory( &msg, sizeof( MSG ) );

    // Get the performance timer frequency.
    __int64 cntsPerSec = 0;
    bool prefExists = QueryPerformanceFrequency( ( LARGE_INTEGER* )&cntsPerSec) != 0;
    if( !prefExists )
    {
        MessageBox( 0, "Perforamce timer doesn't exist.", 0, 0 );
        return 0;
    }

    double timeScale = 1.0 / (double)cntsPerSec;

    // Get the current time.
    __int64 lastTime = 0;
    QueryPerformanceCounter( ( LARGE_INTEGER* )&lastTime );

    double timeElasped = 0;

    while( msg.message != WM_QUIT )
    {

        // IF there is a windows message then process it.
        if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else // DO GAME STUFF
        {
            // Get the time now.
            __int64 currTime = 0; 
            QueryPerformanceCounter( ( LARGE_INTEGER* )&currTime );

            // Compute the differences in time from the last time
            // we checked. Since the last time we checked was given
            // the previous loop iteration, this difference gives
            // us the time between loop iterations...
            // or, I.E., the time between frames.
            double deltaTime = double( currTime - lastTime ) * timeScale;

            timeElasped += deltaTime;

            // Get the backbuffer DC.
            HDC bbDC = gBackBuffer->getDC();

            // Clear the entire backbuffer to black. This gives up a black background.
            HBRUSH oldBrush = (HBRUSH)SelectObject( bbDC, GetStockObject( BLACK_BRUSH ) );
            Rectangle( bbDC, 0, 0, 800, 600 );

            // Draw the base of the tank--a rectangle surrounding the tanks'
            // center position point.
            SelectObject( bbDC, GetStockObject( DKGRAY_BRUSH ) );
            Rectangle( bbDC, (int)gTankPos.x - 50, (int)gTankPos.y - 75, (int)gTankPos.x + 50, (int)gTankPos.y + 75 );

            // Draw the gun base of the tank--an ellipse surrounding the tanks'
            // center position point.
            SelectObject( bbDC, GetStockObject( GRAY_BRUSH ) );
            Ellipse( bbDC, (int)gTankPos.x - 40, (int)gTankPos.y - 40, (int)gTankPos.x + 40, (int)gTankPos.y + 40 );

            // Draw the gun itself--a line from the tanks' center
            // position point to the tip of the gun.
            HPEN oldPen = (HPEN)SelectObject( bbDC, gGunPen );
            MoveToEx( bbDC, (int)gTankPos.x, (int)gTankPos.y, 0 );
            LineTo( bbDC, (int)(gTankPos.x + gGunDir.x), (int)(gTankPos.y + gGunDir.y) );

            // Draw any bullets that get fired.
            SelectObject( bbDC, GetStockObject( WHITE_BRUSH ) );
            SelectObject( bbDC, oldPen );

            // Bullet velocity is 5X the guns' directions' magnitude.
            Vec2 bulletVel = gGunDir * 5.0f;
            list<Vec2>::iterator i;
            i = gBulletList.begin();
            while( i != gBulletList.end() )
            {
                // Update the bullet position.
                *i += bulletVel * deltaTime;

                // Get POINT form.
                POINT p = *i;

                // Only draw bullet if it is inside the map boundaries, otherwise delete it.
                if( !PtInRect( &gMapRect, p ) )
                    i = gBulletList.erase( i ); // Deletes element as position i and increments i by 1.
                else
                {
                    // Draw bullet as a circle.
                    Ellipse( bbDC, p.x - 4, p.y - 4, p.x + 4, p.y + 4 );

                    // Next in the list.
                    ++i;
                }
            }

            // Select old brush.
            SelectObject( bbDC, oldBrush );

            DrawFramesPerSecond( ( float )deltaTime );

            // Present the backbuffer contents.
            gBackBuffer->present();

            // We are at the end of the loop iteration, so prepare for the
            // next loop iteration by making the "current time" the "last time".
            lastTime = currTime;

            // Free 20 miliseconds to Windows so we don't hog the system resources.
            Sleep( 20 );
        }

    }

    // Return exit code back to the operating system.
    return (int)msg.wParam;

}

//=========================================================
// Name: DrawFramesPerSecond
// Desc: This function is called every frame and updates
//       the frame per second display in the main window
//       caption.
//=========================================================
void DrawFramesPerSecond( float deltaTime )
{
    // Make static variables persist even after the function returns.
    static int   frameCnt = 0;
    static float timeElasped = 0;
    static char  buffer[256];

    // Function called implies a new frame, so
    // increment frame count.
    ++frameCnt;

    // Also increment how much time has passed since
    // the last frame.
    timeElasped += deltaTime;

    // Has one second passed?
    if( timeElasped >= 1.0f )
    {
        // Yes, so compute the frames per second.
        // FPS = frameCnt / timeElasped, but since we
        // compute only when timeElasped = 1.0f, we can
        // reduce to:
        // FPS = frameCnt / 1.0 = frameCnt.

        sprintf_s( buffer, "--Frames Per Second = %d", frameCnt );

        // Add the frames per second string to the main
        // window caption.
        string newCaption = gWndCaption + buffer;

        SetWindowText( ghMainWnd, newCaption.c_str() );

        // Reset the counters to prepare for the next time
        // we compute the frames per second.
        frameCnt = 0;
        timeElasped = 0.0f;
    }
}

//=========================================================
// Name: Window Procedure
// Desc: The main window procedure.
//=========================================================
LRESULT CALLBACK
WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    LOGPEN lp;

    switch( msg )
    {
    case WM_CREATE:

        // Create the tanks' gun pen.
        lp.lopnColor   = RGB( 150, 150, 150 );
        lp.lopnStyle   = PS_SOLID;
        lp.lopnWidth.x = 10;
        lp.lopnWidth.y = 10;

        gGunPen = CreatePenIndirect( &lp );

        // Create the backbuffer.
        gBackBuffer = new BackBuffer( hWnd, gClientWidth, gClientHeight );

    return 0;

    case WM_DESTROY:

        DeleteObject( gGunPen );
        delete gBackBuffer;
        PostQuitMessage( 0 );

    return 0;

    case WM_COMMAND:

        switch( LOWORD( wParam ) )
        {
        case ID_FILE_EXIT:
            DestroyWindow( ghMainWnd );
            break;
        }

    return 0;

    case WM_KEYDOWN:

        switch( wParam )
        {
        case 'A':
            gTankPos.x -= 5.0f;
            break;
        case 'D':
            gTankPos.x += 5.0f;
            break;
        case 'W':
            gTankPos.y -= 5.0f;
            break;
        case 'S':
            gTankPos.y += 5.0f;
            break;

        case 'Q':
            gGunDir.rotate( -0.1f );
            break;
        case 'E':
            gGunDir.rotate( 0.1f );
            break;

        case VK_SPACE:
            gBulletList.push_back( gTankPos + gGunDir );
            break;
        }

    return 0;
    }

    return DefWindowProc( hWnd, msg, wParam, lParam );
}

Clearly it has something to do with BackBuffer, but I am not sure. I've tried to create an entire new project and copied the code however this doesn't work. I've deleted the BackBuffer.obj file and rebuilt, throws the same errors.

Can anyone shed some light on why this is happening?

Thanks.

Cypras
  • 478
  • 7
  • 19

0 Answers0