初心者の私にはコードはこう見えます。。。

きょーちゃん
記事: 11
登録日時: 15年前

初心者の私にはコードはこう見えます。。。

投稿記事 by きょーちゃん » 14年前

*おことわり*
これはプログラミング超初心者の私が、platinumというマップエディタに付属の
fmfファイルを読み込むためのサンプルコードを一生懸命解読してみたものです。
新しく覚えたことや、疑問に思ったことを思うがままに書きなぐってます。
乱筆(?)乱文のとても読みづらい(そして長い)ものですが、間違ってる点などご指摘いただければ幸いです。

※引用しているコードはHyperDevice Softwareさんが製作されたものです。
著作権などまずかったら削除します…

[fmfmap.cpp]より

CODE:

 
#include "fmfmap.h"

//-----------------------------------------------------------------------------
//	コンストラクタ
//-----------------------------------------------------------------------------
CFmfMap::CFmfMap(void) : m_pLayerAddr(NULL)
{}
//-----------------------------------------------------------------------------
//	デストラクタ
//-----------------------------------------------------------------------------
CFmfMap::~CFmfMap()
{
	Close();
}	

この辺はよく分からんからパス^^;クラスの何かですね…今度勉強する。。。

CODE:

 
//-----------------------------------------------------------------------------
//	マップを開いてデータを読み込む
// 引数:	szFilePath	= マップファイルのパス
// 戻り値:	正常終了	= TRUE
//			エラー		= FALSE
//-----------------------------------------------------------------------------
BOOL CFmfMap::Open(const char *szFilePath)
{
	Close();

	// ファイルを開く
	HANDLE hFile = CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,	OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

ここではパブリック関数の宣言をしている。
CFmfMapがクラス名、Openが関数名、(const char *szFilePath)は引数。
ちなみにBOOLは型名。真か偽か判定。代入する値はtrueかfalse。

そっから下がずーっと関数の内容。なが~い!

Close()はファイルハンドルをクローズする関数。ここで何してるかはよく分からん。

CreateFileはAPI。オブジェクトを作成または開く。そのオブジェクトのハンドルを返す。
式はこんな感じ。

HANDLE CreateFile(LPCTSTR lpPathName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);

lpPathName:作成または開くオブジェクト名
dwShareMode:オブジェクトへのアクセス・タイプ
dwShareMode:共有方法の設定
lpSecurityAttributes:セキュリティ設定
dwCreationDisposition:ファイルの扱い方を設定
dwFlagsAndAttributes:ファイルの属性とフラグ
hTemplateFile:テンプレート・ファイルのハンドル

ここでは、
作成または開くオブジェクト名:szFilePath
オブジェクトへのアクセス・タイプ:GENERIC_READ(読み取り)
共有方法の設定:FILE_SHARE_READ(読み取り)
セキュリティ設定:NULL(デフォルト)
ファイルの扱い方:OPEN_EXISTING:既存のファイルを開く。指定ファイルが存在しない場合、関数失敗
ファイルの属性とフラグ: FILE_ATTRIBUTE_NORMAL(設定なし)、フラグは省略されてるっぽい。
テンプレート・ファイルのハンドル:NULL(何もない??)

ごちゃごちゃ書いてるが、要はszFilePathで指定したファイルを開いて、それを操作するためのハンドルをhFileに代入しているようだ。

そしてとりあえずここまでの成否を判定。
INVALID_HANDLE_VALUEというのはこのAPIが失敗したときの戻り値。
失敗したらOpen関数はFALSE値を返すということ。

Open関数はまだまだ続く・・・
こっからはヘッダ情報に関して。

CODE:

 

// ヘッダ情報を読む
	DWORD dwReadBytes;
	if (!ReadFile(hFile, &m_fmfHeader, sizeof(FMFHEADER), &dwReadBytes, NULL) ||
		dwReadBytes != sizeof(FMFHEADER))
		goto error_return;

DWORDは型名。32ビット符号なし整数。???^^;
ReadFileはファイルを読み取るAPI。冒頭の!は・・・論理演算子か?

ReadFileの型は以下のとおり。
ReadFile(
HANDLE hPort,
LPVOID lpBuffer,
DWORD NumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);

hPort:CreateFileで得られたハンドル。
lpBuffer:読み取ったデータを受け取るバッファのアドレス
NumberOfBytesToRead:読み取るデータのサイズをバイト単位で指定
lpNumberOfBytesRead:実際に読み取ることができたデータサイズを受け取る変数
lpOverlapped:オーバーラップ構造体と呼ばれる構造体のアドレス

ここでは、
CreateFileで得られたハンドル:hFileに代入されてるやつ。
読み取ったデータを受け取るバッファのアドレス:&m_fmfHeader
読み取るデータのサイズをバイト単位で指定:sizeof(FMFHEADER)

:sizeofは演算子。渡された型や変数のメモリサイズを調べる。

[fmfmap.h]によると、FMFHEADERは構造体のようだ。
以下[fmfmap.h]より
typedef struct tag_FMFHeader
{
DWORD dwIdentifier; // ファイル識別子 'FMF_'
DWORD dwSize; // ヘッダを除いたデータサイズ
DWORD dwWidth; // マップの横幅
DWORD dwHeight; // マップの高さ
BYTE byChipWidth; // マップチップ1つの幅(pixel)
BYTE byChipHeight; // マップチップ1つの高さ(pixel)
BYTE byLayerCount; // レイヤーの数
BYTE byBitCount; // レイヤデータのビットカウント
}FMFHEADER;
つまりFMFHEADERのサイズ分データを読み取る。過不足なしってことか。

実際に読み取ることができたデータサイズを受け取る変数:&dwReadBytes
何のためにあるのかわからんが不要でもNULLにできないらしい。

オーバーラップ構造体と呼ばれる構造体のアドレス:NULL(よくわからんが不要なときはNULLらしい)。

この後に出てくる||はorの意味。!=は両辺が等しくない。
ここのif分は条件式がtrueならgoto error_returnを実行することになっている。
!ReadFile=true→not ReadFile=true→ReadFile=falseということだろう。
つまりReadFileが失敗(読み取り失敗)または
読み取るべきデータのサイズと実際に読み取れたデータのサイズが一致しない場合、error_returnに飛ぶ。

次いってみよう!

CODE:

// 識別子のチェック
	if (memcmp(&m_fmfHeader.dwIdentifier, "FMF_", 4) != 0)
		goto error_return;

memcmpはn バイトメモリブロックの比較を行う関数。

int memcmp(const void *buf1, const void *buf2,size_t n);

const void *buf1 : 比較元メモリブロック1
const void *buf2 : 比較元メモリブロック2
size_t n : 比較バイト数

ここでは、&m_fmfHeader.dwIdentifier
(さっきヘッダ情報を読み込んだバッファのファイル識別子が入ってるはずのところ)と、

"FMF_"を4バイト比較している。
"_"まで比較する必要があるのね…

で、戻り値はこう。
正 : buf1 > buf2
0  : buf1 = buf2
負 : buf1 < buf2
ここでは戻り値が0じゃなかったらerror_returnに飛びます。

次~

CODE:

// メモリ確保
	m_pLayerAddr = new BYTE[m_fmfHeader.dwSize];
	if (m_pLayerAddr == NULL)
		goto error_return;

[fmfmap.h]によると、
// レイヤーデータへのポインタ
BYTE* m_pLayerAddr;
さらにウインドウズにもともとあるっぽい定義から、
typedef unsigned char BYTE;


unsigned:
int型とchar型の変数に対して負の数を扱えないようにするために使う型修飾子。
符号を記憶しておく必要がなくなるため、より大きい正数が扱えるようになるらしいが、その必要性全くわからず。
まあいい、次。

new:
メモリの動的確保を行う演算子。関数ではないってとこがポイントらしいがよく分からん。
ここで、
dwSize; // ヘッダを除いたデータサイズ
ということだったので、

m_pLayerAddr = new BYTE[m_fmfHeader.dwSize];
というのは
m_pLayerAddrに、BYTE型の構造体(要素数はm_fmfHeader.dwSize)を入れるための領域を確保する感じか。
で、もしm_pLayerAddrが空っぽになっちゃったらerror_returnへ。

で、ここまで来てようやくレイヤーデータを読めるらしい・・・

CODE:

// レイヤーデータを読む
	if (!ReadFile(hFile, m_pLayerAddr, m_fmfHeader.dwSize, &dwReadBytes, NULL) ||
		dwReadBytes != m_fmfHeader.dwSize)
		goto error_return;

また出たなReadFile。
ところで、この流れってデータ読み取りしてから内容をチェックするんじゃなくて、
内容をチェックするためのif文を書くだけで読み取りもしてくれるってことなのか??
まあいい。
ここは、m_pLayerAddr(さっき作ったやつね)に読み取ったデータを入れて、
サイズをチェックして、読み取りがちゃんとできてないようならerror_return。
さっきのはヘッダ部分だけだったけど、今度はレイヤーデータで同じチェックをするらしい。ご苦労なこって。

そしていよいよフィニッシュです。

CODE:

// 正常終了
	CloseHandle(hFile);
	return TRUE;

error_return:
	// エラー終了
	CloseHandle(hFile);
	Close();
	return FALSE;
}

