Visual Studio .NET: Für alle Aufgaben gerüstet

Veröffentlicht: 19. Feb 2002 | Aktualisiert: 16. Jun 2004
Von Bernd Marquardt

Mit der neuen Sprache C#, jede Menge hilfreicher Funktionen und rund 3,2 Gigabyte Platzbedarf steht Visual Studio .NET nun endlich zur Verfügung. Der Artikel zeigt, wie lang der Weg bis zu VS .NET war und erklärt die wichtigsten Funktionen.

Auf dieser Seite

 Von Microsoft C 3.0 zu Visual Studio 7.0
 Die neue Sprache C#
 Die Common Language Runtime
 Der Garbage Collector
 Die Entwicklungsumgebung
 Assemblies
 Remoting
 Web-Services
 ASP .NET
 Fazit: Eines für alles

Von Microsoft C 3.0 zu Visual Studio 7.0

Mein Einstieg in die Programmiersprache "C" erfolgte mit der Version 3 des Microsoft C-Compilers – ja, genau: Microsoft C Version 3. Damals passte die gesamte Software-Entwicklungsumgebung auf fünf Disketten zu je 360 KB. Das war in diesen historischen Zeiten schon viel: Festplatten nahmen gerade mal zwischen zehn und maximal 40 MB Daten auf. Und als Integrated Development Environment feierte der DOS-Editor "Edlin" zu dieser Zeit seine größten Erfolge.

Die einzelnen Version von C/C++ sollen hier unerwähnt bleiben, nur der Version 7 gilt es, einige Worte zu schenken. C/C++ Version 7 war die erste C++-Version des Microsoft Compilers. Mit dieser Version wurde auch die MFC (Microsoft Foundation Classes) geboren. Dabei handelte es sich damals aber eher um einige hauchdünne Wrapperklassen für das Windows-API.

Wer damals unter Windows 3.0 oder 3.1 ein Programm schreiben wollte, hatte einen steinigen Weg vor sich. Zunächst wurde das DOS-Programm PWB (Programmers Workbench) gestartet. Code eintippen und übersetzen - hoffentlich fehlerfrei. Nun wurde von der PWB aus Windows im Real-Mode gestartet. Da Windows die unteren 640 KB RAM unbedingt benötigte, wurde die PWB in das Extended Memory verschoben, das Programm gestartet und die Ergebnisse (oder Fehler) überprüft.

Nach dem Beenden des Programms galt es, Windows herunterzufahren und die PWB wieder in den unteren RAM-Bereich zu verschieben. Dann ging der ganze Zyklus von vorne los. Kaum zu glauben, aber selbst das Debuggen ging in diesem Szenario irgendwie.

Der erste C-Compiler von Microsoft mit einer Windows-IDE erblickte im Jahr 1991 das Licht der Softwarewelt: QuickC for Windows., zu dieser Zeit das beste C-Entwicklungswerkzeug für Windows-Programmierer.

Bild01

Bild 1: Visual C++ Version 1

Visual C++ 1.0 kam zwei Jahre später auf den Markt und zwar auf 20 Disketten. In Bild 1 können Sie eine kleine Zeitreise unternehmen. Sie sehen die IDE und das zusätzlich gestartete "App Studio", mit dem in dieser Version die Windows-Resourcen (Dialoge, String-Tabellen, und so weiter) verwaltet wurden. Einen Class Wizard gab es jetzt auch (Bild 2) und auch der App Wizard war vorhanden. Zur gleichen Zeit kam auch Visual Basic Version 2 auf den Markt. Dieses Werkzeug machte die Programmierung unter Windows populär, das heißt, die VB-Programmierer mussten sich nicht, wie ihre C/C++-Kollegen, mit hunderten von komplizierten APIs herumschlagen.

Bild02

Bild 2: Der Class Wizard im Visual C++ Version 1

Visual C++ 2.0 stellte Microsoft 1994 fertig und lieferte es schon auf einer CD aus. Es handelte sich um eine echte 32-bit-Software mit der Applikationen für Windows NT entwickelt werden konnten. Ein Visual C++ Version 3 gab es nicht, da Microsoft die Versionsnummern der MFC und der IDE angleichen wollte.

