Office VBA und die Windows-API

Veröffentlicht: 16. Dez 2001 | Aktualisiert: 09. Nov 2004

Von David Shank

Wir alle wissen, dass Visual Basic für Applikationen (VBA) eine leistungsstarke Programmiersprache ist, die Sie in Ihren benutzerdefinierten Microsoft Office-Lösungen verwenden können. Durch Verwendung von VBA für die Arbeit mit einem oder mehreren der Office-Anwendungsobjektmodelle können Sie die Funktionen einer Office-Anwendung ganz einfach ändern oder es zwei oder mehr Office-Anwendungen ermöglichen zusammenzuarbeiten, um Aufgaben umzusetzen, die sie nicht allein realisieren können. Mit VBA können Sie nur einen kleinen Teil des Betriebssystems steuern: den Teil, der über die Funktionen und Objekte verfügbar ist, die direkt für VBA offen gelegt werden. Die Windows-API (Application Programming Interface = Schnittstelle zur Anwendungsprogrammierung) bietet Funktionen, mit denen Sie selbst kleinste Details des Betriebssystems steuern können. Sie können Ihre benutzerdefinierten Office-Lösungen erweitern und optimieren, indem Sie Funktionen in der Windows-API über VBA aufrufen.
In meiner Kolumne erhalten Sie in diesem Monat eine Einführung in das Arbeiten mit der Windows-API über VBA sowie einige Beispiele, die Sie in Ihre benutzerdefinierten Lösungen kopieren und direkt dort verwenden können.

Warnung Das Aufrufen der Windows-API und anderer DLL-Funktionen kann die Stabilität Ihrer Anwendung gefährden. Wenn Sie eine DLL-Funktion direkt über Code aufrufen, umgehen Sie einige der Sicherheitsmechanismen, die VBA normalerweise bereitstellt. Wenn Ihnen beim Definieren oder Aufrufen einer DLL-Funktion ein Fehler unterläuft (wie dies bei allen Programmierern irgendwann einmal der Fall ist), können Sie einen Anwendungsfehler auslösen. Dieser wird auch als allgemeine Schutzverletzung bzw. GPF (General Protection Fault) bezeichnet. Die beste Strategie beim Arbeiten mit einer API besteht darin, Ihr Projekt zunächst zu speichern, bevor Sie den Code ausführen, und sicherzustellen, dass Sie die Prinzipien verstehen, die dem Aufrufen von DLL-Funktionen zu Grunde liegen.

APIs

Eine API setzt sich einfach aus Funktionen zusammen, die Sie für das Arbeiten mit einer Komponente, einer Anwendung oder einem Betriebssystem verwenden können. In der Regel besteht eine API aus einer oder mehreren DLLs, die bestimmte Funktionen bereitstellen.
DLLs sind Dateien, deren Funktionen über jede Anwendung aufgerufen werden können, die unter Windows ausgeführt wird. Eine Funktion in einer DLL wird zur Laufzeit dynamisch mit einer Anwendung verknüpft, von der sie aufgerufen wird. Unabhängig davon, wie viele Anwendungen eine Funktion in einer DLL aufrufen, ist diese Funktion nur in einer einzelnen Datei auf dem Datenträger enthalten, und die DLL wird nur einmal im Arbeitsspeicher erstellt.
Die API, die Ihnen vermutlich am geläufigsten sein dürfte, ist die Windows-API. Sie umfasst die DLLs, aus denen sich das Windows-Betriebssystem zusammensetzt. Jede Windows-Anwendung interagiert direkt oder indirekt mit der Windows-API. Die Windows-API stellt sicher, dass sich alle Anwendungen, die unter Windows ausgeführt werden, konsistent verhalten.
Neben der Windows-API sind noch weitere veröffentlichte APIs verfügbar. So setzt sich z.B. die Schnittstelle für die Programmierung von nachrichtenverarbeitenden Anwendungsprogrammen (MAPI, Messaging Application Programming Interface) aus DLLs zusammen, die für das Schreiben von E-Mail-Anwendungen verwendet werden können.
APIs werden zwar i.d.R. für C- und C++-Programmierer geschrieben, die Windows-Anwendungen erstellen, die Funktionen in einer DLL können jedoch auch über VBA aufgerufen werden. Da die meisten DLLs hauptsächlich für C-/C++-Programmierer geschrieben und dokumentiert werden, gibt es einige Unterschiede zwischen dem Aufrufen einer DLL-Funktion und dem Aufrufen einer VBA-Funktion. Für das Arbeiten mit einer API müssen Sie verstehen, wie Argumente an eine DLL-Funktion übergeben werden.
Für das Aufrufen von Funktionen in der Windows-API benötigen Sie eine entsprechende Dokumentation, in der beschrieben wird, welche Funktionen verfügbar sind, wie sie in VBA deklariert werden und wie sie aufgerufen werden. Dabei sind v.a. die folgenden beiden Ressourcen von Nutzen:

  • Die Datei Win32API.txt, die in Microsoft Office 2000 Developer und Microsoft Visual Basic enthalten ist. Die Datei Win32API.txt enthält VBA-Declare-Anweisungen für die meisten Funktionen der Windows-API. Sie können das API Viewer-Add-In in Office 2000 Developer verwenden, um die benötigte Declare-Anweisung zu suchen und zu kopieren. Weitere Informationen zum Installieren und Verwenden des API Viewer-Add-Ins finden Sie in der Datei apiload.txt, die im Lieferumfang von Office 2000 Developer enthalten ist. Die API Viewer-Anwendung für Microsoft Visual Basic bietet zwar dieselben Funktionen, ist jedoch eine eigenständige Anwendung. Wenn Sie die API Viewer-Anwendung zum ersten Mal ausführen, wird die Datei Win32API.txt geladen. Diese Datei kann in eine Microsoft Access-Datenbank (MDB) exportiert werden. Dadurch wird das Laden der API-Daten sowie der Bildlauf beschleunigt.

  • Das Microsoft Plattform-SDK, das eine umfassende Dokumentation für die Windows-API enthält. Es ist kostenlos über die Microsoft Developer Network-Site (in Englisch) verfügbar: https://msdn.microsoft.com/library/default.asp?URL=/library/psdk/portals/win32start_1n6t.htm.

