バイナリ操作
ファイルを大きく分けると、「テキストファイル」と「バイナリファイル」になります。
テキストファイルとは、文字列データ以外の情報を含まないテキストだけで構成されたプレーンテキストで、
英数1バイト以外は文字コードにより違いこそあれど、極めて単純な構造となっています。
一方のバイナリファイルとは、画像ファイルやサウンドファイルのように
関連しているアプリケーションで開かなければ、データを正常に編集できませんし表示もされません。
ファイルフォーマットにより、データスタイルやレイアウト等、各コードが何を意味するか設定されてます。
画像の表示や、サウンド・ムービーの再生はHSPに用意された命令を使用することで
プログラマであるアナタは読み込むデータを解析する必要はありません。
しかし、対応していない拡張子のファイルを読み込む場合や、対応しているファイルであっても、
本来とは異なる情報を読み込む(例えば画像ファイルの場合に画像表示ではなくサイズだけ読み込むとか)には
自前で解析して展開する必要がありますね。
コチラで紹介しているバイナリファイルを読み込む命令を使用して変数にデータを格納した後、
部分的にデータの一部を取り出すワケですが、noteget関数等のメモリノートパッド命令はおろか、
strmid関数、instr関数、getstr命令等でもうまく行かないことがあります。
これらは全てテキスト操作用の命令または関数であり、
既に紹介しているように、(通常はEOFを使うが)HSPでの文字列終端を示すNULLをまたぐと、
途中までしか取り出せない、または検索できない問題が起こるわけです。
特定位置のデータを確実に取り出すには、次の関数を使用しましょう。
コード = peek(対象変数, 位置)
コード対象変数の取出位置から1バイト分のコードを返す。
対象変数取出元の変数を指定する。
位置変数の先頭を0とした取出インデクスを指定する。
第1パラメータの変数の、第2パラメータの位置から1バイト分だけデータを取り出し、
バイナリデータ・テキストデータ関係なく、そのコード(0〜255)を結果として返します。
テキストも取り出せますが、テキストを主としていませんから、文字列データであっても、
取り出しはコンピュータが判別できる0と1の組み合わせによるコード(数値)になります。
表示させても文字ではなく数値であるため、テキストにおいてはその文字コードがどんな文字を表すのか、
デバック時などにおいては確認し辛くなりますが、その一方でif命令で範囲指定できますので、
取り出したコードが「A」から「Z」に含まれるか等の条件指定が容易となります。
下記に、取り出した文字コードがシフトJISにおいて、全角文字か半角文字かを判別し、
処理を分岐させてみるサンプルを載せておきます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
	sdim data, 20
	pos 10, 10 : input data, 120, , 10
	pos 120, 10 : button "表示", *show
	stop

*show
	color 255, 255, 255 : boxf : color
	pos 50, 50
	repeat strlen(data)
		// 対象文字コードが129〜159、または224以上は全角文字
		code = peek(data, cnt)
		if code >= 129 & code <= 159 | code >= 224 : i = 2 : else : i = 1
		mes "" + i + "byte : " + strmid(data, cnt, i)
		continue cnt + i
	loop
文字コードを取り出すpeek関数に対し、文字コードを変数に埋め込む命令が存在しますので、
peek関数のペアとして次の命令も覚えておきましょう。
poke 対象変数, 位置, 文字コード
対象変数埋込先の変数を指定する。
位置変数の先頭を0とした埋込先インデクスを指定する。
文字コード埋め込む半角文字の文字コード、または文字列を指定する。
使い方は、第1パラメータに書き換える変数を、
第2パラメータに変数内の書換位置を、第3パラメータに書換後の文字コードか文字列を指定します。
もし、第3パラメータに文字コードを指定したなら、第2パラメータ位置が指定文字コードの文字に変わり、
第3パラメータに文字列を指定したなら、第2パラメータ位置以降の文字列が指定文字列に置き換わります。
「ABCDEFG」と設定された文字列のインデクス2から「XYZ」文字列で置き換えたら、「ABXYZFG」ではなく、
「ABXYZ」になるということです。「ABXYZFG」のように、後に続く元の文字列を残しておきたい場合は、
第3パラメータに文字列ではなく、埋め込む文字コードを1つずつ入れていくしかありません。
さて、HSPは検索等の文字列操作を高速に処理するためにNULLを文章末端と位置づけ、
それ以降は処理対象とはしないと書きました。このpoke命令による文字列指定もそうなんです。
poke命令で原文より短い文字列を指定すると、上記「ABXYZ」へと置き換わったように見えますが、
実は、末端にNULL制御文字を埋め込んで「ABXYZ[NULL]G」としているだけです。
poke命令での書き換え処理方法はさておき、何でこんな話を書くかと言うと、
この「文章末端にNULLを置くだけ」の仕様により、HSP2時代に比べると改善はされましたが、
「文章末端にゴミ(意図しない余計な文字列)がつく」という問題があります。
その原因として、何らかの問題で末端に付くはずのNULLが付かないがために、
今回は必要のない文字列が付いてしまうというものです。
プログラマであるアナタは、問題がないようにソフト作成して一般公開しても、
この問題が発生して利用者から苦情が出たとしましょう。
HSP側に問題があっても、利用者にとっては公開しているアナタへ問題の目を向けるわけで、
現時点のHSPにあるバグをアナタの手で回避しなければなりません。
その時にこの仕様・問題を知っているのと知らないのとでは、
修正に掛かる時間や方法が全く変わってきますから、覚えておくに越したことはありません。
ゴミが付く場合は、HSPが自動的にセットしてくれているNULLを、自前でセットしてしまいましょう。
 1
 2
 3
 4
 5
 6
	buf = "文章末端にゴミが残ってる" // 元の文字列
	buf = "書換test!"                // 変数bufを置き換える
	poke buf, 9, '?'                 // 何らかの問題でNULLがセットされなかったと仮定する
	mes buf                          // 表示したらゴミがついている
	poke buf, 9, 0                   // うまくいかない時は自前でNULL(コード0)を末端位置にセット
	mes buf                          // 正常にいきました
peek関数は1バイト分の読込しかできません。
半角・全角文字の判別はできるけど、全角1文字分を取得したい時でも2回実行しなければなりません。
こんな時は2バイト分を読み込む次の関数を使用しましょう。
コード = wpeek(対象変数, 位置)
コード対象変数の取出位置から2バイト分のコードを返す。
対象変数取出元の変数を指定する。
位置変数の先頭を0とした取出インデクスを指定する。
関数名はW(Double)PEEK。
使い方はpeek関数と同じで、違いは第3パラメータ位置から2バイト分取得するということだけ。
当然ながら、2バイト取得しますので、コードの範囲は2バイト分(0〜65535)となります。
対象文字列が半角2文字の場合、wpeek関数で取得すると2文字の合計値、
下位に1バイト目、上位に2バイト目の値が同時にセットされますので、
半角・全角が混在する可能性があるなら、1バイトずつ取り出してチェックしましょう。
wpeek関数だけで、それぞれを確認する場合、次のようにするとそれぞれ取り出すことができます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
	sdim buf, 4
	sdim tmp, 4
	pos 10, 10 : input buf, , , 2
	pos 80, 10 : button "確認", *check
	stop

*check
	color 255, 255, 255 : boxf :color
	i = wpeek(buf) & 0xFF // 1バイト目を取り出す
	pos  10, 50 : mes "1バイト目:0x" + strf("%2x", i)
	poke tmp, , i
	pos 160, 50 : mes "文字:" + tmp
	i = wpeek(buf) >> 8 // 2バイト目を取り出す
	pos  10, 70 : mes "2バイト目:0x" + strf("%2x", i)
	poke tmp, , i
	pos 160, 70 : mes "文字:" + tmp
2バイト版のpeek関数があるように、2バイト版のpoke命令も存在しますのでコレも一緒に覚えましょう。
wpoke 対象変数, 位置, 文字コード
対象変数埋込先の変数を指定する。
位置変数の先頭を0とした埋込先インデクスを指定する。
文字コード埋め込む全角文字の文字コードを指定する。
命令名はW(Double)POKEpoke命令とほぼ同じですが、poke命令と違い、第3パラメータには文字列を指定できず、
第1パラメータの変数の、第2パラメータ位置に2バイト分の文字コードを指定するだけです。
全角文字ではなく半角文字2つをセットする時は、
wpeek関数の取得と同じ様に下位バイトに1バイト目、上位バイトに2バイト目を設定してください。
尚、下位バイトだけしか設定していない場合は、上位バイトは0なので、NULLをセットしたとみなし、
以降の文字列が予めセットされていても、HSPの仕様(NULLまでが処理対象)により、
ソレよりも前の文字列しか表示されなくなりますので注意しましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	sdim data, 128
	sdim tmp, 4
	mesbox data, ginfo_winx, 45

*main
	if len ! strlen(data) : gosub *draw
	wait 50
	goto *main

*draw
	redraw 0
	color 255, 255, 200 : boxf : color
	x = ginfo_winx - 10
	gosub *kaigyou
	len = strlen(data)
	repeat len
		// 2バイト文字
		if peek(data, cnt) >= 129 & peek(data, cnt) <= 159 | peek(data, cnt) >= 224 {
			wpoke tmp, , wpeek(data, cnt) // 2バイト読込、2バイト書込
			type = 2
		// 1バイト文字
		} else {
			// 改行
			if peek(data, cnt) = 13 {
				gosub *kaigyou
				continue cnt + 2
			}
			wpoke tmp, , peek(data, cnt) // // 1バイト読込、2バイト書込(2バイト目を消去)
			type = 1
		}
		// 文字描画
		pos x, y : mes tmp
		if y >= 420 : gosub *kaigyou : else : y += 20
		continue cnt + type
	loop
	redraw 1
	return

*kaigyou
	x -= 20
	y = 50
	return
HSP2では、HSP用変数ではなくポインタの指し示す先のアドレスから4バイト読み込むll_peek4命令、
ポインタの指し示す先のアドレスへ4バイト書き込むll_poke4命令があったものの、
HSPの変数への読み書きを行うpeek系、poke系命令では、2バイトまでだけでした。
HSP3からは4バイトの読み込みを行う関数が追加されたのでこれから紹介します。
コード = lpeek(対象変数, 位置)
コード対象変数の取出位置から4バイト分のコードを返す。
対象変数取出元の変数を指定する。
位置変数の先頭を0とした取出インデクスを指定する。
恐らく4バイト読み込みを意味するLongPEEKの省略形であると思われるlpeek関数は、
使い方は、peek関数、wpeek関数と同じで、対象が4バイトに変わるだけです。
文字には半角(1バイト)文字と、全角(2バイト)文字しかありませんから、
文字から文字コードへの変換用には4バイトも取得できるlpeek関数を使用することありません。
今章の初めに挙げたバイナリファイルから4バイト範囲の数値を取得するといった場合に使えるものですので、
peek関数、wpeek関数と比較すると、その用途・使用頻度は随分と劣るものかと思います。
下記はBMPファイルから画像サイズだけを読み込んでみるサンプルです。
尚、BMPのファイルフォーマットがそれぞれ何を意味しているか、については載せておりません。
TIPSに掲載するまでの間、各自で参考ページを検索するようにしてください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
	dialog "bmp", 16
	if stat {
		sdim bmp, 26
		bload refstr, bmp, 26
		// ファイルタイプ(BM)の確認
		if wpeek(bmp, 0) = 'B' + ('M' << 8) {
			// OS/2 Bitmap
			if lpeek(bmp, 14) = 12 {
				w = wpeek(bmp, 18)
				h = wpeek(bmp, 20)
			// Windows bitmap
			} else {
				w = lpeek(bmp, 18)
				h = lpeek(bmp, 22)
			}
			dialog "幅\t" + w + "pixel\n高さ\t" + h + "pixel", , getpath(refstr, 8) + "のサイズ"
		} else {
			dialog "正常なBMPファイルではありません。"
		}
	}
	end
HSP3から追加された4バイトの書き込みを行う命令を紹介します。
lpoke 対象変数, 位置, 文字コード
対象変数埋込先の変数を指定する。
位置変数の先頭を0とした埋込先インデクスを指定する。
文字コード埋め込む4バイト分のコードを指定する。
命令名は4バイト書き込みを意味するLongPOKEの省略形であると思われ、
使い方はwpoke命令(文字列の埋め込みができないのでpoke命令とは若干異なります)と同じで、
埋め込む文字コードが4バイト分に増えているだけです。
lpeek関数での説明で既述したのと同じく、文字には1〜2バイトコードしかなく、
文字単位で考えた場合、文字コードから文字に変換するには大きすぎる範囲であり、
使い道としては、バイナリファイルへ4バイト範囲の数値を設定するといった場合に使える程度ですので、
poke命令、wpoke命令と比較すると、その用途・使用頻度は随分と劣るものかと思います。
wpoke命令で説明したのと同じくり、4バイト未満のコード設定を行うと、
4バイト目に0(NULL文字)を設定したとみなされ、lpoke命令の埋込位置より後に文字列が続いていても
埋め込んだ位置までしか表示されなくなり、文字列操作関数・命令での編集もできなくなってしまいます。
ファイルフォーマットの仕様上、NULLが入って途切れようが構わない場合は問題ありませんが、
一連の文字列のうちの一部を書き換える場合には注意するようにしてください。
手軽な良いサンプルを思いつかなかったので、
4バイト数値を複数保存し、それを読み込むサンプルを載せておきます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	dim data, 6
	sdim tmp, length(data) * 4 + 1
	objsize 100, 20
	pos 10, 10 : button gosub "保存", *save
	pos 10, 35 : button gosub "読込", *load
	pos 10, 60 : button gosub "乱数", *rand
	pos 10, 85 : button gosub "クリア", *clear
	foreach data
		pos 200, cnt * 25 + 10 : input data.cnt, 70, , 9
	loop
	randomize
	gosub *clear
	stop

*save
	foreach data
		lpoke tmp, cnt * 4, data.cnt // 4バイトを積み重ねる
	loop
	bsave "hspbc_beginner39.dat", tmp, length(data) * 4
	dialog "保存しました"
	return

*load
	exist "hspbc_beginner39.dat"
	if strsize = -1 : return
	bload "hspbc_beginner39.dat", tmp
	foreach data
		objprm 4 + cnt, lpeek(tmp, cnt * 4)
	loop
	return

*rand
	foreach data
		objprm 4 + cnt, rnd(1024) * rnd(1024) * rnd(1024)
	loop
	return

*clear
	foreach data
		objprm 4 + cnt, 0
	loop
	return