Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs

.NET Komponenten in COM+ Clients einsetzen

Geschrieben von: Christoph Wille
Kategorie: .NET Allgemein

This printed page brought to you by AlphaSierraPapa

Mit dem .NET Framework sind Funktionen in die Griffweite eines jeden Programmierers gerückt, von denen man noch vor nicht allzu langer Zeit nur träumen durfte. Trotz all dieser neuen Features kann man meist seine alten Applikationen nicht einfach wegwerfen und in .NET neu schreiben, sondern man muß sie weiterwarten. Dieser Artikel zeigt, wie man seine existierende Applikation mit in .NET geschriebenen Komponenten verbinden kann.

Viele existierende VB6 oder ASP Applikationen werden uns noch Jahre begleiten, doch sind in diesen Programmiersprachen bestimmte Dinge nur schwer oder gar nicht realisierbar. Diese gleichen Aufgaben sind jedoch in .NET in wenigen Zeilen erledigt; wie kann man also neuen .NET Code in seine existierende Anwendung bringen? Die Antwort sind COM Callable Wrapper, kurz CCW.

COM Callable Wrapper

Der Name ist Programm: der COM Callable Wrapper ist nichts anderes als ein Wrapper rund um unsere .NET Komponente, damit sie von VB6 und anderen COM Clients aus angesprochen werden. Ein CCW ist das Gegenstück zum Runtime Callable Wrapper (RCW), den wir bereits im Artikel Verwenden von COM Komponenten in ASP.NET näher betrachtet haben.

Was alles muß man für CCW's beachten? Nun, es gibt einige wichtige Punkte:

Weiters sollte die Assembly, in der die .NET Komponente lebt mit einem Strong Name versehen sein, und der Einfachheit halber im Global Assembly Cache (GAC) abgelegt sein.

Beispiel

Als Beispiel verwenden wir die ImageInfo Klasse aus dem Artikel Bildinformationen selbst ermitteln, die wir um einige Best Practices für CCW's erweitern werden. Beginnen wir mit dem erwähnten Strong Name. Dieser legt die Identität der Assembly kryptographisch fest, und für diese Prozedur benötigen wir ein Schlüsselpaar, das man mit dem sn Utility erzeugen kann:

F:\Inetpub\wwwroot\AspHeute\dotnetccw>sn -k ImageInfo.key

Microsoft (R) .NET Framework Strong Name Utility  Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Key pair written to ImageInfo.key

Der weitere Teil der Prozedur ist einfach, weil den Großteil der Compiler erledigt - man muß dem Compiler nur mitteilen, daß er das Schlüsselpaar zur Erzeugung des Strong Names verwenden soll. Dies geschieht am besten in einer separaten Datei (der Übersichtlichkeit halber), die man AssemblyInfo.cs nennen sollte:

using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: AssemblyTitle("ImageInfo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("AlphaSierraPapa")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("(c) 2002 Christoph Wille")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.0.0.0")]

[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("ImageInfo.key")]

Die letzte Zeile sorgt dafür, daß der Compiler automatisch den Strong Name anlegt. Damit, und mit einer korrekten Versionsnummer, ist man dann später in der Lage, die Assembly in den GAC (Global Assembly Cache) zu installieren. So weit sind wir aber noch nicht, wir müssen die Komponente noch ein wenig anpassen.

Die im Artikel Bildinformationen selbst ermitteln vorgestellte ImageInfo Komponente entspricht noch nicht ganz unseren Vorgaben. Wir sollten für eine schnelle Zerstörung der "teuren" Bitmapresource sorgen (mittels Dispose Pattern), einen Konstruktor anlegen, und auch die Methoden explizit für COM markieren. Beginnen wir bei den leichten Dingen, dem Konstruktor (ImageInfo.cs):

   // Ein öffentlicher Konstruktor muß vorhanden sein
   public ImageInfo()
   {
    m_bmpRepresentation = null;
   }

Nun markieren wir alle Methoden mit dem ComVisible Attribut. Da wir keine Methode verstecken wollen, könnte man argumentieren, daß dieser Schritt unnotwendig ist. Er hat aber einen guten Grund: jeder, der den Code später liest weiß, daß diese Komponente für die Verwendung in COM Applikationen vorgesehen ist, und er keine Änderungen machen sollte, die diese Verwendung verhindert. Außerdem kann man mit externen Tools die Attribute auslesen, was zB für die Dokumentation wichtig sein kann. Für die beiden Eigenschaften sieht unser Code dann so aus:

   [ComVisible(true)]
   public int Height
   {
     get { return m_bmpRepresentation.Height; }
   }
   
   [ComVisible(true)]
   public int Width
   {
     get { return m_bmpRepresentation.Height; }
   }  

Es ist kein großer Aufwand, dokumentiert aber die Absicht des Programmierers. Klarerweise kann man mit false als Argument zum Attribut eine Methode verstecken (zB falls diese für COM Applikationen keinen Sinn machen würde).

Kommen wir zum letzten Punkt, dem Dispose Pattern. Ein Bitmap Objekt ist teuer, weil es viele Resourcen verbraucht. Daher sollte man es nach Verwendung wieder freigeben, und hier setzt der Dispose Pattern an - er definiert, was man in seine Klasse einbauen muß, damit jeder das Objekt deterministisch zerstören kann (der Dispose Pattern wird auch gerne als DF, Deterministic Finalization, bezeichnet). Ich habe die einfachste Variante gewählt, indem ich von der Component Klasse abgeleitet habe, und deren Dispose Methode überlade:

 using System.ComponentModel; // für Component Basisklasse

 public class ImageInfo : Component
 {
   protected override void Dispose(bool disposing)
   {
    if (disposing) {
      // explizit aufgerufen, wir können das Bitmap weggeben
      if (null != m_bmpRepresentation) m_bmpRepresentation.Dispose();
      // Dispose könnte mehr als einmal aufgerufen werden, daher setzen
      // wir die zerstörte Referenz auf null
      m_bmpRepresentation = null;
    }
    base.Dispose(disposing);
   }
   
   [ComVisible(true)]
   public void Close()
   {
     Dispose();  // Close ruft nur Dispose auf
   }

Für einige Programmierer ist es ein "Problem", ein Objekt "wegzuwerfen". Für viele ist es natürlicher, ein Objekt zu schließen, was speziell für Dateien oder Datenbankverbindungen zutrifft. Deshalb kann man - man muß nicht - zusätzlich zu Dispose weitere synonyme Methoden anbieten (hier Close).

Damit sind die Änderungen beendet, wir müssen die Komponente nun kompilieren:

csc /out:ImageInfo.dll /target:library ImageInfo.cs AssemblyInfo.cs /r:System.Drawing.dll /r:System.dll

Dadurch erhalten wir die Assembly mit einem Strong Name, die wir nun für COM registrieren können.

CCW erstellen und registrieren

Die Schritte zum CCW sind bestechend einfach - prinzipiell ist es nur der Aufruf des regasm Tools:

F:\Inetpub\wwwroot\AspHeute\dotnetccw>regasm ImageInfo.dll /tlb:ImageInfo.tlb
Microsoft (R) .NET Framework Assembly Registration Utility 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001.  All rights reserved.

Types registered successfully
Assembly exported to 'F:\Inetpub\wwwroot\AspHeute\dotnetccw\ImageInfo.tlb', and
the type library was registered successfully

Allerdings kann man leicht in Probleme laufen, wenn man die Assembly dann nicht in den GAC installiert (Pfade). Deshalb sollte man noch

F:\Inetpub\wwwroot\AspHeute\dotnetccw>gacutil /i ImageInfo.dll

Microsoft (R) .NET Global Assembly Cache Utility.  Version 1.0.3705.0
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

Assembly successfully added to the cache

ausführen. Damit ist die Assembly von allen Applikationen aus verwendbar, und ein potentielles Problem ausgeräumt, das einem stundenlange Fehlersuche bereiten kann.

.NET Komponente verwenden

Als Beispiel habe ich die .NET Komponente im Windows Scripting Host verwendet (es soll ja so einfach wie möglich sein). Die ProgId einer .NET Komponente ist Namespacename.Classname, also in unserem Falle AspHeute.ImageInfo. Damit sieht der Sourcecode in demo.vbs so aus:

strImageFile = "myImage.jpg"
bLoadedOK = True
Set imgInfo = WScript.CreateObject("AspHeute.ImageInfo")

On Error Resume Next
imgInfo.Load strImageFile

If Err.Number <> 0 Then
  Wscript.Echo Err.Description
  bLoadedOK = False
End If

If (bLoadedOK) Then
  Wscript.Echo "Breite: " & imgInfo.Width & ", Höhe: " & imgInfo.Height & ", Bildformat: " & imgInfo.Format
End If

imgInfo.Close
Set imgInfo = Nothing

Dieser Code kann auch in VB6 oder Office Applikationen eingesetzt werden. Und schon hat man Funktionalität in seiner alten Applikation, von der man ohne externe Komponente nur träumen konnte.

Schlußbemerkung

Dieser Artikel soll als Kochrezept und Ermunterung dazu dienen, existierende Anwendungen mit in .NET geschriebenen Komponenten zu erweitern und zu verbessern. Der Nutzen steht einem sehr geringem Aufwand gegenüber.

This printed page brought to you by AlphaSierraPapa

Download des Codes

Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20020702.zip

Verwandte Artikel

Bildinformationen selbst ermitteln
http:/www.aspheute.com/artikel/20001130.htm
Eine kleine Bilddatenbank, Teil 1
http:/www.aspheute.com/artikel/20020703.htm
On-the-fly Generierung von Graphiken
http:/www.aspheute.com/artikel/20000728.htm
Scrapen von Webseiten
http:/www.aspheute.com/artikel/20000824.htm
Thumbnailgenerierung in .NET
http:/www.aspheute.com/artikel/20020225.htm
Verwenden von COM Komponenten in ASP.NET
http:/www.aspheute.com/artikel/20000828.htm
WHOIS Abfragen a la .NET
http:/www.aspheute.com/artikel/20000825.htm

 

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.