次の方法で共有


Office VBA と Windows API

David Shank
Microsoft Corporation

March 12, 2001

私たちは、Visual Basic® for Applications (VBA) が、Microsoft Office カスタム ソリューションで使用できる優れたプログラミング言語であることを知っています。VBA を使用することにより、1 つ以上の Office アプリケーション オブジェクト モデルを使って作業するために、Office アプリケーションの機能を容易に変更できます。また、2 つ以上の Office アプリケーションを組み合わせて作業し、単独のアプリケーションでは達成できない作業を可能にすることもできます。 VBA では、オペレーティング システムの小さな単位だけを制御します。その部分は、VBA に直接公開されている関数やオブジェクトを使って利用できます。Windows® アプリケーション プログラミング インターフェイス (API) は、オペレーティング システムの最も細部を制御する関数を提供します。VBA から Windows API の関数を呼び出すことにより、Office のカスタム ソリューションを拡張および微調整できます。

今月のコラムでは、VBA から Windows API を使った作業をご紹介し、ユーザー独自のカスタム ソリューションにすぐにコピーして使用できる便利な例をいくつか提供するつもりです。

注意 Windows API やその他の DLL 関数を呼び出すことにより、アプリケーションに問題が生じる可能性があります。コードから直接 DLL 関数を呼び出すと、VBA が通常提供している安全性のメカニズムの一部を回避することになります。(すべてのプログラマがつい行ってしまいますが) DLL 関数の定義や呼び出しを間違って行うと、アプリケーション エラー (一般保護違反や GPF とも呼ばれています) が発生することになります。API を使って作業する場合の最善の方法は、コードを実行する前に常にプロジェクトを保存し、DLL 関数を呼び出す背景にある概念を理解していることを確認することです。

API の理解

API は、コンポーネント、アプリケーション、またはオペレーティング システムで作業するために使用できる単なる関数のセットです。一般的には、API はいくつか特定の機能を提供する 1 つ以上の DLL で構成されます。

DLL は、Windows で実行されている任意のアプリケーションから呼び出せる関数を含んでいるファイルです。DLL 内の関数は、その関数を呼び出しているアプリケーションに、実行時に "動的にリンク" されます。多くのアプリケーションがある DLL 内のある関数を呼び出したとしても、その関数はディスク上の 1 つのファイルのみに存在し、DLL はメモリ内に一度だけ作成されます。

おそらく一番耳にする API は Windows API で、この API は Windows オペレーティング システムを構築する DLL を含んでいます。あらゆる Windows アプリケーションは、直接的または間接的に Windows API と対話します。Windows API は、Windows の環境下で実行されるすべてのアプリケーションが一貫した手法で動作することを保証しています。

Windows API 以外にも利用可能な公開されたその他の API が存在します。たとえば、Mail Application Programming Interface (MAPI) は、電子メール アプリケーションを作成するために使用できる DLL のセットです。

API は、伝統的に Windows アプリケーションを構築する C および C++ プログラマのために記述されていますが、VBA を使用して DLL 内の関数を呼び出すこともできます。大部分の DLL は C/C++ プログラマのために作成され、ドキュメントが用意されています。C/C++ での DLL 関数の呼び出しは、 VBA 関数の呼び出しとはいくぶん異なっています。API を使って作業するためには、DLL 関数に引数を渡す方法を理解する必要があります。

Windows API 内の関数を呼び出すためには、利用できる関数、VBA でその関数を宣言する方法、およびその関数を呼び出す方法を説明したマニュアルが必要になるでしょう。これには次の 2 つのリソースが役に立ちます。

  1. Win32API.txt ファイルが Microsoft Office 2000 Developer と Microsoft Visual Basic に含まれています。Win32API.txt ファイルには、Windows API の大部分の関数に対する VBA Declare ステートメントが含まれています。Office 2000 Developer に含まれている API ビューア アドインを使用して、必要な Declare ステートメントに位置付け、コピーすることもできます。API ビューアのインストールと使用の詳細については、Office 2000 Developer に含まれる apiload.txt ファイルを参照してください。Microsoft Visual Basic に含まれるAPI ビューア アプリケーションは、スタンドアロン アプリケーションであることを除いて、同じ機能を提供します。.

    API ビューア アプリケーションは初回実行時に Win32API.txt ファイルを読み込みます。API データの読み込みと、スクロールの処理を高速化するために、このテキスト ファイルを Microsoft Access データベース (.mdb ファイル) にエクスポートできます。

  2. Microsoft プラットフォーム SDK には、Windows API の完全なドキュメントが含まれています。これは、https://msdn.microsoft.com/library/aa383750.aspx の MSDN Library Online サイトから無償で入手できます。

