第5回は、VBAのデータ型の一つLongPtrについて、説明したいと思います。このLongPtr、簡単に言うと、メモリのアドレス値に対して、使用されるデータの型です。また、VBAで、変数のメモリアドレスを取得する関数にVarPtr(変数名)があります。先にこのVarPtrの説明をしたいと思いますので、以下の、プログラムコードを見てください。これは、CBtnVarPtr_Click関数内で、宣言されている変数の配置されているメモリアドレスを出力するプログラムになっています。
'VarPtr関数の使い方
Private Sub CBtnVarPtr_Click()
Dim tmp1 As Byte
Dim tmp2 As Integer
Dim tmp3 As Long
Dim tmp4 As Variant
'メモリ上のアドレス:tmp1 Byte型
Debug.Print "tmp1_adr = &H" & Hex(VarPtr(tmp1))
'メモリ上のアドレス:tmp2 Integer型
Debug.Print "tmp2_adr = &H" & Hex(VarPtr(tmp2))
'メモリ上のアドレス:tmp3 Long型
Debug.Print "tmp3_adr = &H" & Hex(VarPtr(tmp3))
'メモリ上のアドレス:tmp4 Variant型
Debug.Print "tmp4_adr = &H" & Hex(VarPtr(tmp4))
End Sub
サンプルのダウンロード
サンプルをダウンロードし、【VarPtr1】ボタンを押すと上記の処理が実行され、イミディエイト ウインドウに実行結果が出力されます。以下が私の環境で、実行された出力結果です。
ここで一つ大事なのは、出力されたアドレス値は、変数の型・サイズに関わらず、全て4バイト以下の値(出力結果は、全て3バイト)となっていることです。
tmp1_adr = &H30E61A
tmp2_adr = &H30E618
tmp3_adr = &H30E614
tmp4_adr = &H30E604
過去の豆知識でも、お話しましたが、1つのプロセス(プログラム)は4Gの仮想メモリ空間を保持していて、特にVBAなどのユーザモードに至っては、使用するメモリ空間は0~2G(2147483647)【Bytes】のアドレス空間となります。
この点を踏まえると、VBAで使用されるメモリアドレス値は、Long型(-2147483648~2147483647)の変数を使用すれば、すべて表現・格納できることになります。特にVBAにおいては、メモリアドレス値(ポインタ)を管理するために使用するデータの型をLongではなく、LongPtr(Long Pointerの略)として定義しています。これは明示的にPtrと表現することで、変数の用途が一目で、メモリのアドレス値と理解でき、プログラムの内容もメモリ操作を行っているということが、瞬間的に分かるようになります。
※Long Pointerの略なのですが、Longのポインタという意味ではありません。Longと同じサイズのデータ型で、アドレス値(ポインタ)を格納するための型であると、解釈してください。
上記のソースコードでも、何ら問題はないのですが、ソースコードをLongPtrを使って書き直すと、以下のようになります。
'CBtnVarPtr_ClickをLongPtr型を使って書き直したもの。
Private Sub CBtnVarPtr2_Click()
Dim tmp1 As Byte
Dim tmp2 As Integer
Dim tmp3 As Long
Dim tmp4 As Variant
Dim adr As LongPtr 'LongPtrの代わりにLongを使用してもOK。
'メモリ上のアドレス:tmp1 Byte型
adr = VarPtr(tmp1)
Debug.Print "tmp1_adr = &H" & Hex(adr)
'メモリ上のアドレス:tmp2 Integer型
adr = VarPtr(tmp2)
Debug.Print "tmp2_adr = &H" & Hex(adr)
'メモリ上のアドレス:tmp3 Long型
adr = VarPtr(tmp3)
Debug.Print "tmp3_adr = &H" & Hex(adr)
'メモリ上のアドレス:tmp4 Variant型
adr = VarPtr(tmp4)
Debug.Print "tmp4_adr = &H" & Hex(adr)
End Sub
※メモリアドレスは4バイトの整数値で表現できるので、LontPtrの代わりにLongを使用しても問題ありません。また、サンプルファイルの【VarPtr2】ボタンを押すと上記の処理が実行できます。
上記のようにソースコードを、LongPtrを使って書き直すことが出来るのですが、LongPtrは、VBAの範疇だけで使用している場合には、ほとんど利用する機会はありません。何故なら、VBAでは、メモリ上のデータをアドレス値を介して、直接操作することができない仕様となっているからです。では、どのような時に、LongPtrを使用するかと言うと、それは、Windows API(以下WinAPIと略す。)を使用する場合に、利用する機会が多くなります。
前にもお話ししましたが、WinAPIの大半は、C言語で開発された仕様となっています。C言語では、ポインタという概念が存在し、メモリアドレス値を介して、直接メモリの値を変更することが可能です。その為、API関数のインターフェースには、ポインタとなる引数が多く存在しています。
※関数のインターフェース(IF)とは、関数の呼び出し時における、引数や戻り値などのことです。関数を介して、データを受け渡しすることから、そのように呼ばれています。
それでは、ポインタって何??ってなると思うのですが、C言語のソースコードを使って、少しだけ説明したいと思います。以下の関数は、指定されたメモリアドレスptrから、size分、0でメモリをクリアする仕様となっています。
// ptrで指定されたメモリをsize分、0クリアします。
void ClearMemory(BYTE* ptr, long size)
{
int i;
// size分ループを回す。
for( i=0; i<size; i++) {
// 0クリア
*ptr++=0;
}
}
引数のptrの前に付与している、*(アスタリスク)が、ポインタ変数を表しています。 ptrはアドレス値を表していますので、ptr=ptr+1すると指し示すアドレス値が1加算されます。C言語の場合、1インクリメントする場合は、ptr++と記述することもできます。また、そのアドレス値の示す、メモリ上の値を変更する場合は、*ptrでアクセスし、イコール演算子を使って値を代入することで、値が変更されます。
*ptr++=0、の箇所を説明すると、これは、アドレス値を1インクリメントしつつ、そのアドレス値が示すメモリ上の値を0クリアしているということになります。
もう1点だけ、C言語の少し複雑なところなのですが、今回の例は、BYTEのポインタなので、1インクリメントすると、アドレスは1インクリメントされています。ところが、longのポインタの場合、ポインタを1インクリメントすると、アドレス値は4増加します。shortのポインタの場合、ポインタを1インクリメントすると、アドレス値は2増加します。これが構造体のポインタになると、構造体のサイズ分、アドレスが加算されます。
以下は、ポインタ操作の例です。
以上が、C言語のポインタの概念なのですが、私がVBAの開発をしていて思ったことが、『C言語の知識がない人には、WinAPIの使用は少し難しいのでは?』ということでした。これが、今回の豆知識を公開しようと思ったきっかけです。
最後に総括なのですが、VBAでWinAPIを使用する場合に、APIのインターフェースに、ポインタが含まれている場合、BYTEのポインタであろうが、longのポインタであろうが、変数の型が何であれ、ポインタの引数は、VBAの宣言文では、全て、LongPtrを使用してください。
そうするとは、今度は、WinAPIのインターフェースの引数が、ポインタかどうかを正しく判断できるかどうかが、非常に大切になってきます。このあたりのお話しを、また機会があればしてみたい思います。
以下から、今回の豆知識で紹介した内容のエクセルファイルがダウンロードできます。
サンプルのダウンロード
ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。