Win32 Part3 プロセス、スレッド編

ヒープはプロセス一個につき、最初から一個用意されている
HeapAlloc、VirtualAllocの違いについては、
http://keicode.com/windows/win11.php
が詳しい。
VirtualAllocはページ単位でメモリを操作する。(どっさと領域を調達してくる) たくさんメモリを使う場合
HeapAllocは、予めある程度 の仮想メモリ 領域を予約しておき、要求されれば、ちょっとずつそこから使う。
小さなメモリブロックを多 数割り当てたいとき。
  • プライベートヒープ(HeapCreate)[手動:複数作れる]とプロセスピープ(GetProcessHeap)[自動:ヒープへの同時アクセスは禁止される]

コピーコンストラクタ

//AutoPtr.h
#include <cstring>
#include <iostream>

class AutoPtr {
private:
	char *ptr;
public:
	AutoPtr();
	~AutoPtr();
	char *operator=(char *ptr);
	operator char* ();
	char& operator[](int index);

	//深いコピー
	AutoPtr(AutoPtr& src);
	AutoPtr& operator=(AutoPtr& src);
};

AutoPtr::AutoPtr() : ptr(0) {
	std::cout << "AutoPtr::AutoPtr()" << std::endl;
}

AutoPtr::~AutoPtr() {
	std::cout << "AutoPtr::~AutoPtr()" << std::endl;
	delete[] ptr;
}

char& AutoPtr::operator[](int index) {
	std::cout << "char& AutoPtr::operator[](int index)" << std::endl;
	return ptr[index];
}

//AutoPtrを配列のようにキャスト
AutoPtr::operator char* () {
	std::cout << "AutoPtr::operator char* ()" << std::endl;
	return ptr;
}

char* AutoPtr::operator=(char* ptr) {
		std::cout << "char* AutoPtr::operator=(char* ptr)" << std::endl;
	delete[] this->ptr;
	this->ptr = ptr;
	return this->ptr;
}

AutoPtr::AutoPtr(AutoPtr& src) {
		std::cout << "AutoPtr::AutoPtr(AutoPtr& src)" << std::endl;
	ptr = new char[strlen(src.ptr)+1];
	strcpy(ptr, src.ptr);
}

AutoPtr& AutoPtr::operator=(AutoPtr& src) {
		std::cout << "AutoPtr& AutoPtr::operator=(AutoPtr& src)" << std::endl;
	delete[] ptr;
	ptr = new char[strlen(src.ptr)+1];
	strcpy(ptr, src.ptr);

	return *this;

}

//main.cpp
#include "AutoPtr.h"
#include <cstdio>
#include <cstring>

void reverse(char* str);


class C {
public:
	C(int value) : mInt(value) {
		std::cout << "C(int value)" << std::endl;
	}
	~C() {
		std::cout << "~C()" << std::endl;
	}
	C(C& m) {
		std::cout <<"C(C& m)" << std::endl;
		this->mInt = m.mInt;
	}

private:
	int mInt;
};


int main() {
	AutoPtr a,b; //生成コンストラクタ
	char s[] = "idohoiho";
	a = new char[20]; //char* AutoPtr::operator=(char* ptr)
	strcpy(a, "test opeartor="); //AutoPtr::operator char* ()
	b = a; //AutoPtr& AutoPtr::operator=(AutoPtr& src)
	AutoPtr c = a; //AutoPtr::AutoPtr(AutoPtr& src) コピーコンストラクタが呼ばれる。(注意)

	int num = 4;

	//C test0; //デフォルトコンストラクタは(もう1つ以上コンストラクタを作ってしまったので)呼び出されない。
	C test1 = 4; //C(int value) コンストラクタ
	C test2 = num; //C(int value)  あくまでtest2はnumで初期化されている。
	C test3 = test2; //C(C& m) コピーコンストラクタ 浅いコピー
	
	//test1,test2についてデストラクタが呼ばれる。
	//a,b,cについてデストラクタが呼ばれる。
}

コピーコンストラクタで初期化される場合もあるので、気を付けること。

もちろん、この場合もデストラクタは呼ばれる。

クラスメモ4-2

//Map.h
#ifndef INCLUDED_PLAYER_H__
#define INCLUDED_PLAYER_H__