Declare ステートメントを使った作業

VBA から DLL 内の関数を呼び出す前に、その関数の場所と呼び出す方法に関する情報を VBA に提供する必要があります。これを行うには次の 2 つの方法があります。

  1. DLL のタイプ ライブラリへの参照を設定する方法。

  2. モジュール内で Declare ステートメントを使用する方法。

DLL のタイプ ライブラリへの参照を設定するのが、DLL 内の関数を使って作業するもっとも簡単な方法です。参照を設定後は、DLL 関数をプロジェクトの一部であるかのように呼び出すことができます。ただし、2、3 の注意事項があります。まず第 1 に、複数のタイプ ライブラリへの参照を設定すると、アプリケーションのパフォーマンスに影響します。第 2 に、すべての DLL がタイプ ライブラリを提供しているわけではありません。タイプ ライブラリを提供していない DLL への参照を設定することはできますが、その DLL 内の関数をプロジェクトの一部であるかのように呼び出すことはできません。

Windows API を形成する DLL はタイプ ライブラリを提供しないことに注意してください。したがって、これらの DLL への参照を設定して、その関数を呼び出すことはできません。Windows API の関数を呼び出す場合は、プロジェクトの Declarations セクションに Declare ステートメントを含める必要があります。

Declare ステートメントは、特定の DLL 関数を探す場所とそれを呼び出す方法を VBA に指示する定義です。コードに Declare ステートメントを追加するもっとも簡単な方法は、API ビューア アドインを使用することです。API ビューア アドインは、Windows API の大部分の関数の Declare ステートメントと、一部の関数が必要とする定数と型定義を含んでいます。

以下に、GetTempPath 関数に対する Declare ステートメントの例を示します。この関数は Windows 一時ディレクトリのパス (既定では C:\Windows\Temp) を返します。


Private Declare Function GetTempPath Lib "kernel32" _
   Alias "GetTempPathA" (ByVal nBufferLength As Long, _
   ByVal lpBuffer As String) As Long

Declare キーワードは、プロジェクトに DLL 関数の定義を含めたいと思っていることを VBA に通知します。標準モジュール内の Declare ステートメントは、API 関数を 1 つのモジュールだけで使用するか、プロジェクト全体で使用するかによって、プライベートまたはパブリックにできます。クラス モジュールでは、Declare ステートメントをプライベートにする必要があります。

Function キーワードの後に指定する関数の名前は、VBA からその関数を呼び出すために使用する名前です。この名前を API 関数自体の名前と同じにすることができます。また、Declare ステートメント内で Alias キーワードを使用して、VBA からその関数の呼び出しに使用する別の名前 (「エイリアス」) を指定できます。

上記の例では、DLL 内の API 関数の名前は GetTempPathA で、VBA から呼び出す名前は GetTempPath です。DLL 関数の実際の名前を Alias キーワードの後に指定することに注意してください。また、GetTempPath は Win32API.txt ファイルがその関数のエイリアスに使用している名前ですが、希望する任意の名前に変更できることにも注意してください。

次のように、Declare ステートメント内でエイリアスを使用する理由がいくつかあります。

  • 一部の API 関数の名前がアンダースコア文字 (_) で始まります。これは VBA では正しくありません。VBA から呼び出すにはエイリアスを使用する必要があります。

  • エイリアスを使用して DLL 関数に希望するどんな名前でも付けられるので、関数名を VBA 内の独自の命名規則に準拠させることができます。

  • API 関数は大文字小文字を区別し、VBA 関数は区別しないので、エイリアスを使用して関数名の大文字小文字を変更することができます。

  • 一部の DLL 関数は異なるデータ型を受け取る引数を持つことができます。これらの関数に対する VBA Declare ステートメントはこれらの引数を型 Any として定義します。VBA は任意のデータ型のチェックを実行できないので、Any として宣言された DLL 関数を呼び出すことは危険です。Any として引数を渡す危険性を回避したい場合は、同じ DLL 関数の複数のバージョンを異なる名前で宣言し、それぞれ異なるデータ型を指定するようにします。

  • Windows API には、文字列引数を受け取るすべての関数の ANSI バージョンと Unicode バージョンの 2 つのバージョンが含まれています。上記の例で示したように、ANSI バージョンはサフィックス "A"を持つのに対して、Unicode バージョンはサフィックスとして "W"を持ちます。VBA は内部的には Unicode を使用しますが、DLL 内の関数を呼び出す前にすべての文字列を ANSI 文字列に変換します。したがって、通常 VBA から Windows API 関数を呼び出すときは、ANSI バージョンを使用します。API ビューアは文字列引数を受け取るすべての関数に自動的にエイリアスを付けます。そのため、サフィックス "A" を含めないで関数を呼び出すことができます。

