ExcelVBAからも、Win32 APIや自作のDLLを呼び出すことが出来ます。
例えばMessageBoxというAPI(VBAのMsgBoxと同じ)を呼び出すことを考えます。この関数は、C言語では以下の様に定義されています。
WINUSERAPI int WINAPI MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType );
VBAから呼び出す為には、まず「この関数を呼び出すぞ」という宣言をしてやらなければなりません。
Declare Function 関数名 Lib "DLL名" Alias "API名" (引数…) As 戻り値の型
戻り値の型や引数の型を、C言語のものから対応するVBAの型にしてやります。
C言語 | VBA | 説明 | |
---|---|---|---|
int | Integer | 整数なので、Integer。 | |
HWND | ByVal | Long | HWNDはポインター。NULL(0)を渡すなら、Longにしておけばよい。 (MessageBoxではNULLを渡してもいいから) |
LPCSTR | ByVal | Long | LPCSTRは文字列を意味するポインター。 C言語では文字列はchar(バイト)の配列で表す。 なおかつ、配列の場合は先頭アドレスを渡すことになっている。 |
UINT | ByVal | Integer | 符号なしint。符号の有無はC言語側で勝手に解釈してくれるので、Integerでよい。 |
DLL名はそのAPIの実体が入っているDLLを指定します。
ファイル名だけでもいいし(環境変数PATHから探される)、フルパス(絶対パス)で書いても構いません。
Declare Function MessageBoxPtr Lib "user32.dll" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal text As Long, ByVal caption As Long, ByVal nType As Integer) As Integer Sub MessageBoxPtrTest() Dim text() As Byte Call ToBytes("message", text) Dim caption() As Byte Call ToBytes("title", caption) Dim ret As Integer ret = MessageBoxPtr(0, VarPtr(text(0)), VarPtr(caption(0)), vbOKOnly) 'ret = MessageBoxPtr(0, StrPtr("message"), StrPtr("title"), vbOKOnly) Debug.Print ret End Sub '文字列からバイト配列にする(半角文字しか対応してない) Private Sub ToBytes(ByRef src As String, ByRef dst() As Byte) ReDim dst(Len(src)) Dim i As Integer For i = 1 To Len(src) dst(i - 1) = Asc(Mid(src, i, 1)) Next End Sub
VBAの文字列(String)とC言語の文字列(LPCSTR(char*))は異なるので、バイト配列に変換するサブルーチンを用意しました。
(手抜きで、全角文字には対応していません)
VarPtrというのは、変数の先頭アドレスを返す関数です。隠し関数らしい。MSXでは標準だが(爆)
StrPtrというのは文字列の先頭アドレスを返す関数らしいですが、Excel2000/Excel2003で試した限りでは、先頭1文字しか渡りませんでした…。
text(0)は配列の一番先頭なので、VarPtr(text(0))は、textという配列の先頭アドレスということになります。
ちなみにMessageBoxの第四引数はMsgBoxのと同じ意味なので、vbOKOnly(0)を使っちゃってます(苦笑)
VBAでByRefを指定するとアドレスを渡してくれるようです。
したがってByteのByRefにしておけば、C言語のchar*(LPCSTR)に相当します。
Declare Function MessageBoxByte Lib "user32.dll" Alias "MessageBoxA" (ByVal hwnd As Long, ByRef text As Byte, ByRef caption As Byte, ByVal nType As Integer) As Integer Sub MessageBoxRefTest() Dim text() As Byte Call ToBytes("message", text) Dim caption() As Byte Call ToBytes("title", caption) Dim ret As Integer ret = MessageBoxByte(0, text(0), caption(0), vbOKOnly) Debug.Print ret End Sub
text(0)は配列の一番先頭。ByRefなので、そのアドレスが渡ります。すなわち、textという配列の先頭アドレスが渡ることになります。
なお、「ByRef text As Byte」を「ByVal text() As
Byte」とすれば配列の先頭アドレスが渡らないかな〜と期待しましたが、「配列引数はByRefでなければなりません。」というコンパイルエラーが出てしまいました。
試しにByRefにしてみたところ、正しい文字は表示されませんでした…。
上の2つのやり方では、バイト配列を使わないといけないので ちょっとくどいです。
で、ByValのStringにしてやると、うまく行くようです。(日本語(全角文字)もちゃんと表示される!)
このやり方で本当にいいのかどうか知らないけど…(爆)
Declare Function MessageBox Lib "user32.dll" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal text As String, ByVal caption As String, ByVal nType As Integer) As Integer Sub MessageBoxStrTest() Dim ret As Integer ret = MessageBox(0, "message", "title", vbOKOnly) Debug.Print ret End Sub
最後につまらない話ですが、「Declare Function」は、ソースの先頭の方にまとめて書かないとコンパイルエラーになります。(サブルーチンとサブルーチンの間で宣言 することは出来ない)
Declare Function MessageBoxByte Lib "user32.dll" Alias "MessageBoxA" (ByVal hwnd As Long, ByRef text As Byte, ByRef caption As Byte, ByVal nType As Integer) As Integer Declare Function MessageBoxPtr Lib "user32.dll" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal text As Long, ByVal caption As Long, ByVal nType As Integer) As Integer Declare Function MessageBox Lib "user32.dll" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal text As String, ByVal caption As String, ByVal nType As Integer) As Integer Sub MessageBoxPtrTest() 〜 End Sub Sub MessageBoxRefTest() 〜 End Sub Sub MessageBoxStrTest() 〜 End Sub
序数を使ってDLLの関数を呼ぶには、Aliasに序数値を指定します。[2006-08-22]
Declare Function Func Lib "c:\temp\test.dll" Alias "#1" (ByVal ptr As Long) As Long
ちなみにWin32 APIの序数値はバージョンによって変わる可能性があるので、使わない方が無難です。