ポインタを使ったメモリの読み書き
既にHSP3系が登場してしばらく経ち、皆はソチラに移行しているだろうから、
HSP2の講座を使う人も少なくなってしまったのでしょうけど更新しておきます。
メモリを読み込む命令としてpeek命令、wpeek命令を、
メモリに書き込む命令としてpoke命令、wpoke命令を以前に紹介しました。
主にバイナリ操作を行う時に使用していたもので、対象となるものは変数内のデータ。
通常はコレで何の問題もありませんが、HSPに用意されたものではないAPI関数を使用するとき、
API関数によってはHSPで用意した変数を使わないことがあるのです。
言い換えると、大体の場合は外部のAPI関数といえども、
パラメータとして使用するデータは変数に入れて渡したり、
受取先として変数のポインタを渡して、その領域に何らかのデータを入れてもらったりするわけでが、
中には、予めWindows側が用意した領域にデータがセットされて、
呼出元のHSP側に通知されるのはそのデータ領域を指し示すポインタだけと言うAPI関数も存在します。
もし、アドレスだけがわかるAPI関数をHSPで使いたくても、
今までに紹介した命令だと「HSPで用意した変数にしかアクセスする手段がない」ため手詰まりです。
この章でコレから紹介する命令は、HSPで用意した変数だろうがそうでなかろうが、
任意のアドレスから任意のバイト数分を対象とするものなので、
ポインタが渡されるAPI関数やマシン語による処理を使うことができるようになります。
1つ目に紹介するのは、任意のアドレスから1バイト読み込むll_peek1命令。
書式は「ll_peek1 読込先変数, アドレス」となっており、
アドレス部分から読み込んだ1バイト分を読込先変数にセットするというもの。
peek命令のp2である変数指定部分が変数位置を示す基点アドレスに変わって、
p3であるインデクスとが合わさって1つになったイメージですね。
サンプルとしてはわざわざする意味のない無駄なことですが、
変数のポインタを取得して、そのアドレスからデータを読み込んでみるというものを書いてみます。
data = "ABC"
ll_getptr data
ll_ret ptr
repeat 3
ll_peek1 v, ptr + cnt // 「peek v, data, cnt」と同じこと
mes v
loop
stop
続いて、読み込みに対する1バイト書き込みを行うll_poke1命令について紹介しましょう。
書式は「ll_poke1 書き込む値, アドレス」となっており、
アドレス部分に1バイト分のデータ(0〜255)をセットするというもの。
コレもpoke命令のp1とp2である「変数(のポインタ基点)とインデクス」が一緒になった感じですね。
sdim data, 4 // 文字列型にしなければ数値データとして扱われる
ll_getptr data
ll_ret ptr
repeat 3
ll_poke1 cnt + 'X', ptr + cnt // 「poke data, cnt, cnt + 'X'」と同じこと
loop
mes data
stop
続いて、ll_peek1命令の2バイト読込版であるll_peek2命令を紹介します。
1バイトから2バイト読み込みに変わった以外に違いはなく、書式は「ll_peek2 読込先変数, アドレス」。
コレもwpeek命令のp2とp3である「変数(のポインタ基点)とインデクス」が一緒になった感じです。
ll_peek2命令はll_peek1命令と使い方が一緒ですから、次のll_poke2命令も一緒に紹介しておきましょう。
ll_poke2命令も書式は「ll_poke2 書込値, アドレス」となっており、
ll_poke1命令の2バイト書き込みを行うwpoke命令の「変数(のポインタ基点)とインデクス」が
一緒になった感じで、それ以外に違いはありません。
get = "あいうえお"
sdim set, 12
ll_getptr get : ll_ret ptr.0
ll_getptr set : ll_ret ptr.1
repeat 5
ll_peek2 v, ptr.0 + (cnt * 2) // 「wpeek v, get, cnt * 2」と同じこと
ll_poke2 v, ptr.1 + (cnt * 2) // 「wpoke set, cnt * 2, v」と同じこと
loop
mes set
stop
続いての紹介は4バイト版の読み書きを行う命令について。
HSP3には1バイト、2バイトの他、4バイト単位で変数にデータを読み書きする命令が追加されました。
HSP2の段階では用意されてないのでイメージしにくいかもしれませんが、
使い方は1バイトや2バイト単位のものと同じで、読み書きするサイズが4バイトに変わっただけです。
なぜ1、2、と来た次が3ではなくて4なのか?
1や2は半角文字、全角文字の取り扱い単位として存在意義がありますが、
3バイト単位で行うものはあるにはありますが、そこまで重要なものではありません。
逆に4バイトは32ビットパソコンにおいて1つの単位ですから、利用価値が色々とありそうですね。
4バイト単位で読み込みを行うのはll_peek4命令。
ll_peek1命令、ll_peek2命令と変わらず、書式は「ll_peek4 読込先変数, アドレス」。
書き込みを行うのは、そう…ll_poke4命令で、書式は「ll_poke4 書込値, アドレス」。
サンプルはloadlibのサンプルフォルダにある総和を算出する機械語スクリプトを抜粋したものです。
ll_getptr proc_sum : ll_ret p_sum
p = p_sum
ll_poke4 0x8B4C2404, p, 1 : p += 4 // mov ecx,[esp+4]
ll_poke2 0x33C0, p, 1 : p += 2 // xor eax,eax
ll_poke1 0xEB, p : p++ : ll_poke1 3, p : p++ // jmp __jmp1
// __jmp2
ll_poke2 0x03C1, p, 1 : p += 2 // add eax,ecx
ll_poke1 0x49, p : p++ // dec ecx
// __jmp1
ll_poke2 0x85C9, p, 1 : p += 2 // test ecx,ecx
ll_poke1 0x7F, p : p++ : ll_poke1 -7, p : p++ // jg __jmp2
// ret
ll_poke1 0xC3, p : p++
max = 1000000
ll_callfunc max, 1, p_sum
ll_ret total
mes "1から" + max + "までの和:" + total
total = 0
repeat max, 1
total += cnt
loop
mes "1から" + max + "までの和:" + total + "(確認用)"
stop
最後に紹介するのは、指定したバイト数を読み書きする命令です。
1バイト指定すればll_peek1命令、ll_poke1命令と同じこと、
2バイト指定すればll_peek2命令、ll_poke2命令と同じこと、
4バイト指定すればll_peek4命令、ll_poke4命令と同じことになります。
そういう意味では、別に1・2・4バイト用と分けて用意せずにコレ1つだけあればよいと思うのですが、
あまり使い道がないと言ってた3バイトや5バイト以上の読み書きを行う時に有効なものです。
任意のアドレスから任意のバイト数分だけ読み込むのはll_peek命令で、
書式は「ll_peek 読込先変数, アドレス, 読込サイズ」です。
一方の任意のアドレスから任意のバイト数分だけ書き込むのはll_poke命令で、
書式は「ll_poke 書込値, アドレス, 書込サイズ」です。
サンプルはVRAMデータのRGB3バイト分を読み込むというもの。
ウィンドウ右端以外はll_peek4命令で4バイト一度に取得して4バイト目だけを切り捨てればOKですが、
ウィンドウ右端のVRAMデータを取得する場合だけは、
パディングデータも取得範囲内に含めるとエラーになってしまうという問題があるので、
サンプルの要領で3バイト取り出しを行うか、
1バイトを3回または、1バイト2回と3バイト1回の取り出しにわけなければなりません。
尚、VRAMの詳細についてはTipsをご覧下さい。
mref vram, 66
ll_getptr vram : ll_ret ptr
color 0x24, 0x68, 0xAC
pset 0, winy - 1
ll_peek col, ptr, 3 // (0, winy - 1)のRGB輝度3バイト分を取得
str col, 16
mes "0x" + col // 指定した色と同じ輝度が返る
stop