Lib キーワードはその関数がどの DLL に含まれているかを指定します。DLL の名前が Declare ステートメント内の文字列に含まれていることに注意してください。Lib キーワードの後に指定されている DLL がユーザーのシステムに見つからない場合は、関数の呼び出しに失敗し、実行時エラー番号 48、「DLL 読み込み時のエラーです。」が発生します。VBA 内でこのエラーを処理できるので、エラーを適切に処理する強固なコードを記述できます。(基本的な Windows DLL はアプリケーションを読み込むために存在する必要があるので、この DLL の 1 つにある関数を呼び出している場合はこの問題は発生しません。)

以下の表は Windows API でもっとも一般的に使用される DLL を説明しています。

DLL 内容
Kernel32.dll メモリ管理やリソース処理などの低レベルのオペレーティング システム関数
User32.dll メッセージ処理、タイマ、メニュー、および通信などの Windows 管理関数
GDI32.dll 描画、表示、コンテキスト、およびフォント管理などのデバイス出力用の関数を含むグラフィック デバイス インターフェイス (GDI) ライブラリ。

Windows API の DLL も含めて、大部分の DLL は C/C++ で記述されています。そのため、DLL 関数に引数を渡すときは、 C/C++ 関数が期待する引数やデータ型を理解しておく必要があります。これは、VBA 関数が期待する引数やデータ型とは渡される方法が一部異なっています。

また、DLL 関数の多くの引数は値渡しです。VBA での引数は既定では参照渡しです。したがって、DLL 関数が値渡しの引数を必要とするときは、その関数の定義に ByVal キーワードを含めることが必須になります。関数の定義で ByVal キーワードを省略すると、場合によってはアプリケーションで無効ページ違反が発生することがあります。ページ違反にならない場合でも、VBA 実行時エラー番号 49、「DLL が正しく呼び出せません。」が発生することがあります。

参照渡しで引数を渡すことは、呼び出しているプロシージャにその引数のメモリ アドレスを渡すことになります。プロシージャがその引数の値を変更する場合、その引数の唯一のコピーを変更することになります。したがって、呼び出し側のプロシージャに実行が戻ると、その引数は変更された値を持つことになります。

それに対して、DLL 関数に値渡しで引数を渡すことは、引数のコピーを渡すことになります。したがって、関数は引数のコピーを操作することになります。この結果、関数が実際の引数の内容を変更することはなくなります。呼び出し側のプログラムに実行が戻ると、その引数は別のプロシージャが呼び出される前に設定していたのと同じ値を持っています。

参照渡しでは引数がメモリ内を変更できるので、引数を間違って参照渡しにすると、DLL 関数が変更してはいけないメモリに上書きする可能性があります。その結果、エラーが発生するか、予期しない動作を行う場合があります。Windows は上書きしてはいけない多くの値を管理しています。たとえば、Windows はすべてのウィンドウに "ハンドル" と呼ばれる重複しない 32 ビットの識別子を割り当てています。ウィンドウのハンドルが変更されると Windows はそのウィンドウを管理できなくなるので、ハンドルは常に値渡しで API 関数に渡されます。(String 型の引数の前に ByVal キーワードが指定されることがありますが、文字列は常に参照渡しで Windows API 関数に渡されます。)

定数を使った作業

一部の関数は、DLL 関数に対する Declare ステートメント以外に、その関数が使用する定数と型を定義することを必要とします。定数とユーザー定義型の定義を、それを必要とする関数に対する Declare ステートメントと共にモジュールの Declarations セクションに含めます。

関数が必要とする定数とユーザー定義型をどのようにして知ればよいのでしょうか ? やはり、その関数のドキュメントを調べる必要があります。Win32API.txt ファイルは、関数と共に定数とユーザー定義型の定義を含んでいます。API ビューア アドインを使用して、これらの定数とユーザー定義型に位置付け、それらをコピーしてコードに貼り付けることができます。残念ながら、定数とユーザー定義型を、それを必要とする Declare ステートメントに関連付ける方法はありません。したがって、どの定数と型がどの Declare ステートメントと関係があるかを判断するために、DLL 関数のドキュメントを調べる必要があります。

