Vista UAC vs. Full Trust

Da ich nun Vista habe, habe ich auch die UAC aktiviert. Mit einem sehr eigenartigen Effekt betreffs der Nutzung von DVB.NET 3.9: ich wollte mal eben so ein kleines Testprogramm machen und starte Visual Studio 2008 als Normalanwender (nicht als Administrator). C# Projekt angelegt und JMS.DVB.Common aus dem GAC als Referenz hinzugefügt. Keine Fehlermeldung, aber die Klassen der Bibliothek stehen mir nicht zur Verfügung. Nachdem ich das Projekt mit einem Strong Name versehen habe, geht es. Was ist passiert? Ich vermute, dass hier AllowPartiallyTrustedCallers(Attribute) zuschlägt, das DVB.NET bewusst nicht verwendet: mein C# Projekt ist ohne Strong Name nicht berechtigt, die Bibliothek zu verwenden. Ich weiss nicht, ob es gegangen wäre, wenn ich VS als Administrator gestartet habe, aber in jedem Fall ein erst mal (zumindest für mich) überraschender Effekt – ohne irgendwelche Fehlermeldungen im VS.

Happy Coding

Jochen

Spaß mit GetHashCode() oder wie man ein Dictionary verwirrt

Möchte man sehr eigenartige und kaum nachvollziehbare Seiteneffekte vermeiden, so sollte man tunlichst darauf achten, dass die Schlüsselklasse eines Dictionary unveränderlich ist, wie etwa Dictionary<string, …> oder Dictionary<int, …>. Aber eigentlich tut es doch jede Klasse, die vernünftig GetHashCode() und Equals() unterstützt, oder?

Nehmen wir einmal eine solche Klasse:

class ChangableNumber
{
public int Number { get; set; }
public override int GetHashCode()
{
return Number.GetHashCode();
}
public override bool Equals( object obj )
{
ChangableNumber other = obj as ChangableNumber;
if (null == other) return false;
return (Number == other.Number);
}
}

Und bauen uns ein Dictionary mit einem Schlüssel, den wir verändern. Was erzeugt wohl das folgende Programm als Ausgabe:

ChangableNumber c1 = new ChangableNumber { Number = 1 };
Dictionary dict = new Dictionary();
dict[c1] = true;
Console.WriteLine( dict.ContainsKey( c1 ) );
c1.Number = 4;
Console.WriteLine( dict.ContainsKey( c1 ) );
c1.Number = 1;
Console.WriteLine( dict.ContainsKey( c1 ) );

Nun, erst mal wie erwartet: true (1 ist drin), false (4 natürlich nicht) und true (1 ist wieder drin). Was aber, wenn GetHashCode() zufälligerweise für den veränderten Wert den selben Schlüssel liefert (respektive dieser versehen mit dem Modulo des Dictonary den selben Wert hat)? Das ist einfach zu testen: der Inhalt von GetHashCode() wird durch return 1; ersetzt. Nun ist das Ergebnis true, true und wieder true. Wie kommt die 4 denn nun in das Dictionary?

Der Trick ist einfach: ein Dictionary nimmt den Wert von GetHashCode() und ermittelt daraus zum Beispiel durch ein Modulo (mit einer Primzahl, muss aber nicht) einen Indexwert. Für jeden Indexwert wird eine Liste von Schlüssel / Wert [interessiert hier nicht] Paaren verwaltet, i.e. eine Liste enthält alle Schlüssel, die aus Sicht des Dictionary den gleichen Hash haben.

Im ersten Beispiel waren diese (zufällig) unterschiedlich, so dass das Dictionary beim Anlegen eine Liste (sagen wir mal 1) mit einem Schlüssel / Wert Paar anlegte. Das Ändern in 4 führt dazu, dass bei der Prüfung mittels ContainsKey eine nicht vorhandene Liste (4 oder was auch immer) gesucht wird. Diese ist leer, also liefert ContainsKey false.

Im zweiten Beispiel wird nach der Änderung aber die (immer einzige) Liste (1) untersucht. Diese ist nun nicht leer, so dass alle Schlüssel mittels Equals verglichen werden. Natürlich passt unser Schlüssel nun immer, da er mit sich selbst verglichen wird (i.e. in diesem Beispiel kann man jeden beliebigen Wert zuweisen, das zweite ContainsKey ist immer true). Für das ursprüngliche Beispiel wäre übrigens -2147483647 (0x80000001) der zugehörige Killerwert – vermutlich der einzige.

Und das Fazit? Veränderliche Schlüssel für ein Dictionary sollten nur mit vorherigem Nachdenken verwendet werden.

