Freitag, 25. Juli 2008

entwickler.com Magazine Konferenzen Akademie Entwickler-Forum Jobbörse Bücher
Software & Support Verlag




April 2006
aus Der Entwickler Ausgabe: 5.2004
Äpfel und Birnen?
Entscheidungshilfen lindern die Qual der Wahl: VCL.NET oder FCL?
von Andreas Kosch

Borland Delphi 8 für das Microsoft .NET Framework ist gleich aus mehrerer Hinsicht einzigartig. Nur bei dieser Entwicklungsumgebung müssen Sie sich bei einem neuen Projekt für eine fensterorientierte Windows-Anwendung für eine von zwei grundlegend unterschiedlichen Ausführungen unterscheiden. Aufgrund der sich aus der einmal getroffenen Entscheidung im späteren Projektverlauf ergebenden Konsequenzen will die Antwort auf die Frage VCL.NET oder FCL gut überlegt sein.


Normalerweise lassen sich die unter .NET verfügbaren Anwendungen bequem in übersichtliche Schubkästen einordnen. Zuerst geht es um die Frage, ob diese Anwendung auf dem Server oder auf dem Client ausgeführt wird und danach müssen wir uns nur noch festlegen, ob die Anwendung eine Benutzeroberfläche hat oder nicht. Bisher gab es im Fall der .NET-Anwendung mit einer Benutzeroberfläche nur die Wahl zwischen der ASP.NET Web Form und der Windows Form aus der FCL (Framework Class Library), aber bei Delphi 8 kommt mit der VCL.NET eine dritte Alternative hinzu. Gerade für die Entwickler, die einen Delphi-Background haben, sieht die VCL.NET auf den ersten Blick sehr verlockend aus. Denn dort verspricht Borland, dass der Lernaufwand beim Wechsel von Win32 zu .NET am geringsten ist, die bisherigen Erfahrungen ungeschmälert weiter genutzt werden können und sich praktisch jede alte VCL-Anwendung per Mausklick zu einer .NET-Anwendung veredeln lässt. Die alten Hasen unter Ihnen werden beim letzten Satz geschmunzelt haben - selbstverständlich verhält sich das Ganze in der Realität anders. Wenn man sich die Mühe macht, die VCL.NET etwas gründlicher auszuleuchten, treten schnell einige unschönere Ecken und Kanten in Erscheinung. Zur Ehrenrettung von Borland darf ich an dieser Stelle nicht verschweigen, dass es kaum eine andere Lösung als diesen Kompromiss gegeben hätte. Denn Borland musste mit Delphi 8 eine Art Quadratur des Kreises auf den Markt bringen, um gleich vier verschiedene Käuferschichten zu bedienen:

  • Bei der Käufergruppe A steht zurzeit immer noch Win32 im Vordergrund, .NET wird nur perspektivisch am Rande verfolgt. Diese Gruppe hat ein Interesse daran, dass bestimmte neue .NET-Sprachfeatures (wie z.B. die Attribute) demnächst auch in der Win32-Welt von Delphi zur Verfügung stehen.
  • Bei der Käufergruppe B steht .NET im Vordergrund, allerdings sollen dort die alten Win32-Anwendungen weitergepflegt werden. Es kommt somit verstärkt auf die Kompatibilität zur VCL an, wenn es keinen Konverter gibt, der eine VCL-Anwendung zu einer FCL-Anwendung transformieren kann (nur zur Erinnerung, Microsoft hat sich im Fall von Visual Basic zu dieser Konverter-Lösung entschieden).
  • Bei der Käufergruppe C stehen neu begonnene .NET-Anwendungen im Vordergrund, so dass eine Entwicklungsumgebung für die neuen .NET-Features benötigt wird, die aber anstelle C# oder VB.NET die gewohnte Delphi-Syntax unterstützt.
  • Damit die Sprache Delphi im Lauf der Zeit nicht ausstirbt, müssen Neukunden als Käufergruppe D gewonnen werden. Für diese Zielgruppe muss sowohl die Sprache als auch die Entwicklungsumgebung so attraktiv sein, dass sich die Neukunden für Delphi 8 entscheiden.