関数によっては、その関数が何の情報を返すかを示すために、定数を渡すことを要求する場合があります。たとえば、GetSystemMetrics 関数はオペレーティング システムの異なる側面を指定するために、75 個の定数のうちの 1 つを受け取ります。どの定数が渡されたかによって、その関数が返す情報が異なります。GetSystemMetrics を呼び出すために、75 個の定数をすべて知っておく必要はありません。使用する予定のものを含めるだけで十分です。

単純に値を渡すのではなく、その値を表す定数を定義することをお勧めします。弊社は、将来のバージョンでも定数が同じように使えることを保証しますが、その定数が表す値自体が同じであることは保証しません。

DLL 関数が要求する定数の意味がまったくわからない場合があります。したがって、特定の値を返すためにはどの定数を渡すのかを判断するために、その関数のドキュメントを調べる必要があります。

以下の例には GetSystemMetrics 関数に対する Declare ステートメントと、関数が受け取る 2 つの定数が含まれています。また、画面の高さをピクセル単位で返すために、プロパティ プロシージャ内部から GetSystemMetrics を呼び出す方法を示しています。


Declare Function GetSystemMetrics Lib "User32" (ByVal nIndex As Long) As Long

Const SM_CXSCREEN As Long = 0
Const SM_CYSCREEN As Long = 1

Public Property Get ScreenHeight() As Long
   ' ピクセル単位で画面の高さを返します。

   ScreenHeight = GetSystemMetrics(SM_CYSCREEN)
End Property

Public Property Get ScreenWidth() As Long
   ' ピクセル単位で画面の幅を返します。
   
   ScreenWidth = GetSystemMetrics(SM_CXSCREEN)
End Property
ユーザー定義型を使った作業

"ユーザー定義型" は、異なる型の関連する変数を格納できるデータ構造体です。これは、C/C++ の構造体に相当します。場合によっては、空のユーザー定義型を DLL 関数に渡し、その関数が値を設定します。また、VBA でユーザー定義型に値を設定し、DLL 関数に渡す場合もあります。

ユーザー定義型は整理ダンスと考えることができます。それぞれの引き出しは、異なる型の項目を収めることができますが、すべての引き出しを関連する項目のタンスとして扱うことができます。さらに、ほかの引き出しに収められている項目とは無関係に、ある引き出しから項目を取り出すことができます。

ユーザー定義型を作成するには、Type...End Type ステートメントを使用します。Type...End Type ステートメント内で、各要素に含める値とデータ型をリストします。ユーザー定義型の要素を配列にすることもできます。

以下のコードは、RECT ユーザー定義型を定義する方法を示しています。いくつかの Windows API 関数はこのユーザー定義型を使用して、画面上の四角形の領域を管理します。たとえば、GetWindowRect 関数は RECT 型のデータ構造体を受け取り、ウィンドウの左、上、右、および下の位置に関する情報をその構造体に設定します。


Type RECT
      Left As Long
      Top  As Long
      Right  As Long
      Bottom As Long
End Type

DLL 関数にユーザー定義型を渡すためには、その型の変数を作成する必要があります。たとえば、DLL 関数に RECT 型のユーザー定義型を渡す予定であれば、モジュール内に以下のように変数宣言を含めることができます。


Private rectWindow As RECT

次のコードに示すように、ユーザー定義型内の個別の要素を参照できます。


Debug.Print rectWindow.Left
ハンドルを使った作業

DLL 内の関数を呼び出す前に理解しておく必要のある別の重要な概念に "ハンドル" があります。ハンドルは単なる 32 ビットの正の整数値で、Windows はこの値を使用して、ウィンドウ、またはフォントやビットマップなどのほかのオブジェクトを識別します。

Windows では、1 つのウィンドウがさまざまなものになります。実際、画面上で見ることができるほとんどのものはウィンドウ内にあります。また、見ることができない多くのものもウィンドウ内に存在します。馴染みのあるアプリケーション ウィンドウのように、ウィンドウは画面上で区切られた四角形の領域です。すべての種類のコントロールがウィンドウになるわけではありませんが、リスト ボックスやスクロール バーなどのフォーム上のコントロールもウィンドウになることがあります。デスクトップ上のアイコンや、デスクトップ自体もウィンドウです。

これらの種類のオブジェクトはすべてウィンドウなので、Windows はこれらをすべて同じように扱うことができます。Windows はすべてのウィンドウに重複しないハンドルを割り当て、このハンドルを使ってそのウィンドウで作業します。多くの API 関数がハンドルを返すか、ハンドルを引数として受け取ります。