Arbeiten mit der "Declare"-Anweisung
Bevor Sie eine Funktion in einer DLL über VBA aufrufen können, müssen Sie VBA Informationen über den Speicherort dieser Funktion bereitstellen und angeben, wie die Funktion aufgerufen wird. Dafür gibt es zwei Möglichkeiten:

  • Festlegen eines Verweises auf die Typbibliothek der DLL.

  • Verwenden einer Declare-Anweisung in einem Modul.

Das Festlegen eines Verweises auf die Typbibliothek einer DLL ist die einfachste Möglichkeit für das Arbeiten mit Funktionen in der DLL. Sobald Sie den Verweis festgelegt haben, können Sie die DLL-Funktion so aufrufen, als wäre sie Bestandteil Ihres Projekts. Dabei gibt es jedoch einige Nachteile. Zunächst einmal kann das Festlegen von Verweisen auf mehrere Typbibliotheken die Leistung Ihrer Anwendung beeinträchtigen. Zweitens bieten nicht alle DLLs Typbibliotheken. Sie können zwar einen Verweis auf eine DLL festlegen, die keine Typbibliothek bereitstellt, Funktionen in dieser DLL jedoch nicht so aufrufen, als wären sie Bestandteil Ihres Projekts.
Beachten Sie, dass die DLLs, aus denen sich die Windows-API zusammensetzt, keine Typbibliotheken bereitstellen. Sie können also keine Verweise darauf festlegen und die entsprechenden Funktionen nicht aufrufen. Zum Aufrufen einer Funktion in der Windows-API müssen Sie eine Declare-Anweisung in den Deklarationsabschnitt eines Moduls in Ihrem Projekt einfügen.
Eine Declare-Anweisung ist eine Definition, die VBA darüber informiert, wo sich eine bestimmte DLL-Funktion befindet und wie sie aufgerufen wird. Die einfachste Möglichkeit für das Hinzufügen einer Declare-Anweisung zum Code besteht darin, das API Viewer-Add-In zu verwenden, das sowohl Declare-Anweisungen für die meisten Funktionen in der Windows-API als auch die Konstanten und Typdefinitionen enthält, die einige Funktionen erfordern.
Hier sehen Sie ein Beispiel der Declare-Anweisung für die GetTempPath-Funktion, die den Pfad zum Windows-Ordner Temp zurückgibt (standardmäßig C:\Windows\Temp):

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

Das Schlüsselwort Declare informiert VBA, dass Sie die Definition für eine DLL-Funktion in Ihr Projekt aufnehmen möchten. Eine Declare-Anweisung in einem Standardmodul kann öffentlich oder privat sein, je nachdem, ob die API-Funktion nur für ein einzelnes Modul oder aber für das gesamte Projekt verfügbar sein soll. In einem Klassenmodul muss eine Declare-Anweisung privat sein.
Der Name der Funktion, die auf das Schlüsselwort Function folgt, ist der Name, den Sie für das Aufrufen der Funktion über VBA verwenden. Dieser Name kann mit dem Namen der API-Funktion identisch sein. Sie können jedoch auch das Schlüsselwort Alias in der Declare-Anweisung verwenden, um anzugeben, dass Sie die Funktion über einen anderen Namen (einen Alias) in VBA aufrufen möchten.
Im obigen Beispiel lautet der Name der API-Funktion in der DLL GetTempPathA. Der Name, über den Sie sie aus VBA abrufen würden, ist GetTempPath. Beachten Sie, dass der eigentliche Name der DLL-Funktion hinter dem Schlüsselwort Alias angezeigt wird. Beachten Sie außerdem, dass GetTempPath zwar der Name ist, den die Datei Win32API.txt als Aliasnamen für die Funktion verwendet, Sie diesen Namen jedoch nach Belieben ändern können.
Im Folgenden sind einige Gründe für die Verwendung eines Aliasnamens in einer Declare-Anweisung aufgeführt:

  • Einige API-Funktionsnamen beginnen mit einem Unterstrich (_). Dies ist in VBA nicht zulässig. Für das Aufrufen der Funktion aus VBA benötigen Sie einen Aliasnamen.

  • Da Sie über einen Alias einen beliebigen Namen für eine DLL-Funktion verwenden, können Sie einen Funktionsnamen wählen, der Ihren eigenen Benennungsstandards in VBA entspricht.

  • Da bei API-Funktionen die Groß-/Kleinschreibung beachtet wird, bei VBA-Funktionen jedoch nicht, können Sie einen Aliasnamen verwenden, um die Groß-/Kleinschreibung eines Funktionsnamens zu ändern.

  • Einige DLL-Funktionen haben Argumente, die verschiedene Datentypen verarbeiten können. Die VBA-Declare-Anweisungen für diese Funktionen definieren diese Argumente als Typ Any. Das Aufrufen von DLL-Funktion mit Argumenten, die als Any deklariert sind, kann riskant sein, da VBA den Datentyp nicht überprüft. Wenn Sie die Risiken vermeiden möchten, die sich beim Übergeben von Argumenten als Any ergeben, können Sie mehrere Versionen derselben DLL-Funktion deklarieren, jeweils mit einem anderen Namen und Datentyp.

  • Die Windows-API enthält zwei Versionen aller Funktionen, die Zeichenfolgenargumente verarbeiten: eine ANSI-Version und eine Unicode-Version. Die ANSI-Version hat das Suffix "A", wie im obigen Beispiel veranschaulicht, während die Unicode-Version das Suffix "W" hat. VBA verwendet zwar intern Unicode, konvertiert jedoch vor dem Aufrufen einer Funktion in einer DLL alle Zeichenfolgen in ANSI-Zeichenfolgen. Daher verwenden Sie beim Aufrufen einer Windows-API-Funktion über VBA i.d.R. die ANSI-Version. Das API Viewer-Add-In verwendet automatisch Aliasnamen für alle Funktionen, die Zeichenfolgenargumente verarbeiten, damit Sie die Funktion aufrufen können, ohne das Suffix "A" angeben zu müssen.

