Typelib-Konstanten auf der Spur

Veröffentlicht: 16. Feb 2002 | Aktualisiert: 15. Jun 2004

Von Ralf Westphal

Die Werte von Enum-Konstanten in ihre Namen rückübersetzen

Seit Visual Basic erlaubt, Konstanten mit der Enum-Anweisung zu definieren, geht die Klage, warum es denn keine Möglichkeit gibt, Konstantenwerte in ihre Namen zurückzuübersetzen. Zum Glück bieten die Typelib-Informationen von ActiveX-Komponenten einen Weg, um die fehlende Funktionalität in VB doch zu realisieren.

Auf dieser Seite

 Konstanten deklarieren in VB
 Konstanten für COM-Komponenten
 Das Problem: Konstantennamen können nicht ermittelt werden
 Die Lösung: Rückübersetzung mittels Typelib
 Fazit
 Ressourcen

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

Bild04

Konstanten deklarieren in VB

Zu einem guten Programmierstil gehört es, insbesondere konstante Zahlenwerte nicht direkt im Code zu verwenden. Vermeiden Sie also Anweisungen wie

rs.Cursorlocation = 2

oder

umfang = 2 * 3.14 * radius

oder

err.Raise –2147221504 + 1, "MySub", "Ein Fehler ist aufgetreten!"

Ein Leser des Codes hätte große Schwierigkeiten zu erkennen, welches Flag, welche Option konkret gemeint ist. Außerdem ist der Wartungsaufwand bei solchem Code
groß, falls sich eine Zahl ändert.
Statt also konstante Zahlenwerte im Code direkt zu benutzen, sollten Sie die Werte hinter Konstantennamen verbergen, z.B.:

umfang = 2 * pi * radius

Jetzt müssen Sie den Wert von pi nur noch an einer Stelle im Code pflegen: in der Konstantendeklaration.

const pi = 3.141592

Const-Deklarationen gehören zum Standardsprachumfang von VB. Darüber hinaus können Sie noch so genannte Enumerations-Konstanten definieren
(s. [1] für Details), z. B.:

Enum MathKonstanten 
pi = 3.141592 
End Enum

Innerhalb einer Enum-Anweisung sind auch mehrere Deklarationen erlaubt:

Enum Farben 
blau = 0 
grün = 1 
rot = 2 
gelb = 3 
violett = 4 
End Enum

Diese Art der Konstantendeklaration ist den COM-Typbibliotheken entlehnt. Hier ein Ausschnitt aus der Datei enums.idl, der Typelib-Definition für die ActiveX-Komponente enums.dll:

[ 
  uuid(D52E4721-951D-11D3-AB62-000000000000), 
  version(1.0) 
] 
library Enums 
{ 
… 
typedef [uuid(D52E4724-951D-11D3-AB62-000000000000), version(1.0)] 
enum { 
blau = 0, 
grün = 1, 
rot = 2, 
gelb = 3, 
violett = 4 
} Farben; 
… 
}

Sie sehen, die Syntax ist ganz ähnlich wie in VB (s. [2] für eine ausführlichere Besprechung der Konstantendefinition per IDL und Typelibs).

 

Konstanten für COM-Komponenten

Solange Sie Konstanten nur innerhalb eines Projekts bzw. eines Moduls benutzen möchten, ist es egal, wie Sie sie definieren. Const und Enum sind in diesen Fällen gleichberechtigt. Allein Public und Private entscheiden über die Sichtbarkeit der Konstanten im Code.
Wenn Sie jedoch Konstanten als Bestandteile einer ActiveX-Komponente publizieren möchten, dann müssen Sie sie als Enumerationskonstanten deklarieren.
Abbildung 1 zeigt eine Reihe von Konstanten der CommonDialog-Komponente.

Bild01

Abbildung 1: Ein Ausschnitt aus der großen Zahl von Konstanten der CommonDialog-Komponente.
Alle Konstanten sind in der Typelib des OCX als Enum-Konstanten abgelegt.

Enum-Deklarationen in öffentlichen Klassen gehen in die Typelibrary Ihrer ActiveX-DLLs und ActiveX-EXE-Projekte ein. Die Klasse FarbenUndFormen.cls enthält mehrere Enum-Anweisungen (s.o. Farben), die beim Übersetzen in die Typelibrary von enums.dll eingeschlossen wurden (s.o. den Typelib-Auszug).