Windows は、ウィンドウが作成されるときにハンドルを割り当て、そのウィンドウが破棄されるときにハンドルを解放します。ウィンドウが作成されてから破棄されるまでは、ハンドルは同じで変化しませんが、ウィンドウが破棄されて再作成された場合は、ウィンドウに同じハンドルが割り当てられる保証はありません。そのため、変数にハンドルを格納する場合、ウィンドウが破棄されるとそのハンドルが無効になることに注意してください。

GetActiveWindow 関数は、ウィンドウへのハンドルを返す関数の一例です。この関数は、現在アクティブなアプリケーション ウィンドウのハンドルを返します。GetWindowText 関数はウィンドウへのハンドルを受け取り、そのウィンドウにキャプションがある場合はそのキャプションを返します。以下のプロシージャは GetActiveWindow を使用してアクティブ ウィンドウのハンドルを取得し、GetWindowText を使用してそのウィンドウのキャプションを取得しています。


Declare Function GetActiveWindow Lib "user32" () As Long
Declare Function GetWindowText Lib "user32" _
    Alias "GetWindowTextA" (ByVal Hwnd As Long, _
    ByVal lpString As String, ByVal cch As Long) As Long

Function ActiveWindowCaption() As String
    Dim strCaption As String
    Dim lngLen   As Long

    ' すべて null 文字の文字列を作成します。
    strCaption = String$(255, vbNullChar)
    ' 文字列の長さを返します。
    lngLen = Len(strCaption)

    ' GetActiveWindow を呼び出して、アクティブ ウィンドウのハンドルを返し、
    ' そのハンドルを文字列とその文字列の長さと共に GetWindowText に
    ' 渡します。
    If (GetWindowText(GetActiveWindow, strCaption, _
        lngLen) > 0) Then
        ' Windows が文字列に書き込んだ値を返します。
        ActiveWindowCaption = strCaption
    End If
End Function

GetWindowText 関数は 3 つの引数を受け取ります。1 つはウィンドウのハンドルで、もう 1 つはウィンドウのキャプションが返される Null 文字で終わる文字列です。最後の 1 つはその文字列の長さです。

関数の呼び出し

DLL 関数を呼び出す方法は多くの点で VBA 関数の呼び出しに似ていますが、初めのうちは混乱しかねない違いがあります。このセクションでは、DLL 関数内で引数の型やプレフィックスが付けられる方法、データ構造体を渡す方法、期待できる戻り値の種類、およびエラー情報を取得する方法を説明します。

引数のデータ型

C/C++ で使用するデータ型とその表記方法が VBA とは異なります。以下の表は、DLL 関数内で一般的ないくつかのデータ型と、それに相当する VBA のデータ型を説明しています。

C/C++ データ型 ハンガリアン記法のプレフィックス 説明 VBA で等価な型
BOOL b 8 ビットの Boolean 値。0 は False を示し、0 以外は True を示します。 Boolean または Long
BYTE ch 8 ビットの符号なし整数 Byte
HANDLE h Windows オブジェクトへのハンドルを表す 32 ビットの符号なし整数 Long
int n 16 ビットの符号付き整数 Integer
long l 32 ビットの符号付き整数 Long
LP lp C/C++ の構造体、文字列、関数、またはメモリ内のその他のデータへの 32 ビットの長整数のポインタ。 Long
LPZSTR lpsz C 形式の Null 文字で終わる文字列への 32 ビットの長整数ポインタ Long

これらのデータ型やプレフィックスに精通する必要はありますが、以前に説明した Win32API.txt ファイルに含まれる Declare ステートメントは VBA でそのまま使用できます。コード内でこれらの Declare ステートメントを使用すると、関数の引数は既に正しい VBA データ型で定義されています。

大部分の場合は、正しいデータ型を定義し、渡している限り、DLL 関数の呼び出しは VBA 関数の呼び出しと同じ方法で機能します。例外については、次のセクションで説明します。

DLL 関数から文字列を返す

DLL 関数が文字列を返す方法は、VBA 関数とは異なります。文字列は、DLL 関数へは常に参照渡しで渡されるので、DLL 関数は文字列引数の値を変更できます。VBA で行うように、関数の戻り値として文字列を返すのではなく、DLL 関数は関数に渡された String 型の "引数" に文字列を返します。この場合、関数の実際の戻り値は文字列引数に書き込まれたバイト数を示す長整数になるのが一般的です。

文字列引数を受け取る DLL 関数は、メモリ内の文字列の場所を指す "ポインタ" を取得します。ポインタは文字列が格納されている場所を示す単なるメモリ アドレスです。したがって、VBA から DLL 関数に文字列を渡すと、DLL にメモリ内の文字列へのポインタを渡すことになります。その後、DLL 関数はそのアドレスに格納されている文字列を変更します。