Das Schlüsselwort Lib legt fest, welche DLL die Funktion enthält. Beachten Sie, dass der Name der DLL in einer Zeichenfolge in der Declare-Anweisung enthalten ist. Wenn die DLL, die nach dem Schlüsselwort Lib angegeben ist, im System des Benutzers nicht gefunden wurde, schlägt ein Aufruf der Funktion mit Laufzeitfehler 48 fehl: "Fehler beim Laden einer DLL". Sie können jedoch stabilen VBA-Code schreiben, der diesen Fehler behebt. (Dieses Problem tritt nicht auf, wenn Sie eine Funktion in einer der Standard-Windows-DLLs aufrufen, da diese DLLs vorhanden sein müssen, damit Ihre Anwendung geladen werden kann.)
In der folgenden Tabelle werden die am häufigsten verwendeten DLLs in der Windows-API beschrieben.

DLL

Inhalt

Kernel32.dll

Betriebssystemfunktionen auf niedriger Ebene, z.B. für die Arbeitsspeicherverwaltung und Ressourcenverarbeitung

User32.dll

Windows-Verwaltungsfunktionen, z.B. für die Meldungsverarbeitung, Zeitgeber, Menüs und Kommunikation

GDI32.dll

Die Bibliothek für die Grafische Geräteschnittstelle (GDI, Graphics Device Interface), die Funktionen für die Geräteausgabe enthält, z.B. für das Zeichnen, den Anzeigekontext und die Schriftartverwaltung

Die meisten DLLs, einschließlich der DLLs in der Windows-API, sind in C/C++ geschrieben. Das Übergeben von Argumenten an eine DLL-Funktion erfordert daher ein Grundverständnis der Argumente und Datentypen, die eine C-/C++-Funktion erwartet. Diese unterscheiden sich in mehreren Aspekten von den Erwartungen einer VBA-Funktion.
Außerdem werden viele Argumente für DLL-Funktionen nach Wert (By Value) übergeben. Da Argumente in VBA standardmäßig nach Verweis (By Reference) übergeben werden, müssen Sie das Schlüsselwort ByVal in die Funktionsdefinition einfügen, wenn die DLL-Funktion die Übergabe eines Arguments nach Wert erfordert. Das Auslassen des Schlüsselworts ByVal in einer Funktion kann in einigen Fällen einen Fehler aufgrund einer ungültigen Seite nach sich ziehen. In anderen Fällen kann der VBA-Laufzeitfehler 49 eintreten: "Falsche DLL-Aufrufkonvention".
Beim Übergeben eines Arguments nach Verweis wird die Arbeitsspeicherposition dieses Arguments an die aufgerufene Prozedur übergeben. Wenn die Prozedur den Wert dieses Arguments ändert, wird die einzige Kopie dieses Arguments geändert, d.h. wenn die Ausführung zu der aufrufenden Prozedur zurückkehrt, enthält das Argument den geänderten Wert.
Beim Übergeben eines Arguments an eine DLL-Funktion nach Wert wird andererseits eine Kopie des Arguments übergeben und von der Funktion verwendet. Auf diese Weise wird verhindert, dass die Funktion den Inhalt des eigentlichen Arguments ändert. Wenn die Ausführung zur aufrufenden Prozedur zurückkehrt, enthält das Argument denselben Wert wie vor dem Aufrufen der anderen Prozedur.
Da beim Übergeben nach Verweis ein Argument im Arbeitsspeicher geändert werden kann, überschreibt die DLL u.U. Arbeitsspeicher, falls Sie ein Argument fehlerhaft nach Verweis übergeben, und löst so einen Fehler oder ein anderes unerwartetes Verhalten aus. Windows verwendet viele Werte, die nicht überschrieben werden sollten. So weist Windows z.B. jedem Fenster einen eindeutigen 32-Bit-Bezeichner zu, ein sog. Handle. Handles werden stets nach Wert an API-Funktionen übergeben, denn wenn Windows den Wert eines Fensterhandles ändern würde, könnte dieses Fenster nicht mehr verfolgt werden. (Das Schlüsselwort ByVal ist zwar einigen Argumenten vom Typ String vorangestellt, Zeichenfolgen werden jedoch stets nach Verweis an Windows-API-Funktionen übergeben.)