Sie werden mir sicherlich Recht geben, wenn ich die VCL.NET den Käufergruppen A und B zuordne. Im Gegensatz dazu zielt die Unterstützung für die FCL speziell auf die Käufergruppen C und D. Wenn man bei dieser Theorie bleibt, ergibt sich zwangsläufig, dass die VCL.NET langfristig nur dann eine Zukunft hat, wenn sich die Käufergruppen C und D für die VCL.NET entscheiden. Für einen nicht vorbelasteten Neukunden (für den auch die Sprache Delphi neu ist) spielen andere Kriterien bei der Entscheidung eine Rolle, die sich in den folgenden Stichpunkten zusammenfassen lassen:
  • Investitionssicherheit (Verbreitung, Zukunftssicherheit)
  • Leistungsfähigkeit (Funktionsumfang, Produktivität und Performance)
  • Dokumentation
  • Quantität und Qualität der verfügbaren Komponenten

Im Folgenden stelle ich Ihnen einige ausgewählte Detail-Aspekte vor, die die VCL.NET aus der Sichtweise der Käufergruppen C und D betrachten. Aus Platzgründen kann ich dabei weder auf BDP.NET noch auf ECO eingehen (denn auch diese vom Borland-Marketing lautstark beworbenen Delphi 8-Features lassen sich in einem VCL.NET-Projekt nur mit drastischen Einschränkungen nutzen).

VCL.NET - was ist das?
Um gleich mit einem häufig auftretenden Missverständnis aufzuräumen, fange ich mit dem Hinweis an, dass die VCL.NET kein Aufsatz für die FCL ist, sondern völlig eigene Wege geht. Bisher waren wir es gewohnt, dass die VCL nur ein objektorientierter Aufsatz auf das native Win32-API von Windows ist. Im Fall von .NET ist das .NET-Framework das native API (Application Programming Interface). Die VCL.NET setzt jedoch fast direkt auf das Win32-API auf, sodass die in der Zwischenzeit von Microsoft eingezogene Abstraktionsschicht .NET-Framework in weiten Teilen außen vor bleibt (siehe Abb. 1). Hinter der Bezeichnung P/Invoke verbergen sich die Platform Invocation Services der CLR (Common Language Runtime), die eine Brücke zur alten Win32-Welt bauen, indem der Aufruf von aus DLLs exportierten Funktionen erlaubt wird.


Abb. 1: Positionsbestimmung der VCL.NET

An dieser Stelle bin ich dann auch beim ersten Nachteil der VCL.NET. Dank der Abstraktionsschicht (.NET-Framework) kann Microsoft zu einem späteren Zeitpunkt das Win32-API durch etwas völlig Neues ersetzen, ohne dass die bestehenden .NET-Anwendungen davon beeinträchtig werden. Solange es eine CLR für dieses Betriebssystem gibt, bleiben die Anwendungen kompatibel (das Open Source-Projekt Mono tritt den Beweis an, dass dies nicht nur in der Theorie, sondern auch in der Praxis in Gestalt von Linux funktioniert). Wenn also in den nächsten drei Jahren die neue Windows-Version (zurzeit noch unter dem Codenamen Longhorn bekannt) und somit WinFX, Indigo und Avalon das Licht der Welt erblicken, kann die VCL.NET nicht automatisch daraus Vorteile ziehen. Ein Entwickler, der zu diesem Zeitpunkt auf die VCL.NET aufsetzt, muss darauf vertrauen, dass Borland einen Teil der neuen Funktionen auch in der VCL.NET umsetzt. Dies hat Borland auf seiner Community-Webseite auch bereits angekündigt, aber der Haken an der Sache liegt woanders. Viele der Neuheiten von künftigen Windows-Versionen wird Microsoft nicht mehr auf der Win32-API-Seite umsetzen, sondern direkt als neue Klasse in der dann aktuellen Fassung des .NET-Frameworks. Dies hat aber zur Folge, dass dann das heutige Fundament der VCL.NET nicht mehr den Anforderungen genügt.

Falls Sie nun der Auffassung sind, dass ich das Ganze zu pessimistisch betrachte, sei mir der Hinweis gestattet, dass dieses grundsätzliche Problem bereits heute sichtbar wird. Denn Delphi 8 stellt nicht ohne Grund gleich zwei völlig unterschiedliche Ausführungen für den Objektinspektor und den Formular-Designer zur Verfügung. Nur bei der VCL.NET kommt die Funktionalität des Formular-Designers und des Objektinspektors direkt aus Delphi. Bei einer FCL-Anwendung stammen beide Teile jedoch direkt aus dem .NET-Framework, sodass Borland nur einen geringen Einfluss auf das Verhalten innerhalb der eigenen Entwicklungsumgebung hat. Dies führt dazu, dass sich Delphi 8 gegenüber dem Entwickler in einer VCL.NET-Anwendung stellenweise völlig anders verhält als in einer FCL-Anwendung.

Leistungsfähigkeit
Wie machen sich die grundlegenden Unterschiede zwischen der FCL und der VCL.NET bei der Performance bemerkbar? Wir als Entwickler werden an unterschiedlichen Stellen damit konfrontiert. Das erste Mal bereits beim Öffnen eines Projekts. Bei einem sehr umfangreichen VCL.NET-Projekt (ca. 300 Formulare und 500 sonstige Units) müssen Sie mit ca. 10 Minuten für das Öffnen rechnen, während das gleiche Projekt in Delphi 7 innerhalb von 30 Sekunden online war. Auch der RAM-Verbrauch für dieses Projekt ist bei Delphi 8 um den Faktor 10 höher als bei Delphi 7. Das zweite Mal machen sich die Unterschiede beim Kompilieren des Projekts negativ bemerkbar. Und das dritte Mal bekommt auch der Anwender zur Laufzeit diese Unterschiede mit, denn die VCL.NET ist deutlich träger als die FCL. Da in diesem Artikel die Benutzeroberfläche im Vordergrund steht, stelle ich Ihnen ein Beispielprojekt in gleich drei Ausführungen zur Verfügung, das diese Unterschiede demonstriert:
  • VCL-Fassung für Delphi 6/7
  • VCL.NET-Fassung für Delphi 8
  • FCL-Fassung für Delphi 8

Alle drei Fassungen erledigen die gleiche Aufgabe und auch beim Blick auf den Sourcecode muss man schon ganz genau hinschauen, um Unterschiede erkennen zu können. Dies liegt zum Teil auch daran, dass einer der Väter der FCL in Person von Anders Hejlsberg auch der Vater von Delphi 1 (und somit auch der Vater der VCL) war. Als Beispiel für diese Ähnlichkeiten der beiden Klassenbibliotheken stelle ich Ihnen die Implementierung in VCL.NET und in FCL vor (die dritte Fassung für Delphi 6/7 finden Sie auf der Heft CD). In der VCL.NET-Fassung (die intern direkt auf die Win32-API-Funktionen zugreift) nutze ich auch den bisher üblichen Weg über die Win32-API-Funktion GetTickCount, um den Zeitbedarf zu stoppen. Als Aufgabe sollen der ListView-Komponente 501 Einträge hinzugefügt und dann im zweiten Schritt auch alle als angekreuzt markiert werden (siehe Listing 1).

Listing 1

procedure TForm2.Button1Click(<br></br>  Sender: TObject);
const
cITEMCOUNT = 500;
var
iStart : Integer;
aLI : TListItem;
iLoop : Integer;
begin
iStart := GetTickCount;
ListView1.Items.BeginUpdate;
for iLoop := 0 to cITEMCOUNT do

begin
aLI := ListView1.Items.Add;
aLI.Caption := 'Eintrag ' + IntToStr(iLoop);
end;
for iLoop := 0 to Pred(ListView1.Items.Count) do
ListView1.Items[iLoop].Checked := True;
with ListView1.Columns.Add do
begin
Caption := 'Überschrift 1';
Width := 100;
end;
ListView1.Items.EndUpdate;
StatusBar1.SimpleText := IntToStr(GetTickCount - iStart);
end;