String 変数に書き込む DLL 関数を呼び出す場合、文字列を正しく書式化する別の手順が必要になります。まず第 1 に、String 変数は、 "Null 文字で終わる文字列" である必要があります。Null 文字で終わる文字列は、特殊な Null 文字で終端されています。この Null 文字は、VBA 定数 vbNullChar で指定します。

第 2 に、DLL 関数は一度作成された文字列のサイズを変更できません。そのため、関数に渡す文字列は戻り値を格納するのに十分な長さがあることを確認しておく必要があります。通常、DLL 関数に文字列を渡すときは、別の引数にその文字列のサイズを指定する必要があります。Windows は文字列の長さを監視し、その文字列が使用していないメモリに上書きしないようにしています。

DLL 関数に文字列を渡す適切な方法は、関数が返す文字列を保持するために十分な大きさで String 変数を作成し、String$ 関数を使用してその文字列全体にに Null 文字を設定することです。たとえば、次のコードは 144 バイトの長さの文字列を作成し、その文字列全体に Null 文字を設定します。


Dim strTempPath As String

strTempPath = String$(144, vbNullChar)

DLL 関数に文字列を渡すときに、文字列の長さが分からない場合は、Len 関数を使用してサイズを調べることができます。

Windows の一時フォルダへのパスを取得する GetTempPath 関数は、String 値を返す DLL 関数の一例です。この関数は、Null 文字で終わる String 変数とその文字列の長さを含む数値変数の 2 つの引数を受け取り、たとえば "C:\Temp\" のようなパスを含むように文字列を変更します。 (Windows はコンピュータを起動するために一時フォルダの存在を必要とします。従って、この関数は常にそのフォルダへのパスを返します。何らかの理由でフォルダが存在しない場合は、GetTempPath は 0 を返します。)