Viel Glück dabei

Jochen

Kleine Falle ASP.NET Hosting: HttpListener und Threads

Die einfachste Methode, ASP.NET zu hosten ist es, den HttpListener als HTTP Protokollschicht einzusetzen (wenn man nicht gerade eine eigene Implementierung schreiben will). Allerdings gibt es hier einen kleinen Haken: der HttpListener scheint an den Thread gebunden zu sein, der ihn gestartet hat (Start oder BeginGetContext).

Startet man den Listener auf dem Haupthread der Anwendung, funktioniert alles wie gewünscht und der Listener erkennt automatisch das Ende der Anwendung. So weit so gut. Wenn man allerdings einen Thread verwendet, der explizit oder implizit beendet wird, so terminiert auch der Listener. In meinem Fall knallte es beim expliziten Beenden: ich hatte einen Thread, der einfach nur den Listener konfigurierte und startet und sich dann beendet – damit die Initialisierung der Anwendung (in diesem Fall der VCR.NET Windows Dienst) im Hauptstrang so schnell wie möglich durchläuft.

Mit ThreadPool.QueueUserWorkItem geht es dann, aber mit einer im Hintergrund drohenden Gefahr: der ThreadPool beendet die Aufgabe und verwendet den Thread dann später für andere Aufgaben weiter. Der Listener bleibt an diesen Thread gebunden, der überhaupt nichts mehr mit ihm zu tun hat. Und wenn der ThreadPool irgendwann mal einen Thread wegwirft? Den Fehler findet man vermutlich nie!

Ich habe mich daher entschieden, mich nicht auf die Willkür zu verlassen und halte den Thread, der den Listener hochfährt, bis zum Ende der Anwendung oben (über ein Manual/AutoResetEvent). Nur so habe ich ein gutes Gefühl, dass nicht doch plötzlich die Kommunikation des Clients zum ASP.NET zusammenbricht.

Zumindest sollte man das im Hinterkopf haben!

Jochen

Serverarchitektur bei ASP.NET Hosting

Vielleicht einfach ein paar kurze Gedanken zu den letzten Arbeiten an VCR.NET 3.9, etwas abstrahiert. Im Endeffekt geht es darum, eine Serverfunktionalität mit präziser Lebenszeitkontrolle mit einer Web Oberfläche anzubieten. Die Kontrolle der Lebenszeit läßt sich ideal durch das Hosten des Servers in einem Windows Dienst erreichen, für die Web Oberfläche wäre natürlich der Microsoft Internet Information Server die bevorzugte Lösung. Mit dem ASP.NET Hosting läßt sich beides weitgehend kombinieren (es ist allerdings kaum möglich, alle Autorisierungsoptionen des IIS nachzubilden, aber das braucht man auch nicht immer).

Zu beachten ist allerdings, dass ein virtuelles Verzeichnis immer in einer eigenen .NET AppDomain läuft. Unser Ansatz besteht daher immer aus mindestens zwei AppDomains, was einige besondere Aspekte mit sich bringt. Ich stelle hier in Stichworten einmal die beim VCR.NET implementierte Lösung vor.

1. Der .NET Dienst wird natürlich in einer ersten AppDomain gestartet. Hier soll auch der Server leben.
2. Der Server wird als .NET Instanz einer MarshalByRefObject Klasse (Server) erzeugt.
3. Für das ASP.NET Hosting wird eine weitere MarshalByRefObject Klasse (Host) benötigt, die in dem virtuellen Verzeichnis erstellt wird.
4. Nach dem Starten des virtuellen Verzeichnisses erhält Host die Server Referenz aus der ersten AppDomain.
5. Alle Zugriffe aus dem virtuellen Verzeichnis erfolgen über dese Referenz und werden in der ersten AppDomain ausgeführt.
6. Damit die beiden (Server und Host) Inter-AppDomain Referenzen nicht durch die Remoting Lebenszeitkontrolle spontan gelöst werden, melden deren InitializeLifetimeService Methoden null.
7. Alle Klassen, die zwischen den AppDomains ausgetauscht werden sollen, müssen MarshalByRefObject oder [Serializable] sein. VCR.NET setzt auf serialisierbare Klassen, so dass zwar für die Serialisierung und Deserialisierung bezahlt werden muss, die Objekte dann aber ohne Kommunikationsverluste weiter verwendet werden können. Man beachte, dass hier die binäre Serialisierung zum Einsatz kommt – man siehe dazu einen früheren Eintrag in diesem BLog.

