myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » Artikel » [Artikel] Resourcen in .NET Assemblies
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[Artikel] Resourcen in .NET Assemblies

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
egrath egrath ist männlich
myCSharp.de-Mitglied

avatar-2119.jpg


Dabei seit: 24.07.2005
Beiträge: 871
Entwicklungsumgebung: MonoDevelop, NetBeans, Vi
Herkunft: Österreich / Steyr


egrath ist offline

[Artikel] Resourcen in .NET Assemblies

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Verwendung von Resourcen in .NET Assemblies



Vorwort

In diesem Artikel werden wir uns die verwendung von Resourcen unter .NETansehen. Viele benutzen diese schon seit langem indem einfach im Visual Studio die entsprechende Datei eingefügt und "benutzt" wird. Dass hinter all dem aber viel IDE-Magic steckt und im hintergrund schon eine ganze Menge Code erforderlich sein kann damit das so schön funktioniert wird man sehen wenn man diesen Artikel fertig gelesen hat.

Überblick

Resourcen sind in Assemblies (genauer gesagt in den Metadaten) eingebettete Dateien welche zur Laufzeit gelesen und verarbeitet werden können. Diese Daten können für verschiedene Zwecke genutzt werden - dies sind zum beispiel:
  • Lokalisierung der in der Applikation benutzten, textuellen Informationen (beispielsweise Benutzer Texte)
  • Grafiken und andere Daten für die Applikation.
Grundsätzlich gibt es zwei verschiedene Typen von Resourcen in Assemblies:
  • Direkt eingebundene
    Eine Datei wird direkt in die Assembly gelinkt und kann benutzt werden.
  • Gesammelt in einer ".resources" Datei
    Der Zugriff erfolgt über eine spezielle Klasse welche sich auch um die Lokalisierung kümmert.
Grafisch gesehen sieht dies also so aus:


Abb. 1: Wie können Resourcen in Assemblys gespeichert sein?

Beiden Arten von Resourcen können beim kompilieren der Applikation angegeben werden. Dies geschieht mit dem Paramter "/resource", welcher als Parameter die entsprechende Datei entgegebennimmt.

Das erstellen von Resource Dateien

Bei direkt eingebundenen Dateien stellt sich die Frage der erstellung per Definition nicht, da diese üblicherweise mit einer externen Applikation wie Bild- oder Tonverarbeitung erstellt werden. Die ".resources" Dateien hingegen können mit verschiedenen, .NET eigenen Bordmitteln erstellt werden:
  • Das Tool "resgen"
  • Die Klasse "ResourceWriter" des Frameworks
Beginnen wir mit dem Tool ResGen. Als Parameter nimmt dieses Tool verschiedene Dateien entgegeben, welche die notwendigen Informationen enthalten. Dies sind:
  • txt
    Jede Zeile der Textdatei repräsentiert ein Key/Value Pair welche durch das '=' Zeichen getrennt sind. Das kann zum beispiel dafür benutzt werden um lokalisierungs-spezifische Texte als Resource aufzunehmen.
  • resx
    Eine XML Darstellung von Resourcen. Mit diesem Format können neben Textuellen Daten auch Binärdaten (wie bsp. Image) als Resource gespeichert werden. Leider ist im .NET SDK kein Tool enthalten mit dem ResX Dateien direkt editiert werden können. Zum Glück ist die Freeware-Szene hier in die Presche gesprungen und Lutz Roeder hat mit dem "Resourcer" ein entsprechendes Tool zur verfügung gestellt.
Zur Verdeutlichung erstellen wir uns die beiden folgenden Dateien:

AppStrings.en-US.txt:

C#-Code:
strWelcome = Welcome to our Application
strEnter = Enter your Name

AppStrings.de-DE.txt:

C#-Code:
strWelcome = Willkommen in der Applikation
strEnter = Bitte geben Sie Ihren Namen ein

