Rubrik: Variablen/Strings · UDT (Benutzerdefinierte Datentypen) | VB-Versionen: VB6 | 11.05.05 |
UDT als Parameter einer Klassen-Prozedur III Das Schlüsselwort FRIEND bei Methoden und Eigenschaften | ||
Autor: Manfred Bohn | Bewertung: | Views: 14.539 |
ohne Homepage | System: Win9x, WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 | kein Beispielprojekt |
Korrekturhinweis zum Tipp 1231 "UDT als Parameter einer Klassenprozedur'" per API-Funktion "rtlMoveMemory"
Der Tipp UDT als Parameter einer Klassen-Prozedur ist irreführend.
Bei der Festlegung des Geltungsbereichs von Prozeduren in Klassenmodulen stellt VB6 drei Möglichkeiten zur Verfügung:
- Das Schlüsselwort 'Private' führt bei Prozeduren dazu, dass die Methode/Eigenschaft nur innerhalb der Klasse 'sichtbar' ist, in der die Prozedur deklariert ist.
- Das Schlüsselwort 'Friend' sorgt dafür, dass die Methode/Eigenschaft innerhalb der gesamten ActiveX-Komponente sichtbar wird, innerhalb der die Prozedur deklariert ist.
- Die Verwendung des Schlüsselworts 'Public' bei Prozeduren in Klassen, deren Instancing-Eigenschaft auf 'PublicNotCreatable' oder auf 'MultiUse' eingestellt ist, macht die Methode/Eigenschaft für den Zugriff aus anderen ActiveX-Objekten verfügbar.
Bei Klassen in Standard-Exe Projekten ist prinzipiell die Instancing-Eigenschaft 'Private'. Andere ActiveX-Objekte können nicht auf solche Klassen zugreifen. Dem EXE-Projekt fehlt ja die ActiveX-Umgebung. Es kann zwar selber ActiveX-Objekte ankoppeln - aber nicht an andere angekoppelt werden.
Insofern ist das Schlüsselwort 'Public' für Prozeduren in Klassen, die in Standard-Exe-Projekten definiert sind, eigentlich sinnlos. Genau genommen werden deren Prozeduren wie 'Friend' behandelt, weil sie nur innerhalb des Projekts sichtbar sind.
Aus dem Abschnitt 'Friend-Eigenschaften und -Methoden' in der VB6-Dokumentation:
Standard-EXE-Projekte können keine ActiveX-Komponenten sein, da ihre Klassenmodule nicht öffentlich sein können und deshalb nicht von anderen Anwendungen verwendet werden können. Jegliche Kommunikation zwischen Objekten in einem Standard-EXE-Projekt ist daher privat, und es besteht keine Notwendigkeit für Friend-Elemente. Friend-Elemente haben jedoch ein besonders nützliches Merkmal. Da sie nicht Teil einer ActiveX-Schnittstelle sind, lassen sie sich für das Übergeben von benutzerdefinierten Datentypen zwischen Objekten einsetzen, ohne daß sie öffentlich offengelegt werden.
Soweit die Dokumentation.
Damit wird die Lösung des Problems erkennbar.
Man deklariert seine Klassen-Prozeduren mit dem Schlüsselwort 'Friend', weil das Schlüsselwort 'Public' den Compiler irritiert und er deshalb eine in Standard-Exe-Projekten nicht passende Fehlermeldung ausgibt.
Der Compiler geht vermutlich davon aus, er sei in einer ActiveX-Umgebung, sobald er eine 'Public'-Klasse übersetzen soll. Derartige Klassen dürfen aber nicht von Standardmodulen, sondern nur von anderen ActiveX-Objekten abhängig sein (vgl. Tipp 1252).
In Standard-Exe-Projekten akzeptieren als 'Friend' deklarierte Klassenprozeduren öffentliche UDTs, die in Standardmodulen definiert sind!!
Ich empfehle deshalb, den Tipp 1231 durch einen Hinweis auf das Schlüsselwort 'Friend' zu ersetzen.
Bei der Deklaration von Ereignissen ist das Schlüsselwort 'Friend' nicht zulässig. Hier muss man sich tatsächlich mit der Ausgabe eines Pointers auf eine Variable des Datentyps behelfen und mit rtlMoveMemory kopieren, wie es in Tipp 1231 vorgemacht wird, was aber nur bei elementaren numerischen Datentypen zu empfehlen ist.
Einige weitere nützliche Hinweise:
Der Nachteil der Deklaration von Klassen-Prozeduren als 'Friend' besteht darin, dass auf sie nicht von anderen AktiveX-Objekten zugegriffen werden kann.
Verwendet man statt dessen 'Public' in Prozedur-Deklarationen kann die gleiche Klasse parallel verwendet werden: mit Einstellung Instancing=Private in Standard-Exe-Projekten und mit Einstellung Instancing=Multiuse ohne Einschränkung komponentenübergreifend in AktiveX-Projekten.
Zudem gilt:
In einer objektorientierten Sprache wie Visual Basic sollte man keine Datentypen an Objekte übergeben (schlechter Stil), sondern besser aus solchen Datentypen Klassenmodule machen.
Erforderliche Schritte:
- Datentyp im Deklarationsteil eines Klassenmoduls 'Private' festlegen
- Private klassenglobale Zugriffsvariable dieses Datentyps (im Deklarationsteil der Klasse) deklarieren
- geeignete Property-Prozeduren für die einzelnen Elemente des Datentyps im Klassenmodul ergänzen.
Klassen bieten gegenüber Datentypen zahlreiche Vorteile (die Initialisierung, díe Überwachung der Zuweisungen in Property-Prozeduren, die Definition von Ereignissen u.v.a.).
Beispiel:
- Neues Standard-Exe-Projekt anlegen
- Standardmodul Module1 hinzufügen
- Klassenmodul Class1 hinzufügen
' --------------------------------------------------------- ' Code bitte in das Start-Formular Form1 einfügen Option Explicit ' Kopierfunktion Windows-API Private Declare Sub CopyMemory Lib "kernel32" _ Alias "RtlMoveMemory" ( _ ByVal Addr As Long, _ ByVal source As Long, _ ByVal bytes As Long) ' Zugriffsvariable zu einer Instanz der Klasse (mit Ereignissen) Dim WithEvents cl As Class1
Sub cl_Meldung(ByVal pointer As Long) ' Ereignis ' (ausgelöst von Instanz cl des Klassenmoduls class1) Dim t As MyTyp ' WARNUNG: Variable vom Typ String (Deskriptor-Kopie) ' sowie variable Arrays können hier ernste Probleme machen !!!! ' Die Anwendung ist deshalb nur bei numerischen Datentypen ' zu empfehlen CopyMemory VarPtr(t), pointer, Len(t) With t MsgBox "Meldung: " + vbCrLf + .comment + vbCrLf + _ .s + vbCrLf + CStr(.v) + vbCrLf + CStr(.z) + vbCrLf + CStr(.d) + vbCrLf + .st.s End With End Sub
Private Sub Form_Load() ' Formular anzeigen Me.Show ' Instanz der Klasse erstellen Set cl = New Class1 ' Variable des Demotyps in 'Module1' deklarieren Dim t As MyTyp ' Variable mit Werten füllen With t .v = CDec("100,123") .s = "was man als Typ so braucht" .d = -1234567890.12346 .z = CDate("22:45:18") ' einige Werte des Untertyps füllen .st.z = CDate("14:20:25") .st.s = "Untertyp" .comment = "Ich möchte in cl eingetragen werden" End With ' Zuweisung der Eigenschaft (geschieht automatisch ByVal) cl.Eigenschaft = t ' der Inhalt des Elements 'Comment' hat sich nicht geändert ' obwohl in der Property eine Zuweisung erfolgt ist Debug.Print "Formular: "; t.comment ' Die abgefragten Daten anzeigen With cl.Eigenschaft MsgBox "Abgefragte Daten: " + vbCrLf + _ .s + vbCrLf + CStr(.d) + vbCrLf + CStr(.v) + _ vbCrLf + CStr(.z) + vbCrLf + CStr(.st.s) + vbCrLf + .comment End With ' Ereignismeldung im Klassenmodul auslösen cl.Melde ' Objekt wieder zerstören Set cl = Nothing End Sub ' Codeende - Formular Form1 ' --------------------------------------------------------
Code für Modul "Module1"
' ------------------------------------------------- ' Code bitte in Standardmodul Module1 einfügen Option Explicit ' in MyType enthaltener Untertyp ' (muss vor MyType deklariert werden) Public Type MySubTyp s As String d As Double v As Variant z As Date comment As String End Type ' öffentlicher Demo-Datentyp in Standardmodul ' Elemente bestehen aus diversen Datentypen Public Type MyTyp s As String d As Double v As Variant z As Date comment As String st As MySubTyp End Type ' Codeende - Standardmodul Module1 ' --------------------------------------------------------
Code für das Klassenmodul "Class1"
' -------------------------------------------------------- ' Code bitte einfügen in Klassenmodul Class1 Option Explicit ' Variablen zur Speicherung der Daten der Eigenschaft ' Variable zum Speichern des Inhalts eines Datentyps ' (Friend geht hier nicht, aber der ' öffentliche Datentyp steht zur Verfügung !!) Private gt As MyTyp ' Ereignis definieren ('Friend' nicht möglich) Public Event Meldung(ByVal pointer As Long)
Friend Property Let Eigenschaft(ByRef t As MyTyp) ' Eigenschaft akzeptiert eine UDT, die an ein ' Klassenmodul übergeben wird ' Auslesen der Daten aus der übergebenen Datentyp-Variable ' und Speichern in der klasseninternen Datentyp-Variable gt = t ' Die folgende Zuweisung wird in LET_Property nicht zurückgegeben !! ' (byref wird ignoriert !) t.comment = "Ich war in Class1 / Eigenschaft LET" End Property
Friend Property Get Eigenschaft() As MyTyp ' Abfrage der gespeicherten Daten ' aus der modulglobalen Variable Eigenschaft = gt Eigenschaft.comment = "Daten aus Class1 / abgefragt durch Eigenschaft GET" End Property
Public Function Melde() ' Die Funktion löst das Ereignis 'Meldung' aus ' Die Meldung liefert nur einen Zeiger auf die internen Daten RaiseEvent Meldung(VarPtr(gt)) End Function ' Codeende - Klassenmodul Class1 ' --------------------------------------------------------