Bild03

Bild 3: Visual C++ Version 3

Aus diesem Grund ging es dann im Jahr 1995 mit VC++ 4.0 weiter (Bild 3). Diese Version von Visual C++ hat schon eine geringe Ähnlichkeit mit der heutigen IDE von Visual Studio .NET. Wie man in Bild 3 auch sehen kann, hat VC++ 4 ein kleines Problem mit der Jahreszahl im "About"-Dialog bei der Erzeugung der Rahmenapplikation mit dem AppWizard. Nebenbei: Dieser Bug existiert in Visual Studio .NET nicht mehr!

In der Version 4 gab es massive Weiterentwicklung der MFC. Microsoft führte sehr viele neue Klassen ein und die MFC bekam in etwa die Struktur, die sie noch heute hat. Außerdem gab es im VC++ 4 sogenannte Projekte. Ein Projekt enthält auch heute noch alle Dateien, die zu einer Applikation gehören – zusätzlich zu den Informationen, die nötig sind, um die Applikation zu übersetzen und zusammenzubauen. Mit der Version 4.2 wurde auch erstmals die STL (Standard Template Library) ausgeliefert.

Das war auch die Zeit, als OLE (Object Linking and Embedding) und COM (Component Object Model) aufkamen. COM bot zum ersten Mal eine gewisse Interoperabilität zwischen unterschiedlichen Programmiersprachen. Eine in C++ geschriebene COM-Komponente konnte zum Beispiel von Visual Basic und Visual C++ aufgerufen werden. Die Programmierung dieser Komponenten war am Anfang jedoch so knifflig, dass es nur Wenigen vergönnt war, sie erfolgreich zu schreiben.

Microsoft Visual Studio 5 enthielt 1997 auf einer CD alle gängigen Microsoft Programmiersprachen. Dazu gehörten Visual C/C++, Visual Basic und Visual Java. Jede Sprache hatte jedoch ihre eigene Entwicklungsumgebung und Runtime-Bibliothek mit speziellen Möglichkeiten.

Die wichtigste Neuerung in dieser Version war jedoch die ATL (Active Template Library). Mit dieser C++-Bibliothek war es nun endlich möglich, COM-Komponenten auf eine einfache Weise zu erstellen. Auch mit Visual Basic konnte man solche COM-Komponenten programmieren. Es war nun möglich, mehrschichtige Applikationen zu bauen, die in einer Schicht den Datenzugriff, in einer anderen Schicht die Applikationslogik und in einer dritten Schicht die Benutzerschnittstelle enthielten (Bild 4). Das Wichtigste daran war: Die COM-Komponenten mit der eventuell hochkomplexen Geschäftslogik konnten immer wieder in vielen Projekten verwendet werden – auch von ASP-Seiten (Active Server Pages) für Internet-Applikationen.

Bild04

Bild 4: Die Dreischichtige Architektur

Visual Studio 6 erleichterte die Entwicklung solcher Windows-DNA-Applikationen noch weiter. Ein Problem blieb aber auch in der neuen Version bestehen: COM war ein binärer Standard, der nur auf Microsoft-Plattformen (Windows) problemlos genutzt werden konnte.

Die zukünftige Welt bringt uns eine Vielfalt von Computern und anderen Devices (Palmtops, Telefone, intelligente Kaffeemaschinen), die alle miteinander vernetzt sind. Das bringt natürlich auch Probleme mit sich. Beispielsweise sind viele Technologien wie ActiveX-Controls, ASP, Registry, COM/DCOM, Deployment durch das Internet selber überholt worden.

Die neue Sprache C#