#include <utility>
using namespace std;

class Image;
class Map;

//Playerはいつでも一人なので、シングルトンにする。
class Player {
public:
	static Player* instance();
	void draw() const;
	//staticメンバ関数はインスタンスが存在する前から呼び出せる。
	static void create(const char* filename, Map* map);
	static void destroy();
	void changeMap(Map* map); //マップ変更

	void update();
private:
	int mCounter; //Playerが動いて見えるようにするためのカウンター
	static Player* mPlayer; //唯一のインスタンス

	Player(const char* filename, Map* map);

	////newで動的に生成するとき、コピーコンストラクタが呼ばれるので、何もしないようにすること。
	Player( Player& );
	~Player();

	typedef pair<int,int> pii;
	pii mDir; //プレイヤーの向き (x,y)
	pii mPos; //プレイヤーの位置

	int mSize; //constはつけることができない
	Image* mImage; //これはnewする
	Map* mMap; //ポインタを替えたりするだけ。newしない。

};


#endif



//Map.cpp
#include <utility>
using namespace std;

#include "Map.h"
#include "Image.h"

#include "Player.h"

#include "GameLib/Framework.h"

#include "GameLib/Input/Manager.h"
#include "GameLib/Input/Keyboard.h"

using namespace GameLib;
using namespace GameLib::Input;


//staticメンバ変数の実体化
Player* Player::mPlayer = 0; 

Player::Player(const char* filename, Map* map) : mSize(16), mMap(0), mImage(0) ,mCounter(0), mPos(0,0),mDir(0,0) {
	mMap = map;
	mImage = new Image(filename);
	mPos = pii(1,1);
	mDir = pii(0,1);
}

Player::~Player() {
	delete mImage;
	mImage = 0;
}


Player* Player::instance() {
	return mPlayer;
}

void Player::create(const char* filename, Map* map) {
	STRONG_ASSERT(!mPlayer && "Player::create() called twice!");

	//コンストラクタと、コピーコンストラクタが呼ばれる。
	mPlayer = new Player(filename, map);
}

void Player::destroy() {
	delete mPlayer;
	mPlayer = 0;
}

void Player::draw() const{
	//Framework f = Framework::instance();
	//unsigned* vram = f.videoMemory();
	//int mSize=16;
	mImage->draw(0,0,(mPos.first)*mSize,(mPos.second)*mSize,mSize,mSize);
}

void Player::update() {
	Manager manager = Manager::instance();
	Keyboard keyboard = manager.keyboard();
	mDir = pii(0,0); //普通は
	if(keyboard.isTriggered(Keyboard::KEY_UP) ) {
		mDir = pii(0,-1);
	} else if(keyboard.isTriggered(Keyboard::KEY_DOWN) ) {
		mDir = pii(0,1);
	}
	if(keyboard.isTriggered(Keyboard::KEY_LEFT) ) {
		mDir = pii(-1,0);
	} else if(keyboard.isTriggered(Keyboard::KEY_RIGHT) ) {
		mDir = pii(1,0);
	}

	Framework f = Framework::instance();
	mPos.first += mDir.first;
	mPos.second += mDir.second;

	
	if(mPos.first <0 || mPos.first > (f.width()/mSize - 1) ) {
		mPos.first -= mDir.first;
	}
	if(mPos.second < 0 || mPos.second > (f.height()/mSize - 1) ) {
		mPos.second -= mDir.second;
	}
	//先に進めるかどうか取得-->MapのmStageDataを参照できない!mStageDataはpublicにはしたくない
	
	draw();
}

Mapを参照したいが詰まってしまいました。
(friend関数使うとか可能性はあるが、どっちにしろ、根本的に修正が必要な気がする。)
だいたい、Mapの各ObjectのサイズとPlayerのサイズは同じなのがまずいというのが一点ある。
もう一転まずいところは、描画関数がPlayerにあったり、Mapにあったりととびとびであるというのも問題なような気がする。

クラスメモ4-1

クラスを新しく組むときは、まず、コンストラクタ、デストラクタ、もっとも関係しそうなメンバ変数から先に与えていく。
組んだら、テストして動作するか確かめる。(特に,デストラクタのdelete忘れに気を配る.)
で、もう一段複雑にする(関数を入れたり、さらに必要なメンバ変数を入れたり…)

