Laden...

FAQ

[FAQ] Programm läuft in anderer Umgebung nicht (richtig)

Letzter Beitrag vor 12 Jahren 2 Posts 21.722 Views
[FAQ] Programm läuft in anderer Umgebung nicht (richtig)

Damit eine .NET-Applikation auf einem anderen Rechner oder auch nur in einer anderen Umgebung läuft, sind einige Voraussetzungen notwendig. Diese sind im folgenden logisch aufeinander aufbauend angegeben. Dadurch stehen die häufigen Fehlerursache allerdings nicht zwangsläufig am Anfang. Lest also auf jeden Fall alles bis zum Ende.

Zugegeben, in dem Beitrag werden sehr viele Ursachen genannt, aber das liegt nur daran, dass es so viele mögliche Ursachen gibt. Diese sorgfältig zu überprüfen, könnt ihr nur selber leisten. Man sollte alle genannten Punkte gründlich studieren und keinen davon leichtfertig oder vorschnell verwerfen.

1. Framework

Zur Ausführung benötigt eine .NET Applikation eine Runtime, das sogenannte .NET-Framework. Dies gibt es bei Microsoft zum Download oder im Verzeichnis des Visual Studio:

Framework 2.0: dotnetfx.exe (23 MB)
Framework 3.5: dotnetfx35.exe (243 MB)
Framework 4.0: dotNetFx40_Full_x86_x64.exe (48 MB)
(ohne Anspruch auf Vollständigkeit)

Das Framework, das man im Projekt verwendet, stellt man unter "Projekt"/"Eigenschaften"/"Ziel-Framework" ein.

Das ab .NET 3.5 vorhandene Client-Profile besitzt nur eine Untermenge des kompletten Frameworks, so ist es kleiner, hat aber nicht alle Funktionen des vollen Frameworks. Ein Programm für das volle Framework läuft also nicht (zwangsläufig) auf einem Rechner, auf dem nur das Client-Profil installiert ist.

Wenn man seine Anwendung mit einem Setup-Projekt weitergibt, dann kann man dort angeben welches Framework vorausgesetzt wird. Wenn es fehlt, wird es dann automatisch installiert (Bootstrapper). Als Quelle für das zu installierende Framework kann man eine Download-Adresse angeben oder ein Verzeichnis auf der CD-ROM (wenn man es auf CD-ROM weitergibt).

Obwohl die Framework-Versionen vom Grundsatz her meistens abwärtskompatibel sind, so gibt es teilweise doch Änderungen, die dazu führen, dass sich ein Programm unter einer anderen Framework-Version anders verhält, als unter der, für die es ursprünglich geschrieben wurde.

2. Berechtigungen auf dem Laufwerk

Möchte man seine Applikation von einem Netzwerk-Laufwerk starten, so muss man erst mal Berechtigungen vergeben.
Man kann seine Applikation signieren und mit der Sicherheitsrichtlinien-Verwaltung die Berechtigung geben.
Man kann einem Netzwerklaufwerk auch Berechtigungen für alle .NET Applikationen geben:
[FAQ] Anwendung von Netzlaufwerk starten

3. Assemblies/DLLs

Einige Funktionen sind in Bibliotheken ausgelagert, den sogenannten DLLs (Dynamic Link Libraries). Diese DLLs muss man mit seiner Applikation mit ausliefern. Am besten ins Applikationsverzeichnis kopieren. Managed DLLs (Assemblies) müssen im Anwendungsverzeichnis, einem Unterverzeichnis davon oder im GAC liegen, damit sie geladen werden können, siehe auch So sucht Common Language Runtime nach Assemblys.

Wenn man seine Anwendung mit einem Setup-Projekt weitergibt, dann kann das Setup-Projekt die Abhängigkeiten feststellen und die benötigten DLLs mit ausliefern und installieren.

DLLs müssen in der richtigen Bittigkeit (32 oder 64 Bit) vorliegen und zur Bittigkeit der EXE passen. In 32bit/64bit unmanaged DLL "importen" - wie? und den folgenden Beiträgen steht, wie man eine Anwendung dazu bringen kann, abhängig davon, ob sie als 32Bit- oder 64Bit-Anwendung gestartet wurde, die passenden DLLs zu laden.

Das in diesem Abschnitt Gesagte gilt für eigene DLLs genauso wie für DLLs von Fremdbibliotheken oder Fremdkomponenten (z.B. Infragistics, DevExpress, Telerik, ...)

4. Datenbank

Wenn man Daten in einer Datenbank speichert und verwaltet, dann muss auf dem fremden Rechner auch eine Datenbank und die Datenbank-Provider installiert werden bzw. vorhanden sein. Diese müssen außerdem korrekt konfiguriert und die Zugriffsrechte passend gesetzt sein.