Die Liste der Neuerungen fängt damit an, dass sich zu den Programmiersprachen VC++ und Visual Basic eine neue Sprache gesellt: C#, gesprochen C Sharp. C++ ist sehr komplexe Programmiersprache, die einen Softwareentwickler einige Monate Lernaufwand kostet. Visual Basic hingegen ist leicht zu erlernen und war sicherlich ein Grund für den Erfolg von Microsoft Windows. Viele Programmierer begannen Anfang der 90er Jahre mit dieser Sprache ihre Windows-Applikationen zu entwickeln. Aber VB hat auch seine Grenzen. C# dagegen ist eine C++/Java-ähnliche Sprache, die leichter zu Erlernen und zu Benutzen ist, als C++, auf der anderen Seite jedoch die komplette Funktionalität des .NET-Frameworks unterstützt.

using System; 
namespace dotNet 
{ 
  class Class1 
  { 
[STAThread] 
static void Main(string[] args) 
{ 
  string s; 
  s = "Hello, C#!"; 
  Console.WriteLine("Ausgabe: {0}", s); 
} 
 } 
} 

C# allein macht die Programmierer aber nicht glücklich. Eine neue Programmiersprache bedeutet normalerweise eine neue Runtime-Bibliothek und das Lernen neuer APIs. Aber nicht mit Visual Studio .NET! Alle .NET-Programmiersprachen benutzen eine gemeinsame Runtime! Was bedeutet das im Einzelnen?

Die Common Language Runtime

Das .NET-Framework basiert auf der Common Language Runtime (CLR), also auf einer Runtime-Bibliothek, die von allen .NET-Sprachen genutzt wird. Auf dieser Runtime setzt wiederum die Base Class Library (BCL) auf. Diese Klassenbibliothek kapselt die APIs des Betriebsystems. Beide Elemente – Common Language Runtime und Base Class Library – werden von diversen Sprachen wie Visual Basic, C++, C# oder J# benutzt.

Die Base Class Library ist eine sehr reichhaltige Klassenbibliothek. Eine gleichartige Namensvergabe und Parameteranordnung sorgt dafür, dass man sich schnell zurechtfindet und der Lernaufwand relativ gering ist. Alternativ zur Base Class Library kann der Programmierer aber auch API-Aufrufe machen, die allerdings den Nachteil haben, plattformabhängig zu sein.

Ziel der Common Language Runtime ist es, alles – vom Typsystem, Kontext für Transaktionen bis hin zum Loader für ferne Komponenten – in einer einzigen Runtime zusammenzufassen. Um das zu erreichen, hat jede Programmiersprache ihren eigenen Compiler, der so genannten IL-Code (Intermediate Language) erzeugt. IL-Code ist ein Stack-orientierter Code, der sehr schnell weiter verarbeitet, jedoch nicht direkt auf einem Prozessor ausgeführt werden kann.

Der IL-Code ist sprach- und plattformunabhängig. VS .NET schreibt beim Erzeugen eines Executable nicht mehr den prozessorabhängigen Maschinencode sondern den IL-Code in die EXE-Datei. Beim Aufruf der .NET-Applikation übersetzt ein JIT-Compiler (Just In Time) den IL-Code in nativen Maschinencode. Alle Compiler arbeiten über diesen Zwischenschritt, nur der Microsoft C++-Compiler kann noch nativen Maschinencode erzeugen.

Das hört sich nach einem sehr zeitaufwändigem Prozess an. Dem ist aber nicht so. Denn es werden immer nur die Programmteile übersetzt, die auch wirklich benötigt werden. Außerdem ist die Geschwindigkeit des JIT-Compilers ernorm: Auf einem normalen Computer schafft er pro Sekunde etwa 20.000 Zeilen IL-Code.

Ein weiterer Vorteil des JIT-Compilers: Er erkennt während der Ausführung der Applikation den bereits übersetzten Code. Dadurch spart sich der JIT-Compiler "doppelte Arbeit", wenn zum Beispiel Methoden einer Klasse mehrfach aufgerufen werden. Er schreibt den übersetzten nativen Code jedoch nicht auf die Festplatte, sondern erzeugt ihn vor jedem Programmaufruf wieder neu. Aber auch dieses Vorgehen hat positive Aspekte. Der JIT-Compiler kann sich dadurch beispielsweise an das momentan verfügbare RAM oder andere Systemzustände anpassen. Durch IL-Code und JIT-Compiler wird eine Unabhängigkeit von der Hardwareplattform erreicht.

