次のエラーが表示されます。
1>------ ビルド開始: プロジェクト: TankSample、構成: Win32 をデバッグ ------
1>Debug\BackBuffer.obj: 警告 LNK4042: オブジェクトが複数回指定されました。エキストラは無視
1>Source.obj: エラー LNK2019: 未解決の外部シンボル "public: thiscall BackBuffer::BackBuffer(struct HWND *,int,int)" (??0BackBuffer@@QAE@PAUHWND__@@HH@Z) 関数 "long で参照stdcall WndProc(struct HWND *,unsigned int,unsigned int,long)" (?WndProc@@YGJPAUHWND__@@IIJ@Z)
1>Source.obj : エラー LNK2019: 未解決の外部シンボル "public: __thiscall BackBuffer::~BackBuffer(void)" (??1BackBuffer@@QAE@XZ) 関数で参照されている "public: void * __thiscall BackBuffer::`scalar の削除デストラクタ'(unsigned int)" (??_GBackBuffer@@QAEPAXI@Z)
1>Source.obj: エラー LNK2019: 未解決の外部シンボル "public: struct HDC__ * thiscall BackBuffer::getDC(void)" (?getDC@BackBuffer@@QAEPAUHDC @@XZ) が関数 "int __cdecl Run(void)" で参照されています(?Run@@YAHXZ)
1>Source.obj: エラー LNK2019: 未解決の外部シンボル "public: void __thiscall BackBuffer::present(void)" (?present@BackBuffer@@QAEXXZ) が関数 "int __cdecl Run(void)" で参照されています (?Run@@ YAHXZ)
1>C:\Users\Josh\Documents\Game Institute\C++\Module Two\Exercises\Chapter 17\TankSample\Debug\TankSample.exe : 致命的なエラー LNK1120: 4 つの未解決の外部 ========== ビルド: 0 成功、1 失敗、0 最新、0 スキップ ==========
このコードをコンパイルしようとすると:
バックバッファ.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.
};
バックバッファ.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;
}
ソース.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 );
}
明らかに BackBuffer と関係がありますが、よくわかりません。新しいプロジェクト全体を作成してコードをコピーしようとしましたが、うまくいきません。BackBuffer.obj ファイルを削除して再構築すると、同じエラーがスローされます。
なぜこれが起こっているのか、誰かが光を当てることができますか?
ありがとう。