第2章の終わりに書いたように、第2章では基本的な命令についてはごく一部しか解説していません。 従って、第3章では基本的な命令について解説していこうかと思ったのですが、個々の命令の解説に時間を割いていてはHSP特有の部分まではなかなかたどり着けないのではないかと思いました。 そこで、個々の命令の解説は他のサイトや参考書などに任せるとして、第3章からはHSPのプラグインやWindowsのプログラミングに特有の事柄を中心に解説していきたいと思います。
さて、それでは早速HSPのプラグイン特有のPVAL構造体について解説したいのですが、そもそも構造体って何?という方も多いのではないでしょうか? 構造体に関する説明はC言語の入門書ならたいてい載っているのですが、C言語が分からない人にとっては何のことだかさっぱり分からないのではないかと思います。 これについてはT's-Neko氏のHP(ソフトウェア・デザイン館 Sage Plaisir 21)に比較的分かりやすく書いてありますので、そちらを参照してください。
上のリンクをクリックし、トップページから「ソフトウェア・デザイン館より(エッセイ)」、「6.『パソコン・プログラミング言語の歴史(6) - 構造体』」とたどっていくと、構造体についての説明があります。
HSPでは、変数に数値を代入したり、文字列を代入したりできます。 また、dim命令で配列の要素の数を宣言をすることもできます。 さらに、str命令で数値を文字列に変換したり、int命令で文字列を数値に変換したりする事ができます。
例えば、ある変数aがあったとしましょう。 この変数aは数値が入った変数かもしれないし、文字列が入った変数かもしれません。 10個の要素を持った配列かもしれないし、100個の要素を持った配列かもしれません。 つまり、変数の型は何であるかとか、要素はいくつあるかといったような事が、HSPの内部できちんと管理されている必要があるわけです。
このように、スクリプト内で使われている個々の変数に関する情報(変数の型や、要素の数など)を覚えておくための、HSPの内部で使われている特別な変数の事をPVAL構造体と呼んでいます。 また、バージョン2.5以降では変数の仕様が拡張されているため、PVAL2構造体にも変数の型などの情報が入っています。
さて、PVAL構造体とは何かが分かったところで、MASM32でPVAL構造体を利用できるようにPVAL構造体を宣言しましょう。 HSPの公式サイトにある「HSPからのDLL呼び出し方法リファレンスマニュアル」の中で、PVAL構造体とPVAL2構造体は以下のように宣言されています。それぞれのメンバの意味については、「HSPからのDLL呼び出し方法リファレンスマニュアル」を参照してください。
typedef struct PVAL2 { // Memory Val structure (ver2.5 Type) // short flag; // type of val short mode; // mode (0=normal/1=clone/2=alloced) int len[5]; // length of array 4byte align (dim) int size; // size of Val (not used) char *pt; // (direct val) or (ptr to array) } PVAL2; typedef struct PVAL { // Memory Val structure (Old Type) // short flag; // type of val short mode; // mode (0=normal/1=clone/2=alloced) short len[5]; // length of array 4byte align (dim) short version; // version check code (2.4 = 0) char *pt; // (direct val) or (ptr to array) PVAL2 *realptr; // real ptr to ver2.5 PVAL } PVAL; |
「HSPからのDLL呼び出し方法リファレンスマニュアル」では、C言語でHSPのプラグインを作るという前提で解説されています。 従って、上記のプログラムをそのまま書き写してもMASM32は理解してくれません。 MASM32では以下のように記述します。 ちなみにMASM32では、sizeという名前は予約語になっていて使えないので、代わりに_sizeという名前にしました。
PVAL2 struct ; Memory Val structure (ver2.5 Type) ; flag word ? ;type of val mode word ? ;mode (0=normal/1=clone/2=alloced) len dword 5 dup(?) ;length of array 4byte align (dim) _size dword ? ;size of Val (not used) pt dword ? ;(direct val) or (ptr to array) PVAL2 ends PVAL struct ; Memory Val structure (Old Type) ; flag word ? ;type of val mode word ? ;mode (0=normal/1=clone/2=alloced) len word 5 dup(?) ;length of array 4byte align (dim) version word ? ;version check code (2.4 = 0) pt dword ? ;(direct val) or (ptr to array) realptr dword ? ;real ptr to ver2.5 PVAL PVAL ends |
PVAL2構造体を利用したサンプルとして、0〜255の文字コードが入った数値型の変数を、文字列型の変数に変換するプラグインを作ってみます。 まずは以下のソースを見てください。
PVALTEST.ASM | .486 .model flat,stdcall .code option casemap :none PVAL2 struct ; Memory Val structure (ver2.5 Type) ; flag word ? ;type of val mode word ? ;mode (0=normal/1=clone/2=alloced) len dword 5 dup(?) ;length of array 4byte align (dim) _size dword ? ;size of Val (not used) pt dword ? ;(direct val) or (ptr to array) PVAL2 ends DLLMain proc p1,p2,p3 mov eax,1 ret DLLMain endp pvaltest proc export pval2:ptr,dummy1,dummy2,dummy3 push esi push edi mov esi,pval2 cmp [esi].PVAL2.flag,4 jz @f mov eax,6 ;パラメータの型が違います pop edi pop esi ret @@: mov edi,[esi].PVAL2.pt cmp dword ptr [edi],255 jbe @f mov eax,3 ;パラメータの数値が異常です pop edi pop esi ret @@: mov [esi].PVAL2.flag,2 mov eax,0 pop edi pop esi ret pvaltest endp end DLLMain |
まず、push esi、pop ediは第2章で解説したので分かりますね。 esiレジスタとediレジスタの値をスタックへ退避しています。 次に、PVAL2構造体へのポインタをesiレジスタへ代入しています。
さて、重要なのはその次の行です。 第2章の説明では、esiレジスタが指しているメモリを参照するには[esi]と書くんでしたね。 では、esiレジスタが指している構造体のメンバを参照するにはどうするのかというと、[esi]の後ろにピリオドを書き、構造体の名前を書き、ピリオドを書き、構造体のメンバ名を書きます。 つまり、[esi].PVAL2.flagというのは、esiレジスタが指しているメモリをPVAL2構造体とみなし、flagという名前のメンバを参照しなさい、という意味になります。
次のcmp命令は、第1オペランドと第2オペランドの値を比較する命令です。 第1オペランドと第2オペランドの値を比較した結果、値が等しければ、その次のjz @fで@@:にジャンプします。 「HSPからのDLL呼び出し方法リファレンスマニュアル」によると、変数の中身が数値の場合flagは4ですから、変数の中身が数値の時にジャンプする事になります。
もし、変数の中身が数値ではなかった場合は、eaxレジスタに6を代入し、退避しておいたediレジスタとesiレジスタの値を元に戻してから、HSPに制御を戻します。 このように、eaxレジスタに1〜255の値を入れておくと、その値をエラーコードとしてエラーが出力されます。 エラーコード6の場合は、「パラメータの型が違います」というエラーが出力されます。
さて、変数の値が数値の場合、ジャンプした後はどうなるかというと、まず[esi].PVAL2.ptをediレジスタに代入しています。 ptには、変数の値が入っているメモリの番地が入っています。 次のcmp dword ptr [edi],255で、変数の値を255と比較します。 ここで、dword ptrというのは、ediが指しているメモリに入っている数値をダブルワード(4バイト)の数値とみなしなさい、という意味です。 そして、次のjbe @fで、255以下の場合@@:にジャンプします。 逆に、変数の値が255より大きければ、eaxレジスタに3を代入し、退避しておいたediレジスタとesiレジスタの値を元に戻してから、HSPに制御を戻します。
変数の値が255以下の場合、ジャンプした後はどうなるかというと、[esi].PVAL2.flagに2を、eaxレジスタに0を代入し、退避しておいたediレジスタとesiレジスタの値を元に戻してから、HSPに制御を戻します。 flagに2を代入したことにより、この変数は文字列型になります。
意図した通りに動くかどうか、以下のスクリプトで確認してみてください。
|
|
ちゃんと65とAが表示されたでしょうか? うまくいかない場合はソースが間違っていないか、バージョン2.5以降のHSPを使っているか確認してください。
この章ではPVAL2構造体を利用したサンプルを示しましたが、バージョン2.5以降とそれ以前のバージョンの両方に対応したプラグインを作りたい場合は、バージョン2.5以降かどうかを判別し、それぞれに応じた処理を行う必要があります。 しかし、プログラムが若干ややこしくなるため、それぞれに応じた処理の仕方についての説明は省略します。 HSPのバージョンに応じてPVAL構造体とPVAL2構造体を使い分けたい場合は、「HSPからのDLL呼び出し方法リファレンスマニュアル」を参照してください。