Rubrik: Entwicklungsumgebung · VB-IDE allgemein | VB-Versionen: VB6 | 11.07.05 |
Veränderung der Größe des Stapelspeichers Änderung des Stack-Size beim Linken und Anforderung einer MAP-Datei | ||
Autor: Manfred Bohn | Bewertung: | Views: 21.090 |
ohne Homepage | System: WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 | kein Beispielprojekt |
Stapel (engl.: Stack): Ein festgelegter Speicherbereich, der von Visual Basic verwendet wird, um lokale Variablen und Argumente zwischen Prozeduraufrufen zwischenzuspeichern (VB-Dokumentation).
Bei jedem Aufruf einer Funktion wird Platz auf dem Stapel für die Parameter der Funkton und für die lokalen Variablen benötigt und beim Verlassen der Funktion automatisch wieder freigegeben.
Mögliche Ursachen für einen Überlauf des Stapelspeichers:
Bei rekursiven Funktionsaufrufen (d.h. eine Funktion ruft sich selbst auf oder ruft eine zweite Funktion die ihrerseits die erste wieder aufruft usw.) kann es zu einer langen Schlange im Stapel kommen. Der Fehler „Nicht genügend Stapelspeicher" zeigt an, dass der Stapelbereich voll ist. Falls in diesem Fall statt lokaler Variablen modulglobale Variable verwendet werden, "schont" das den Stapelspeicher. Allerdings greifen dann alle rekursiven Aufrufe auf die gleichen Variablen zu - ein Effekt, der häufig nicht gewünscht wird. Ein bekanntes Beispiel für rekursive Programmierung ist der Quicksort-Algorithmus.
Ein andere Ursache für das Anwachsen des Stapelspeicher-Bedarfs sind sogenannte Ereignisketten: Wenn in einer Ereignisbehandlungs-Routine auf bestimmte Steuerelemente-Eigenschaften zugegriffen wird, werden automatisch andere Ereignisse ausgelöst, deren Behandlungsroutinen ihrerseits Ereignisse provozieren (Kaskadeneffekt). Man sollte deshalb vor der Abarbeitung von Code zur Ereignisbehandlung eine formular-globale boolsche Variable auf 'true' setzen und danach wieder auf 'false'. In allen 'sensiblen' Ereignisroutinen (z.B. 'Change'-Ereignisse) ist am Anfang diese Variable abzufragen: Ist sie auf 'true' gesetzt, wird nichts gemacht.
Da VB-Programme standardmäßig mit einem genügend großen Stapel (1 Megabyte) ausgestattet werden, weisen Stapelprobleme meist auf Fehler in Rekursion oder Ereignisbehandlung hin.
Die VB-Dokumentation enthält leider nur wenige Hinweise bezüglich der Verwendung des Stapels:
- lokale Variable, die als STATIC deklariert werden, benötigen nur wenig Stapelspeicher
- Strings variabler Länge benötigen weniger Stapelspeicher als Strings fester Länge
- verschachtelte Aufrufe in Folge häufiger DOEVENTS-Anweisungen können den Stapel stark beanspruchen
Manipulation der Größe des Stapelspeichers:
Die Größe des Stapelspeichers, den ein Programm verwenden kann, wird beim Linken festgelegt. Beim Start eines Programms wird die vom Linker verzeichnete Größe des Stapelspeichers für die Anwendung reserviert. Eine dynamische Anpassung der Größe des Stapels während der Programmausführung ist nicht möglich.
Wenn die Stapelgröße eines Programms geändert werden soll, muss beim Linken die Option 'STACK' gesetzt werden.
Um den Linkeraufruf der VB-IDE zu manipulieren, wird das Verfahren angewendet, das bereits im Tipp 1289 'Erstellen von Compiler-Listing-Dateien' zur Ergänzung einer Kompiler-Option herangezogen worden ist:
- Erstellen Sie ein neues Standard-Exe-Projekt und fügen Sie ein Standardmodul ein
- Entfernen Sie das Formular 'Form1' aus dem Projekt.
- Kopieren Sie den beigefügten Quellcode in das Standardmodul.
- Übersetzen Sie das Projekt.
- Gehen Sie in das Stammverzeichnis von VB6 (meist: "C:\Programme\Microsoft Visual Studio\VB98" oder ähnlich) und benennen Sie die Datei 'LINK.EXE' (das ist der Linker!) in 'VBLINK.EXE' um.
Ab sofort werden alle Linkeraufrufe der VB-IDE von LINK.EXE abgefangen.
Zunächst wird gefragt, ob vom Linker eine MAP-Datei erstellt werden soll, die folgende Angaben enthält:
- Zeitangabe aus dem Header der Programmdatei
- Liste von Gruppen im Programm (jeweils Startadresse [als Abschnitt:Offset], Länge, Gruppennamen und Klasse)
- Liste der öffentlichen Symbole (jeweils Adresse [als Abschnitt:Offset], Symbolname und definierende .OBJ-Datei)
- Einsprungpunkt (als Abschnitt:Offset)
Vor dem Aufruf des Linkers wird dann eine VB-Input-Box angezeigt, in der die gewünschte Größe des Stapels festgelegt werden kann (in Kilobyte). Eingaben unter 250 werden auf 250 KB gesetzt, Eingaben über 5000 werden auf 5000 KB gesetzt. Bei Betätigung der Taste 'Abbrechen' und bei 'leerer' oder unplausibler Eingabe wird der Standardwert von 1 MB verwendet.
Details zum Stapel:
(Die Angaben zum Stapel-Bedarf sind durch Probieren gefundene ungefähre Werte, die für Funktions-Aufrufe innerhalb eines Standard-Exe-Projekts gelten):
- Die Mindestgröße des Stapel beträgt ca. 250 KB; geringere Anforderungen werden vom Linker automatisch hochgesetzt.
- Der Stapel kann durch die STACK-Option auf 5 MB hochgesetzt werden (noch größere Werte sind nicht getestet worden!)
- Ein Funktionsaufruf (ohne Argumentliste) benötigt ca. 90 Byte auf dem Stapel (innerhalb einer Form oder einem Klassenmodul), aber nur ca. 65 Byte innerhalb eines Standardmoduls.
- Der Stapelbedarf eines lokalen, statischen Array beträgt ca. 35 Byte und ist unabhängig von der Zahl der Array-Elemente und vom Datentyp des Array (im Hinblick auf 'integrierte VB-Datentypen') - gilt auch für String-Arrays variabler oder fester Länge und für Arrays aus benutzerdefinierten Datentypen. (Jede zusätzliche Dimension belegt etwa 10 Byte auf dem Stapel).
- Bei dynamisch deklarierten lokalen Arrays wird nur ein Zeiger (8 Byte) auf dem Stapel abgelegt, unabhängig von Datentyp und von den Dimensionen des Array.
- Bei Übergabe eines dynamisch deklarierten Array als Argument 'BYREF' werden ca. 100 Byte auf dem Stapel abgelegt (innerhalb Form oder Klasse) bzw. 70 Byte innerhalb eines Standardmoduls.
- Bei Übergabe eines dynamisch deklarierten Array als Argument 'BYVAL' werden ca. 135 Byte auf dem Stapel abgelegt (innerhalb Form oder Klasse) bzw. 105 Byte innerhalb eines Standardmoduls.
Fazit:
Unter normalen Umständen gibt es keinen Grund, die Standard-Größe des 'STACK' zu ändern. Visual Basic nutzt den Stapelspeicher 'sparsam' und setzt dabei meist Zeiger ein.
' ==================================================================== ' Programm, das die IDE-Linkeraufrufe abfängt und ' die Optionen hinzufügt (falls gewünscht) ' Schritt-Folge: ' 1. Dieses Programm übersetzen ('Sub Main') ' 2. VB6-Linker 'Link.Exe' in VBLink.EXE umbenennen ' 3. Dieses Programm muss 'Link.EXE' genannt und ' in den VB6-Ordner kopiert werden ' ==================================================================== Option Explicit ' vereinfachte UDTs für die Prozess-Erzeugung Private Type PROCESS_INFO hProcess As Long x(1 To 12) As Byte End Type Private Type STARTUP_INFO Size As Long Reserved As String Desktop As String Title As String x(1 To 28) As Byte Flags As Long Show As Integer y(1 To 16) As Byte End Type ' API-Funktionen für die Prozess-Erzeugung Private Declare Function CreateProcess Lib "Kernel32" _ Alias "CreateProcessA" ( _ ByVal AppName As Long, _ ByVal CmdLine As String, _ ByVal ProcAttr As Long, _ ByVal ThreadAttr As Long, _ ByVal InheritedHandle As Long, _ ByVal CreationFlags As Long, _ ByVal Env As Long, _ ByVal CurDir As Long, _ Startup As STARTUP_INFO, _ Process As PROCESS_INFO) As Long Private Declare Function WaitForSingleObject Lib "Kernel32" ( _ ByVal Handle As Long, _ ByVal MilliSeconds As Long) As Long Private Declare Function CloseHandle Lib "Kernel32" ( _ ByVal Object As Long) As Long ' Rückgabe von WaitForSingleObject Private Const WAIT_TIMEOUT = 258& Private Const WAIT_FAILED = &HFFFFFFFF Private Const INFINITE = &HFFFFFFFF Const cTitel As String = "VB-Link (Systemcode)"
Private Sub Main() ' VB-Linkeraufrufe anfangen und MAP/STACK setzen Dim rs As String, r As Long ' Dialog-Reaktion Dim CmdLine As String ' Kommandozeile Dim st As Long ' Größe des Stack Dim t1 As Long, t2 As Long ' Position Dateiname Dim exedat As String ' Name der zu erstellenden Datei Dim dat As String, d() As String ' Abfrage des IDE-Linkeraufrufs CmdLine = Command() ' Name der Ausgabedatei des Linkers ermitteln (für Dialog) t1 = InStr(CmdLine, "/OUT:") t2 = InStr(CmdLine, "/BASE:") If t2 > t1 + 10 And t1 > 0 Then exedat = Mid(CmdLine, t1 + 6, t2 - (t1 + 6) - 2) d() = Split(exedat, "\") dat = Trim(d(UBound(d))) End If ' Mapfile gewünscht? r = MsgBox("Erstellte Datei: " + vbCrLf + dat + _ vbCrLf + vbCrLf + "Map-File erstellen?", vbYesNo, cTitel) If r = vbYes Then CmdLine = CmdLine + " /MAP" ' Stacksize festlegen rs = InputBox("Erstellte Datei: " + vbCrLf + dat + vbCrLf + vbCrLf + _ "Stackgröße (KB): " + vbCrLf + _ "mindestens 250 KB", cTitel, " 1024 ") If StrPtr(rs) <> 0 And rs <> "" Then ' Stackgröße auslesen und in Bytes wandeln st = DLNG(rs) * 1024 CmdLine = CmdLine + " /STACK:" + CStr(st) End If ' VB6-Linker aufrufen: umbenannt als 'VBLink.EXE' ShellWait App.Path + "\VBLink.exe " + CmdLine End Sub
Private Function DLNG(ByVal rs As String) As Long ' Auswertung des Rückgabe-Strings Dim d As Long On Error GoTo fehler d = CLng(rs) ' sinnvolle Stackgröße setzen (Kilobytes) If d < 250 Then d = 250 If d > 5000 Then d = 5000 DLNG = d Exit Function fehler: DLNG = 1000 ' Standardwert bei unplaus. Eingabe End Function
Public Function ShellWait(ByVal Execute As String) As Boolean ' Funktion erstellt einen Prozess und wartet ' bis der Prozess sich meldet bzw. abgeschlossen ist Dim Prc As PROCESS_INFO ' Infos zum erstellten Prozess Dim Startup As STARTUP_INFO ' Prozess-Start-Infos Dim Ret As Long ' API-Rückgabe Const MilliSeconds As Long = INFINITE ' unbegrenzt warten ' Initialisierung Prozess-Start With Startup .Size = Len(Startup) .Flags = 1 End With ' Prozess generieren Ret = CreateProcess(0&, Execute, 0&, 0&, 1&, &H20&, 0&, 0&, Startup, Prc) If Ret = 0 Then MsgBox "Warnung: Linker lässt sich nicht starten", vbCritical, cTitel DoEvents Exit Function End If ' Kompilierung abwarten Ret = WaitForSingleObject(Prc.hProcess, MilliSeconds) If Ret = WAIT_FAILED Or Ret = WAIT_TIMEOUT Then MsgBox "Warnung: Linker reagiert nicht", vbCritical, cTitel DoEvents End If ' Prozess-Handle freigeben CloseHandle Prc.hProcess End Function ' ====================================================================