Zur Verwaltung der Konfiguration wird ein einziges Konfigurationsobjekt eingesetzt, über das alle Zugriff (auch schreibend) erfolgen. Dieses wird in der ersten AppDomain angelegt. Damit es nicht in alle Klassen durchgereicht werden muss (hier gibt es etwa Methoden wie Log(string message)), erhählt die Konfigurationsklasse (Config) ein statisches Feld Current, dass bei der Intialisierung des Windows Dienstes auf der ersten AppDomain belegt wird (Config.Current = new Config()). Zusätzlich bietet die Server Klasse eine Eigenschaft zum Zugriff auf Config.Current an. In Schritt 4. oben wird in der AppDomain des virtuellen Verzeichnisses eine Referenz des eindeutigen Konfigurationsobjektes in das statische Feld als Referenz übernommen (Config.Current = Server.CurrentConfiguration) – ein anderes statisches Feld, da statische Felder nur pro AppDomain eindeutig sind. So arbeiten beide AppDomains immer mit der selben Konfiguration – Config ist natürlich ein MarshalByRefObject, dessen InitializeLifetimeService null liefert.

Nun, für einige ist das vermutlich trivial, aber vielleicht hilft die Information hier dem einen oder anderen, einige Fallstricke zu vermeiden und etwas Zeit zu sparen.

Viel Glück

Jochen

VCR.NET 3.9 Verluste: SOAP Web Service

Schon von Anbeginn an bot der Windows Dienst VCR.NET Recording Service eine SOAP Schnittstelle an, die von Clients zur Steuerung verwendet werden konnten. Tatsächlich gibt es in 3.5 zwei verschiedene Schnittstellen, eine für den regulären Betrieb wie die Aufzeichnungsplanung und einen für den so genannten Zapping oder LIVE Modus. Beide Schnittstellen werden zudem in zwei Varianten angeboten, eine für die Versionen vom VCR.NET, die nur ein Geräteprofil verwenden konnten und eine, bei der das Geräteprofil bei jeder Operation über seinen Namen ausgewählt werden kann.

Zukünftige VCR.NET Versionen (die nach der 3.9, so es sie denn geben sollte) werden möglicherweise veränderte SOAP Schnittstellen auf Basis von WCF (Windows Communication Framework) anbieten. Für die 3.9 wird es keine neuen Varianten der beiden primären Web Services geben, vielmehr wird VCR.NET 3.9 versuchen, auf Basis der neuen Infrastruktur die alten Schnittstellen unverändert anzubieten. Das geht soweit auch schon, hat aber einige Nachteile – die vermutlich aber kaum jemanden betreffen.

Zum einen können neue Funktionalitäten nicht genutzt werden, wenn diese über die Protokollstrukturen der alten Schnittstellen nicht verfügbar sind. Das ist eher unkritisch, hier ging es etwa um die präzisere Auswahl der Quellen (früher Sender genannt). Kritischer ist aber, dass VCR.NET einige Informationen intern nicht mehr pflegt und auf Anfrage daher auch nicht mehr zur Verfügung stellen kann. Hier ist vor allem die Senderbeschreibung zu erwähnen: DVB.NET stellt ab der Version 3.9 (auf der VCR.NET 3.9 basiert) zum Beispiel die Daten zu den Tonspuren nicht mehr zur Verfügung. Bei Anfrage an VCR.NET 3.9 über die alten Schnittstellen werden hier leere Listen geliefert. Tatsächlich benötigt der DVB.NET / VCR.NET Viewer als einer der VCR.NET SOAP Clients diese Information überhaupt nicht, anderen Clients wird es ähnlich gehen. Genauso ist es nicht exakt möglich, die Auswahl der Teildatenströme auf die vier Wahrheitswerte (Alle Sprachen, Dolby Digital, DVB Untertitel, Videotext) abzubilden, da die interne Verwaltung nun sehr viel feinere Variationen zulässt. Allerdings versucht VCR.NET sein Bestes, ein möglichst exaktes Abbild der tatsächlichen Konfiguration zu übermitteln.

In Einzelfällen ist zu klären, an welchen Stellen die modifizierten Informationen ein Problem darstellen. Im Wesentlichen bleibt aber die folgende Aussage: VCR.NET 3.9 wird voraussichtlich keine neuen SOAP Dienste oder Varianten der alten anbieten. Die vorhandenen Clients wie der Viewer oder das Kontrollzentrum werden die alten SOAP Schnittstellen benutzen – vielleicht wird es das eine oder andere Hintertürchen geben, um eine versionsunabhängige Version des Viewers zu erstellen – das Kontrollzentrum ist da eher harmlos.

Bis bald

Jochen