ここは二つまとめて。
CloseHandle:
これもウインドウズにもともとあるっぽい関数で、開いているオブジェクトハンドルを閉じる。
hFileはもう用済みってことか。
ここまで問題なくきた場合にはTRUEが返り、どこかでerror_returnに飛ばされた場合にはFALSEが返る。

ふぅ・・・ここまででひとつの関数だ。
BOOL型のOpenという名前の関数で、真か偽か判定するものです。すっかり忘れry
ここで復習Open関数の流れ。
ファイルを開く

ヘッダ情報を読む

識別子がFMFであることを確かめる

レイヤーデータを入れるメモリを確保

そこにデータを入れる

この関数がうまく行ったかどうか判定

ってことですね。
全部見出しにかいてあるんですが、ここまで調べないと何のことだか分からなかったのです^^;

なんでファイルを開く所だけerror_returnじゃなくて直接FALSEを返してるのかね??

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。

投稿記事 by Dixq (管理人) » 14年前

勉強熱心ですね^^
最初からWINAPIに手を出してしまうとは・・・、私にはそんな超人みたいなことできませんでした!

DXライブラリってもうやりたいこと簡単になんでもできてしまうので最初のうちはず~っとDXライブラリ使ってました。
しかしゲームはDXライブラリを使うんですよね?それならWINAPI使う必要ないのでは?

