PDF 303K

Werbung
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
Herunterladen