Arbeiten mit Konstanten
Neben der Declare-Anweisung für eine DLL-Funktion erfordern einige Funktionen die Definition von Konstanten und Typen für die Verwendung mit dieser Funktion. Sie müssen Konstanten und benutzerdefinierte Typdefinitionen in den Deklarationsabschnitt eines Moduls einfügen, zusammen mit den Declare-Anweisungen für die Funktionen, für die sie erforderlich sind.
Woher wissen Sie, welche Konstanten und benutzerdefinierten Typen eine Funktion erfordert? Auch hier sollten Sie sich die Dokumentation für die Funktion ansehen. Die Datei Win32API.txt enthält Definitionen für die Konstanten und benutzerdefinierten Typen, die zu den enthaltenen Funktionen gehören. Sie können das API Viewer-Add-In verwenden, um diese Konstanten und benutzerdefinierten Typen zu suchen und zu kopieren sowie in Ihren Code einzufügen. Leider sind die Konstanten und benutzerdefinierten Typen nicht mit den Declare-Anweisungen verknüpft, die sie erfordern. Daher müssen Sie weiterhin die Dokumentation für die DLL-Funktion konsultieren, um zu erfahren, welche Konstanten und Typen zu welchen Declare-Anweisungen gehören.
Für eine Funktion müssen Sie u.U. eine Konstante übergeben, um anzugeben, welche Informationen die Funktion zurückgeben soll. So verwendet die GetSystemMetrics-Funktion beispielsweise eine von 75 Konstanten, die jeweils einen anderen Aspekt des Betriebssystems festlegen. Welche Informationen von der Funktion zurückgegeben werden, hängt davon ab, welche Konstante Sie übergeben haben. Für das Aufrufen von GetSystemMetrics müssen Sie nicht alle 75 Konstanten angeben - fügen Sie einfach die Konstanten ein, die Sie verwenden möchten.
Sie sollten Konstanten definieren, anstatt einfach die Werte zu übergeben, die sie darstellen. Microsoft stellt zwar sicher, dass die Konstanten auch in künftigen Versionen beibehalten werden, es gibt jedoch keine Garantien für die Konstantenwerte selbst.
Da die von einer DLL-Funktion benötigten Konstanten oft recht kryptisch sind, sollten Sie in der Dokumentation für die Funktion nachschlagen, welche Konstante übergeben werden muss, damit ein bestimmter Wert zurückgegeben wird.
Das folgende Beispiel enthält die Declare-Anweisung für die GetSystemMetrics-Funktion sowie zwei der Konstanten, die sie verarbeiten kann. Anschließend wird aufgezeigt, wie GetSystemMetrics aus Property-Prozeduren heraus aufgerufen werden kann, um die Bildschirmhöhe (und -breite) in Pixel zurückzugeben:

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 
   ' Zurückgeben der Bildschirmhöhe in Pixel. 
   ScreenHeight = GetSystemMetrics(SM_CYSCREEN) 
End Property 
Public Property Get ScreenWidth() As Long 
   ' Zurückgeben der Bildschirmbreite in Pixel. 
   ScreenWidth = GetSystemMetrics(SM_CXSCREEN) 
End Property

Arbeiten mit benutzerdefinierten Typen
Ein benutzerdefinierter Typ ist eine Datenstruktur, die mehrere verwandte Variablen von unterschiedlichen Typen speichern kann. Sie entspricht einer Struktur in C/C++. In einigen Fällen können Sie einen leeren benutzerdefinierten Typ an eine DLL-Funktion übergeben, und die Funktion trägt die Werte für Sie ein. In anderen Fällen füllen Sie den benutzerdefinierten Typ über VBA aus und übergeben ihn an die DLL-Funktion.
Sie können sich einen benutzerdefinierten Typ wie eine Kommode vorstellen. Jede Schublade kann unterschiedliche Typen von Elementen enthalten, doch zusammen können sie als Kommode verwandter Elemente behandelt werden. Und Sie können ein Element aus einer Schublade entnehmen, ohne sich über die Elemente in den anderen Schubladen kümmern zu müssen.
Für das Erstellen eines benutzerdefinierten Typs verwenden Sie die Type.End Type-Anweisung. Führen Sie in der Type.End Type-Anweisung jedes Element auf, das einen Wert enthalten soll, sowie den entsprechenden Datentyp. Ein Element eines benutzerdefinierten Typs kann ein Array sein.
Das folgende Codefragment veranschaulicht, wie Sie den benutzerdefinierten Typ RECT definieren, der für verschiedene Windows-API-Funktionen zur Verwaltung von Rechtecken auf dem Bildschirm verwendet wird. So ruft z.B. die GetWindowRect-Funktion eine Datenstruktur vom Typ RECT ab und füllt sie mit Informationen über die linken, oberen, rechten und unteren Koordinaten eines Fensters.

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

Für das Übergeben eines benutzerdefinierten Typs an eine DLL-Funktion müssen Sie eine Variable dieses Typs erstellen. Wenn Sie z.B. vorhaben, einen benutzerdefinierten Typ vom Typ RECT an eine DLL-Funktion zu übergeben, könnten Sie eine Variablendeklaration wie die folgende in das Modul einfügen:

Private rectWindow As RECT

Sie können, wie im folgenden Codefragment veranschaulicht, auf ein einzelnes Element innerhalb des benutzerdefinierten Typs verweisen:

Debug.Print rectWindow.Left

