Wichtig: Dieser Beitrag basiert auf diesem Artikel:
dotnetaddict.dotnetd...al_world_example.htm
Diesen Beitrag gibt es auch als
Artikel auf meiner
Seite.
Einleitung
Es kommen immer wieder Anwendungen vor, bei denen man erreichen möchte, dass nur eine Anwendungs-Instanz von ihnen existiert. Sowohl unter Win32 als auch unter .NET kann man dies mit einem Mutex erreichen, in beiden Welten geht es gleich einfach. Vor einem Problem steht man unter .NET, wenn man Daten von der neuen Anwendungs-Instanz an die bestehende Instanz übergeben möchte, z.B. Aufrufparameter.
Unter Win32 konnte dies z.B. relativ einfach mittels WM_COPYDATA erreicht werden. Diese Möglichkeit bleibt einem unter .NET verschlossen. Unter .NET 1.1 war es nötig, mittels TCP einen Server und einen Client aufzumachen, welche sich dann Daten schickten. .NET 2.0 bietet für die Kommunikation auf einem einzelnen Rechner entsprechende IPC-Klassen, welche deutlich schneller als TCP arbeiten.
Wie man dies nutzt, um Aufrufparameter von einer Anwendungs-Instanz zur anderen zu schaufeln, sei im folgenden gezeigt. Ich werde hierbei auf die im Anhang zu findende Anwendung eingehen.
Trockenübung
Zur Kommunikation zwischen den beiden Anwendungs-Instanzen wird ein spezielle Klasse verwendet. An diese Klasse werden kaum Anforderungen gestellt, einzig von
MarshalByRefObject muss sie abgeleitet sein. Kurz gesagt kann man dadurch über Applikations-Grenzen hinweg auf ein und dasselbe Objekt zugreifen! Ein wichtiger Baustein also bei der Kommunikation zwischen zwei Anwendungs-Instanzen.
Man kann sich dieses Objekt nun als Vermittler zwischen den beiden Anwendungs-Instanzen vorstellen. Intern arbeiten dabei sogenannte "Marshaler", die das Objekte serialisieren bzw. deserialisieren. Beim Serialisieren werden einfach alle Eigenschaften des Objektes in einen Speicherblock "gestopft", welcher somit den Zustand des Objektes zum aktuellen Zeitpunkt repräsentiert. Dieser Speicherblock wird nun einfach in den anderen Prozess kopiert und deserialisiert, womit man ein Objekt erhält, dessen Zustand dem aus dem anderen Prozess entspricht.
Erstellt wird dieses Objekt von der ersten Instanz. Dabei soll dem Objekt ein Delegat übergeben werden (eine Methode in der ersten Instanz), welcher (via Objekt) von der zweiten Instanz aufgerufen werden kann.
Die zweite Anwendungs-Instanz stelllt also erst einmal mittels Mutex fest, dass sie nicht die erste ihrer Art ist. Dann holt sie sich das (durch die erste Anwendungs-Instanz erstellte und mit Delegat bestückte) Objekt und ruft den Delegaten auf. Der Delegat erhält dabei als Parameter die Aufrufparameter, welche an die erste Anwendungs-Instanz übergeben werden sollen.
Der Aufruf des Delegaten ist die Brücke in die erste Anwendungs-Instanz. Sie kann nun den Parameter verarbeiten.
Wie es geht ...
Der erste Start
Beim Programmstart wird in der Datei "program.cs", bevor überhaupt eine Form erstellt wurde, mittels eines Mutex überprüft, ob bereits eine Anwendungs-Instanz dieser Anwendung existiert. Als Name des Mutex verwende ich die GUID aus der Datei "AssemblyInfo.cs". Wurde ein neuer Mutex erzeugt, existierte noch keine Anwendungs-Instanz und die Anwendung darf normal weitermachen.
Die erste Instanz
Ist die Anwendung also vorschriftsmäßig gestartet, muss sie nun (in "Form_Load" in der Datei "Form1.cs") das "Vermittlungsobjekt" (kein Fachbegriff
) erstellen. Dazu würde man normalerweise einen so genannten IpcChannel erstellen (zur Datenübertragung) und dann den Typen des Vermittlungsobjektes als Dienst dieses Channels registrieren.
Leider soll das Vermittlungsobjekt ja einen Delegaten erhalten, was die Sache etwas komplizierter macht. Bei einer "normalen" Erstellung eines Channels, bei der einfach nur ein Name festgelegt wird, kann ein solcher Delegat nicht korrekt serialisiert werden, was eine Voraussetzung dafür ist, dass die ganze Sache funktioniert.
An dieser Stelle war der ganz oben verlinkte Artikel eine gigantische Hilfe, im Prinzip der Schlüssel zum Ganzen. In ihm wird demonstriert, dass man dem Channel ein Objekt zur Verfügung stellen kann, welches für die korrekte Serialisierung sorgt. Es hat den handlichen Namen "BinaryServerFormatterSinkProvider".
Über MessageSinks kann man sich
hier informieren. Um eine kurze Vorstellung hier eine recht unfachliche Beschreibung: Eine Nachricht, welche sich durch einen Channel vom Client zum Server bewegt, passiert dabei MessageSinks. Diese verarbeiten diese Nachricht und verändern diese dabei. Jeder Sink tut dabei andere Dinge. Der hier vorliege Sink sorgt halt für eine korrekte Serialisierung.
Mittels dieses Objektes kann man nun einen Channel erstellen, der auch Delegaten korrekt verwenden kann. Dieser muss dann noch registriert werden. Nun kommt das "Vermittlungsobjekt" zum Einsatz. Es muss als Service registriert werden. Dabei ist wichtig, es im Modus "WellKnownObjectMode.Singleton" zu registrieren, damit immer nur eine einzige Instanz davon verwendet wird.
Der Rest ist einfach: Das Objekt der
Activator-Klasse besorgen (an dieser Stelle wird es erzeugt, weil noch keine Instanz vorhanden ist) und den Delegaten zuweisen.
Die zweite Anwendungs-Instanz
Beim Start der zweiten Anwendungs-Instanz wird in der "program.cs" kein neuer Mutex erzeugt, da er ja schon existiert. Anstatt die Anwendung zu Starten, wird nun erneut ein IpcChannel erstellt, dieses Mal jedoch ohne das Objekt zu Serialisierung. Das wird hier nicht benötigt, da kein Delegat gesetzt wird.
Stattdessen holt man sich erneut das Vermittlungsobjekt mittels der
Activator-Klasse. Dieses Mal wird der Aufruf von
GetObject keine neue Instanz erzeugen, sondern die von der ersten Anwendungs-Instanz erstellte zurückgeben. Man greift also auf dasselbe Objekt wie die erste Anwendungs-Instanz zu!
Nun braucht man nur noch den Delegaten mit dem passenden Parameter aufrufen und ist in der zweiten Anwendungs-Instanz fertig.
Eine kleine Tücke ...
... gibt es aber noch: der Delegat wird in einem anderen Thread aufgerufen, was den Zugriff auf Elemente des Formulars erschwert. Daher habe ich als Delegaten auch nicht direkt die verarbeitende Methode zugewiesen, sondern noch eine Methode drum herum gebaut: Diese sorgt mittels
Invoke dafür, dass die eigentliche Methode im richtigen Thread ausgeführt wird und es zu keinen "Unfällen" kommt.
Der Test
Das angehängt Projekt sollte problemlos kompilieren. Die Anwendung, die dabei herauskommt, braucht man einfach nur zweimal zu starten. Beim zweiten Start sollte keine zweite Anwendungs-Instanz erscheinen, sondern in der Listbox der ersten Anwendungs-Instanz der Programmname eingefügt werden, der ja immer in der Parameterliste steht.
Dank an Manuel, welcher diesen Artikel Korrektur gelesen hat, Anregungen zur Verbesserung des Quellcodes gab und den Teil der Serialisierung des Objektes beitrug! Danke!
Aktualisierte und verbesserte Version
Weiter unten gibt es eine für VS 2015 (und höher) aktualisierte und verbesserte Version, da es bei diesem Code Probleme mit der "Lebenszeit" des IPC-Objektes gibt (wenn er länger als ein paar Minuten läuft), so daß dann ein leeres Objekt übertragen wird und es zu einer
Exception kommt!
Moderiert von Th69: "Aktualisierte und verbesserte Version" hinzugefügt.
Moderiert von Th69: Weitere Rechtschreibfehler korrigiert.
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".