きょーちゃん
記事: 11
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。

投稿記事 by きょーちゃん » 14年前

ありがとうございます^^
できればAPIとか触れたくなかったのですが、DXライブラリでfmfファイルを扱うコードを自分では書けなくて・・・
サンプルは描画処理のところが一部しか書かれてなくて、そこを書き足す必要があるようなのですが、
どの変数をどのように扱えば描画できるのか分からなくて手が付けられなかったのです。
そこまでの処理が何してるのか理解できたら描画処理に必要な部分をひっぱってこれるかな~ってことで始めました。
正直APIとか使えるようになる気はしないです・・・

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前

Re: 初心者の私にはコードはこう見えます。。。

投稿記事 by softya(ソフト屋) » 14年前

>Close()はファイルハンドルをクローズする関数。ここで何してるかはよく分からん。

Close()はメモリを破棄するだけの関数ですよ。あとで出てくるのでまたそこで書きます。

newは、クラスを動的確保するときは重要なんですけどね。

ちなみにReadFileを2回に分けているのはレイヤー部分のサイズがヘッダを読まないと分からないからです。

>なんでファイルを開く所だけerror_returnじゃなくて直接FALSEを返してるのかね??
それはファイルオープンに成功していないのにCloseHandle(hFile);するとまずいからですね。

いや、ご苦労様です。
十分解析できていると思います。
なんで色々チェックするかというとファイルの破損やファイルの間違いなども考慮してるからですね。
人に配布する物は思わぬエラーが出るので、最初から対策してあるんです。


ちなみにRPG講座のCFmfMap::Openと同じ部分は、

CODE:

	//	マップファイルをオープンする。
	FILE *fp = fopen( MapMngData.cur_mapDef->mapFile, "rb" );
	MACRO_ASSERT(fp!=NULL);
	
	//	マップヘッダをロードする。
	fread( &MapMngData.fmfHeader, sizeof(FMF_HEADER), 1, fp );
	
	//	メモリを確保する。
	BYTE *mapdata = (BYTE *)malloc( MapMngData.fmfHeader.dwSize );
	MapMngData.allMapData = mapdata;//保存
	MACRO_ASSERT(mapdata!=NULL);
	
	//	残りの部分を確保したメモリに読み込む
	fread( mapdata, MapMngData.fmfHeader.dwSize, 1, fp );
	
	//	ファイルを閉じる。
	fclose( fp );
	
	//	レイヤー毎にアドレスを振り分ける。
	int layerSize = MapMngData.fmfHeader.dwSize / MapMngData.fmfHeader.byLayerCount;
	for( int ly=0 ; ly<MAP_LAYER_MAX ; ly++ ) {
		if( ly < MapMngData.fmfHeader.byLayerCount ) {
			MapMngData.MapData[ly] = mapdata + (layerSize*ly);
		} else {
			MapMngData.MapData[ly] = NULL;//このレイヤーは存在しない。
		}
	}
となっています。