Untersuchungen einiger Fehler in .NET

Please note: This article is only available in German.
Behandelt die Lösung einiger Bugs in .NET wie die OutOfMemory-Exception in ASP.NET, die Excel-API Lokalisierungsaffinität und die Verwendung der Konsolenausgabe in einer WinForms Anwendung

Aufgrund meiner erhöhten Programmieraktivität in der letzten Zeit bin ich auch auf einige Fehler gestoßen, welche nicht-intuitiv sind und sich als relativ schwer zu umgehen herausstellen. In diesem Artikel möchte ich auf einige dieser Probleme eingehen und mögliche Lösungsmethoden erläutern.

OutOfMemory-Exception in ASP.NET mit GDI+

Beim Verwenden einer Grafik mit Transperenz in ASP.NET stieß ich auf eine OutOfMemory-Exception die wirklich gar nichts mit dem Speicher zu tun hat. Der Fehler liegt im Prinzip daran, dass des ASP Modul im IIS die Grafik in einen falschen Modus verwendet, d.h. mit geringerer Farbtiefe als eigentlich vorhanden.

Woher der Fehler kommt ist mir unbekannt, aber ich weiß durch den Blogeintrag, welcher unten angegeben ist, dass man den Fehler durch Erstellen eines Vorschaubildes über die Funktion GetThumbnailImage() von PixelFormat.Format8bppIndexed auf PixelFormat.Format32bppArgb geändert wird. Der folgende Codeabschnitt zeigt den Workaround anhand eines Beispiels.

System.Drawing.Imaging.ImageAttributes ia = new System.Drawing.Imaging.ImageAttributes();
/* lade Bitmap aus MemoryStream */
using(...)
{
	Bitmap logo = (Bitmap)Bitmap.FromStream(msLoadTnLogo);
	logo = (Bitmap)logo.GetThumbnailImage(logo.Width, logo.Height, null, IntPtr.Zero);
	/* schließe MemoryStream */
	msLoadTnLogo.Close();
}
/* lege Transparenz fest */
Color alpha = logo.GetPixel(0, 0);
ia.SetColorKey(alpha, alpha);
/* zeichne Bild mit angegebener Transparenz */
g.DrawImage(logo, new Rectangle(x, y, width, height), 0, 0, width, height, GraphicsUnit.Pixel, ia);

Damit kann man diesen nervigen Fehler mehr oder weniger leicht umgehen.

Fehler in Excel API - ungültige Typenbibliothek

Das Automatisieren der Office Produkte von Microsoft geht dank .NET sehr leicht von der Hand. Für den professionellen Einsatz gibt es leider häufig zu viele Einschränkungen und Probleme, so dass sich eine Lösung die auf den Einsatz der API verzichtet immer besser eignet. Von allen Microsoft Produkten ist Word noch am Besten zu steuern. Beim Automatisieren von Excel stieß ich auf folgenden Fehler: Old format or invalid type library.

Durch eine kleine Internetrecherche kam ich auf die Microsoft Seite, auf welcher ich feststellen musste, dass der Fehler anscheinend aufgrund der unterschiedlichen Lokalisierung von Excel im Vergleich zum Betriebssystem bzw. des aufrufenden Programmes verursacht wird. Von der Microsoft-Seite: Dieser Fehler tritt also beim Aufruf einer Excel-Methode auf, wenn die folgenden Bedingungen zutreffen:

  • Die Methode erfordert einen Gebietsschemabezeichner.
  • Sie arbeiten mit einer englischen Version von Excel.
  • Die regionalen Einstellungen für den Computer sind jedoch für eine andere Sprache konfiguriert.
Wenn auf dem Clientcomputer die englische Version von Excel ausgeführt wird und das Gebietsschema für den aktuellen Benutzer für eine andere Sprache konfiguriert ist, versucht Excel, das Sprachpaket für die konfigurierte Sprache zu finden. Wenn das Sprachpaket nicht gefunden wird, wird der Fehler gemeldet.