特に、あるクラスが別のクラスを参照したり、使っているような場合、いっぺんにやってしまうと、どれがどういう風に間違っているのかよくわからなくなる。(必ず、ちょっと組む-->動かしてみる-->ちょっと組む-->動かしてみる、の繰り返しをすること。)

//Image.h
#ifndef INCLUDED_IMAGE_H__
#define INCLUDED_IMAGE_H__

class Image {
public:
	Image(const char* filename);
	~Image();
	const int getWidth() const;
	const int getHeight() const;
	void draw(int srcX,int srcY,int dstX,int dstY,int width,int height); //ウィンドウに書きだす。
private:
	int mWidth;
	int mHeight;
	unsigned* mImageData;
};
#endif


//Image.cpp
#include "Image.h"
#include "File.h"

#include "GameLib/Framework.h"
using namespace GameLib;


Image::Image(const char* filename) {
	File imageFile(filename); //dds形式
	mHeight = imageFile.getUnsigned(8+4);
	mWidth = imageFile.getUnsigned(12+4);
	
	mImageData = new unsigned[mHeight*mWidth];
	for(int i=0;i<mHeight*mWidth;i++) {
		mImageData[i] = imageFile.getUnsigned(128+i*4); //128から画像開始
	}


}


Image::~Image() {
}

const int Image::getHeight() const {
	return mHeight;
}

const int Image::getWidth() const {
	return mWidth;
}

void Image::draw(int srcX,int srcY, int dstX, int dstY, int width, int height) {
	Framework f;
	unsigned* vram = f.instance().videoMemory();
	int windowWidth = f.width();
	for(int y=0; y<height; y++) {
		for(int x=0;x<width;x++) {
			unsigned& e = mImageData[(srcY+y)*mWidth + (x+srcX)];
			vram[(dstY+y) * windowWidth +(x+dstX)] = e;
		}
	}


}

Imageクラスは参考書のコードと似たり寄ったり。
あとはmaptipをウィンドウに描画するコードをMapクラス内にメンバ関数を置いて、書く。

void Map::drawMap(){
	for(int y=0;y<mHeight; y++) {
		for(int x=0; x<mWidth; x++) {
			int size = 16; //マップチップのサイズ
			drawCell(x,y,size); //16*16のマスを描画

		}
	}
}

void Map::drawCell(int x, int y, int size) {
	Object &tmp = mStageData(y,x);
//	if(tmp.checkFlag(Object::OBJ_WALL) ) {
//		mImage->draw(0,0,x*size,y*size,size,size);
//	}
//	if(tmp.checkFlag(Object::OBJ_FLOOR) ) {
//		mImage->draw(size*1,0,x*size,y*size,size,size);
//	}
	for(int i=0;i<2;i++) {
		if(tmp.checkFlag(1<<i) ) {
			mImage->draw(size*i,0,x*size,y*size,size,size);
		}
	}
}

汚いコードだと自分でも思うが、とにかく完成させることが今は大事だ。

                                                                                                                                                                                • -

つづいて、Playerを動かしたりしたいので、新たに関数を作る。

//Player.h
#ifndef INCLUDED_PLAYER_H__
#define INCLUDED_PLAYER_H__

#include <utility>
using namespace std;

class Image;
class Map;

//Playerはいつでも一人なので、シングルトンにする。
class Player {
public:
	static Player* instance();
	void draw();
	//staticメンバ関数はインスタンスが存在する前から呼び出せる。
	static void create(const char* filename, Map* map);
	static void destroy();

	void changeMap(Map* map); //マップ変更
private:
	int mCounter; //Playerが動いて見えるようにするためのカウンター
	static Player* mPlayer; //唯一のインスタンス

	Player(const char* filename, Map* map);

	////Playerのインスタンスを複製させないためにコピーコンストラクタを何もしない処理にする。
	Player( Player& );
	~Player();

	typedef pair<int,int> pii;
	pii mDir;
	Image* mImage; //これはnewする
	Map* mMap; //ポインタを替えたりするだけ。newしない。

};


#endif


