第7回は、VBAで、WinAPIを使用する際に、WinAPIに使用されているデータ型がポインタかどうかの見極め方について解説したいと思います。ポインタの見極め方は、大まかに言うと以下の3通りが考えられます。(最終的には、厳密に確認しなければなりません。)
※3つ目については、単純に英単語の先頭がLPまたは、Pで始まっている単語にちなんだデータ型の場合もありますので、注意してください。
いずれの場合も、全てポインタですので、これらのデータ型が存在した時には、VBAでは、全てLongPtrに置き換えてください。
では、それぞれの場合について、C言語側が、どのように実装しているか、非常に簡単な例を使って説明したいと思います。
データ型にアスタリスクが付与されている場合は、ポインタを意味しています。ですので、WinAPIにおいて、このようなアスタリスクを付与したデータ型が存在した場合は、VBAでは、全てLongPtrに置き換えてください。
以下に、メモリをクリアするC言語の例をあげますが、これは、ptrポインタで渡された、アドレスから、size分メモリをクリアする処理となっています。
// ptrで指定されたメモリをsize分、0クリアします。
void ClearMemory(BYTE* ptr, long size)
{
int i;
// size分ループを回す。
for( i=0; i<size; i++) {
// 0クリア
*ptr++=0;
}
}
WinAPIにおいて、このタイプの型はデータ格納用に使用していることが多いと思います。以下に、C言語による、加算処理の例を示します。
SumCalc関数は、VBAでいうところの参照渡しと同じで、関数内で、加算した計算結果が変数sumに、そのまま格納されます。
これと同等の処理を、ポインタを使って実装することができます。それが、SumCalc2関数です。
この2つの関数は、ソースレベルでは、同じではないのですが、コンパイルしてマシン語になれば、いずれも同等の処理となります。従って、WinAPIでこのような&を付与したデータ型が存在した場合には、VBAでは、LongPtrに置き換えてください。
// sumにaとbを加算した結果を代入します。
// VBAの参照渡しと同等です。
void SumCalc(int a, int b, int& sum)
{
sum = a + b;
}
// 上記の関数をこう書き換えることも出来ます。
void SumCalc2(int a, int b, int* sum)
{
*sum = a + b;
}
※コンパイルとは、VBAやC言語などで、人間が分かりやすい文章で書いたプログラミング言語を、コンピュータが理解できる マシン語に変換する処理のことです。C言語の場合は、コンパイル後、リンク処理を施すことで、実行形式のファイルを出力することができます。
WinAPIを使用していると、データ型名の先頭がLPまたは、Pで始まっているデータを良く見かけると思います。これは、それぞれ、Long Pointer、Pointerを表している可能性があります。以下は、C言語による、構造体を初期化する関数の例ですが、
//サンプル構造体
typedef struct _SAMPLE_STRUCT
{
int a;
int b;
void* c;
BYTE szBuf[256];
} SAMPLE_INFO, *PSAMPLE_INFO
//引数が構造体のポインタ
void ClearSampleStruct(PSAMPLE_INFO smpl)
{
// 引数で渡された構造体を0初期化しています。
// VBAと違って、変数を宣言しても、C言語の場合
// 0クリアされている保障がありません。
// 0クリアする必要がある場合、自分で0初期化
// しなければなりません。
smpl->a = 0;
smpl->b = 0;
smpl->c = 0;
memset(smpl->szBuf, 0, 256);
}
構造体の宣言箇所を見ると、以下のようになっています。
typedef struct _SAMPLE_INFORMATION {
…
メンバ変数の宣言
…
} SAMPLE_INFO, *PSAMPLE_INFO
これは、『_SAMPLE_INFORMATION構造体を、別名としてSAMPLE_INFOとして使用し、そのポインタは、PSAMPLE_INFOと表現します。』と宣言していることを意味しています。このことを型の別名宣言とC言語では呼んでいます。
このように、型の別名宣言を使用して、C言語では、ポインタをPSAMPLE_INFOのようにPやLPで始まる型名として使用しています。ですので、このようなPやLPで始まるデータ型が存在した場合、VBAではLongPtrに置き換えてください。
※但し、ポインタを表す訳ではなく、単純に英単語の先頭がLPまたは、Pで始まっている単語にちなんだ名前をつけているデータ型の場合もありえますので、注意してください。
ちなみに、_SAMPLE_INFORMATION構造体をVBAに移植すると、以下のようになりますが、皆さん、構造体のサイズを計算するとき、どのようにしていますか?もちろん、構造体のメンバ変数のサイズを一つずつカウントしてもOKなのですが…
' _SAMPLE_INFORMATION構造体(VBA版)
Type StructSampleInfo
a As Long
b As Long
c As LongPtr
buf(255) As Byte
End Type
以下のようにすると、構造体のサイズが計算できます。ちょっとしたテクですが、覚えておくと良いかも知れません。
Public Sub 構造体のサイズ()
Dim dumy As Long
Dim smpl As StructSampleInfo
'スタックにdumyが積まれて、その上にsmplが乗っかっているので、
'以下の引き算でサイズが計算できます。
MsgBox VarPtr(dumy) - VarPtr(smpl)
End Sub
C言語では、以下のような、型の別名宣言により、あるデータ型を他の表現で利用することが可能になります。以下の例は、『符号無し整数型の unsigned int のデータを、 UINT として、プログラムで利用する。』といった内容の宣言となっています。
typedef unsigned int UINT;
これ以外にも、C言語には、同一のデータ型を違う表現で利用する手段があります。それは、プリプロセッサによる、文字列置換という手段です。プリプロセッサとは、コンパイル前(pre)の処理(process)で、以下のように #define 文で、宣言されているプログラム中のデータを、事前に文字列置換するというものです。 (プログラムソース自体を書き換えている訳ではなく、プリプロセッサ処理の中で、置き換えていると解釈してください。)
#define TEMPERATURE long
この例は、『long型をTEMPERATUREとして使用します。』という宣言文で、これにより、TEMPERATUREが宣言されている変数が温度(TEMPERATURE)を表す値ということが、一目でわかり、単にlongを使用するよりも、プログラムを読みやすくなるメリットがあります。
このような点が、VBAユーザにとって、『C言語は、データ型が多く、非常に分かりにくい』と感じてしまう要因になっているような気がします。
では、どうやったら、解決できるかと言うと、残念ながら、きっちり調べて正しく理解するしか、すべがないように思います。過去の豆知識でもお話しましたが、データ型のサイズなどを間違えて、WinAPIを利用するとスタックの破壊につながったりしてしまいます。
このような不具合を回避するために、WinAPIを利用するにあたり、データ型や、構造体の情報について、調査する2つの方法を紹介したいと思います。
C言語の開発環境には、フリー(無料)のものもありますので、それをインストールして試しても良いと思います。私自身は、お試し版で期間限定のC++Builder(エンバカデロ・テクノロジーズ製)をインストールして使っていた開発環境を検索して調査しています。期間が満了となっても、開発環境が使えなくなるだけで、定義ファイル自体は、残っていますので、検索して閲覧する分には、まったく問題ありません。
※検索には、テキストエディタの検索機能を利用しています。
また、VBAはMS製ですので、VBAについて調べたいことがある場合は、やはり、MSが提供している情報を確認することが、一番望ましいと思います。
インターネット上のMSDNに、データ型の一覧がありますので、そちらも参考にしてみてください。
MSDN:
Windows データ型 (BaseTsd.h) - Win32 apps | Microsoft Learn
ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。