Microsoft selber ist sich also des Problems bewusst und gibt einige Workaround Anweisungen. Ein offensichtlicher Workaround besteht darin, dass entsprechende Sprachpacket zu installieren, damit dieses beim Programmstart gefunden wird. Das Multilingual User Interface Pack für die Office-Version zu installieren mag für einige Benutzer die (bequemste) Lösung sein - für die meisten wird diese Lösung jedoch inakzeptabel sein.

Ein weiterer Workaround ist das Ausführen der Excel-Methoden oder -Eigenschaften mit InvokeMember(). Damit kann man den CultureInfo für den Aufruf angeben. Der folgende Code zeigt z. B., wie man die Methode Add() des Objekts Workbooks mit en-US als CultureInfo aufrufen kann.

Excel.Application oApp = new Excel.Application();
oApp.Visible = true;
oApp.UserControl = true;
object oBooks oApp.Workbooks;
System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo("en-US");
oBooks.GetType().InvokeMember("Add", Reflection.BindingFlags.InvokeMethod, null, oBooks, null, ci);

Folgt man diesem Weg, so kann man dieses Problem umgehen. Man sollte sich bewusst sein, dass Excel sehr stark lokalisierungsabhängig ist - so sind Formeln und Eingabezeichen (wie Komma-oder Punkttrennung) in der englischen Sprachversion anders als in der deutschen Version.

Konsolenausgabe in einer WinForms Anwendung

Auch bei WinForms Anwendungen möchte man manchmal eine Konsolenschnittstelle implementiert haben. Ein gutes Beispiel hierfür ist die Verwendung von Kommandozeilenparametern, welche den Startvorgang der Anwendung beeinflussen (quasi nur einen bestimmten Programmteil starten). Um dem Benutzer eine Auflistung der möglichen Kommandozeilenparameter zu geben ist möchte man bei Eingabe des Parameter -?, -help oder dergleichen eine Auflistung der Kommandozeilenparameter (inkl. Hilfe/Erklärung) in der Kommandozeile ausgeben.

Versucht man mit Console.WriteLine(...) zu arbeiten zu stellt man schnell fest, dass dies in einer WinForms Anwendung nichts bewirkt. Der Grund liegt darin, dass das Konsolenfenster (cmd.exe) zu einem anderen Prozess gehört als die eigene WinForms Anwendung. Was man also machen muss um dieses Problem zu umgehen ist im Prinzip nichts anderes als sich das Handle der Konsole zu holen und dann in dieses Handle zu schreiben.

Microsoft hat für dieses Problem eine Win32-API erstellt, mit der es sehr leicht möglich ist mit der Konsole zu agierien d.h. sich an diese zu hängen und Ausgaben direkt an diese weiterzuleiten, sowie Eingaben zu empfangen. Das einzige Problem mit diesem Verfahren ist, dass es sich hierbei um eine getrennte Konsole handelt, d.h. von unserem WinForms Programm generierter Output wird immer in der Konsole erscheinen und kann nicht per Kommandozeilenargument in eine Textdatei umgeleitet werden. Der folgende Code zeigt den prinzipiellen Ablauf:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MyWinFormsApp
{
    static class Program
    {
        [DllImport("kernel32.dll")]
        static extern bool AttachConsole(int dwProcessId);
        private const int ATTACH_PARENT_PROCESS = -1;

        [STAThread]
        static void Main(string[] args)
        {
            /* Weiterleitung am Anfang festlegen */
            AttachConsole(ATTACH_PARENT_PROCESS);

            /* Beispielausgabe */
            int argCount = args == null ? 0 : args.Length;
            Console.WriteLine("
Es wurden {0} Argumente geg.:", argCount);
			
            for (int i = 0; i < argCount; i++)
                Console.WriteLine(args[i]);

            /* Die eigentliche WinForms Anwendung starten */
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
Created . Last updated .

References

Sharing is caring!