Werne, den 05.06.04 Seminar komponentenorientierte Softwareentwicklung bei Prof.Dr.Frank Thiesing Vergleich .Net und COM von Daniel Oberländer und Marek Wyborski Ausarbeitung von Marek Wyborski Inhaltsverzeichnis Einleitung……………………………….…………………………………………….3 Common Type System……………………………………………………………….4 Intermediate Language……………………………………………………………....4 Common Language Runtime………………………………………………………..7 Codeprüfender JIT Compiler……………………………………………………….7 Standardbibliothek…………………………………………………………………..7 Visual Studio.Net……………………………………………………………………..8 COM-Objekte in .Net verwenden………………………………………………...…8 ActiveX-Steuerelemente in .Net…………………………………………………....10 COM-Objekte mit .Net erstellen…………………………………………………...11 Programmierbeispiel „MyExplorer“………………………………………………13 Programmierbeispiel „InteropDemo“……………………………………………..18 Quellenangaben……………………………………………………………………..20 2 Einleitung In der Vergangenheit der Programmierung bestand das Problem der Interoperabilität zwischen Code der in verschiedenen Sprachen geschrieben wurde. Das beste Beispiel dafür ist die Windows-Plattform. Das Problem bestand – auch zwischen „hauseigenen“ Sprachen – in Differenzen der Typsysteme und Aufrufkonventionen. Compilerbauer für VC++, VB, Delphi, Eiffel, Smalltalk und andere Sprachen hatten einfach immer wieder unterschiedliche Vorstellungen davon, wie eine Zeichenkette intern abgebildet, ein Stack-Frame zum Aufruf eines Unterprogramms aufgebaut oder ein Objekt im Speicher angelegt werden sollte. Eine gewisse Abhilfe brachten zwar die Dynamic Link Libriaries (DLL) mit ihren Vorgaben für den Aufbau für Funktionsbibliotheken und Aufrufkonventionen, doch das Problem der TypsystemInkompatibilität und seine mangelnde Objektorientierung haben ihren Nutzen für die Interoperabilität immer begrenzt. Eine grundsätzliche Lösung brachte erst COM (Component Object Model). Erwachsen aus einer Kombination aus DLL und DDE (Dynamic Data Exchange), versprach COM die Interoperabilität beliebiger Programmiersprachen durch Definition eines eigenen, objektorientierten Typsystems. Als binärer „Standard“ machte COM Vorgaben für Maschinencode zum Aufbau von Objekten und einfachen Datentypen, die Speicherverwaltung und den Aufruf von Methoden. Sprachen, deren Compiler diese Konventionen bedienen konnten, waren damit interoperabel. Es war ein schöner Ansatz, der zwar knapp zehn Jahre tragfähig war, aber am Ende doch zu viele Beschränkungen aufwies – nicht zuletzt, weil sich Microsoft vom Erfolg von COM überrollt fühlte, das gerade mit VB5 und VB6 eine Technologie zur massenhaften Produktion von Komponenten wurde. Kritikpunkte waren unter anderem die nur beschränkte Objektorientierung mit zu wenigen Ausdrucksmöglichkeiten für Sprachen wie C++ oder Eiffel, eine umständliche Objektlebenszeitverwaltung mittels Referenzzählung, der Zwang zu einer fragilen Anmeldung von Komponenten in der Registry und die Unmöglichkeit eines Parallelbetriebs mehrerer Versionen einer Komponente auf einem Rechner (DLL-Hölle). Angesichts dieser Situation sah sich Microsoft Ende der 90’er Jahre gezwungen, seine Softwareentwicklungsplattform von Grund auf zu überdenken. Im Jahr 2000 stellten die Redmonder erstmalig einer breiten Öffentlichkeit .Net als Antwort auf das Problem der Interoperabilität vor. Jim Miller, einer der Architekten, formuliert eines der treibenden Motive so:“Ich möchte nur zwei einfache Dinge tun können – und das schon seit über 30 Jahren. Erstens Programme in einer Sprache schreiben, die mir gefällt, dabei aber Bibliotheken von anderen benutzen, die in andern Sprachen entwickelt wurden. Zweitens Bibliotheken in einer Sprache meiner Wahl schreiben, die dann von anderen in ihren Sprachen genutzt werden.“ Die Realisierung dieses Wunsches sieht wie folgt aus: 3 Common Type System Auf der untersten Schicht wurde das Konzept von COM konsequent zu Ende gedacht. Das .Net Framework gibt mit seinem Common Type System (CTS) allen Programmiersprachen ein Typsystem vor. Das CTS ist konsequent objektorientiert und enthält alle allgemein anerkannten OOP-Konzepte wie Klassen, Schnittstellen, Einfachvererbung für Klassen, Mehrfachvererbung für Interfaces, Polymorphie, virtuelle Methoden etc.. Darüber hinaus bietet das CTS Innovationen gegenüber anerkannten Typsystemen wie dem von C++ oder Java, beispielsweise Eigenschaftenmethoden (Properties), Attribute (typisierte erweiterbare Metadaten) oder typisierte Funktionszeiger (Delegates): Alle Typen des CTS sind von „der Mutter aller Klassen“, der Klasse System.Object abgeleitet . Das betrifft auch Werttypen wie ganze Zahlen (System.Int32) oder logische Werte (System.Boolean). Das CTS erlaubt darüber hinaus die Definition eigener Werttypen entsprechend den C-struct-Typen. Indem das .Net Framework mit dem Common Type System ein allgemeines Typsystem diktiert, entfällt bei der Zusammenarbeit von Code unterschiedlicher Sprachen prinzipiell jedes Marshalling zwischen verschiedenen Typsystemen, wie es bei COM nötig war. Ein int in C# entspricht zu 100 Prozent einem Integer in VB.Net, eine Class in VB.Net ist dasselbe wie in J# (Microsofts Version von Java für .Net) und so weiter. Microsoft hat während der Entwicklung von .Net intensiv mit Compilerherstellern zusammengearbeitet, um das CTS den Bedürfnissen möglichst vieler Programmiersprachen anzupassen. Zwar stellt es immer noch notwendig einen kleinen gemeinsamen Nenner dar, ist aber leistungsfähig genug, um Sprachen von C# über Eiffel und Pascal bis zu Haskell, Lisp und Smalltalk eine Basis zu bieten. Das CTS bietet sogar mehr Typen beziehungsweise Varianten, als die meisten Programmiersprachen benötigen. So kennt VB.Net keine vorzeichenlosen Ganzzahlen, und weder C# noch VB.Net implementieren Unions oder Felder mit einem anderen Startindex als 0. Um angesichts dessen die grundsätzliche Interoperabilität zwischen allen .Net-Sprachen sicherzustellen, definierte das so genannte Common Language Subsystem (CLS) eine Untermenge des CTS, die alle implementieren müssen. Intermediate Language Das CTS allein wäre schon ein Gewinn gegenüber früher, weil es die Zahl der Typsysteme auf eins reduziert, statt wie COM den vorhandenen Typsystemen aller Sprachen noch eins hinzuzufügen. Für sich allein genommen ist das CTS jedoch nur eine Konvention, die zu implementieren und einzuhalten fehlerträchtig wäre. Aus diesen und anderen Gründen gibt das .Net Framework nicht nur ein Typsystem vor, sondern auch eine für alle Programmiersprachen gemeinsame Zielsprache. Compiler für .Net-Sprachen erzeugen also keinen Maschinencode mehr, sondern ausschließlich Code in der Zwischensprache Microsoft Intermediate Lanuage (MSIL oder kurz IL). Die IL ist eine streng typisierte, prozessorunabhängige, Stack-orientierte Assemblersprache. Äußerlich ist sie Javas Bytecode ähnlich, hat jedoch einen anderen Anspruch. IL ist nicht zur Interpretation gedacht, sondern wird immer zur Laufzeit 4 kompiliert und ist so ausgelegt, dass möglichst viele Programmiersprachen ihre Konstrukte vorteilhaft auf ihre Instruktionen abbilden können. Statt IL in die Linie von Javas Bytecode oder Pascals P-Code zu stellen, ist es eher angemessen, sie mit C zu vergleichen: Portabilität von und Interoperabilität zwischen vielen Programmen für Unix wird immer noch erreicht, indem man sie als C-Quellcode ausliefert und erst auf dem Zielsystem kompiliert. Heute, bei .Net, definieren CTS und IL die Ausdrucksmöglichkeiten aller Programmiersprachen. Der Vorteil gegenüber C ist dabei die Kombination von simplen Op-Codes für eine Stack-Maschine gekoppelt mit strenger Typisierung. IL ist damit genauso typsicher wie eine moderne Hochsprache, aber wesentlich kompakter in der Notation und schneller in Maschinencode zu übersetzten. Die Abbildung auf der nächsten Seite zeigt ein Hello-World-Programm, notiert in mehreren .Net-Sprachen, und seine Übersetzung in IL. Anders als üblicher Assemblercode ist IL sehr gut lesbar und ähnelt in der Notation C#. Alle Konstrukte, gerade des C#- und VB.Net-Quelltextes, haben ihr Pendant in IL. Insbesondere sind dort die Typbeschreibungen und Methodenaufrufe inklusive Signaturen erhalten. Der IL-Code stellt damit eine vollständige Beschreibung des Hochsprachenquelltextes nebst aller Metadaten dar. Mit der IL und ihrem Typsystem CTS müssen sich Compilerbauer also keine Gedanken mehr um die Implementierung von Typen machen. Überlegungen welches Speicherlayout ein Objekt haben sollte, oder ob ein String seine Länge enthält oder mit 0 terminiert, gehören der Vergangenheit an. Compilerbauer müssen nur noch entscheiden, welche CTS- und IL-Konstrukte in welcher Kombination ihren Sprachkonstrukten entsprechen. Deutlich einfacher fällt auch die Codeerzeugung in Compilern für .Net-Sprachen aus. Man kann sich viele Optimierungen sparen, da der IL-Compiler diese vornimmt. So entfallen zum Beispiel Entscheidungen über Registerallozierung oder Inlining. Mit der Kombination IL + CTS sind .Net-Sprachen also per Definition interoperabel, da sie sich nach der Übersetzung nicht mehr voneinander unterscheiden. Alle Programme liegen dann in derselben Sprache vor. Das war früher, als Compiler noch Maschinencode erzeugten nicht der Fall. In den generierten Instruktionen waren Programmen zwar noch gleich, aber zum Beispiel nicht mehr in dem, wie sie mit Daten (Instanzen von Typen) umgingen. Diese Unterschiede existieren mit IL + CTS nicht mehr. C#-Typen entsprechen VB.Net-Typen, und VB.Net-Aufrufkonventionen gleichen denen von Eiffel und so weiter. Das gilt auch für den Umgang mit Fehlern während der Programmausführung: Die Fehlermeldung erfolgt auf IL-Ebene einheitlich durch das Werfen von Ausnahmen in Form von Exception-Objekten ganz in der Linie von C++ oder Java. Selbstredend besteht auch hier kein Unterschied zwischen den Hochsprachen, das heißt, ein Fehler, geworfen in einem C#-Programm, kann in VB.Net-Code mit einer üblichen try-catchAnweisung gefangen werden. Und sogar ein Fehler in einer COM-Komponente, einer DLL oder in einem XML-Web-Service wird über denselben Mechanismus gemeldet. 5 6 Common Language Runtime Da der Intermediate Language Code nicht direkt auf einem Prozessor ausgeführt werden kann, muss es eine Infrastruktur für IL-Programme geben. Diese Aufgabe übernimmt die Common Language Runtime (CLR). Sie hat die Aufsicht über jeglichen IL-Code, der daher auch Managed Code genannt wird. Wird ein Managed-Code-Programm (Assembly) im Betriebssystem als Exe-Datei gestartet, läuft nur ein sehr kurzes Stück Maschinencode ab, das die CLR in den Prozess lädt und dann sofort die Kontrolle dorthin abgibt. Die CLR ermittelt daraufhin den Eintrittspunkt in den IL-Code und startet den Just-in-Time-Compiler (JITCompiler). Der übersetzt jeweils nur die nächste auszuführende Methode in Maschinencode und springt sie an. Werden während der Ausführung Typen benötigt, die noch nicht geladen sind, sucht die CLR deren Assemblies und lädt sie. Trifft die Ausführung bei Aufruf einer Methode auf IL-Code, startet wieder automatisch der JIT-Compiler. Die Compilierung von IL-Programmen in Maschinencode erfolgt methodenweise nur bei Bedarf, also in kleinsten Häppchen. Die schrittweise Übersetzung führt gewöhnlich zu keinem Performanceverlust, da der JIT-Compiler extrem schnell ist (er übersetzt in der Größenordnung von mehreren MB pro Sekunde) und für das Zielsystem optimierten Code erzeugt. In die Optimierung können Parameter wie Prozessortyp, Registerauslastung etc. einfließen. Codeprüfender JIT-Compiler Die CLR lädt aber nicht nur Code und übersetzt ihn, sondern prüft auch seine Korrektheit und Integrität. IL ist typsicher, auch wenn einzelne Op-Codes untypisiert sind. Der JIT-Compiler prüft zum Beispiel statisch, ob die auf den Stack geschobenen Parameter der Signatur der aufzurufenden Methode genügen. Das kann er, weil Assemblies komplette Informationen über ihre Typdefinition enthalten. Assemblies sind damit selbstbeschreibend und benötigen keine zusätzlichen Informationen mehr in externen Ressourcen wie Registry oder Typelibs. Das dient auch einem deutlich vereinfachtem Deployment von Programmen inklusive ihrer Komponenten. Im einfachsten Fall müssen nur alle zugehörigen Dateien in ein Verzeichnis auf dem Zielsystem kopiert werden. Der CLR-Lade-Algorithmus ist dann in der Lage, benötigte Bibliotheken zur Laufzeit zu finden. Nicht nur die Korrektheit und Übersetzung von IL-Code wird von der CLR kontrolliert, sondern auch die Speicherverwaltung. Speicher für Referenztypen wird auf dem Heap alloziert und automatisch wieder durch eine Garabage Collection freigegeben, wenn er nicht mehr referenziert ist. Das .Net Framework gibt damit die fehlerträchtige Referenzzählung von COM auf und folgt dem Beispiel von Smalltalk und Java. Standardbibliothek Bestandteil von Hochsprachen sind nicht nur eine Menge von Anweisungen und ein Typsystem, sondern immer auch eine mehr oder weniger umfangreiche Standardbibliothek für Ein-/Aus-gabe, mathematische Aufgaben, Datenbankzugriff etc. Auch hier strebt das .Net Framework eine Vereinheitlichung an. Es enthält eine 7 umfassende Standardbibliothek, die selbst als Managed Code vorliegt, die Base Class Library (BCL). Sie bietet, aufgeteilt auf mehrere Assemblies, mehrere tausend, in Namensräumen organisierte Klassen. Deren Spektrum reicht von Multithreading und IO-Operationen auf Dateien über die Windows-Grafikprogrammierung oder Reflection bis zu Webprogrammierung (ASP.Net), XML Webservices, Datenbankprogrammierung, XML-Programmierung, Containerklassen, Netzwerkkommunikation und Applikationsserverprogrammierung. Natürlich ist die BCL nicht lückenlos, so fehlen Klassen zum Umgang mit RAS und der seriellen Schnittstelle oder für 3D-Ausgaben. Aber die BCL ist umfassend genug, um allen .Net-Sprachen auf der Basis von IL+CTS eine gemeinsame Grundlage zu geben. Somit besteht bei Neuerlernen einer .Net-Sprache zum Beispiel nicht mehr die Frage, wie man eine Datenbank anspricht. Konzepte wie Parallelprogrammierung, Datenbankzugriff oder Windows-GUIs sind sprachunabhängig. In einer Sprache erworbenes Wissen kann verlustfrei auf andere übertragen werden. Visual Studio .Net Da grundsätzlich alle .Net-Sprachen interoperabel sind, stellt sich die Frage, wie Teillösungen in unterschiedlichen Notationen in einem Projekt möglichst einfach zusammengeführt werden können. Die Interoperabilität von .Net-Sprachen unterstützt VS.Net durch verschieden Maßnahmen. Mehrsprachige Projektmappen: Eine nicht triviale Anwendung besteht immer aus mehreren Komponenten (Assemblies). In VS.Net werden Assemblies durch Projekte repräsentiert, mehrere Projekte können in einer Projektmappe (Solution) zusammengefasst werden. Jedes Projekt lässt sich dabei in einer anderen .Net-Sprache realisieren. Projekte referenzieren darin einander, um ihre Typen nutzen zu können. Cross-Language Debugging: Die Unterstützung von VS.Net für mehrsprachige Projekte erstreckt sich aber auch über die Entwicklungszeit hinaus in die Laufzeit. Beim Debugging ist es unerheblich in welcher Assembly ein Abbruch erfolgt, oder ein Breakpoint gesetzt wurde. Mit dem Debugger ist eine schrittweise Codeausführung über Projekt- und Sprachgrenzen möglich und reicht bis in Stored Procedures im SQLServer. VS.Net ist für die sprachübergreifende Programmierung mit .Net nicht notwendig, vereinfacht die Arbeit jedoch erheblich. COM-Objekte in .Net verwenden Die Integration von COM-Objekten in die .Net-Laufzeitumgebung geschieht über Stellvertreterklassen in der jeweiligen Sprache. Klassen dieser Art unterscheiden sich syntaktisch in keiner Weise von anderen Klassen und können auch als Basis weiterer Klassen dienen. Diese Stellvertreterklassen werden Runtime Callable Wrapper (RCW) genannt. Die automatisierte Generierung einer Hüllklasse in VS.Net setzt voraus, dass zu der COM-Schnittstelle eine Typbibliothek in Form einer .tlb-Datei existiert. Diese liefern die Metadaten (Methodennamen, Parametertypen, etc.), die in einer COM-Dll nicht enthalten sind. Um eine COM-Komponente zu nutzen muss man in seinem Projekt einen Verweis zu dieser erstellen. Selbstverständlich muss die Komponente registriert sein. Das 8 Ergebnis ist eine Dll im Stil von .Net (also mit Metadaten in Form eines Manifest). Der Name der Dll ergibt sich durch den in der .tlb-Datei angegebenen Namen, dem .Net die Zeichenfolge „interop.“ voranstellt. Beim Anlegen von Hüllklassen über das VisualStudio.Net ruft VS.NET das Hilfsprogramm TlbImp (Type Library Importer) auf und belässt es bei den Standardvorgaben dieses Utilities: Konfigurationsmöglichkeiten aus VS.NET heraus gibt es hier nicht. Wenn man sich selbst um die Benennung der DLL oder den entstehenden Namensraum kümmern will, muss man TlbImp.exe über die Konsole aufrufen und dabei entsprechende Schalter setzten: /out:NeuerName.Dll legt den Namen der Dll explizit auf „NeuerName“ fest. Die Referenz auf die von TlbImp.exe erzeugte Dll muss man dann durch einen Verweis selbst einbauen. Wenn keine TLB vorhanden ist, geht es zur Not auch mit der direkten Codierung der Metadaten. Microsoft demonstriert das auf der MSDN-Website http://msdn.microsoft.com/library/default.asp?url=/library/enus/vbcn7/html/vaconIntroductionToCOMInteroperability.asp anhand des Mediaplayers. Unabhängig davon, wie die Klassendeklaration zu Stande gekommen ist, bietet die kontextbezogene Hilfe auch in diesem Fall Unterstützung. Wie auch nicht anders zu erwarten, bietet die .NET-Laufzeitumgebung für Instanzen von Stellvertreterklassen exakt dieselben Annehmlichkeiten wie für „normale“ Objekte: -Die Deklaration einer Instanz der Stellvertreterklasse sorgt für das Laden des RCW. -Der Konstruktor des RCW erledigt (über die API-Funktion CoCreateInstance) das Heraussuchen der Registrierungsinformationen und das Laden des COM-Servers. -Der Garbage Collector räumt irgendwann die Instanzen der Stellvertreterklasse wieder ab und erniedrigt damit die entsprechenden COMReferenzzähler, was letztendlich erst zum Entladen des COM-Servers und dann zum Entladen der RCWDLL führt. Der in anderen Programmiersprachen bei der COM-Programmierung unumgängliche finally-Block erübrigt sich also. Da beim Einsatz von COM zwei externe Dateien geladen werden müssen und diese Ladeoperationen beide erst auf Anforderung hin geschehen, sind entsprechende Fehlerprüfungen ein unbedingtes Muss. Die Deklaration einer Stellvertreterklasseninstanz ist mit Sicherheit der unkritischere der beiden Schritte: Sie kann nur dann zu einer Exception führen, wenn die RCW-DLL fehlt, also die Installation beschädigt wurde. Mit der Konstruktion einer Instanz der Stellvertreterklasse sieht es dagegen etwas anders aus: Auch unter normalen Umständen hat das Programm damit zu rechnen, dass der entsprechende COM-Server nicht auf dem Zielsystem installiert ist. Sinnvollerweise sorgt man dafür, dass die entsprechenden Prüfungen nicht erst dann stattfinden, wenn der Benutzer die jeweilige Funktion der Anwendung tatsächlich einsetzen will. Ein denkbarer Ort und Zeitpunkt ist nach dem Laden des Formulars (der Konstruktor ist aber genauso gut geeignet). Der unvermeidliche Nachteil einer solchen „vorauseilenden“ Prüfung liegt darin, dass sie die Startzeit der Anwendung 9 auch dann erhöht, wenn der Benutzer die COM-Komponente gar nicht einsetzt. Die (optimistische) Variante nimmt die Prüfung erst auf Anforderung vor. ActiveX_Steuerelemente in .Net ActiveX-Steuerelemente stellen in mehrfacher Hinsicht eine Ausnahme der in den vorangehenden Abschnitten erläuterten Regeln dar. Damit sich ein (beliebiges) visuelles Steuerelement in ein Formular einsetzen lässt, muss seine Klasse von System.Windows.Forms.Control abgeleitet sein. OCX-Dateien enthalten im Gegensatz zu normalen COM-DLLs die zur Erzeugung der Hüllklassen benötigten Metadaten direkt. Eine .tlb-Datei ist deshalb unnötig. Um der Forderung nach einer spezifischen Basisklasse gerecht zu werden, legt VS.NET für ActiveX-Steuerelemente nicht eine, sondern zwei Klassen (in zwei DLLs) an: Eine Hüllklasse für die COM-DLL, und eine zweite, von Control abgeleitete Klasse, die ihrerseits die COM-Hüllklasse verkapselt. Zum Erzeugen dieser beiden Hüllklassen für ein ActiveX-Steuerelement in VS.NET verwendet man das über das Kontextmenü der TOOLBOX erreichbare Dialogfeld Element hinzufügen/entfernen. Wie die Abbildung zeigt, listet die Karteikarte COMSTEUERELEMENTE dieses Dialogfelds die beim System registrierten ActiveXKomponenten auf. Für den Import von ActiveX-Steuerelementen gilt Ähnliches wie beim Anlegen von Hüllklassen ohne visuelle Komponente: VS.NET verwendet ein externes Hilfsprogramm dafür und arbeitet mit dessen Standardvorgaben. Das für ActiveXSteuerlemente zuständige Konsolenprogramm heißt AXImp.exe (Active X Importer). Es bietet über verschiedene Schalter analog zu TlbImp.exe die Möglichkeit, Namensräume, DLL-Namen usw.festzulegen. 10 COM-Objekte mit .Net erstellen Um eine .Net-Klasse als COM-Schnittstelle zu benutzen muss sie gewissen formalen Kriterien entsprechen: - Konstruktoren mit Parametern sind nicht verwendbar, da COM ausschließlich die parameterlose Variante kennt. - Statische Elemente sind nicht über die COM-Schnittstelle aufrufbar (aus der Implementation heraus dagegen schon). - Konstanten werden wie statische Elemente behandelt: Sie stehen den Methoden der .Net-Klasse zur Verfügung, sind aber nicht direkt über die COMSchnittstelle abfragbar, sondern nur über den Umweg einer (nicht statischen) Zugriffsmethode. Die weiteren Voraussetzungen sind ein COM-gerechter Eintrag in der Registrierung und die Installation im globalen Cache der .NET-Laufzeitumgebung (was seinerseits wiederum einen starken Namen voraussetzt). Praktische Vorgehensweise: Nachdem man eine Klasse geschrieben hat, die den genannten Kriterien von COM entsprechen, muss man die daraus entstandene Dll mit RegAsm (Register Assembly) registrieren. Nun muss man der Dll einen „starken Namen“ zuweisen, damit man sie im Global Assembly Cache (GAC) registrieren kann. Dazu benutzt man das mit dem .Net Framework ausgelieferte Konsolentool sn.exe auf die Dll an. Dieses liefert nun eine Datei mit dem benötigten Schlüsselpaar. Nun trägt man in der Datei AssemblyInfo, die VS.Net zu jedem Projekt automatisch anlegt, am Ende den Namen der Schlüsseldatei ein. Nach einer erneuten Kompilierung mit VS.NET, bindet der Compiler nun auch den starken Namen in die DLL ein, besteht der letzte Schritt schließlich aus der Installation im globalen Cache. Dazu kann man entweder erneut im Konsolenfenster nach Bin\Debug wechseln und dort das Utility gacutil aufrufen oder schlicht im Explorer die Dll in den globalen Cache von .NET ziehen, der standardmäßig den Ordner \Windows\assembly belegt. 11 Mit dem Programm TlbExp als Gegenstück zu TlbImp lässt sich aus einem Assembly eine .tlb-Datei erzeugen, die sich zumindest theoretisch in keinem Punkt von einer .tlbDatei für eine „echte“ COM-Schnittstelle unterscheidet. Vorausgesetzt wird dabei die explizite Vergabe des Attributs ClassInterfaceType. AutoDual – ansonsten ist das Ergebnis eine weitgehend leere IDLBeschreibung. Nach entsprechender Kompilierung und erneuter Registrierung mit RegAsm und dem Aufruf von TlbExp die erstellte Dll lässt sich im OLE-Viewer von VS 6 sehen, was die dabei erzeugte .tlb-Datei an Informationen enthält. Obwohl eine als COM-Schnittstelle verpackte .NET-Komponente einen nicht unbeträchtlichen Überbau mit sich herumschleppt (auf den Clients wie Delphi dann noch einmal eine weitere Schicht in Form einer Stellvertreterklasse draufpacken), zeigte sich in das die Verluste bei der Laufzeit im erträglichen Rahmen bleiben – vor allem, weil das doppelte und dreifache Umschichten von Parametern gegenüber dem Zeitbedarf von COM selbst praktisch nicht ins Gewicht fällt. Der wesentliche Punkt: Eine als COM getarnte .NET-Komponente ist kein „besseres COM“ – sondern eben ein Zwitter, der sowohl auf der .NET- als auch auf der COMSeite Installation und Wartung verlangt, und mit sämtlichen Problemen behaftet ist, die mit .NET eigentlich abgeschafft werden sollten. Das betrifft sowohl die nicht mögliche Koexistenz verschiedener Versionen (es sei denn, man schafft jeweils eigene COM-Schnittstellen dafür) als auch die Rechtevergabe und die Installation. 12 Programmierbeispiel „MyExplorer“ Um den praktischen Anwendungswert von COM-Objekten in .Net zu veranschaulichen haben wir uns ein kurzes effektives Beispiel überlegt, welches im Seminar für die Zuhörer komplett nachvollziehbar sein soll. Wir haben das Beispiel extra so angelgt, dass es in seinem Aufwand direkt im Seminar fertiggestellt werden kann. Das Produkt ist ein einfacher Browser der auf der ActiveX-Komponente vom MS Internet Explorer basiert, diese ist im System32 Verzeichnis Registriert (shdovw.dll). Um eine neue Komponente in die Toolbox einzufügen, muss man innerhalb der Toolbox mit der rechten Maustaste auf die Steuerelemente klicken und dann aus dem Menus "Elemente hinzufügen/entfernen" wählen. Es erscheint ein neues Fenster mit den im System registrierten COM-Steuerelementen und .NET Framework- Komponenten. Daraus ist das gewünschte Element zu wählen. 13 Daraufhin erscheint das neue Steuerelement in der Toolbox unter Windows Forms und verbleibt dort für jede weitere Windows Anwendungen griffbereit. 14 Das Internet- Explorer- Steuerelement, das zuvor in die Tollbox eingefügt wurde, kann mit einfachem Rüberziehen in die Form1 des jeweiligen Projekts verwendet werden. Die Verweise werden nun vom Visual Studio .NET automatisch erstellt. Im bin Verzeichnis dieses Projektes befinden sich zwei neue Assemblies: .NET Assemblies: .../bin/AxInterop.SHDocVw.dll ... /bin/Interop.SHDocVw.dll Wenn das Steuerelement auf dem Form1 markiert ist, kann man aus dem Eigenschaften- Fenster den neuen Namen des Ojekts sehen: AxWebBrowser1 15 Nun legt man ein Textfeld und 4 Buttons an, die später die Funktionen Vor, Zurück, Home, und Go (Die im Textfeld angegebenen Seite anzeigen) zugewiesen bekommen. Der nun folgende Programmierteil gestaltet sich denkbar einfach. Um einen Klick auf einen Button zu programmieren klickt man diesen im Designer einfach doppelt an worauf man im Code direkt im Rumpf der Methode _Click des Buttons landet. Der Code für den Button „Zurück“ sieht dann so aus: axWebBrowser1.GoBack(); Button „Vor“ wird analog realisiert: axWebBrowser1.GoForward(); Button „Home“ : axWebBrowser1.GoHome(); Lediglich der Code für unseren „Go“-Button gestaltet sich etwas komplizierter. 16 Wie man im Screenshot sehen kann möchte die Methode Navigate ausser dem String mit der anzusteuernden URL noch vier weitere Objekte vom „Urtyp“ System.Object per Referenz übergeben bekommen. Das ActiveX Control hat in der Navigate Methode alle Parameter als Variants deklariert. Unter .NET gibt es keine Variant Typen mehr. Bei der Standardkonvertierung werden daraus System.Object Typen. Da diese Parameter per Referenz übergeben werden sollen, kann man leider nicht null als Parameter angeben, sondern ist gezwungen sich eine Hilfsvariable vom Typ System.Object anzulegen und diese dann mit null zu initialisieren. Nichtsdestotrotz gestaltet sich der Code sehr kurz und sieht wie folgt aus: Object a=null; axWebBrowser1.Navigate(textBox1.Text,ref a,ref a,ref a,ref a); Unser eigener kleiner Webbrowser ist nun fertig: 17 Programmierbeispiel „InteropDemo“ Dieses kleine Programmierbeispiel dient der Veranschaulichung wie verschiedene Komponenten – auch anderssprachlich – mit in ein Programm eingebunden werden. Es besteht aus einem Hauptprojekt in C# und zwei Klassenbibliotheken, von denen eine in C# und eine in VB.Net geschrieben sind. Die C# Klassenbibliothek greift ausserdem noch auf ein COM-Objekt zu, und die C# Hauptanwendung bedient sich der nativen Windows-API „user32.dll“. Die VB Klassenbibliothek besteht aus einer Klasse „DomainInfo“ die lediglich einen String mit Namen „Domain“ besitzt. Public Class DomainInfo Public Domain As String End Class Die C# Klassenbibliothek besteht aus 2 Klassen. Die Klasse „UserInfo“ erbt von der VB-Klasse „DomainInfo“ und stellt einen weiteren String mit der Bezeichnung „Name“ bereit. public class UserInfo : VBKomponente.DomainInfo { public string Name; } Die andere Klasse in der C#-Klassenbibliothek heisst „CSKlasse“ sie besitzt lediglich eine Methode „getUserInfo“ die ein Objekt vom Typ „UserInfo“ zurückgibt (Also die beiden Strings „Domain“ und „Name“). Dieses Element füllt sie mit den Daten die sie von der COM-Komponente „Windows Script Host“ geliefert bekommt. Es ist der Name und die Domain des aktuellen Benutzers. public class CSKlasse { public UserInfo GetUserInfo() { IWshRuntimeLibrary.WshNetworkClass wshnet = new IWshRuntimeLibrary.WshNetworkClass(); UserInfo u= new UserInfo(); u.Name= wshnet.UserName; u.Domain= wshnet.UserDomain; return u; } } Den Verweis auf den „Windows Script Host“ nimmt man vor indem man in der Projektmappe auf Verweise einen Rechtsklick macht, und im Kontextmenu Verweis hinzufügen anklickt. Im neuen Fenster wählt man unter COM „Windows Script Host Object Model“ aus. Desweiteren wählt man unter Projekte die VB-Klassenbibliothek aus, da die Klasse „UserInfo“ ja von dieser erbt. Nun kommt das Hauptprojekt die C#-Klasse mit der Main-Methode. Für diese erstellen wir einen Verweis auf die C#-Komponente und einen Verweis auf die VBKomponente. Dass man auf die VB-Komponente verweisen muss ist verwunderlich, 18 da wir sie in diesem Projekt gar nicht direkt verwenden werden, und alle Informationen ja in der C#-Komponente stecken, aber dennoch verlangt VS danach. Nun importieren wir die „user32.Dll“ am Anfang der Klasse, und stellen deren Methode MessageBox bereit. [System.Runtime.InteropServices.DllImport("user32.dll")] public static extern int MessageBox(int hWnd,string txt,string caption,int Typ); Im der Main-Methode wird dann zuerst ein Objekt der CSKlasse und der UserInfo erstellt, die beide in der C#-Klassenbibliothek beherbergt sind. Nun wird das UserInfoObjekt mit dem Ergebnis der „getUserInfo“-Methode der CSKlasse gefüllt (Wodurch natürlich auch die eingebundene COM-Komponente aktiv wird). Am Ende benutzen wir dann die importierte „MessageBox“-Methode um das Ergebnis auszugeben. static void Main(string[] args) { CSKomponente.CSKlasse cs=new CSKomponente.CSKlasse(); CSKomponente.UserInfo ui; string Ausgabe; ui =cs.GetUserInfo(); Ausgabe="Angemeldeter Benutzer: "+ ui.Name+"\r\nDomain: "+ui.Domain; MessageBox(0, Ausgabe, "Interop Beispiel", 0); } Bei Start des Programms erscheint nun wie erwartet ein Fenster, das den aktuellen Benutzernamen und die Domain ausgibt. 19 Quellenangaben: Arne Schäpers, Rudolf Huttary, Dieter Bremes: Das C#-Kompendium; Markt + Technik Verlag 2002 Arne Schäpers, Rudolf Huttary: .NETSpeak; C’T 15/2003 Arne Schäpers, Rudolf Huttary: COMSpeak; C’T 10/2004 Arne Schäpers, Rudolf Huttary: Rückwärsgang – COM aus der .Net-Perspektive (1); C’T 10/2004 MSDN Library; Microsoft Online Referenz; http://msdn.microsoft.com Frank Eller, Holger Schwichtenberg: Programmieren mit der .Net-Klassenbibliothek; Addison-Wesley 2002 20