In der FCL-Fassung stellt jeder direkte Zugriff auf das Win32-API ein Fremdkörper dar, sodass ich auf die Environment-Klasse aus dem .NET-Framework zurückgreifen, um dort über TickCount die Zeit stoppen zu lassen. Ansonsten stimmt die Vorgehensweise gegenüber der VCL.NET-Fassung überein, die FCL nutzt zum Beispiel mit der Methode BeginUpdate die exakt gleiche Bezeichnung wie die VCL beziehungsweise VCL.NET (siehe Listing 2).

Listing 2

procedure TWinForm1.Button1_Click(<br></br>  sender: System.Object; e: System.EventArgs);
const
cITEMCOUNT = 500;
var
iStart : Integer;
aLI : ListViewItem;
iLoop : Integer;
begin
iStart := Environment.TickCount;
ListView1.BeginUpdate;
for iLoop := 0 to cITEMCOUNT do
begin
aLI := ListViewItem.Create('Eintrag ' + iLoop.ToString);
ListView1.Items.Add(aLI);
end;
for iLoop := 0 to Pred(ListView1.Items.Count) do
ListView1.Items[iLoop].Checked := True;
ListView1.Columns.Add('Überschrift 1', 100, HorizontalAlignment.Center);
ListView1.EndUpdate;
StatusBar1.Text := Integer(Environment.TickCount - iStart).ToString;
end;