Durch dieses Konzept werden alle .NET-Sprachen gleichwertig. Die Programme sehen sehr ähnlich aus, auch wenn sie mit unterschiedlichen Sprachen geschrieben wurden. Alle Compiler erzeugen für bestimmte Sprachkonstrukte, wie Schleifen oder if-Befehle, einen sehr ähnlichen IL-Code, der wiederum die für alle Sprachen gleiche Runtime verwendet.

Die Runtime macht es zudem möglich, dass etwa eine C#-Klasse von einer VB .NET- oder einer C++-Klasse abgeleitet werden kann. Aus all diesen Faktoren wiederum resultiert eine einheitliche Fehlerbehandlung in der Runtime sowie eine (nahezu) gleiche Performance. Die Programmierung mit Visual Basic erhält dadurch eine deutliche Geschwindigkeitsverbesserung. Somit müssen sich die Programmierer nicht mehr darüber streiten, welche Programmiersprache den schnellsten Maschinencode erzeugt, sondern sie können sich auf die Lösung des Softwareproblems konzentrieren.

Ein weiteres wichtiges Kriterium ist die Aufnahme des Typsystems in die Common Language Runtime. Der Vorteil liegt auf der Hand: Alle Typen werden eindeutig. Somit ist ein String unter C# identisch mit einem String unter VB .NET. Die Sprachen werden interoperabel, da sie das gleiche Typsystem benutzen.

Vorteile:

  • Eine komplizierte Konvertierung von Parametern bei Methodenaufrufen zwischen den unterschiedlichen Sprachen ist nicht mehr notwendig.

  • Das Microsoft-Handbuch "Cross Language Programming" bleibt im Regal stehen.

  • Zeitaufwändige Überlegungen und Analysen, welche Programmiersprache für ein Projekt zu verwenden ist, gehören der Vergangenheit an.

Duch die Aufnahme vieler Features in die CLR, wie Debugging, Typsystem, Codegenerierung, Multithreading, und vielem mehr, wird auch der Compilerbau für .NET-Sprachen viel einfacher, denn er wird auf die alleinige Implementierung der Sprache reduziert. Dadurch können relativ schnell auch andere Programmiersprachen von Drittanbietern auf dem Markt verfügbar sein.

Der Garbage Collector

Ein weiterer wichtiger Punkt in der .NET-Umgebung ist der Garbage Collector (GC), der von allen .NET-Applikationen benutzt wird. Der Garbage Collector verwaltet alle Objekte, die auf der Speicherhalde angelegt werden. Das bedeutet für den Programmierer: Er kann ein Objekt im Speicher anlegen, es benutzen – und es dann einfach vergessen. Denn der Garbage Collector räumt irgendwann auf und entfernt nicht mehr referenzierte Objekte aus dem Speicher. Dadurch ist es leichter geworden, Anwendungen zu schreiben, die auch über Monate stabil laufen können.

Allerdings erfordert der Garbage Collector eine etwas andere Programmierweise. Der Programmierer weiss nicht, wann nun genau das Objekt im Speicher zerstört wird. In den meisten Fällen ist das nicht weiter wichtig. Es gibt aber auch Situationen, in denen die Objektzerstörung genau gesteuert werden soll. Hierzu ein Beispiel: Ein Programmierer schreibt eine Klasse, die im Konstruktor eine Datei öffnet. Die Methoden der Klasse schreiben diverse Daten in die Datei und im Destruktor befindet sich der Code, der die Datei wieder schliesst.

