S-JIS[2006-06-16/2006-08-22] 変更履歴

Excel VBAからDLL呼び出し

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の序数値はバージョンによって変わる可能性があるので、使わない方が無難です。


参考


Excelへ戻る / 技術メモへ戻る
メールの送信先:ひしだま