//Player.cpp
#include <utility>
using namespace std;

#include "Map.h"
#include "Image.h"

#include "Player.h"

#include "GameLib/GameLib.h"
#include "GameLib/Framework.h"
using namespace GameLib;

//staticメンバ変数の実体化
Player* Player::mPlayer = 0; 

Player::Player(const char* filename, Map* map) : mMap(0), mImage(0) ,mCounter(0) {
	mMap = map;
	mImage = new Image(filename);
}

Player::~Player() {
	delete mImage;
	mImage = 0;
}


Player* Player::instance() {
	return mPlayer;
}

void Player::create(const char* filename, Map* map) {
	STRONG_ASSERT(!mPlayer && "Player::create() called twice!");

	//コンストラクタがよばれる
	mPlayer = new Player(filename, map);
}

void Player::destroy() {
	delete mPlayer;
	mPlayer = 0;
}

Playerは二つ存在することはまぁあり得ないので、(練習もかねて)シングルトンパターンで組んでみた。
(まだ,Player::draw関数は実装していないがそれは後で)
一番はまったのが、

namespace GameLib{
	void Framework::update(){
		Map map("mTen.txt");
		map.drawMap();
		//createは一回しか呼び出せない
		if( !Player::instance() ) {
			Player::create("player.dds", &map);
		}
		Player* p = Player::instance();

	}
}

と組むべきところをif文を除いて書いていたところだ。シングルトンパターンのクラスでは、最初にcreateを一回しか呼び出せない。
(当たり前)update()がループするのをすっかり忘れてしまい、間違いを特定するのにものすごく時間がかかった。
(まぁ、いいべんきょうになったんじゃないかな、と思う。)
コピーコンストラクタを明示的に書くのは、値渡しされない対策かな?

Map* mapを置くのはなんでかというと、Playerが動く際に動けない部分というのはあるはずだ。
具体的には床を進むことはできるが、壁の中を進むことや壁に入ることは普通はできない。ということで、
動けるかどうかを調べるためにmapを置いた。
(ちなみに、Map::Objectの中身に

bool mIsStep;

を追加した。文字通り、進めることが可能ならtrueを返すことにしている。

ポインタとしてのmapをメンバ変数に置くことで一応、マップの切り替えにも対応できる仕様にしている。
Playerが動けるまであともう少しかな。敵も(ランダムで)動かしてみたいな。

std::pairくらいなら自分で実装したほうが(stdを読み込まないで済むというメリットがあるので)いいような気がするが、せっかくだし。

クラスメモPart3-3

Mapのstructをclassに替えたもの。

Array2D<Object> mStageData;

の配列がsetSizeで動的に生成されるが、この時もコンストラクタは呼ばれている。Objectクラスのコンストラクタは
mFlagを0に初期化するようにしたので、邪魔くさいZeroClearは呼び出す必要がなくなった。
また、cpp側に書けるようになったので、使いやすくなった。

ファイルの形は.txtで

#######
#     #
#   ###
#   #
#   ###
#     #
#     #
#######

という形で実験してみた。最後にEnterキー(\r\n)を押しても押さなくても読み込めるようにすべきだったが
めんどくさくてやってない。

//Map.h
#ifndef INCLUDED_MAP_H__
#define INCLUDED_MAP_H__

#include "Array2D.h" //宣言だけではだめで、インクルードする必要がある。

class Map {
public:
	Map(const char* filename);
	~Map();
	int getHeight() const;
	int getWidth() const;
	void Show();
private:
        class Object;
	void zeroClear();
	int mHeight;
	int mWidth;
	Array2D<Object> mStageData;
};

#endif





//Map.cpp
#include "Map.h"
#include "File.h"


#include "GameLib/Framework.h" //coutも
using namespace GameLib;

/*-----------------------*/
//一番最初に定義書く。
class Map::Object {
public:
	Object();
	enum Obj{
		OBJ_WALL = 0,
		OBJ_FLOOR = (1<<0),
		OBJ_ITEM = (1<<1),

		OBJ_UNKNOWN = (1<<3),
	};
	unsigned mFlags;
	//引数をObj型にしないのは、(OBJ_WALL | OBJ_ITEMのような、複数のflagを引数にしたいときに対応するため。
	bool checkFlag(unsigned o);
	void setFlag(unsigned o);
	void resetFlag(unsigned o);
	void switchFlag(unsigned o); //オフならオン、オンならオフ
};


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



namespace {
	//Mapコンストラクタ用
	unsigned GetMapSize(const char* stageData) {
		int x=0; int y=0; int tmp =0;
		for(int i=0; stageData[i] != '\0' ;i++) {
			char ch = stageData[i];
			if(ch == '\n') {
				x = (tmp>x) ? tmp : x;
				y++;
				tmp =0;
			} else if(ch >= ' ' && ch <= '~') {
				tmp++;
			}
		}
		return ((x<<16) | y);
	}

}


Map::Map(const char* filename) {
	File file(filename);
	unsigned siz = GetMapSize(file.getData());

	mWidth = (siz >> 16);
	mHeight = (siz & 0x0000ffff);
	mStageData.setSize(mHeight,mWidth);

	//zeroClear(); //mStageDataのflagをすべて0に。クラスのコンストラクタで呼び出されるのでいらない

	//dataの解析
	//ここも無名名前空間の中に入れればいいかも。
	const char* str = file.getData();
	int x=0; int y=0;
	for(int i=0;str[i] != '\0';i++) {
		Object o;
		o.mFlags = Object::OBJ_UNKNOWN;
		switch(str[i]) {
		case '#': 
			o.mFlags = Object::OBJ_WALL;
			break;
		case ' ' :
			o.mFlags = Object::OBJ_FLOOR;
			break;
		default:
			break;
		}
		if(o.mFlags != Object::OBJ_UNKNOWN) {
			mStageData(y,x).mFlags = o.mFlags;
			x++;
		} else if(str[i] == '\n') {
			y++; x=0;
		}
	}	
}
//いらん
void Map::zeroClear() {
	for(int i=0;i<mHeight;i++) {
		for(int j=0;j<mWidth;j++) {
			mStageData(i,j).mFlags = Object::OBJ_WALL; //定義していなければすべて壁とみなす。
		}
	}
}
Map::~Map() {
}

int Map::getHeight() const {
	return mHeight;
}

int Map::getWidth() const {
	return mWidth;
}

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

void Map::Show() {
	int h=getHeight(); int w = getWidth();
	for(int i=0;i<h;i++) {
		for(int j=0;j<w;j++) {
			cout << mStageData(i,j).mFlags;
		}
		cout << endl;
	}
}

/*-------------------------
Object型
-----------------------*/




Map::Object::Object() : mFlags(0) {
}
bool Map::Object::checkFlag(unsigned o) {
	if(mFlags&o) {
		return true;
	}
	return false;
}

void Map::Object::setFlag(unsigned objs) {
	mFlags |= objs;
}
void Map::Object::resetFlag(unsigned objs) {
	mFlags = mFlags&(~objs);
}
void Map::Object::switchFlag(unsigned objs) {
	//例:mFlags=111000,objs=001100のとき、110100となり所要のflagが得られる。
	mFlags ^= objs; //eXclusive OR
}


フラグ操作の勉強になりました!XORなんて初めて使った。
とはいえ、クラスを配列のtypeにするのは、速度的に心配ではある。大丈夫なんだろうか?
(どうしても気になるなら、Objectクラスの変数だけstructとして外に出しちゃっていじくるのもいいかもしれないけど…

あと、Mapクラスのprivateないにさらにclassを置いている。いまのところは大丈夫だが、いずれ、publicに置くかもしれない。

ところで静的なクラスの配列を作るにはどうすればよいのかな?

//例
//test.h
#include "GameLib/Framework.h"
using namespace GameLib;


class Test {
public:
	Test();
	Test(int num);
	void Show();
private:
	int a;
};
Test::Test() :a(0){
}

Test::Test(int num) :a(num) {
}
void Test::Show() {
	cout << a << endl;
}


//main.cpp
#include "GameLib/Framework.h"
#include "test.h"
using namespace GameLib;


namespace GameLib{
	void Framework::update(){
//		Map test("mTen.txt");
//		test.Show();

		Test samp[5] = {
			Test(1),Test(2),Test(3),Test(4),Test(5)
		};
		samp[4].Show(); //しっかり4が呼び出される。
		Test samp2[5]; //この場合、デフォルトコンストラクタか、自作の引数なしコンストラクタが呼び出される。
		samp2[3].Show(); //しっかり0が呼び出される。

	}
}

それぞれの要素でコンストラクタが呼ばれている。あとはvector使うとか、placement new使うとかの方法があるらしい。
一番いいのが、stl::vectorを使う方法らしい。

でも静的なクラスの配列を作るのって使い道はあまりなさそう...

クラスメモPart3-2

//Map.cpp
#include "Map.h"
#include "File.h"


#include "GameLib/Framework.h" //coutも
using namespace GameLib;


namespace {
	//Mapコンストラクタ用
	unsigned GetMapSize(const char* stageData) {
		int x=0; int y=0; int tmp =0;
		for(int i=0; stageData[i] != '\0' ;i++) {
			char ch = stageData[i];
			if(ch == '\n') {
				x = (tmp>x) ? tmp : x;
				y++;
				tmp =0;
			} else if(ch >= ' ' && ch <= '~') {
				tmp++;
			}
		}
		return ((x<<16) | y);
	}

}


Map::Map(const char* filename) {
	File file(filename);
	unsigned siz = GetMapSize(file.getData());

	mWidth = (siz >> 16);
	mHeight = (siz & 0x0000ffff);
	mStageData.setSize(mHeight,mWidth);

	zeroClear(); //mStageDataのflagをすべて0に。

	//dataの解析
	//ここも無名名前空間の中に入れればいいかも。
	const char* str = file.getData();
	int x=0; int y=0;
	for(int i=0;str[i] != '\0';i++) {
		FlagOpr o;
		o.Flags = FlagOpr::OBJ_UNKNOWN;
		switch(str[i]) {
		case '#': 
			o.Flags = FlagOpr::OBJ_WALL;
			break;
		case ' ' :
			o.Flags = FlagOpr::OBJ_FLOOR;
			break;
		default:
			break;
		}
		if(o.Flags != FlagOpr::OBJ_UNKNOWN) {
			mStageData(y,x).Flags = o.Flags;
			x++;
		} else if(str[i] == '\n') {
			y++; x=0;
		}
	}	
}

void Map::zeroClear() {
	for(int i=0;i<mHeight;i++) {
		for(int j=0;j<mWidth;j++) {
			mStageData(i,j).Flags = FlagOpr::OBJ_WALL; //定義していなければすべて壁とみなす。
		}
	}
}
Map::~Map() {
}

int Map::getHeight() const {
	return mHeight;
}

int Map::getWidth() const {
	return mWidth;
}

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

void Map::Show() {
	int h=getHeight(); int w = getWidth();
	for(int i=0;i<h;i++) {
		for(int j=0;j<w;j++) {
			cout << mStageData(i,j).Flags;
		}
		cout << endl;
	}
}

//Map.h
#include "Array2D.h" //宣言だけではだめで、インクルードする必要がある。

class Map {
public:
	Map(const char* filename);
	~Map();
	int getHeight() const;
	int getWidth() const;
	void Show();
private:

	//これをcpp側に書くとエラーになる。(たぶん、変数の定義しかしていないので)enumと同じように内部に書く必要がある?
	struct FlagOpr {
		enum Object {
			//列挙子の値はint型
			OBJ_WALL =(0),
			OBJ_FLOOR =(1<<0),
			OBJ_EQUIP = (1<<1),

			OBJ_UNKNOWN = (1<<3),
		};
		unsigned Flags; //これで列挙子のフラグが排他的でなくなる。
	};
	void zeroClear();
	int mHeight;
	int mWidth;
	Array2D<FlagOpr> mStageData;
};

データの解析のところがかなり手間取った感があるが、これでいいだろう。
Width,Heightや、x,yの部分で、(疑似)二次元配列のrow,colがarr(y,x)だったり、arr(Height,Width)になることに注意しよう。

あと、あの本では、Mapコンストラクタの引数がstagedataになっているが、まぁ、ファイル形式で渡せるほうが便利かなと思って
変えてみた。(その結果、処理が若干めんどくさくなっている)