Arbeiten mit Handles
Ein weiteres wichtiges Konzept, mit dem Sie sich vor dem Aufrufen von Funktionen in DLLs vertraut machen sollten, ist das Handle. Ein Handle ist einfach eine positive 32-Bit-Ganzzahl, die Windows für das Identifizieren eines Fensters oder eines anderen Objekts verwendet, z.B. einer Schriftart oder Bitmap.
Der Begriff "Fenster" umfasst in Windows viele verschiedene Objekte. Tatsächlich befindet sich fast alles, was Sie auf dem Bildschirm sehen, in einem Fenster, ebenso wie viele Dinge, die Sie nicht sehen können. Ein Fenster kann ein begrenzter rechteckiger Bildschirmbereich sein, wie die Anwendungsfenster, mit denen Sie arbeiten. Ein Steuerelement in einem Formular, z.B. ein Listenfeld oder eine Bildlaufleiste, kann ebenfalls ein Fenster sein - allerdings sind nicht alle Typen von Steuerelementen Fenster. Die Symbole, die auf dem Desktop angezeigt werden, sowie der Desktop selbst sind Fenster.
Da alle diese Objekttypen Fenster sind, kann Windows sie ähnlich behandeln. Windows verleiht jedem Fenster ein eindeutiges Handle und verwendet das Handle, um mit diesem Fenster zu arbeiten. Viele API-Funktionen geben Handles zurück oder rufen sie als Argumente ab.
Windows weist einem Fenster bei seiner Erstellung ein Handle zu und gibt dieses wieder frei, wenn das Fenster gelöscht wird. Obwohl das Handle während der gesamten Lebensdauer des Fensters beibehalten bleibt, ist nicht gewährleistet, dass ein Fenster dasselbe Handle beibehält, wenn es gelöscht und neu erstellt wird. Wenn Sie ein Handle in einer Variablen speichern, sollten Sie daher bedenken, dass das Handle nach dem Löschen des Fensters nicht mehr gültig ist.
Die GetActiveWindow-Funktion ist ein Beispiel für eine Funktion, die ein Handle auf ein Fenster zurückgibt - in diesem Fall das derzeit aktive Anwendungsfenster. Die GetWindowText-Funktion ruft ein Handle auf ein Fenster ab und gibt die Beschriftung des Fensters zurück, sofern vorhanden. Die folgende Prozedur verwendet GetActiveWindow, um ein Handle für das aktive Fenster zurückzugeben, und GetWindowText, um dessen Beschriftung zurückzugeben:

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 
    ' Erstellen einer Zeichenfolge mit Nullzeichen. 
    strCaption = String$(255, vbNullChar) 
    ' Zurückgeben der Zeichenfolgenlänge. 
    lngLen = Len(strCaption) 
    ' Aufrufen von GetActiveWindow, um Handle für aktives Fenster zurückzugeben, 
    ' und Übergeben des Handles an GetWindowText, zusammen mit der Zeichenfolge 
    ' und ihrer Länge. 
    If (GetWindowText(GetActiveWindow, strCaption, _ 
        lngLen) > 0) Then 
        ' Zurückgeben des Wertes, den Windows in die Zeichenfolge geschrieben hat. 
        ActiveWindowCaption = strCaption 
    End If 
End Function

Die GetWindowText-Funktion verarbeitet drei Argumente: das Handle für ein Fenster, eine auf Null endende Zeichenfolge, in die die Beschriftung des Fensters zurückgegeben wird, sowie die Länge dieser Zeichenfolge.

Aufrufen von Funktionen
Das Aufrufen von DLL-Funktionen ähnelt zwar in vielerlei Hinsicht dem Aufrufen von VBA-Funktionen, es gibt jedoch einige Unterschiede, die DLL-Funktionen zunächst verwirrend erscheinen lassen. In diesem Abschnitt wird beschrieben, wie Argumente in DLL-Funktionen typisiert und mit Präfixen versehen werden, wie Sie eine Zeichenfolge zurückgeben und eine Datenstruktur übergeben, welche Rückgabewerte Sie erwarten können und wie Sie Fehlerinformationen abrufen.

Argumentdatentypen
Die in C/C++ verwendeten Datentypen sowie die Notation für ihre Beschreibung weichen von den in VBA verwendeten Typen ab. In der folgenden Tabelle werden einige Datentypen beschrieben, die häufig in DLL-Funktionen verwendet werden, und ihre VBA-Entsprechungen aufgeführt.

C-/C++-Datentyp

Ungarisches Präfix

Beschreibung

VBA-Entsprechung

BOOL

b

8-Bit-Boolean-Wert. Null zeigt False an, Nichtnull hingegen True.

Boolean oder Long

BYTE

ch

Unsignierte 8-Bit-Ganzzahl

Byte

HANDLE

h

Unsignierte 32-Bit-Ganzzahl, die ein Handle für ein Windows-Objekt darstellt

Long

int

n

Signierte 16-Bit-Ganzzahl

Integer

long

l

Signierte 32-Bit-Ganzzahl

Long

LP

lp

32-Bit-Long-Zeiger auf eine C-/C++-Struktur, -Zeichenfolge, -Funktion oder andere Arbeitsspeicherdaten

Long

LPZSTR

lpsz

32-Bit-Long-Zeiger auf eine auf Null endende C-Typ-Zeichenfolge

Long

Sie sollten schon mit diesen Datentypen und deren Präfixen vertraut sein. Die zuvor erwähnte Datei Win32API.txt enthält jedoch Declare-Anweisungen, die ohne große Einarbeitung in VBA verwendet werden können. Wenn Sie diese Declare-Anweisungen im Code verwenden, sind die Funktionsargumente bereits mit den richtigen VBA-Datentypen definiert.
Wenn Sie die richtigen Datentypen definiert und übergeben haben, funktioniert das Aufrufen von DLL-Funktionen im Wesentlichen so wie das Aufrufen von VBA-Funktionen. Die Ausnahmen werden in den folgenden Abschnitten erörtert.

