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:
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.
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
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.
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: https://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.