Bei SQL-Compact muss man nur einige DLLs auf den Zielrechner kopieren. Beim größeren SQL-Server muss dieser installiert werden.

Wenn man seine Anwendung mit einem Setup-Projekt weitergibt, dann kann man z.B. den SQL Server Express als Voraussetzung angeben. Der wird dann still und heimlich ohne User-Eingaben installiert und kann sofort von der eigenen Applikation benutzt werden.

5. Sonstige Umgebung

Die Pfade und Dateien, die die Anwendung verwendet, müssen existieren und die nötigen Zugriffsrechte darauf bestehen. Insbesondere wenn man auf dem Entwicklungsrechner mit Adminrechten arbeitet, kann es unter normalen Benutzerkonten Überraschungen geben.

Das Arbeitsverzeichnis (WorkingDirectiory/CurrentDirectory) muss richtig gesetzt sein, d.h. normalerweise auf das Verzeichnis, in dem sich die zu ladenden oder zu ändernden Dateien befinden oder die neu zu schreibenden Dateien abgelegt werden sollen.

Umgebungsvariablen (insbesondere der Suchpfad PATH) oder Registry-Einträge, die die Anwendung (direkt oder indirekt) verwendet, müssen vorhanden und richtig gesetzt sein und es müssen die nötigen Zugriffsrechte darauf bestehen.

Auch abweichende Ländereinstellungen können Probleme verursachen, z.B. wenn beim Parsen von Zahlen oder Datumswerten andere Trennzeichen erwartet bzw. verwendet werden, als bei der Entwicklung des Programms. Hier kann die Verwendung von CultureInfo.InvariantCulture Unabhängigkeit schaffen.

Dienste oder Programme, die über geplante Aufgaben gestartet werden, werden möglicherweise unter einem anderen Benutzerkonto ausgeführt, das andere Umgebung-, Pfad-, Ländereinstellungen und/oder Zugriffsrechte besitzt. Auch bei Programmen, die über Process.Start oder die diversen Autostart-Möglichkeiten gestartet werden, können Umgebungseinstellungen u.ä. nicht oder anders gesetzt sein. Wird mit Process.Start ein Konsolenprogramm (z.B. bsp.exe) gestartet und schließt es sich, bevor man die Fehlermeldung lesen kann, sollte man es testweise mit Process.Start ("cmd.exe", "/k bsp.exe") starten.

Manche Features (z.B. API-Funktionen) stehen erst ab bestimmten Betriebssystemversionen zur Verfügung oder stehen in bestimmten Betriebssystemversionen nicht mehr zur Verfügung. Das gleiche gilt für Treiber (z.B. für externe Hardware, auf die die Anwendung zugreift), die sich unter bestimmten Betriebssystemversionen anders verhalten können oder nicht mehr (korrekt) funktionieren.

Es kann auch Probleme mit der Bittigkeit (32bit vs 64bit) von Komponenten geben. Die Standardeinstellung für C#-Programme bis Visual Studio 2008 war "AnyCPU": Das bedeutet, dass die Anwendung auf 32bit-Systemen im 32bit-Modus und auf 64bit-Systemen im 64bit-Modus ausgeführt wird. Viele (Fremd-)Komponenten sind nur für eine bestimmte Bittigkeit übersetzt und arbeiten deshalb nicht mit einem "AnyCPU"-Programm zusammen, wenn es unter der falschen Bittigkeit ausgeführt wird. Abhilfe verschafft hier die Kompilierung direkt (nur) für 32bit bzw. 64bit.
Die Standardeinstellung ab VS 2010 ist nun x86, so sollten auch alle 32-bittigen Fremdbibliotheken auf allen Plattformen laufen.

Darüber hinaus gibt es mehrere weitere Stellen, an denen die Bittigkeit des Programms Unterschiede macht. So sehen 32bit-Programm in bestimmten Zweigen in der Registry andere Werte als 64bit-Programme, siehe dazu auch 32-bit and 64-bit Application Data in the Registry. Auch im Dateisystem wird an einigen Stellen zwischen 32bit (z.B. "Programme (x86)") und 64bit (z.B. "Programme") unterschieden.

