myCSharp.de - DIE C# und .NET Community (https://www.mycsharp.de/wbb2/index.php)
- Knowledge Base (https://www.mycsharp.de/wbb2/board.php?boardid=68)
-- Artikel (https://www.mycsharp.de/wbb2/board.php?boardid=69)
--- [Artikel] Resourcen in .NET Assemblies (https://www.mycsharp.de/wbb2/thread.php?threadid=32819)


Geschrieben von egrath am 26.02.2007 um 10:43:
  [Artikel] Resourcen in .NET Assemblies
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:Grundsätzlich gibt es zwei verschiedene Typen von Resourcen in Assemblies: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:Beginnen wir mit dem Tool ResGen. Als Parameter nimmt dieses Tool verschiedene Dateien entgegeben, welche die notwendigen Informationen enthalten. Dies sind: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.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: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: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: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;
        }
        }
}


© Copyright 2003-2020 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 30.03.2020 11:59