Diese beiden Dateien werden nun mittels ResGen in die entsprechenden "resources" Dateien übersetzt ("resgen AppStrings.de-DE.txt" und "resgen AppStrings.en-US.txt"). Als ergebnis erhalten wir nun die beiden Dateien "AppStrings.de-DE.resources" und "AppStrings.en-US.resources". Diese könnten wir nun schon entweder direkt in unsere Applikation einbetten oder aber auch eine neue DLL erstellen welche ausschliesslich diese Resourcen enthält. Gehen wir aber einen Schritt weiter. Wir wollen jetzt noch zusätzlich zwei Grafiken in die Resources Datei einfügen. Dazu bemühen wir den Resourcer und laden das entsprechende .resources File:


Abb. 2: Der Resourcer mit dem geladenen AppStrings.de-DE.resources File

Um nun auch noch andere Daten einzufügen reicht es im Menü "Edit" den Punk "Insert File" (respektive "Insert Text" für String Resourcen) auszuwählen und die entsprechende Datei einzufügen. Wird der Typ der Datei nativ vom Framework unterstützt, so wird dieser in Serialisierter Form des entsprechenden Objekts gespeichert, anderenfalls als Byte-Array. Nachdem zum beispiel ein Bild und eine unbekannte Datei eingefügt worden sind, sieht dies so aus:


Abb. 3: Eine .resources Datei mit einigen eingefügten Daten.

Es muss erwähnt werden, dass man sich das Tool ResGen und die ResX Dateien komplett ersparen kann, da der Resourcer in der Lage ist Resources Dateien komplett neu zu erstellen und diese in einem Format beliebiger Wahl speichern kann.
  • Lokalisierbare Resourcen
    Kommen in eigene DLL's, da dies die übersichtlichkeit und einfache Lokalisierung durch Fremdanbieter ermöglicht (Es muss nur die entsprechende DLL weitergegeben werden). Der Grund hierfür ist, dass zusätzliche Sprachen nachträglich einfach eingefügt werden können, ohne dass man die Hauptapplikation neu verteilen muss.
  • Nicht-lokalisierbare Resourcen
    Sollten in die eigentliche Hauptapplikation eingebettet werden. Darunter fallen z.b. Grafiken welche innerhalb der Applikation benutzt werden oder aber auch textuelle Informationen die nichts mit Benuzter-Interaktion zu tun haben (SQL Statements). Es spricht natürlich nichts dagegen wenn man sich auch für diese Resourcen eine eigene DLL erstellt - ist meiner persönlichen Meinung nach nur eine reine Geschmackssache.
Um eine Resourcen-DLL zu erstellen (die ja nur Resourcen und keinen Programmcode enthält) reicht im Endeffekt der folgende Kompileraufruf:

C#-Code:
csc /target:library /out:resources.dll /resource:AppStrings.en-US.resources /resource:AppStrings.de-DE.resources /resource:Images.resources

Sieht man sich nun diese DLL im ILDasm an, so erkennt man deutlich dass die Resourcen wie oben bereits erwähnt im Manifest gespeichert wurden:


Abb. 4: Resourcen in einer DLL, gespeichert im Manifest

Bei der Einbindung in die Hauptapplikation ist es lediglich notwendig über den Kompilerparameter "/resource:<Dateiname>" anzugeben, welches Resources File in das Manifest aufgenommen werden soll.

Benutzung von Resourcen

Resourcen können wie oben erwähnt mittels direkten Zugriff auf den Stream der Datei, oder aber über den ResourceManager angesprochen werden. Zuerst sehen wir uns an, wie dies mittels des Resource Managers funktioniert - da dieser Lokalisierungseigenschaften bietet ist dieser die Primäre Wahl beim erstellen von lokalisierten Applikationen:

C#-Code:
ResourceManager locRm = new ResourceManager( "AppStrings", Assembly.LoadFile( "resources.dll" ));
Console.Out.WriteLine( "strHello = {0}", locRm.GetString( "strHello", new CultureInfo( locale )));

Im Endeffekt beschränkt sich das ganze darauf, dass wir ein Objekt vom Typ ResourceManager instanziieren dem wir als Konstruktor-Parameter folgendes übergeben:
  • Den Basisnamen der eingebetteten Resources Datei. Da bei uns die Resources Dateien für die unterschiedlichen Sprachen alle vom Format "AppStrings.<LangCode>.resources" sind, ist dieser Basisname "AppStrings"
  • Die Assembly in welcher sich die Resourcen befinden
Anschliessend lassen wir uns vom ResourceManager immer jene Strings zurückgeben, welche wir mittels des Parameters "CultureInfo" anfordern. Der Parameter "locale" ist vom Typ "String" und hat als inhalt die entsprechende Lokalität (bsp. en-US oder de-DE ins unserem Fall). Eine komplette Liste der Locales kann im MSDN unter  diesem Link nachgeschlagen werden.

Neben der möglichkeit sich Strings aus der Resource zu holen, kann der ResourceManager uns natürlich auch Streams oder andere Objekte zurückgeben. Die entsprechenden Methoden heissen "GetStream" und "GetObject" und haben die gleichen Eingangsparamter wie "GetString".

Kommen wir nun zum lesen einer Resource direkt als Stream. Diese Methode ist nicht so gebräuchlich, wird allerdings trotzdem unterstützt und hat für bestimmte Einsatzzwecke auf jeden Fall Ihre Berechtigung. So ist es sicherlich sinnvoller bei einer relativ grossen Datei welche eingebettet werden soll dies nicht über den ResourceManager zu machen sondern direkt über Streams - der Vorteil liegt in Bezug auf Performance da der ResourceManager einen gewissen Overhead erzeugt.

Ein Assembly Objekt besitzt zwei für uns interessante Methoden:
  • Stream GetManifestResourceStream( string name )
    Liefert einen Stream zurück, mit welchem die Resource gelesen werden kann
  • string[] GetManifestResourceNames()
    Liefert ein String Array zurück, in dem alle Namen aller eingebetteten Top-Level Resourcen vorliegen
IDE-Magic


Abb. 5: Warum kann in VS so einfach auf den im Resource File "AppResources" enthaltenen String "testString" zugegriffen werden?

Wie oben bereits erwähnt ist es mit Visual Studio relativ leicht, eigene Resourcen in die Applikation einzubinden und auf diese auch noch relativ unproblematisch zuzugreifen. Wenn man im VS ein neues Resource File (ResX) erstellt, so geschieht folgendes:
  • Das ResX File wird erstellt und VS bietet einen grafischen Dialog zum bearbeiten von darin enthaltenen Resourcen (wird beim Kompilieren in ein Resources File umgewandelt und gelinkt)
  • Dem Projekt wird ein neues Quellcode-File hinzugefügt (als Child des ResX Files) welches die notwendige Programmlogik enthält um auf die Elemente dieser Resource direkt zugreifen zu können.
Sehen wir uns einen kleinen ausschnitt aus dem von VS erstellen File an:

C#-Code:
/// <summary>
///   Looks up a localized string similar to Hello World.
/// </summary>
internal static string testString {
    get {
        return ResourceManager.GetString("testString", resourceCulture);
    }
}

Wie man erkennen kann macht VS im Endeffekt auch nichts anderes als wir oben und gibt mittels des ResourceManagers die angeforderten Daten an den Aufrufer zurück. Damit ist auch dieses Mysterium von IDE-Magic aufgelöst.

Quellcode

C#-Code:
using System;
using System.Reflection;
using System.IO;
using System.Collections;
using System.Resources;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.Serialization.Formatters.Binary;

namespace egrath.test
{
        public class Program
        {
                public static void Main( string[] args )
                {
                        Assembly al = Assembly.GetExecutingAssembly();

                        if( args.Length > 0 )
                        {
                                al = Assembly.LoadFile( args[0] );
                        }

                        ResourceDumper dumper = new ResourceDumper( al );
                        Console.Out.WriteLine( "Dumped {0} files", dumper.DumpResources( String.Empty ));
                }
        }

        internal class ResourceDumper
        {
                private Assembly m_DumpAssembly;

                public ResourceDumper( Assembly al )
                {
                        m_DumpAssembly = al;
                }

                public int DumpResources( string filter )
                {
                        int dumped = 0;

            string outputDir = "dumped";
            if( ! Directory.Exists( outputDir )) Directory.CreateDirectory( outputDir );

                        string[] resourceNames = m_DumpAssembly.GetManifestResourceNames();
                        foreach( string resource in resourceNames )
                        {
                                if( resource.EndsWith( ".resources" )) // It's a embedded Resource file which itself can contain resources
                                {
                                        Console.Out.WriteLine( "examining embedded resource: [{0}]", resource );
                                        ResourceReader resReader = new ResourceReader( m_DumpAssembly.GetManifestResourceStream( resource ));
                                        IDictionaryEnumerator resEnum = resReader.GetEnumerator();
                                        while( resEnum.MoveNext() )
                                        {
                                                string resourceType = String.Empty;
                                                byte[] resourceData;

                                                resReader.GetResourceData(( string ) resEnum.Key, out resourceType, out resourceData );
                        resourceType = resourceType.Split( ',' )[0];
                                                Console.Out.WriteLine( "    dumping resource: name={0}, type={1}", resEnum.Key, resourceType );

                        byte[] fileData = ConvertToFile( resourceType, resourceData );

                        BinaryWriter writer = new BinaryWriter( new StreamWriter( String.Format( "{0}/{1}.{2}", outputDir, resEnum.Key, GetExtensionForType( resourceType ))).BaseStream );
                        writer.Write( fileData );
                        writer.BaseStream.Close();
                        writer.Close();

                        dumped ++;
                                        }
                                }
                                else
                                {
                                        Console.Out.WriteLine( "Dumping embedded Resource: [{0}]", resource );
                    BinaryWriter writer = new BinaryWriter( new StreamWriter( String.Format( "{0}/{1}", outputDir, resource )).BaseStream );

                    Stream resourceStream = m_DumpAssembly.GetManifestResourceStream( resource );
                    int byteCode = -1;
                    while(( byteCode = resourceStream.ReadByte() ) != -1 )
                    {
                        writer.Write(( byte ) byteCode );
                    }
                    writer.BaseStream.Close();
                    writer.Close();

                    dumped ++;
                                }
                        }

                        return dumped;
                }

        private string GetExtensionForType( string type )
        {
            switch( type )
            {
                case "System.Drawing.Bitmap": return "bmp";
                case "System.Drawing.Icon": return "ico";
                case "ResourceTypeCode.String": return "txt";
                default: return "unknown";
            }
        }

        private byte[] ConvertToFile( string resourceType, byte[] resourceData )
        {
            byte[] fileData;
            BinaryFormatter formatter = null;

            switch( resourceType )
            {
                case "System.Drawing.Bitmap":
                    formatter = new BinaryFormatter();
                    Image img = ( Image ) formatter.Deserialize( new MemoryStream( resourceData ));
                    MemoryStream imageStream = new MemoryStream();
                    img.Save( imageStream, ImageFormat.Bmp );
                    fileData = imageStream.ToArray();
                    break;

                case "System.Drawing.Icon":
                    formatter = new BinaryFormatter();
                    Icon ico = ( Icon ) formatter.Deserialize( new MemoryStream( resourceData ));
                    MemoryStream iconStream = new MemoryStream();
                    ico.Save( iconStream );
                    fileData = iconStream.ToArray();
                    break;

                default:
                    fileData = new byte[resourceData.Length];
                    Array.Copy( resourceData, fileData, resourceData.Length );
                    break;
            }

            return fileData;
        }
        }
}
26.02.2007 10:43 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 12 Jahre.
Der letzte Beitrag ist älter als 12 Jahre.
Antwort erstellen


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 22.11.2019 22:12