次のプロシージャは、GetTempPath 関数を呼び出して Windows 一時フォルダを取得しています。


Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _
   (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long

Property Get GetTempFolder() As String
   ' ユーザーの一時フォルダへのパスを返します。Windows はコンピュータを 
   ' 起動するために一時フォルダの存在を必要とします。したがって、この関数 
   ' は常にそのフォルダへのパスを安全に返します。ただし、万一に備えて 
   'GetTempPath の戻り値を調べます。
   
   Dim strTempPath As String
   Dim lngTempPath As Long
   
   ' 文字列全体に Null 文字を設定します。
   strTempPath = String(144, vbNullChar)
   ' 文字列の長さを取得します。
   lngTempPath = Len(strTempPath)
   ' 文字列とその長さを渡して GetTempPath を呼び出します。
   If (GetTempPath(lngTempPath, strTempPath) > 0) Then
      ' GetTempPath は文字列にパスを返します。
      ' 最初の Nll 文字の位置で文字列を切り詰めます。
      GetTempFolder = Left(strTempPath, _
         InStr(1, strTempPath, vbNullChar) - 1)
   Else
      GetTempFolder = ""
   End If
End Property

DLL 関数に渡される文字列

図 1. DLL 関数に渡される文字列

文字列が関数に渡されるとき、文字列全体に Null 文字が設定されていることに注意してください。関数は返す String 値、"C:\Temp\" を String 変数の先頭部分に書き込みます。文字列の残りの部分は Null 文字が埋め込まれたままです。そのため、Left 関数を使用して文字列を切り詰めます。

GetTempPath 関数の実際の戻り値は、String 変数に書き込んだ文字数です。返される文字列が "C:\Temp\" の場合は、GetTempPath 関数は 8 を返します。

関数から文字列を返している場合は、Null 文字で終わる文字列とそのサイズを渡すことだけが必要であることに注意してください。関数が文字列引数に文字列を返さず、関数に情報を提供するためにその文字列を使用する場合は、単に通常の VBA String 変数を渡すことができます。

DLL 関数にユーザー定義型を渡す

多くの DLL 関数は、定義済みのフォーマットを使用して、データ構造体を渡すことを必要とします。VBA から DLL 関数を呼び出しているときは、関数の必要条件に従って定義されたユーザー定義型を渡します。

関数の Declare ステートメントを見ることにより、いつユーザー定義型を渡す必要があるかと、どの型定義をコードに含める必要があるかを調べることができます。データ構造体を必要とする引数は、常に long ポインタとして宣言します。このポインタは、メモリ内のデータ構造体を指す 32 ビット数値です。long ポインタ引数のプレフィックス表記は "lp" です。さらに、引数のデータ型はそのデータ構造体の名前になります。

たとえば、GetLocalTime 関数と SetLocalTime 関数の Declare ステートメントを見てみましょう。


Private Declare Sub GetLocalTime Lib "kernel32" _
    (lpSystem As SYSTEMTIME)
Private Declare Function SetLocalTime Lib "kernel32" _
    (lpSystem As SYSTEMTIME) As Long

両方の関数は SYSTEMTIME 型の引数を受け取ります。これは、日付と時刻の情報を含むデータ構造体です。以下に SYSTEMTIME 型の定義を示します。


Private Type SYSTEMTIME
      wYear          As Integer
      wMonth         As Integer
      wDayOfWeek     As Integer
      wDay           As Integer
      wHour          As Integer
      wMinute        As Integer
      wSecond        As Integer
      wMilliseconds  As Integer
End Type

このデータ構造体を関数に渡すには、次の例のように SYSTEMTIME 型の変数を定義する必要があります。


Private sysLocalTime As SYSTEMTIME

SYSTEMTIME 型の変数を渡して GetLocalTime 関数を呼び出すと、この関数はそのデータ構造体に現在のその地域の年、月、日、曜日、時、分、秒、およびミリ秒を設定します。たとえば、以下の Property Get プロシージャは GetLocalTime を呼び出し、現在時刻を示す値を返します。


Public Property Get Hour() As Integer
   ' 現在の時間を取得し、時刻を返します。

   GetLocalTime sysLocalTime
   Hour = sysLocalTime.wHour
End Property

SetLocalTime を呼び出すときも、SYSTEMTIME 型の変数を渡しますが、このときデータ構造体の 1 つ以上の要素に値を設定しておく必要があります。たとえば、以下の Property Let プロシージャはローカル システム時刻の時の値を設定します。まず、GetLocalTime を呼び出し、ローカル時刻のほとんどの現在値をデータ構造体、sysSystem に取得します。その後、データ構造体の sysLocalTime.wHour 要素の値を、プロパティ プロシージャに渡された引数の値で更新します。最後に、SetLocalTime を呼び出し、GetLocalTime で取得した値に新しい時の値を追加した同じデータ構造体を渡します。


Public Property Let Hour(intHour As Integer)
   ' すべての値が現在値になるように現在時刻を取得します。
   ' その後、ローカル時刻の時の値を設定します。
   
   GetLocalTime sysLocalTime
   sysLocalTime.wHour = intHour
   SetLocalTime sysLocalTime
End Property

GetLocalTime 関数と SetLocalTime 関数は、GetSystemTime 関数と SetSystemTime 関数に似ています。主な違いは、GetSystemTime 関数と SetSystemTime 関数が、グリニッジ標準時で時刻を表現することです。たとえば、ローカル時刻が深夜 12:00 で、西海岸に居住している場合、8 時間の時差があるので、グリニッジ標準時では午前 8:00 になります。現在時刻として、GetSystemTime 関数が午前 8:00 を返すのに対して、GetLocalTime は深夜の 12:00 を返します。

Any データ型の理解

一部の DLL 関数は、複数のデータ型を受け取ることができる引数を持つものがあります。DLL 関数の Declare ステートメントでは、このような引数は型 Any で宣言されます。VBA は、この引数に任意のデータ型を渡すことを許可します。ただし、DLL 関数は 2 つまたは 3 つだけのデータ型だけを受け取るように設計されている可能性があるので、間違った型のデータを渡すとアプリケーション エラーが発生する可能性があります。

通常、VBA プロジェクトでコードをコンパイルするときに、VBA は各引数に渡す値の型チェックを実行します。つまり、渡される値のデータ型と、関数定義の引数のデータ型が一致していることを確認します。たとえば、引数が型 Long として定義されている場合に、型 String の値を渡そうとすると、コンパイル時にエラーが発生します。組み込みの VBA 関数、ユーザー定義関数、または DLL 関数のいずれを使用していても、この型チェックは行われます。ただし、型 Any として引数を宣言している場合は、型チェックが行われないので、この型の引数に値を渡すときは注意が必要です。

一部の DLL 関数には、文字列または文字列への null ポインタのいずれかを受け取れる引数を持つものがあります。文字列への null ポインタは、指定した引数を無視することを Windows に指示する特殊なポインタです。長さが 0 の文字列 ("") とは異なります。このような関数を宣言する場合、以前のバージョンの VBA では、引数を型 Any で宣言するか、2 つのバージョンの DLL 関数を宣言する必要がありました。2 つのバージョンの引数を宣言する場合、1 つは型 String として引数を定義し、もう 1 つは型 Long として定義します。しかし、VBA に文字列への null ポインタを表す vbNullString 定数が含まれるようになったので、null ポインタを渡す必要がある場合には、引数を型 String として宣言し、vbNullString 定数を渡すことができます。VBA から null ポインタを渡すことに関する詳細については、Microsoft Developer Network サイトを、キーワード "vbNullString" で検索してください。

エラー情報の取得

DLL 関数で発生する実行時エラーは、VBA 内で発生する実行時エラーとは異なり、エラー メッセージ ボックスが表示されません。実行時エラーが発生したとき、DLL 関数はエラーが発生したことを示す何らかの値を返しますが、エラーの発生により VBA の実行が中断されることはありません。

Windows API の一部の関数は、実行時エラーのエラー情報を格納します。C/C++ でプログラミングしている場合は、GetLastError 関数を使用して、最後に発生したエラーに関する情報を取得できます。ただし、VBA から GetLastError を呼び出した場合、正確な結果が返されないことがあります。VBA から DLL エラーに関する情報を取得する場合、VBA Err オブジェクトの LastDLLError プロパティを使用できます。LastDLLError プロパティは発生したエラーの番号を返します。

LastDLLError プロパティを使用するには、どのエラー番号がどのエラーに対応しているかを知っておく必要があります。この情報を Win32API.txt ファイルから得ることはできませんが、Microsoft Platform SDK から無償で入手できます。

以下の例は、Windows API の関数を呼び出した後に、LastDLLError プロパティを使用する方法を示しています。PrintWindowCoordinates プロシージャは、ウィンドウへのハンドルを受け取り、GetWindowRect 関数を呼び出しています。GetWindowRect は、ウィンドウを構成している四角形の各辺の長さを RECT データ構造体に設定します。もし、無効なハンドルを渡すと、エラーが発生します。このとき、LastDLLError プロパティを使用してエラー番号を取得しています。


Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, _
               lpRect As RECT) As Long