Ob eine Enum-Deklaration in die Typelib einer Komponente Eingang findet, ist tatsächlich nur davon abhängig, ob sie in einer MultiUse-, PublicNotCreatable- oder GlobalMultiUse-Klasse steht. Dabei ist es unerheblich, ob sie als Public oder Private gekennzeichnet wurde! Das Sichtbarkeitsattribut limitiert lediglich den Gebrauch der Konstanten innerhalb des Codes.
Indem Sie nun Konstanten genauso über Ihre ActiveX-Komponenten zugänglich machen wie Ihre Klassen und deren Methoden bzw. Eigenschaften, legen Sie die Grundlage für lesbaren Code bei den Benutzern Ihrer Komponenten. Die Flags des CommonDialog-Steuerelements müssen Sie z.B. nicht umständlich über festverdrahtete Zahlwerte setzen:

dlg.Flags = 10240

Stattdessen benutzen Sie einfach die Konstantennamen aus der OCX-Typelib:

dlg.Flags = cdlOFNCreatePrompt + cdlOFNPathMustExist

Gleiches gilt für den Umgang mit ADO:

rs.Cursorlocation = adUseServer

Aber eben auch für Ihre eigenen Anwendungen:

Dim mycar As new Auto 
mycar.Typ = Cabrio

VB bietet Ihnen in dem Fall per Intellisense sogar eine Liste möglicher Konstantennamen für die Zuweisung an eine Eigenschaft an, deren Typ der Name einer Enum-Deklaration ist (Abbildung 2). Die Enum-Deklaration in der Klasse Auto macht es möglich:

Public Enum PKWTyp 
Limousine 
Zweisitzer 
Cabrio 
Kombi 
End Enum

Bild02

Abbildung 2: Enum-Deklarationen

Wenn Sie Ihre Konstanten über Enum-Deklarationen öffentlich machen, profitieren die Nutzer Ihrer Komponenten. Der Datentyp der Eigenschaft „Typ" der Klasse „Auto" ist der Name einer Enum-Konstantendeklaration (PKWTyp) in der Klasse. Das wertet VB aus und zeigt bei Zuweisungen eine Liste der Konstanten, die zugewiesen werden können.

Der Windows Script Host 2.0 (WSH2) erlaubt Ihnen sogar in Script-Dateien über das neue -Tag den Zugriff auf Konstanten aus Typelibs.
Sie sehen, es lohnt sich, Ihre Konstanten per Enum zu veröffentlichen.

 

Das Problem: Konstantennamen können nicht ermittelt werden

So sehr sich Enum-Deklarationen also insbesondere in Komponenten lohnen, es bleibt doch ein Wunsch unerfüllt: Die Rückübersetzung von Konstantenwerten in ihre Konstantennamen. Sie schreiben z.B. in Ihrem Code

mycar.Typ = Cabrio

und speichern das Objekt irgendwo ab. Später laden Sie es wieder und wollen dem Benutzer zeigen, von welchem Typ das Auto-Objekt ist. Der Befehl

debug.print mycar.Typ

führt aber nicht zur Ausgabe „Cabrio", sondern zeigt nur den Wert der Konstanten, d.h. 2. Dass eine Enum-basierte Eigenschaft den Konstantenwert zurückliefert, ist für die interne Verarbeitung selbstverständlich völlig ausreichend und korrekt – bei der Interaktion mit dem Benutzer dagegen würde man sich wünschen, dass statt des Wertes der Name der ursprünglichen Konstante ausgegeben wird. Schließlich haben Sie sich bei der Benennung viel Mühe gegeben, warum sollte die Benutzerschnittstelle also nicht davon profitieren?
Statt einer Funktion wie z. B.

debug.print NameOfConstant(mycar.Typ)

müssen Sie umständlich die Übersetzung programmieren:

select case mycar.Typ 
case 0 
debug.print "Limousine" 
case 1 
debug.print "Zweisitzer" 
case 2 
debug.print "Cabrio" 
… 
end select

 

Die Lösung: Rückübersetzung mittels Typelib

Zum Glück gibt es für das Problem jedoch eine Lösung – zumindest, wenn es sich um Enum-Konstanten in ActiveX-Komponenten handelt. Der Weg führt über die Typelib der Komponente. Wie Sie im Ausschnitt aus der enums.dll-Typelib gesehen haben, enthält die Typelib neben den Konstantenwerten auch deren Namen:

enum { 
blau = 0, 
grün = 1, 
rot = 2, 
gelb = 3, 
violett = 4 
} Farben;

