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になっているが、まぁ、ファイル形式で渡せるほうが便利かなと思って
変えてみた。(その結果、処理が若干めんどくさくなっている)