Type RECT
      Left As Long
      Top  As Long
      Right  As Long
      Bottom As Long
End Type

Const ERROR_INVALID_WINDOW_HANDLE   As Long = 1400
Const ERROR_INVALID_WINDOW_HANDLE_DESCR As String = "無効なウィンドウ ハンドルです。"

Sub PrintWindowCoordinates(hwnd As Long)
    ' ウィンドウの左、右、上、下の位置を 
    ' ピクセル単位で出力します。
   
   Dim rectWindow As RECT
   
   ' ウィンドウ ハンドルと空のデータ構造体を渡します。
   ' 関数が 0 を返す場合は、エラーが発生しています。
   If GetWindowRect(hwnd, rectWindow) = 0 Then
      ' 無効なハンドルを渡したことにより、エラーが発生した場合は 
      ' LastDLLError を調べ、ダイアログ ボックスを表示します。
      If Err.LastDllError = ERROR_INVALID_WINDOW_HANDLE Then
         MsgBox ERROR_INVALID_WINDOW_HANDLE_DESCR, _
            Title:="エラー !"
      End If
   Else
      Debug.Print rectWindow.Bottom
      Debug.Print rectWindow.Left
      Debug.Print rectWindow.Right
      Debug.Print rectWindow.Top
   End If
End Sub

アクティブ ウィンドウの座標を取得する場合、GetActiveWindow 関数を使用してアクティブ ウィンドウのハンドルを取得し、その結果を上記の例で定義したプロシージャに渡します。GetActiveWindow を使用するには、以下の Declare ステートメントを含めます。


Declare Function GetActiveWindow Lib "user32" () As Long

イミディエイト ウィンドウに次のように入力します。


? PrintWindowCoordinates(GetActiveWindow)

エラー メッセージを表示させるには、ランダムな long 整数を指定してプロシージャを呼び出します。

詳細情報

Windows API を使った作業について、さらに詳しい情報を入手したい場合は、以下のリソースを調べてみてください。

  • Microsoft Platform SDK。ここには、Windows API の完全なマニュアルが含まれています。この SDK は Microsoft Developer Network サイト https://msdn.microsoft.com/library/aa383750.aspx から無償で入手できます。

  • いつものように、Office ソリューション開発の情報や技術資料については、Office Developer Web Forum を定期的に確認するようにしてください。

David Shank は、Office Developer Documentation チームのマネージャーです。彼は、カスケード山脈の山麓の丘に奥さん、二人の娘、4匹の猫そして2匹の犬と暮らしています。時間のあるときは、Pacific Northwest の自然の驚異を楽しむことに費やしていると言っていますが、彼の家族は、Microsoft Officeで開発の解決について考えることに時間を費やしていると言っています。