Die gehen auch nicht verloren, wenn die IDL-Beschreibung einer Typelib in eine TLB-Typelib-Datei übersetzt wird, oder wenn VB die Typelib einer Komponente in die resultierende DLL einbettet. Das bedeutet, Sie können auf Namen und Werte von Enum-Konstanten zugreifen, wenn Sie einen Weg finden, an die Typelib-Informationen in einer Komponente heranzukommen.

tlbinf32.dll – Auf Typelib-Informationen zugreifen

Diesen Zugriff erlaubt die tlbinf32.dll. Sie ist eine spezielle ActiveX-Komponente für das Auslesen von Typelib-Informationen aus DLLs, EXE-Dateien, TLB-Dateien und sogar „lebenden" COM-Objekten. [3] erklärt den Umgang mit tlbinf32.dll genau.
Zur Lösung des vorliegenden Problems müssen Sie nur einen kleinen Teil der tlbinf32.dll-Funktionalität benutzen. Abbildung 3 zeigt, wo die Enum-Konstanten (Constants-Collection) im Objektmodell der Komponente aufgehängt sind.

Bild03

Abbildung 3: Die Enum-Konstantendeklarationen im Objektmodell von tlbinf32.dll.

Die Konstanten sind in einer eigenen Collection innerhalb der Typelib zu finden.
Falls jedoch keine Typelib-Datei geladen wurde, sondern nur ein „lebendes" Objekt zur Verfügung steht, muss dazu ein CoClassInfo- bzw. InterfaceInfo-Objekt in der Typelib gesucht werden, um dann über dessen Parent-Eigenschaft den Weg zur TypelibInfo einzuschlagen.

Einfache Rückübersetzungen

Wenn man auf die Enum-Konstanten zugreifen kann, ist es auch nicht schwer, eine Komponente zu implementieren, die die Werte in ihre Namen rückübersetzt. Seine Klasse Translator stellt Methoden dafür zur Verfügung und ist die Wurzel eines kleinen Objektmodells, das die Konstantendeklarationen widerspiegelt (Tabelle 1).

Klasse Translator

Methode/Eigenschaft

Erläuterung

Function Value2Name(ByVal Value As Long, Optional ByVal enumName As String, Optional typelib) As String

Übersetzt einen Konstantenwert zurück in dessen Namen. Falls der Wert nicht eindeutig ist, muss der Name der zugehörigen Enum-Deklaration mit angegeben werden.

Function Name2Value(ByVal constantName As String, Optional ByVal enumName As String, Optional typelib) As Long

Übersetzt einen Konstantennamen in dessen Wert. Falls der Name nicht eindeutig ist, muss der Name der zugehörigen Enum-Deklaration mit angegeben werden.

Property Get Count() As Long

Anzahl der Enum-Deklarationen in der Typelib.

Property Get Item(ByVal index) As TypelibConstants.Enum

Liefert das Enum-Objekt zurück, dessen Index oder Name angegeben wurde. Die Translator-Klasse ist auch eine Collection-Klasse für Enum-Objekte. Jedes Enum-Objekt repräsentiert eine Enum-Deklaration (ConstantInfo) in der Typelib.

Sub LoadTypelib(ByVal typelib)

Lädt eine Typelib aus einer Datei oder aus einem „lebenden" Objekt. Dateien werden automatisch im Windows-, System- und Applikationsverzeichnis gesucht.

Klasse Enum

Property Get Name() As String

Name der Enum-Deklaration

Property Get Count() As Long

Anzahl der Konstanten in der Deklaration

Property Get Item(ByVal index) As TypelibConstants.Constant

Liefert das Constant-Objekt zurück, dessen Index oder Name übergeben wurde. Jedes Constant-Objekt steht für eine Konstante (MemberInfo) innerhalb einer Enum-Deklaration (ConstantInfo).

Property Get NamesAndValues(Optional ByVal includeValues As Boolean = True) As String

Liefert die Namen und optional auch Werte aller Konstanten der Deklaration in einem String zurück. Namen und Werte sind durch Semikola getrennt.

Klasse Constant

Property Get Name() As String

Der Name der Konstante

Property Get Value() As Long

Der Wert der Konstante

Tabelle 1: Das Objektmodell der Rückübersetzungskomponente für Enum-Konstantenwerte. Eine Typelib laden Sie entweder explizit über LoadTypelib oder implizit, indem Sie ihren Namen bei Value2Name oder Name2Value übergeben. Typelibs können aus DLLs, EXE-Dateien, TLB-Dateien, aber auch aus „lebenden" Objekten ermittelt werden.

Im einfachsten Fall sieht die Benutzung der Translator-Komponente wie folgt aus:

Dim tc As New TypelibConstants.Translator 
debug.print tc.Value2Name(99, "Formen", "enums.dll")

Der Funktion Value2Name übergeben Sie den Wert der Konstanten (99), den Namen der Enum-Deklaration („Formen") und den Namen der Datei, in der die zugehörige Typelib zu finden ist. Als Resultat liefert der Aufruf den Konstantennamen „Kugel" zurück (s. enums.vbp und das Testprojekt group1.vbg).
Sie müssen eine Typelib nicht erneut angeben, sobald Sie sie einmal in die Komponente geladen haben – es sei denn, Sie wollen sie wechseln:

debug.print tc.Value2Name(110, "Formen")

Auch den Namen der Enum-Deklaration können Sie auslassen, falls der Wert der Konstanten innerhalb aller in der Typelib definierten Konstanten eindeutig ist:

debug.print tc.Value2Name(200)

Konstantennamen aus „lebenden" Objekten ermitteln

Statt des Namens einer Datei, die eine Typelib enthält, akzeptiert die Komponente auch „lebende" COM-Objekte:

debug.print tc.Value2Name(1, "LockTypeEnum", CreateObject("ADODB.Recordset"))

Auf das Auto-Beispiel bezogen, sieht die Rückübersetzung des Typs z.B. wie folgt aus:

debug.print tc.Value2Name(myCar.Typ, "PKWTyp", myCar)

Falls Sie es jedoch vorziehen, eine Typelib nur einmal bei Programmstart zu laden, benutzen Sie einfach die LoadTypelib-Methode:

tc.LoadTypelib "comdlg32.ocx"

Auch sie akzeptiert sowohl einen Dateinamen für eine DLL-, EXE- oder TLB-Datei wie auch ein COM-Objekt.

Konstantenwerte aus Konstantennamen ermitteln

Obwohl das eigentliche Problem die Rückübersetzung von Konstantenwerten in ihre Namen ist, bietet die Translator-Komponente auch den umgekehrten Weg. Der mag insbesondere nützlich sein, wenn Sie Ihre Benutzer Eingaben von Hand machen lassen, die Sie sofort in Konstantenwerte übersetzen wollen, z. B.:

myCar.Typ = tc.Name2Value(txtTyp.Text, "PKWTyp")

Das Translator-Objektmodell

Test.vbp in group2.vbg zeigt darüber hinaus einen anderen Weg, wie Sie mit den Konstantendefinitionen im User Interface umgehen können. Das Formular frmTest füllt bei Programmstart einfach einige ComboBox-Steuerelemente und eine ListBox mit den Namen und Werten der Konstanten aus der Auto-Komponente:

Dim tc As New TypelibConstants.Translator, i% 
tc.LoadTypelib New Auto 
With tc("PKWTyp") 
For i = 1 To .Count 
With .Item(i) 
cboTyp.AddItem .Name 
cboTyp.ItemData(cboTyp.NewIndex) = .Value 
End With 
Next 
End With

Es nutzt dabei Translator als Collection von Enum-Objekten, die wiederum Collection-Objekte von Constant-Objekten sind. Die Komponente baut also eine kleine Objekthierarchie auf, sobald sie eine Typelib lädt.
Wenn der Benutzer später seine Auswahl in ein Auto-Objekt übertragen will, ist kein Zugriff auf die Typelib mehr nötig, da alle Angaben bereits im User Interface vorliegen:

Dim a As New Auto 
a.Typ = cboTyp.ItemData(cboTyp.ListIndex)

Das tlbinf32.dll-Objektmodell benutzen

Das Objektmodell von tlbinf32.dll ist einfach aufgebaut. Es macht also keine Probleme, den Namen einer Konstanten zu ermitteln:

Public Function Value2Name(ByVal Value As Long, Optional ByVal enumName As String, Optional typelib) As String 
If Not IsMissing(typelib) Then LoadTypelib typelib 
Dim ci As ConstantInfo, mi As MemberInfo 
For Each ci In m_tli.Constants 
If StrComp(ci.Name, enumName, vbTextCompare) = 0 Or enumName = "" Then 
For Each mi In ci.Members 
If mi.Value = Value Then 
Value2Name = mi.Name 
Exit Function 
End If 
Next 
End If 
Next 
End Function

Die Translator-Komponente durchläuft einfach alle Enum-Deklarationen (Constants) und prüft, ob darin eine Konstante den angefragten Wert hat. Wenn Sie den Namen einer konkreten Enum-Deklaration spezifizieren, berücksichtigt Value2Name selbstverständlich nur diese.

Bevor Sie das tlbinf32.dll-Objektmodell traversieren, müssen Sie es aber aufbauen. Das geschieht entweder explizit über einen Aufruf von LoadTypelib oder implizit wie in der obigen Methode.
LoadTypelib unterscheidet dann, ob Sie einen Dateinamen für die Typelib übergeben, und lädt die Datei:

On Error Resume Next 
Set m_tli = TypeLibInfoFromFile(typelib) 
If Err <> 0 Then 
On Error GoTo 0 
Set m_tli = TypeLibInfoFromFile(App.Path & "\" & typelib) 
End If

Falls die Suche von TypeLibInfoFromFile dabei nicht fündig wird, probiert Translator es auch noch im Pfad der Applikation.
Wenn Sie statt eines Dateinamens jedoch ein „lebendes" Objekt übergeben, dann versucht die Methode, darüber an die Typelib zu kommen:

On Error Resume Next 
Set m_tli = TLI.ClassInfoFromObject(typelib).Parent 
If Err <> 0 Then 
Err.Clear 
Set m_tli = TLI.InterfaceInfoFromObject(typelib).Parent 
If Err <> 0 Then 
On Error GoTo 0 
Err.Raise vbObjectError + 1, "LoadTypelib", "Object does not provide typelib informaion!" 
End If 
End If

Im einfachsten Fall kann LoadTypelib über die CoClass-Information des Objekts auf die Typelib zugreifen (s.a. [4]). Das funktioniert für bereits kompilierte VB-Objekte in einer ActiveX-DLL/EXE auch gut. Bei VB-Objekten jedoch, die bisher nur im Rahmen der VB-IDE existieren (s. z.B. die Klasse Auto in group2.vbg), schlägt diese Vorgehensweise fehl. Für sie existiert noch keine COM-CoClass-Information. In dem Fall muss der Umweg über das IDispatch-Interface des Objekts gegangen werden, das alle VB-Objekte implementieren.

 

Fazit

Mit ein wenig Kenntnis der COM-Typelibs und der Komponente tlbinf32.dll ist es möglich, ein Manko von Visual Basic auszugleichen: die Unmöglichkeit, Konstantenwerte in Konstantennamen zurück zu übersetzen. Die TypelibConstants.Translator-Komponente konvertiert Konstantenwerte in -namen und umgekehrt und stellt darüber hinaus noch ein Objektmodell für die Konstantendeklarationen in Typelibs bereit.

Damit sollte es Ihnen leicht fallen, Programme zu entwickeln, die auf umständliche Übersetzungen von Werten in Zeichenketten und umgekehrt verzichten können. Vielmehr können Sie Teile Ihres Codes und Ihrer Benutzerschnittstelle automatisch und sehr deklarativ aus den Definitionen in Ihren Klassen steuern lassen.

 

Ressourcen

[1] Das Enum Statement: http://msdn.microsoft.com/library/devprods/vs6/vbasic/vbenlr98/vastmenum.htm; MSDN Library
[2] Torsten Zimmermann: Typelibs fest im Griff – Teil 1; BasicPro 1/99, S. 6
[3] Torsten Zimmermann: Typelibs fest im Griff – Teil 3; BasicPro 3/99, S. 8
[4] Torsten Zimmermann: Typelibs fest im Griff – Teil 2; BasicPro 2/99, S. 22

Schnellstart

Schritt 1: Konstanten mit Enum definieren
Definieren Sie Ihre numerischen Konstanten mit der Enum-Anweisung statt über Const. Eine Dekodierung von Enum-Konstantenwerten in Konstantennamen ist allerdings nur möglich, wenn die Enum-Deklaration in einer ActiveX-DLL oder EXE-Datei stattfindet, da nur sie Typelib-Informationen speichern.

Schritt 2: Die TypelibConstants.Translator-Komponente benutzen
Binden Sie in Ihre Projekte die TypelibConstants.Translator-Komponente aus diesem Bericht ein. Der Methode LoadTypelib können Sie den Namen einer ActiveX-DLL/EXE übergeben – oder ein existierendes Objekt.

Schritt 3: Dekodieren Sie Konstantenwerte
Mit der Methode Value2Name können Sie Konstantenwerte in Namen zurückwandeln. Alternativ bietet die Collection-Funktionalität der Komponente Zugriff auf alle Enum-Definitionen und deren Konstanten, die in einer Typelib gespeichert sind.

Für Fragen und Anregungen erreichen Sie Ralf Westphal per E-Mail an ralfw@basicpro.de.