Zurückgeben von Zeichenfolgen aus DLL-Funktionen
DLL-Funktionen geben Zeichenfolgen nicht auf dieselbe Weise zurück wie VBA-Funktionen. Da Zeichenfolgen stets nach Verweis an DLL-Funktionen übergeben werden, kann die DLL-Funktion den Wert des Zeichenfolgenarguments ändern. Anstatt eine Zeichenfolge als Rückgabewert für die Funktion zurückzugeben, wie Sie dies vermutlich in VBA tun würden, gibt eine DLL-Funktion eine Zeichenfolge in ein Argument vom Typ String zurück, das an die Funktion übergeben wurde. Der tatsächliche Rückgabewert für die Funktion ist oft eine Ganzzahl vom Typ long, die angibt, wie viele Bytes in das Zeichenfolgenargument geschrieben wurden.
Eine DLL-Funktion, die ein Zeichenfolgenargument verarbeitet, ruft einen Zeiger auf den Speicherort dieser Zeichenfolge im Arbeitsspeicher ab. Ein Zeiger ist einfach eine Arbeitsspeicheradresse, die anzeigt, wo die Zeichenfolge gespeichert ist. Wenn Sie eine Zeichenfolge aus VBA an eine DLL-Funktion übergeben, übergeben Sie der DLL-Funktion daher einen Zeiger auf Ihre Zeichenfolge im Arbeitsspeicher. Die DLL-Funktion verändert dann die Zeichenfolge, die unter dieser Adresse gespeichert ist.
Für das Aufrufen einer DLL-Funktion, die in eine String-Variable schreibt, müssen Sie weitere Schritte ausführen, um die Zeichenfolge ordnungsgemäß zu formatieren. Zunächst einmal muss die String-Variable eine auf Null endende Zeichenfolge sein. Eine auf Null endende Zeichenfolge endet mit einem speziellen Nullzeichen, das durch die VBA-Konstante vbNullChar festgelegt ist.
Zweitens kann eine DLL-Funktion die Größe einer Zeichenfolge nicht mehr ändern, nachdem diese erstellt wurde. Daher müssen Sie sicherstellen, dass die Zeichenfolge, die Sie an eine Funktion übergeben, groß genug ist, um den gesamten Rückgabewert zu umfassen. Wenn Sie eine Zeichenfolge an eine DLL-Funktion übergeben, müssen Sie i.d.R. die Größe der Zeichenfolge angeben, die Sie in ein anderes Argument übergeben haben. Windows verfolgt die Länge der Zeichenfolge, um sicherzustellen, dass der von der Zeichenfolge verwendete Arbeitsspeicher nicht überschrieben wird.
Eine Möglichkeit für das Übergeben einer Zeichenfolge an eine DLL-Funktion besteht darin, eine String-Variable zu erstellen und die String$-Funktion zu verwenden, um sie mit Nullzeichen zu füllen, damit sie groß genug ist für die von der Funktion zurückgegebene Zeichenfolge. So erstellt das folgende Codefragment beispielsweise eine Zeichenfolge von 144 Byte Länge, die mit Nullzeichen gefüllt ist:

Dim strTempPath As String 
strTempPath = String$(144, vbNullChar)