Ein mögliches Problem, das hier auftreten kann, sind die nicht deterministischen Aufrufe des Destruktors. Der Destruktor der Klasse wird nämlich erst in dem Moment aufgerufen, wenn der Garbage Collector das Objekt entsorgt. Der Garbage Collector wiederum beginnt erst dann aufzuräumen, wenn der Speicherplatz knapp wird. Im oben geschilderten Fall wäre die Datei noch zum Schreiben geöffnet, obwohl der Vorgang insgesamt schon abgeschlossen ist. Ein erneutes Öffnen und Lesen der Datei kann unter diesen Voraussetzungen problematisch sein.

Es gibt jedoch wahrscheinlich nur wenige Fälle, in denen die nicht deterministische Freigabe problematisch ist. Außerdem gibt es Möglichkeiten in der .NET-Umgebung, eine sofortige Zerstörung bestimmter Objekte zu erwirken.

Der Garbage Collector ist sehr schnell, effizient und im Prinzip beim Arbeiten mit dem Computer nicht zu spüren. Die Performance einer Applikation wird also davon nur unmerklich berührt.

Die Entwicklungsumgebung

Neben den Standardaufgaben wie Editieren, Compilieren oder Debuggen soll eine Entwicklungsumgebung dem Programmierer ständig wiederkehrende Aufgaben abnehmen. Außerdem hat jeder Programmierer andere Vorstellungen von seiner Arbeitsumgebung. Es ist also sehr wichtig, dass die IDE in allen Bereichen konfigurierbar ist. All diese Anforderungen erfüllt Visual Studio .NET (Bild 5).

Bild05

Bild 5: Die IDE von Visual Studio .NET

Besonders hilfreich sind die Fenster, die automatisch wieder verschwinden, wenn sie nicht mehr benötigt werden. Das spart sehr viel Platz auf dem Bildschirm und auch auf einen kleinen Notebook-Bildschirm passt damit jede Menge Quellcode. Auch die "dynamische Hilfe" erweist sich als sehr nützlich. Je nach dem, was gerade als Quellcode ansteht, gibt das Programm die zugehörigen Hilfethemen in einem separaten Fenster aus.

Assemblies

Durch die Versions- und Registry-Problematik bei den COM-Komponenten, scheint sich langsam aber sicher eine "DLL-Hölle" anzubahnen: In der Registry ist jeweils nur eine Komponente mit einer Sprache und einer Version registrierbar.

Was fehlt, ist ein Versionierungsverfahren, das konfigurierbar, eindeutig und sicher ist.
Aktuelle Festplatten sind heutzutage groß genug, um hier nicht mehr der beschränkende Faktor zu sein.

.NET-Anwendungen bestehen aus so genannten Assemblies. Sobald ein Modul kompiliert wird, gehört es zu einem Assembly. Ein Assembly entspricht einer Komponente. Es ist ein Container für ein oder mehrere Module, der Code, Ressourcen, Metadaten und ein Manifest enthält. Die Metadaten beschreiben die Typen. Vergleichbar mit der COM-Type Library lassen sich die Metadaten leicht abfragen.

Das Manifest wiederum beschreibt das gesamte Assembly. Es enthält den Namen, die Version und den Ländercode (Kultur). Außerdem beinhaltet das Manifest eine Liste aller Module, aus denen das Assembly besteht, sowie referenzierte Assemblies (mit benötigter Versionsnummer), exportierte Typen und Ressourcen.

Man unterscheidet zwei Typen von Assemblies: "Private Assemblies" und "Shared Assemblies". Bei einer Private Assembly kann das Assembly ausschließlich nur von einer Anwendung benutzt werden. Private Assemblies erlauben ein so genanntes "XCopy-Deployment": Assemblies, die zu einer Applikation gehören, einfach in ein Verzeichnis auf der Festplatte des Rechners kopieren. Dann läuft die Applikation - und zwar ohne weitere Registrierung. Die meisten Anwendungen laufen auf diese Weise.

Bei einem Shared Assembly kann das Assembly global von allen Anwendungen benutzt werden, muss dann aber im Global Assembly Cache (GAC) installiert werden. Der Vorteil ist, dass der GAC mehrere Versionen eines Assemblies verwaltet. Ein Administrator ist in der Lage, die gewünschte Version der Komponente explizit zuweisen. Dafür ist jedoch kein Software-technischer Eingriff notwendig, sondern es muss lediglich ein XML-Datei erzeugt werden, in der die gewünschte Version angegeben ist.

Remoting

Ein weiteres Problemchen in der heutigen Zeit ist die Konfigurierung von verteilten Anwendungen. Diese Anwendungen benutzen auf irgendeine Weise immer DCOM und müssen in der Registrierungsdatenbank (Registry) von Windows korrekt installiert werden.

Um auf "ferne" Assemblies zuzugreifen, kann – ohne Benutzung der Registry – .NET Remoting verwendet werden. "Fern" bedeutet hier, dass auf Assemblies in einer anderen Application-Domain, in einem anderen Prozess oder auch auf einem fernen Rechner zugegriffen wird. Anders als bei DCOM werden dafür keine Registry-Eintragungen benötigt, wodurch die Konfiguration von verteilten Anwendungen deutlich einfacher wird.

Web-Services

Wie spricht ein Rechner Komponenten (Dienste) auf fernen Computern an, ohne dass die Sicherheit des eigenen Netzwerkes leidet? Das Öffnen von Ports in der Firewall dafür ist sicherlich keine gute Idee.

Verteilte Objekte sind für das Internet selten geeignet, denn die verwendeten Kommunikationsprotokolle sind eigentlich für lokale Netzwerke ausgelegt, also verbindungsorientiert. Zudem erfordern sie eine enge Koppelung von Server und Client und die ist im Web nicht gegeben.

Eine gute Lösung dieser Probleme sind Web-Services, die Dienste für externe Aufrufer im Internet oder Intranet bereitstellen. Nach dem Black-Box-Prinzip soll sich ein Web-Service wie eine normale Komponente verhalten. Ganz entscheidend aber ist, dass die Web-Services auf offenen Standards wie HTTP, XML und SOAP basieren. Welcher Client von welcher Plattform den Web-Service aufruft, ist also völlig gleichgültig. Die Daten für die Abfrage werden als XML übermittelt und die Ergebnisse gelangen ebenfalls über XML zurück. Client- und Server-seitige Proxies sogen für die korrekte Umsetzung der Methodenaufrufe und Parameter für die jeweilige Umgebung. Außerdem müssen keine Ports in der Firewall geöffnet werden.

Dank eines Wizards ist das Gerüst eines Web-Service mit Visual Studio .NET auf eine Mausklick hin erzeugt. Der Programmierer muss sich in keiner Weise um die Kommunikation mit SOAP herumschlagen, denn VS .NET erzeugt automatisch die Proxies, die das übernehmen.

ASP .NET

Und was ist nun mit den klassischen Web-Applikationen? Thin-Client-Lösungen zu programmieren, ist heutzutage noch nicht so einfach, wie man es sich eigentlich wünscht. Insbesondere das Zusammenstellen zustandsloser Web-Anwendungen hat es in sich. Mit ASP .NET ist es jetzt möglich, Code (Logik) und Formatierung (HTML) sauber voneinander zu trennen. Außerdem werden für die Programmierung der Logik einer Web-Seite vollständig objektorientierte Programmiersprachen (Visual Basic, C#) benutzt. Die Zeiten der einfachen Script-Sprachen gehört somit der Vergangenheit an.

Es ist nun also möglich, mit ASP .NET performante Web-Applikationen mit dem gleichen Programmierparadigma zu entwickeln, das auch für normale Rich-Client-Applikationen verwendet wird. Außerdem ist die Wiederverwendbarkeit von Code durch die Benutzung vollwertiger Programmiersprachen deutlich verbessert geworden.

Fazit: Eines für alles

Gigantisch – so kann man die Funktionsvielfalt von Visual Studio .NET bezeichnen. Viele Probleme der Vergangenheit werden in der neuen Version durch gute Lösungen und neue Technologien angegangen. Das bedeutet für Softwareentwickler natürlich wieder: lernen, lernen, lernen. Aber: Diese Investition lohnt sich.


Anzeigen: