380 likes | 557 Vues
Migration und Nutzung von vorhandenem Code Interoperability. Migration und Nutzung von vorhandenem Code Interoperability. Bernd Marquardt Software & Consulting berndm@go-sky.de. Agenda. Das Problem des „alten“ Codes Einführung Interop-Möglichkeiten DLL‘s COM ActiveX Wrapper-Klassen
E N D
Migration und Nutzung von vorhandenem CodeInteroperability Bernd Marquardt Software & Consulting berndm@go-sky.de
Agenda • Das Problem des „alten“ Codes • Einführung • Interop-Möglichkeiten • DLL‘s • COM • ActiveX • Wrapper-Klassen • Unmanaged Code ruft managed Code auf • Migration • Zusammenfassung
Einführung • Grundsätzlich gibt es im Moment in der Windows-Welt zwei Arten von Code: • Unmanaged Code (Die „alte“ Welt) • „Alter“ x86-Maschinencode • Wird sofort ausgeführt • Managed Code (Die .NET-Welt) • „Neuer“, maschinenunabhängiger IL-Code • Wird zur Laufzeit in Maschinencode übersetzt und ausgeführt
Einführung • In beiden Welten gibt es unterschiedliche „Codesorten“: • Unmanaged: Clients (EXE), DLL‘s mit API‘s, COM-Komponenten (als DLL und als EXE), ActiveX-Komponenten, „normale“ Klassenbibliotheken • Managed: Clients (EXE), .NET-Komponenten
Einführung • In den Code der „alten“ Welt wurde sehr viel Geld investiert • Es ist unmöglich, in kurzen Zeit alles wieder neu für .NET zu programmieren • Welche Möglichkeiten gibt es in .NET, mit „altem“ Code weiterzuarbeiten?
Einführung • Was macht die gemeinsame Nutzung von .NET und unman. Code schwer? • Monolithischer Code („eine EXE“) • Was macht die gemeinsame Nutzung von .NET und unman. Code leicht? • Aufteilung des gesamten Projektes in kleine Einheiten (Client, DLL‘s, COM- und ActiveX-Komponenten)
DLL (API) aus man. Code • Unmanaged DLL‘s mit Attribut „DllImport“ [DllImport („Kernel32.Dll“)] public extern static void Sleep(uint uiMSec); [DllImport („Kernel32.Dll“, EntryPoint = „Sleep“)] public extern static void Schlaf(uint uiMSec); [DllImport („User32.Dll“, CharSet = CharSet::Ansi)] public extern static void MessageBox(int hWnd, string strMsg, string strTitel, uint uiFlags);
Beispiel für Standard-Marshaller • Methode in einer Standard-DLL • Einfache Deklaration erfordert u.U. spezielle Dekodierung • “Maßgeschneiderte” Versionen möglich int SomeFunc(int sel, void* buf, int size); [DllImport("some.dll")]static extern int SomeFunc(int sel, [MarshalAs(UnmanagedType.LPArray)] byte[] buf, int size); [DllImport("some.dll", EntryPoint="SomeFunc")]static extern int SomeFunc_String(int sel, StringBuilder sb, int size); [DllImport("some.dll", EntryPoint="SomeFunc")]static extern int SomeFunc_Int(int sel, ref int i, int size);
Array als Referenzparameter // Originaldeklaration der Library-Funktion// int f(int** ppArray, int* pSize);[DllImport("PInvokeLib.dll")]public static extern int f(ref IntPtr array, ref int size);// Unmanaged Speicherbereich anlegenIntPtr buffer = Marshal.AllocCoTaskMem( Marshal.SizeOf(size) * array2.Length);// Daten in Speicherbereich kopierenMarshal.Copy(array2, 0, buffer, array2.Length); int res = f(ref buffer, ref size); int[] arrayRes = new int[size]; // Daten zurückkopierenMarshal.Copy(buffer, arrayRes, 0, size);// Unmanaged Speicherbereich freigebenMarshal.FreeCoTaskMem(buffer);
DLL (API) aus man. Code • System-DLL‘s und eigene DLL‘s können aufgerufen werden • Typ-Konvertierungen der Standardtypen werden automatisch gemacht • Aufruf ist etwas langsamer, als aus unmanaged Code • Interfaces als Parameter werden unterstützt • Können nicht als Ausgabeparameter dienen • Arrays von Basistypen können benutzt werden • Attribute steuern Konvertierung
DllImport – wann nicht? • Beispiel: char* DoSomething(...) [DllImport („TheSuperLib“)] extern „C“ String* DoSomething(...); • Problem hier: • Der Speicher, auf den der Zeiger zeigt, kann nicht freigegeben werden
DLL (API) aus man. Code • Zweite Methode: IJW = „It just works“ • Geht nur mit managed C++ Extensions • Benötigte C/C++-Header-Datei muss eingefügt werden • Z.B.: #include <stdio.h> • Aufruf der API-Funktionen ist jetzt möglich
DLL (API) aus man. Code • Vorteile von „IJW“: • Kein „DllImport“ erforderlich • Geringfügig schneller • Bei Aufruf mehrerer unmanaged API‘s: Die Daten müssen nur einmal „gemarshalled“ werden
COM aus man. Code • Erzeugung eines Runtime-Callable-Wrappers (RCW) durch .NET Unmanaged Code Managed Code Runtime Callable Wrapper COM-Objekt Managed Client
COM aus man. Code • Wrapper von Hand erzeugen mit: • TblImp.EXE • Wrapper automatisch erzeugen mit: • Visual Studio .NET • Befehl „Add Reference“ • Ist in vielen Fällen ausreichend • Datenkonvertierung findet statt • Z.B.: String nach BSTR • Geht nicht bei Custom Marshaling
COM aus man. Code • Problem bei der Benutzung der COM-Komponenten: • Durch den Garbage Collector von .NET wird die COM-Komponente nicht „sofort“ freigegeben • Das kann zu unerwartetem Verhalten führen • Lösungen: • Aufruf von System.GC.Collect (nicht gut!) • Aufruf von System.Runtime.InteropServices.Marshal. ReleaseComObject
Rückgabewerte und Exceptions • COM meldet Fehler via Result-Code zurück • .NET-Methoden werfen Exceptions • Runtime setzt Konzepte ineinander um • HRESULT gibt Fehlerklasse an • Zusätzliche Informationen werden via IErrorInfo erhalten • Ein COM-Objekt sollte dieses Interface implementieren
Early und Late Binding • Early Binding: Objekterzeugung mit „new“ • Late Binding: (IDispatch) • Benutzung von Type.GetTypeFromProgID, Activator.CreateInstance und Type.InvokeMember • Parameterübergabe in einem Array
Aufruf eines COM-Servers (late) namespace LateBoundClient { using System.Reflection;...Type typ;Object obj;Object[] prms = new Object[2];int r; typ = Type.GetTypeFromProgID(„MyLib.MyServer"); obj = Activator.CreateInstance(typ);prms[0] = 10; prms[1] = 150; r = (int)typ.InvokeMember(„aMethod", BindingFlags.InvokeMethod, null, obj, prms); ... }
Wrapper-Klassen • Managed Wrapper-Klassen umhüllen eine normale unmanaged C++-Klasse • Die Hüllklasse funktioniert wie ein Proxy • Die Hüllklasse hat die gleiche Funktionalität wie die C++-Klasse • Es wird immer ein „Klassenpaar“ erzeugt, bzw. zerstört
Wrapper-Klassen __gc class ManClass { public: // Konstruktor ManClass() { m_pC = new CppClass(); } // Freigabe ~ManClass() { delete m_pC; } // Methoden void managed_f() { m_pC->native_f(); } private: CppClass * m_pC; }; class CppClass { public: // Konstruktor CppClass() { …} // Destruktor ~CppClass() { …} // Methoden void native_f() { …} };
Wrapper-Klassen • Erzeugung von Wrapper-Klassen • Erzeugung einer man. C++-Klasse • Data member: Zeiger vom Typ der C++-Klasse • In der managed Klasse müssen die Konstruktoren nachgebildet werden • In der Dispose-Methode der managed Klasse die Instanz der unmanaged Klasse zerstören • Alle Methoden in der managed Klasse implementieren
Wrapper-Klassen • Destruktoren • Sowohl den Destruktor als auch die Dispose-Methode implementieren • Wrapper-Klasse von IDisposable ableiten • „Selbst-gesteuertes“ Zerstören des Objektes durch Dispose-Aufruf • Dispose vergessen: Garbage Collector schlägt mit dem Destruktor zu • System.GC.SupressFinalize in der Dispose-Methode nicht vergessen
Probleme mit Wrappern • Variable Parameteranzahl • Daten-Marshaling • Default Argumente • Destruktoren (Garbage Collector) • Properties • Vergleichsoperatoren mit Overloading
.NET-Code aus unman. Code • Man kann .NET-Komponenten aus altem Code aufrufen • CCW = COM Callable Wrapper • using System.Runtime.InteropServices; • Attribut [ClassInterface(ClassInterfaceType.AutoDual)] vor der Klasse definieren • Assembly in das Verzeichnis des Clients kopieren • TypeLib mit TLBEXP erzeugen • Registrieren mit REGASM
Migration: Grundsätzliche Überlegungen • Es gibt verschiedene Arten von Code • Algorithmen • API-intensiver Code • MFC-intensiver Code (UI-Code) • ATL-intensiver Code (COM/DCOM) • Alle Codearten erfordern einen anderen Migrationsaufwand
Grundsätzliche Überlegungen • Was erschwert eine Migration? • Monolithischer Code • Die Verwendung von Klassen-Bibliotheken • Besonders dann, wenn die Klassen als Code in das Projekt aufgenommen wurden Später: Wrapper-Klassen • Viel UI-Code • Templates (Generische Datentypen) • Multiple Inheritance
Grundsätzliche Überlegungen • Was erschwert eine Migration? • Klassen, welche im Destruktor die Resourcenverwaltung implementiert haben (Thema: Garbage Colletion) • Code, der intensiv mit Zeigern arbeitet • Code mit vielen API-Aufrufen
Grundsätzliche Überlegungen • Was erleichtert die Migration? • Komponenten-orientierter Code • Mehrschichtiges Applikations-Design • Wichtig ist eine gute Trennung von UI und Business-Logik • Code in COM-Komponenten ist nützlich • Code in DLL‘s ist nützlich
Grundsätzliche Überlegungen • Eine Migration sollte nach Möglichkeit in mehreren kleinen Schritten durchgeführt werden • Was kann zunächst ohne Migration wiederverwendet werden? • COM-Komponenten • Windows-DLL‘s • Der erste Schritt soll kurz sein! • COM-Komponenten und DLL‘s sind aber auch am leichtesten zu portieren
Grundsätzliche Überlegungen • Komplexe Codeteile zunächst einmal als unmanaged Code übernehmen • „Unsaubere“ Codeteile in managed Code portieren • Resourcen-intensive in managed Code portieren (Ausnutzung des Garbage Collectors)
Grundsätzliche Überlegungen • Vor der Migration: • Test- Szenarien mit der „alten“ Applikation generieren • Während der Migration kann immer wieder vergleichend getestet werden • Migrieren Sie keine Projekte, die im Moment in der Entwicklungsphase sind
Zusammenfassung • Microsoft hat viel für die Interoperabilität von “altem” und .NET-Code getan • Die Investitionen werden weitgehend geschützt • Eine Applikation kann Schritt für Schritt migriert werden • Wichtig: Komponenten müssen vorhanden sein
Further Information • Microsoft Visual C++ .NET Compiler Migration Guidehttp://www.gotdotnet.com/team/cplusplus/articles/compmig.doc • Managed Extensions for C++ Migration Guidehttp://www.gotdotnet.com/team/cplusplus/articles/mcmigrate.doc
Glossary • Managed Code: Code, der unter der Kontrolle der .NET-Common Language Runtime läuft. • Unmanaged Code: Prozessor-abhängiger Maschinencode. • CLR: Common Language Runtime; gemeinsame Runtime für alle .NET-Sprachen. • GC: Garbage Collector; verwaltet den Speicher bei .NET-Anwendungen; zerstört bei Bedarf die nicht mehr referenzierten Objekte im Speicher. • (MS-) IL-Code: Microsoft Intermediate Language; Platform-unabhängiger Zwischencode, der von den .NET-Sprach-Compilern erzeugt wird; wird vom JIT-Compiler zur Laufzeit in nativen Maschinencode übersetzt. • JIT-Compiler: Just In Time-Compiler; übersetzt zur Laufzeit den MS-IL-Code in native Maschinensprache. • ILDASM: Tool zum Anschauen des MS-IL-Codes in einer .NET-EXE-Datei. • RCW: Runtime Callable Wrapper; Wrapper-Klasse, die den Aufruf von „unmanaged“ COM-Komponenten aus „managed“ Code ermöglicht