Nur dann, wenn der Zeitbedarf ermittelt wird, treten drastische Unterschiede zu Tage. Während die VCL-Fassung von Delphi 6 oder Delphi 7 genau so schnell ist wie die FCL-Fassung von Delphi 8, ist die VCL.NET-Fassung von Delphi 8 um den Faktor 16 langsamer (siehe Abb. 2). In der Originalfassung von Delphi 8 (also ohne das Update#2) beträgt der Unterschied sogar den Faktor 22.


Abb. 2: Nur die VCL.NET ist viel langsamer als Win32

Jetzt werden Sie sicherlich von mir wissen wollen, wo die Ursachen für diesen drastischen Unterschied liegen und über welchen Weg das Update#2 das Problem etwas entschärft. Auf diese Frage gibt es eine kurze und eine lange Antwort. Die kurze Antwort beschränkt sich auf den Satz Die VCL.NET greift zu häufig auf die Dienste von P/Invoke zurück!, für die längere Antwort benötige ich in diesem Artikel einen eigenen Absatz.

Sicherheit
Microsoft hat beim .NET-Framework aus den früheren Sünden in Sachen Sicherheit gelernt, so dass nun im Fall von verwalteten Programmanweisungen (Managed Code) über strenge Regeln die ständige Kontrolle gewährleistet wird. Allerdings muss diese geschützte Umgebung verlassen werden, wenn eine nur in bereits binärer Form vorliegende DLL aufgerufen werden soll. Über die Code Access Security (CAS) kann der Entwickler bestimmte Verhaltensweisen für den Türsteher festlegen, der den Grenzübergang zwischen den verwalteten und nicht verwalteten Programmanweisungen bewacht. Die CLR garantiert, dass zur Laufzeit diese Regeln eingehalten werden. Da sich Borland im Fall der VCL.NET von Delphi 8 dazu entschieden hat, parallel zum .NET Framework eine eigene Zugriffsschicht auf das Win32-API bereitzustellen, sind damit zwangsläufig zwei Performance-Bremsen verbunden:
  • Der Zeitbedarf für die ständigen P/Invoke-Aufrufe des Win32-API, da über das so genannte Marshaling die CLR die Datentypen zwischen der .NET-Welt und der Win32-Welt automatisch anpassen muss.
  • Der Zeitbedarf für die ständigen Laufzeit-Sicherheitsprüfungen vor dem Verlassen des Managed Code (d.h. vor dem P/Invoke-Aufruf). Die CLR fügt bei einem P/Invoke-Aufruf zur Laufzeit implizit einen Demand-Aufruf für das Attribut SecurityPermission.UnmanagedCode hinzu, was zu einem vollständigen Stack Walk (der Sicherheitsüberprüfung der vollständigen Aufrufkette) führt.

Um den zweiten Nachteil zu kompensieren, stellt das .NET-Framework das Attribut SuppressUnmangedCodeSecurity zur Verfügung, wobei allerdings der Entwickler dieser Assembly dafür verantwortlich ist, dass dieses neu geschaffene Sicherheitsloch nicht von Dritten missbraucht werden kann. In der Regel sollten daher die so gekennzeichneten Funktionen niemals von Außen aufrufbar sein, was im Fall von Delphi bedeutet, dass die so gekennzeichneten Import-Deklarationen für DLL-Funktionen nur im Implementation-Abschnitt einer Unit stehen dürfen, aber niemals im Interface-Abschnitt. Um nicht gravierend gegen die Sicherheitsregeln zu verstoßen, darf Borland dieses Attribut nicht in der für alle frei nutzbaren Unit Borland.Vcl.Windows.pas verwenden. Stattdessen nutzt Borland die wiederholte Deklaration im (privaten) Implementation-Abschnitt der folgenden Units:
  • Borland.Vcl.Messages.pas
  • Borland.Vcl.ComCtrls.pas
  • Borland.Vcl.Controls.pas
  • Borland.Vcl.Forms.pas
  • Borland.Vcl.Menus
  • Borland.Vcl.StdCtrls.pas

Das hat den Vorteil, dass die ärgsten Performance-Bremsen der VCL.NET entschärft wurden, allerdings zu einem hohen Preis. Denn nun ist Borland allein dafür voll verantwortlich, dass keine der öffentlichen Methoden dieser Units von einem Dritten für einen Angriff über die mit dem SuppressUnmanagedCodeSecurity-Attribut gekenn-zeichneten Win32-API-Funktionen missbraucht werden kann.
Falls Sie nun mit dem Einwand kontern möchten, dass ja auch Microsoft im .NET-Framework selbst auf die Win32-API-Funktionen zugreifen muss, haben Sie an dieser Stelle selbstverständlich Recht. Der Unterschied liegt nur darin, dass diese Zugriffe direkt aus den Assemblies des .NET-Framework heraus erfolgen und somit nicht den gleichen strengen Sicherheitsregeln unterworfen werden, wie das für externe Assemblies (die nicht unter der Kontrolle von Microsoft liegen) gilt. Im Fall von Delphi 8 kommt noch erschwerend hinzu, dass die Delphi 8-Laufzeitumgebung (RTL) in Gestalt von Borland.Delphi.dll ein Fremdkörper für die CLR darstellt, im Fall von C# oder VB.NET sind deren Laufzeitumgebungen ein integrierter Bestandteil des .NET-Frameworks.

Ich habe vorhin als zweiten Grund für die Performance-Unterschiede den Zeitbedarf für die ständigen Laufzeit-Sicherheitsprüfungen vor dem Verlassen des Managed Code genannt. Mit einem kleinen Experiment können Sie diesen Zeitanteil selbst bestimmen. Zuerst prüfe ich den Normalfall, indem die Anwendung Delphi8VCLTest.exe gestartet wird - die Statuszeile zeigt den Zeitbedarf von 516 Windows-Ticks an. Nun öffne ich ein Fenster der Eingabeaufforderung, um dort die Regeln der Code Access Security Policy zu ändern. Über die Anweisung caspol.exe -s off schicke ich den vorhin bereits genannten Türsteher in die Kaffeepause. Nun starte ich mein Testprogramm Delphi8VCLTest.exe erneut, der Zeitbedarf verkürzt sich von 516 auf 421 Ticks. Da das vollständige Abschalten des .NET-Sicherheitssystems nur in Ausnahmefällen sinnvoll ist (der Rechner hat keine Verbindung nach außen und wird nur lokal genutzt), führe ich im Fenster der Eingabeaufforderung die Anweisung caspol.exe -s on aus, um den Türsteher wieder in die Pflicht zu nehmen. Wenn Sie nun das Testprogramm Delphi8VCLTest.exe erneut aufrufen, wird dort wieder der Zeitbedarf von 516 Ticks angezeigt. Mit diesem kleinen Experiment hat sich gezeigt, dass in diesem konkreten Beispiel die Code Access Security mit 95 Windows-Ticks Verzögerung beteiligt ist, aber die ständigen P/Invoke-Aufrufe zu einer Verzögerung von 390 Ticks führen. Solange Borland das Fundament der VCL.NET nicht ändert, wird es bei diesen Performance-Unterschieden gegenüber der FCL bleiben.

Jeder .NET-Code, der P/Invoke nutzt, muss unter der Full Trust-Berechtigung laufen beziehungsweise eine eigene, speziell konfigurierte Berechtigungsmenge erhalten, die das Recht zum Ausführen von nicht verwalteten Programmcode einschließt. Im Fall der VCL.NET bedeutet dies, dass Ihnen die Feinheiten der Sicherheits-Abstufungen von .NET nicht zur Verfügung stehen. Dies wird spätestens dann relevant, wenn eine Assembly beteiligt ist, die nicht direkt von einem lokalen Laufwerk des Rechners geladen wird.

Exceptions?
Völlig andere Wege geht das .NET-Framework bei der Behandlung von den während der Laufzeit im Programm aufgetretenen Exceptions. Während bisher die VCL alles bequem verpackt hat und die VCL.NET dies unter Mithilfe der Class Helper-Klassen beibehält, erwartet die FCL von Ihnen, sich selbst um die globale Behandlung von Exceptions zu kümmern. Damit das für Sie nicht nur graue Theorie bleibt, habe ich wieder drei Beispielprojekte vorbereitet, die alle das Gleiche machen. Die Beispiele sollen nur die Unterschiede im Sourcecode und das unterschiedliche Verhalten zur Laufzeit zeigen. Während die VCL-Fassung für Delphi 6/7 sowie die VCL.NET-Fassung für Delphi 8 eine Exception provoziert, indem die Anweisung StrToInt('Fehler') aufgerufen wird, ruft die FCL-Fassung für Delphi 8 die Anweisung Int32.Parse('Fehler') auf, um das gleiche Problem zu provozieren. Wenn man sich bei der FCL-Anwendung die Mühe macht, eine eigene Ereignis-behandlungsmethode für das OnThreadException-Ereignis zu implementieren, wird man zur Laufzeit mit einer sehr detaillierten Fehlermeldung belohnt, die sowohl den Unit-Namen enthält als auch die Zeilennummer (siehe Abb. 3), an deren Stelle die Exception provoziert wurde. Die Exception-Informationen der FCL können dabei auch verschachtelt sein, sodass ich in einer Schleife solange die einzelnen Ebenen auspacke, bis die Eigenschaft InnerException keine weiteren Details zurückliefert (siehe Listing 3).

Listing 3

procedure OnThreadException(sender: TObject;
t: ThreadExceptionEventArgs);
var
aSB : StringBuilder;
aExc: Exception;
begin
aSB := StringBuilder.Create;
aExc := t.Exception;
while aExc <> nil do
begin
aSB.Append(aExc.GetType.ToString + Environment.NewLine);
aSB.Append(aExc.Message + Environment.NewLine);
aSB.Append(aExc.StackTrace + Environment.NewLine);
aExc := aExc.InnerException
end;
aMainForm.DoSetExcMsg(aSB.ToString);
end;

[STAThread]
begin
Include(Application.ThreadException, OnThreadException);
Application.Run(TWinForm1.Create);
end.


Abb. 3: Nur die FCL ist bei einer Exception sehr auskunftsfreudig

Schauen Sie sich nun bitte noch einmal die Abbildung 3 genauer an. Dort wird eine Nebenwirkung der Class Helpers von Delphi 8 sichtbar, denn im Fall der VCL.NET liefert die Exception-Instanz mit Ausnahme der beiden ersten Zeilen nur Informations-Müll zurück. Im Gegensatz zur FCL-Fassung kann die VCL.NET infolge der allgemeingültigen Class Helper (die ja zur Laufzeit nur in den Kontext des zurzeit aktuellen Objekts eingeblendet werden) die auslösende Stelle nicht erkennen. Zwar steckt hinter den Class Helpers eine geniale und zu Recht inzwischen von Borland zum Patent angemeldete Idee, um zwei unterschiedliche Klassenbibliotheken miteinander zu verbinden, aber es gibt eben auch eine Schattenseite, die sich im konkreten Beispiel der Exception-Auswertung bemerkbar macht.

Funktioniert der Garbage Collector nicht?
Der automatische Speicherverwalter (Garbage Collector) ist ein großer Vorteil von .NET. Als Sicherheits-gurt verhindert er die schädlichen Nebenwirkungen der bisher gefürchteten Speicher-Löcher, die immer dann entstehen, wenn das Programm zwar Speicher angefordert, aber dann nicht wieder freigegeben hat. Insbesondere für Server-Anwendungen, die rund um die Uhr aktiv bleiben, war das bisher ein sehr großes Problem. Wenn Sie nun bisher gelesen haben, dass sich der Garbage Collector bei einer .NET-Anwendung automatisch um dieses Problem kümmert, gilt dies nur für die originalen Klassen aus dem .NET-Framework uneingeschränkt.

An dieser Stelle greife ich auf ein praktisches Beispiel aus dem Entwickler-Forum zurück - dort wurde die folgende Frage gestellt: Ist es möglich, dass der Garbage Collector von .NET nicht richtig funktioniert? Wenn ich in meiner mit Delphi 8 kompilierten VCL.NET-Anwendung die Formulare auf und zu mache, benötigt das Programm immer mehr Speicher, gibt aber nichts mehr frei. Erst beim Beenden der ganzen Anwendung ist der Speicher wieder nutzbar, obwohl ich (wie von Java so gewohnt) jede Formularinstanz explizit auf nil setze, wenn ich sie nicht mehr benötige. Auch wenn sich dieses Verhalten mit einem Beispielprojekt jederzeit reproduzieren lässt, arbeitet der Garbage Collector (GC) korrekt und genau so, wie es von .NET dokumentiert wurde. Allerdings kümmert sich der GC nur um die verwalteten Programmanweisungen (Managed Code), alle direkten Allozierungen von nativen Win32-Handles etc. müssen wie gehabt in eigener Regie freigegeben werden. Dieser feine - aber für uns als Entwickler sehr folgenschwere Unterschied - wird bei einer VCL.NET-Anwendung sehr wichtig. In Delphi 8 stehen uns ja gleich zwei Alternativen zur Verfügung. Zum einen können wir auf die FCL zurückgreifen, um die gleichen Windows Forms zu verwenden, die auch die C#- und VB.NET-Kollegen nutzen. Aber zum anderen stellt nur Delphi 8 die Alternative VCL.NET bereit, um mit den vertrauten VCL-Komponenten weiterarbeiten zu können. Der Haken an der Sache ist nur der, dass diese Bequemlichkeit selbstverständlich ihren Preis hat. Da die VCL.NET hinter den Kulissen ständig direkt am .NET-Framework vorbei auf das Win32-API zugreift, hilft uns das Sicherheits-Netz des GC an diesen Stellen nicht aus der Patsche. Wir müssen uns daher selbst um das Freigeben der nicht mehr benötigten Ressourcen kümmern. In dem vorbereiteten Beispielprojekt sieht das dann so aus: In einer Schleife wird eine TForm-Instanz erzeugt, die in einer TImage-Komponente eine Grafikdatei anzeigt. Über die Radiobutton-Auswahl reagiert das Programm nach dem Anzeigen und dem automatischen Schließen (TTimer-Komponente ruft die TForm-Methode Close auf) unterschiedlich:
  • Im ersten Fall wird gar nichts gemacht.
  • Im zweiten Fall wird die geerbte TForm-Methode Release aufgerufen.
  • Im dritten Fall wird die Formular-Variable auf den Wert nil gesetzt.

procedure TForm1.ButtonFormGrafikClick(Sender: TObject);
var
i, iMax : Integer;
begin
iMax := Convert.ToInt32(EditCount.Text);
for i := 1 to iMax do
begin
Form3 := TForm3.Create(nil);
try
Form3.ShowModal;
finally
case RadioGroupGC.ItemIndex of
1 : Form3.Release;
2 : Form3 := nil;
end;
end;
StatusBar1.SimpleText := i.ToString;
Application.ProcessMessages;
end;
end;

Bei einer VCL.NET-Anwendung entsteht nur dann kein Speicherleck, wenn die erzeugten Objektinstanzen explizit durch uns freigegeben werden. Im Fall eines aufgerufenen Formulars bedeutet dies, dass der Aufruf der Methode Release nicht vergessen werden darf. Das einfache Zuweisen von nil reicht nicht aus, da in diesem Fall zwangsläufig eine Speicherleiche entsteht, die erst mit dem Ende des kompletten Prozesses von Windows entsorgt wird. Wirft man dann einen Blick hinter die Kulissen (d.h. in das Source-Unterverzeichnis von Delphi 8), wird deutlich, dass der Aufruf von Release über die für die zeitliche Entkopplung dazwischen geschaltete Botschaftswarteschlange von Windows (die über P/Invoke aufgerufene Win32-API-Funktion PostMessage) direkt zum Aufruf der geerbten Methode Free führt:

procedure TCustomForm.Release;
begin
PostMessage(Handle, CM_RELEASE, 0, 0);
end;

procedure TCustomForm.CMRelease;
begin
Free;
end;

Damit jetzt bei Ihnen kein falscher Eindruck entsteht, sei gesagt, dass auch bei einer FCL-Anwendung das in eigener Regie kontrollierte Freigeben von speicherintensiven Objektinstanzen über den Aufruf der Methode Dispose oder die using-Anweisung (C#) empfohlen wird. Für uns bleibt im Alltag der Aufwand gleich,. Was bei der VCL.NET ein absolutes Muss ist, wird bei der FCL zu einer freiwilligen Handreichung, um den Zeitpunkt der Speicherfreigabe zu beeinflussen.

Fehlt da nicht etwas?
Während sich dieser Beitrag mit den prinzipiellen Unterschieden und einigen exemplarischen Aus-wirkungen beschäftig hat, fehlen Aussagen zum Hantieren mit den Komponenten und Klassen. Borland liefert zusammen mit Delphi 8 den WinForm Control Import Wizard aus, der ein .NET-Control über eine automatisch generierte VCL.NET-Wrapperkomponente neu verpackt. Aufgrund der Unterschiede des Formular-Designers, des Objektinspektors und der unterschiedlichen Klassen-Hierarchien lassen sich jedoch nicht alle .NET-Controls erfolgreich verpacken beziehungsweise zur Entwicklungszeit erfolgreich visuell konfigurieren. Der Folgeartikel in der nächsten Ausgabe wird sich mit diesen Details näher befassen. Bis dahin können Sie in Delphi 8 selbst auf eine Entdeckungsreise gehen. Legen Sie dazu sowohl ein neues Projekt für eine VCL-Formularanwendung (VCL.NET) und eine Windows Form-Anwendung (FCL) an und vergleichen Sie dann die in der Tool-Palette in verfügbaren Komponenten.


    Hat Ihnen dieser Artikel gefallen? Dann abonnieren Sie das Entwickler Magazin direkt über unser Online-Formular.



zur vorherigen Seite
zurück
an den Anfang der Seite
nach oben
Diesen Artikel drucken
drucken
Diesen Artikel weiterempfehlen
empfehlen
Software & Support Verlag GmbH