Bei jeder Art von Multithreading-Programmierung können schon die kleinsten Unterschiede bei den Speicherzugriffszeiten, beim Caching, bei der Prozessorarchitektur oder auch nur beim Prozessortakt bzw. der momentanen CPU-Last zu einem massiv geänderten Laufzeitverhalten bis hin zur völligen Fehlfunktion führen, insbesondere mit der Möglichkeit von Race Conditions, also einem Fehlverhalten des Programms, das von Anfang an vorhanden ist, aber nur in bestimmten Umgebungen/Situationen/Konstellationen auftritt. ((Wegen der möglichen Race Conditions bei Multithreading-Programmierung hat das Testen nur einen begrenzten Wert und liefert nur eine sehr begrenzte Aussage über das Verhalten des Programms in anderen Umgebungen. Daher sind sind theoretische Überlegungen (z.B. Schreibtischtest unter Berücksichtigung der ungünstigsten Konstellationen) oder gar Korrektheitsbeweise sinnvolle oder notwendige Ergänzungen.)

Und wie finde ich die Ursache?

catch, catch, catch. Sollte die Anwendung starten, also die Hürden 1-3 genommen worden sein, kommt es zum Finden der Ursache darauf an, möglichst alle Exceptions in der Anwendung mitzubekommen und auszugeben oder zu loggen. Das gilt insbesondere dann, wenn der Fehler im Debugger nicht auftritt. Dazu sollte man vor allem im Main und in allen ThreadStart-Methoden und bei Windows Forms zusätzlich in allen GUI-EventHandlern je ein try/catch einbauen, das den gesamten Code der Methode einschließt. Außerdem kann man vorsorglich EventHandler für Application.ThreadException, AppDomain.UnhandledException u.ä registrieren (entsprechenden Code gibt es in dem Beitrag weiter unten) und auch dort die Exceptions ausgeben oder loggen. Beim Anschauen/Ausgeben/Loggen von Exceptions sollte man unbedingt (rekursiv) die InnerExceptions berücksichtigen, denn wenn diese vorhanden sind, enthält nur die innerste InnerException die eigentliche Fehlerursache.

Manchmal finden sich nähere Informationen auch in der Ereignisanzeige von Windows (im Startmenü "ere" eintippen reicht). Dabei sollten alle im Zweifel alle verfügbare Protokolle/Kategorien durchgesehen werden, also nicht nur "Anwendung" und "System".

Natürlich kann man versuchen, sich bei der Suche zunutze zu machen, dass es Unterschiede in der Umgebung geben muss und gezielt entlang dieser Unterschiede suchen. Weiter oben wurden deshalb die wichtigsten Unterschiede genannt. Allerdings gibt es letztlich unzählige Möglichkeiten, an denen sich die Umgebungen unterscheiden können. Daher ist es manchmal einfacher und schneller, den Fehler als das zu betrachten, was er mit an Sicherheit grenzender Wahrscheinlichkeit ist: ein ganz normaler von Anfang an vorhandener (Programmier-)Fehler, der lediglich in der bisherigen Umgebung noch nicht aufgefallen bzw. noch nicht zum Tragen gekommen ist. Wenn man sich gedanklich darauf einlässt, kann man den Fehler in der Umgebung, wo er auftritt, so suchen, wie man es bei jedem anderen Fehler auch tun würde.

Siehe auch

[FAQ] NET Anwendung ohne installiertes Framework ausführen
Openbook C# - Weitergabe von Anwendungen
[Tutorial] Vertrackte Fehler durch Vergleich von echtem Projekt mit minimalem Testprojekt finden

Gemeinsam mehr erreichen

Du hast weitere Unterschiede zwischen verschiedenen Umgebungen gefunden? Super, bitte einfach eine kurze PM ans Team, siehe Kontakt zum Team.

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

Hinweis von herbivore vor 12 Jahren

Beitrag hierher verschoben / hier angefügt. Vielleicht hilft es dem einen oder anderen.

Der folgende Code ist aus diesem FAQ-Thread entstanden... nur habe ich persönlich zumindest ein wenig länger gebraucht, bis ich herausgefunden habe, wo und wie ich Application.ThreadException und AppDomain.CurrentDomain.UnhandledException richtig verwende und wollte an dieser Stelle ein kleines Beispiel liefern...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace AppNameSpace
{
	static class Program
	{
		/// <summary>
		/// Der Haupteinstiegspunkt für die Anwendung.
		/// </summary>
		[STAThread]
		static void Main()
		{
			Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
			Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);
			Application.Run(new frmMain());
		}

		static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
		{
			UnhandledExceptionCaught(e.Exception);
		}

		static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
		{
			UnhandledExceptionCaught((Exception)e.ExceptionObject);
		}

		static void UnhandledExceptionCaught(Exception ex)
		{
			MessageBox.Show("Unbehandelter Fehler in Anwendung \"<Anwendungsname>\":\r\n"
				+ "  ~ Type: " + ex.GetType().ToString()
				+ "  ~ Message: " + ex.Message);
		}
	}
}

so far
Karill Endusa