Wenn Sie beim Übergeben der Zeichenfolge an die DLL-Funktion die Länge der Zeichenfolge nicht kennen, können Sie sie anhand der Len-Funktion ermitteln.
Die GetTempPath-Funktion, die den Pfad zum Windows-Ordner Temp abruft, ist ein Beispiel für eine DLL-Funktion, die einen String-Wert zurückgibt. Sie verwendet zwei Argumente - eine auf Null endende String-Variable und einen numerischen Wert, der die Länge der Zeichenfolge enthält - und ändert die Zeichenfolge so ab, dass sie den Pfad enthält, z.B. C:\Temp\. (Da Windows für den Systemstart einen Temp-Ordner benötigt, sollte diese Funktion stets einen Pfad zu diesem Ordner zurückgeben. Wenn dies aus irgendeinem Grund nicht der Fall sein sollte, gibt GetTempPath Null zurück.)
Die folgende Prozedur ruft den Pfad zum Windows-Ordner Temp ab, indem die GetTempPath-Funktion aufgerufen wird:

Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _ 
   (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long 
Property Get GetTempFolder() As String 
   ' Gibt den Pfad zum Temp-Ordner des Benutzers zurück. Da Windows für den  
   ' Systemstart einen Temp-Ordner benötigt, sollte stets ein 
   ' entsprechender Pfad zurückgegeben werden. Überprüfen Sie vorsichtshalber  
   ' den Rückgabewert von GetTempPath. 
   Dim strTempPath As String 
   Dim lngTempPath As Long 
   ' Auffüllen der Zeichenfolge mit Nullzeichen. 
   strTempPath = String(144, vbNullChar) 
   ' Abrufen der Länge der Zeichenfolge. 
   lngTempPath = Len(strTempPath) 
   ' Abrufen von GetTempPath, Übergeben der Zeichenfolge samt Länge. 
   If (GetTempPath(lngTempPath, strTempPath) > 0) Then 
      ' GetTempPath gibt den Pfad in die Zeichenfolge zurück. 
      ' Abschneiden der Zeichenfolge beim ersten Nullzeichen. 
      GetTempFolder = Left(strTempPath, _ 
         InStr(1, strTempPath, vbNullChar) - 1) 
   Else 
      GetTempFolder = "" 
   End If 
End Property

Bild01

Abbildung 1. An DLL-Funktion übergebene Zeichenfolge

Beachten Sie, dass die Zeichenfolge mit Nullzeichen gefüllt wird, wenn sie an die Funktion übergeben wird. Die Funktion schreibt den zurückgegebenen String-Wert, C:\Temp\, in den ersten Teil der String-Variable, und der Rest ist mit Nullzeichen aufgefüllt, die Sie unter Verwendung der Left-Funktion abschneiden können.
Der tatsächliche Rückgabewert für die GetTempPath-Funktion ist die Anzahl von Zeichen, die in die String-Variable geschrieben wurden. Wenn die zurückgegebene Zeichenfolge C:\Temp\ ist, gibt die GetTempPath-Funktion den Wert 8 zurück.
Beachten Sie, dass Sie eine auf Null endende Zeichenfolge samt Größe nur eingeben müssen, wenn Sie eine Zeichenfolge aus einer Funktion zurückgeben. Wenn die Funktion keine Zeichenfolge in ein Zeichenfolgenargument zurückgibt, sondern stattdessen eine Zeichenfolge abruft, die Informationen für die Funktion bereitstellt, können Sie einfach eine normale VBA-String-Variable übergeben.

Übergeben von benutzerdefinierten Typen an eine DLL-Funktion
Viele DLL-Funktionen erfordern das Übergeben einer Datenstruktur unter Verwendung eines vordefinierten Formats. Wenn Sie eine DLL-Funktion über VBA aufrufen, übergeben Sie einen benutzerdefinierten Typ, den Sie gemäß den Anforderungen der Funktion definiert haben.
Anhand der Declare-Anweisung für die Funktion erkennen Sie, ob Sie einen benutzerdefinierten Typ übergeben müssen und welche Typdefinition Sie in Ihren Code einfügen müssen. Ein Argument, das eine Datenstruktur erfordert, ist stets als Zeiger vom Typ long deklariert: ein numerischer 32-Bit-Wert, der auf die Datenstruktur im Arbeitsspeicher zeigt. Das herkömmliche Präfix für ein Zeigerargument vom Typ long ist "lp". Darüber hinaus ist der Datentyp für das Argument der Name der Datenstruktur.
Nehmen wir als Beispiel die Declare-Anweisungen für die Funktionen GetLocalTime und SetLocalTime:

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

Beide Funktionen verwenden ein Argument vom Typ SYSTEMTIME, d.h. eine Datenstruktur, die Datums- und Uhrzeitinformationen enthält. Für den SYSTEMTIME-Typ gilt folgende Definition:

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

Für das Übergeben der Datenstruktur an eine Funktion müssen Sie, wie im folgenden Beispiel, eine Variable vom Typ SYSTEMTIME deklarieren:

Private sysLocalTime As SYSTEMTIME

Beim Aufrufen von GetLocalTime übergeben Sie eine Variable vom Typ SYSTEMTIME an die Funktion, und die Datenstruktur wird mit numerischen Werten gefüllt, die Jahr, Monat, Tag, Wochentag, Stunde, Minute, Sekunde und Millisekunde für das aktuelle Gebiet anzeigen. So ruft z.B. die folgende PropertyGet-Prozedur GetLocalTime auf, um einen Wert zurückzugeben, der die aktuelle Stunde anzeigt:

Public Property Get Hour() As Integer 
   ' Abrufen der aktuellen Zeit, anschließend Zurückgeben der Stunde. 
   GetLocalTime sysLocalTime 
   Hour = sysLocalTime.wHour 
End Property

Beim Aufrufen von SetLocalTime übergeben Sie auch eine Variable vom Typ SYSTEMTIME, doch zunächst stellen Sie Werte für eines oder mehrere Elemente der Datenstruktur bereit. So legt z.B. die folgende PropertyLet-Prozedur den Stundenwert für die örtliche Systemzeit fest. Zunächst wird GetLocalTime aufgerufen, um die aktuellen Werte für die Ortszeit in die Datenstruktur abzurufen:

sysSystem

. Anschließend wird der Wert des Elements

sysLocalTime.wHour

der Datenstruktur mit dem Wert des Arguments aktualisiert, das an die Property-Prozedur übergeben wurde. Abschließend wird SetLocalTime unter Übergabe derselben Datenstruktur aufgerufen. Diese enthält die Werte, die von GetLocalTime abgerufen wurden, sowie den neuen Stundenwert.

Public Property Let Hour(intHour As Integer) 
   ' Abrufen der aktuellen Uhrzeit, damit alle Werte aktuell sind, 
   ' anschließend Festlegen der Stunde für die Ortszeit. 
   GetLocalTime sysLocalTime 
   sysLocalTime.wHour = intHour 
   SetLocalTime sysLocalTime 
End Property

Die Funktionen GetLocalTime und SetLocalTime ähneln den Funktionen GetSystemTime und SetSystemTime. Der Hauptunterschied besteht darin, dass die Funktionen GetSystemTime und SetSystemTime die Uhrzeit als Greenwich Mean Time (GMT) ausdrücken. Wenn die Ortszeit in Deutschland z.B. 0 Uhr ist, beträgt die Greenwich Mean Time 23 Uhr, es herrscht also ein Zeitunterschied von einer Stunde. Die GetSystemTime-Funktion gibt die aktuelle Zeit als 23 Uhr zurück, während GetLocalTime 0 Uhr zurückgibt.

Der Datentyp "Any"
Einige DLL-Funktionen haben Argumente, die verschiedene Datentypen verarbeiten können. In der Declare-Anweisung für eine DLL-Funktion ist ein solches Argument als Typ Any deklariert. VBA ermöglicht es Ihnen, einen beliebigen Datentyp an dieses Argument zu übergeben. Da die DLL-Funktion jedoch so entworfen sein kann, dass nur zwei oder drei unterschiedliche Datentypen akzeptiert werden, kann die Übergabe des falschen Datentyps einen Anwendungsfehler auslösen.
Wenn Sie Code in einem VBA-Projekt kompilieren, führt VBA normalerweise eine Typüberprüfung der Werte durch, die Sie für die einzelnen Argumente übergeben. Das heißt, es wird sichergestellt, dass der Datentyp des übergebenen Wertes mit dem Datentyp für das Argument in der Funktionsdefinition übereinstimmt. Wenn Sie ein Argument z.B. als Typ Long definieren und versuchen, einen Wert vom Typ String zu übergeben, tritt bei der Kompilierung ein Fehler auf. Dies gilt auch für das Aufrufen einer integrierten VBA-Funktion, einer benutzerdefinierten Funktion oder einer DLL-Funktion. Wenn Sie ein Argument als Typ Any deklarieren, erfolgt jedoch keine Typüberprüfung. Daher sollten Sie beim Übergeben eines Wertes an ein Argument dieses Typs vorsichtig sein.
Einige DLL-Funktionen verfügen über ein Argument, das entweder eine Zeichenfolge oder einen Nullzeiger auf eine Zeichenfolge akzeptieren kann. Ein Nullzeiger auf eine Zeichenfolge ist ein spezieller Zeiger, der Windows anweist, ein bestimmtes Argument zu ignorieren. Er ist nicht identisch mit einer Zeichenfolge der Länge Null (""). In frühen Versionen von VBA mussten Programmierer ein solches Argument entweder als Typ Any deklarieren oder zwei Versionen der DLL-Funktionen deklarieren: eine, die das Argument als Typ String definiert, und eine, die es als Typ Long definiert. VBA bietet nun die vbNullString-Konstante, die einen Nullzeiger auf eine Zeichenfolge darstellt. Damit können Sie das Argument als Typ String deklarieren und die vbNullString-Konstante übergeben, falls Sie einmal einen Nullzeiger übergeben müssen. Für weitere Informationen über das Übergeben eines Nullzeigers aus VBA suchen Sie in der Microsoft Developer Network-Website nach dem Schlüsselwort vbNullString.

Abrufen von Fehlerinformationen
Laufzeitfehler, die in DLL-Funktionen auftreten, verhalten sich anders als Laufzeitfehler in VBA: Es wird kein Fehlermeldungsfeld angezeigt. Wenn ein Laufzeitfehler auftritt, gibt die DLL-Funktion zwar einen Wert zurück, der angibt, dass ein Fehler aufgetreten ist, der Fehler unterbricht jedoch nicht die Ausführung des VBA-Codes.
Einige Funktionen in der Windows-API speichern Fehlerinformationen für Laufzeitfehler. Wenn Sie in C/C++ programmieren, können Sie die GetLastError-Funktion verwenden, um Informationen über den letzten aufgetretenen Fehler abzurufen. In VBA gibt GetLastError jedoch u.U. falsche Ergebnisse zurück. Für das Abrufen von Informationen über einen DLL-Fehler aus VBA können Sie die LastDLLError-Eigenschaft des VBA-Err-Objekts verwenden. Die LastDLLError-Eigenschaft gibt die Nummer des aufgetretenen Fehlers zurück.
Für die Verwendung der LastDLLError-Eigenschaft müssen Sie wissen, welche Fehlernummern welchen Fehlern entsprechen. Diese Informationen sind nicht in der Datei Win32API.txt enthalten, jedoch kostenlos im Microsoft Plattform-SDK (in Englisch) erhältlich.
Im folgenden Beispiel sehen Sie, wie Sie die LastDLLError-Eigenschaft verwenden können, nachdem Sie eine Funktion in der Windows-API aufgerufen haben. Die PrintWindowCoordinates-Prozedur verarbeitet ein Handle für ein Fenster und ruft die GetWindowRect-Funktion auf. GetWindowRect füllt die RECT-Datenstruktur mit der Länge der Rechteckseiten des Fensters. Wenn Sie ein ungültiges Handle übergeben, tritt ein Fehler auf, und die Fehlernummer kann über die LastDLLError-Eigenschaft abgerufen werden.

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 = "Ungültiges Fensterhandle." 
Sub PrintWindowCoordinates(hwnd As Long) 
    ' Druckt die linken, rechten, oberen und unteren Koordinaten  
    ' eines Fensters in Pixel. 
   Dim rectWindow As RECT 
   ' Übergeben eines Fensterhandles und Leeren der Datenstruktur. 
   ' Wenn die Funktion 0 zurückgibt, ist ein Fehler aufgetreten. 
   If GetWindowRect(hwnd, rectWindow) = 0 Then 
      ' Überprüfen von LastDLLError und Anzeigen eines Dialogfelds, falls der Fehler 
      ' aufgetreten ist, weil ein ungültiges Handle übergeben wurde. 
      If Err.LastDllError = ERROR_INVALID_WINDOW_HANDLE Then 
         MsgBox ERROR_INVALID_WINDOW_HANDLE_DESCR, _ 
            Title:="Fehler!" 
      End If 
   Else 
      Debug.Print rectWindow.Bottom 
      Debug.Print rectWindow.Left 
      Debug.Print rectWindow.Right 
      Debug.Print rectWindow.Top 
   End If 
End Sub

Wenn Sie die Koordinaten für das aktive Fenster abrufen möchten, können Sie das Handle des aktiven Fensters unter Verwendung der GetActiveWindow-Funktion zurückgeben und dieses Ergebnis an die Prozedur übergeben, die im vorherigen Beispiel definiert wurde. Wenn Sie GetActiveWindow verwenden möchten, fügen Sie die folgende Declare-Anweisung ein:

Declare Function GetActiveWindow Lib "user32" () As Long

Geben Sie in das Direktfenster Folgendes ein:

? PrintWindowCoordinates(GetActiveWindow)

Zum Erzeugen einer Fehlermeldung rufen Sie die Prozedur mit einer zufälligen langen Ganzzahl auf.

Weitere Informationen über das Arbeiten mit der Windows-API erhalten Sie über folgende Ressourcen: