Autor Beitrag
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Chefentwickler
Beiträge: 19940
Erhaltene Danke: 1768

Win 10
C# (VS 2015)
BeitragVerfasst: So 25.12.05 15:14 
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. deserialisiern. Beim Serialisieren werden einfach alle Eigenschaften des Objektes in einen Speicherblock "gestopft", welcher somit den Zustand des Objektes zum aktuellen Zeitpunkt reprsäntiert. 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 ersellt 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 "Vermittlunsgobjekt" (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 MesssageSinks 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 mann 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 mittles 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 Parmaterliste steht.


Dank an user profile iconManuel, welcher diesen Artikel Korrektur gelesen hat, Anregungen zur Verbesserungs des Quellcodes gab und den Teil der Serialisierung des Objektes beitrug! Danke!
Einloggen, um Attachments anzusehen!
_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
alexpj
Hält's aus hier
Beiträge: 2



BeitragVerfasst: Mi 10.02.16 13:49 
Ich habe das Beispiel übernommen aber ein kleines Problem. Die zweite Instanz kann keine args übergeben, da vorher eine Exception kommt.
Zitat:
Ein Ausnahmefehler des Typs "System.NullReferenceException" ist in MSGBox2.exe aufgetreten. Zusätzliche Informationen: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.


Kann ich mir nicht erklären, da args definitiv nicht leer ist....
Einloggen, um Attachments anzusehen!
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 3321
Erhaltene Danke: 610

Win7
C++, C# (VS 2010/12/13/15)
BeitragVerfasst: Mi 10.02.16 14:24 
Hallo und :welcome:

da wird wohl eher die Variable ac den Wert null haben, d.h. das Objekt konnte nicht instanziiert werden.
alexpj
Hält's aus hier
Beiträge: 2



BeitragVerfasst: Fr 11.03.16 08:59 
ich begreife das nicht:

ich hab ein Tool gemacht, das sich 100% an dem Beispiel orientiert. Als Args werden Pfade und Dateinamen von Outlook attachments übergeben. Das funktioniert soweit perfekt.

Aber:

vergehen ein paar Minuten im Leerlauf passiert garnichts mehr. Es werden weder die alte noch eine neue Instanz aufgerufen. Mit Debug komme ich nicht weiter.
Wie gehe ich da an eine Fehlersuche bzw. ist in dem Beispiel möglicherweise so eine Zeitbombe verborgen?
King2k7
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Sa 31.12.16 12:56 
Ich habe da eine Frage:

In welcher Zeile befindet sich der Code das der Pfad der Exe gespeichert wird. Also ich meine ich sehe nicht wo genau definiert ist das das Programm den Pfad nehmen soll?
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Chefentwickler
Beiträge: 19940
Erhaltene Danke: 1768

Win 10
C# (VS 2015)
BeitragVerfasst: Sa 31.12.16 13:06 
Der Pfad zur Anwendung wird nicht benötigt, die erste und zweite Instanz finden sich über den IPC-Channel.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
King2k7
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Sa 31.12.16 13:20 
Ja, das habe ich gesehn.
Was ich meine ist: Es wird ja der Pfad der Exe in die Listbox geschrieben von jeder Exe außer der ersten. Ich sehe aber nicht wo dieser hergenommen wird um die Listbox zu füllen.

ausblenden Quelltext
1:
listBox1.Items.AddRange(filesOrFolders);					


Wo wird festgelegt das filesorFolder der Pfad ist? Ich würde den Code gerne umschreiben das nur der Dateiname Verwender wird. ICh nutzt für das Verzeichnis z.B. immer Path.GetDirectoryName aber nichts der gleichen ist im Code zu finden.
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Chefentwickler
Beiträge: 19940
Erhaltene Danke: 1768

Win 10
C# (VS 2015)
BeitragVerfasst: Sa 31.12.16 13:29 
Ach so, das meinst Du. :think:

Die zweite Instanz ruft in der program.cs den Delegaten
ausblenden C#-Quelltext
1:
ac.OnAddArgs(System.Environment.GetCommandLineArgs());					

auf, welcher dann in der ersten Instanz ausgeführt wird. Und da kommt der Pfad her, der ist Teil der CommandLineArgs.

In der ersten Instanz wurde für den OnAddArgs-Delegaten die Methode InvokeAddFilesOrFolders registriert und diese ruft (im richtigen Thread) AddFilesOrFolders auf.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
King2k7
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Sa 31.12.16 14:35 
Danke, jetzt hab ich es verstanden.

Kann es sein das nicht mehrere Instanzen gleichzeitig versuchen dürfen an die 1. Instanz über IPC Parameter zu senden. Wenn ich mehrere Dateien auswähle und mittels einen "Rechtsklick Menü" versuche an meine 1. Instanz denn Dateipfad zu senden kommt eine Fehler Meldung das das Programm nicht funktioniert und nur eine Datei wird der Listbox hinzugefügt.
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Chefentwickler
Beiträge: 19940
Erhaltene Danke: 1768

Win 10
C# (VS 2015)
BeitragVerfasst: Sa 31.12.16 14:44 
Das kann ich bei mir nicht reproduzieren, bei mir klappt das.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
King2k7
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Sa 31.12.16 16:49 
Man kann es mit dem Beispielprojekt nachstellen indem man die Exe 3x erstellt. 1. Exe wird einfach ausgeführt und Exe 2 und 3 zusammen mankiert und über einen Rechtsklick geöffnet.

Siehe Bild im Anhang.
Einloggen, um Attachments anzusehen!
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Chefentwickler
Beiträge: 19940
Erhaltene Danke: 1768

Win 10
C# (VS 2015)
BeitragVerfasst: Sa 31.12.16 16:57 
Dann ist jetzt wohl der Programmierer in Dir an der Reihe, das zu debuggen ;)

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
King2k7
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Sa 31.12.16 17:06 
:D Ja, das werde ich wohl tun müssen. Dachte nur es sei ein bekanntes Problem oder vllt ist IPC nicht für sowas ausgelegt.
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Chefentwickler
Beiträge: 19940
Erhaltene Danke: 1768

Win 10
C# (VS 2015)
BeitragVerfasst: Sa 31.12.16 17:15 
Es hat mich jetzt doch interessiert. Das Problem ist, dass die "späteren" Clients (in der program.cs) alle einen Channel mit Port "IPCDemoClient" aufmachen. Das geht aber nur einmal.

Die Lösung ist einfach: man lässt den Port-Namen weg, wir brauchen hier nur einen Client-Channel und der braucht keinen Port-Namen. Aus
ausblenden C#-Quelltext
1:
IpcChannel ipc = new IpcChannel("IPCDemoClient");					

wird
ausblenden C#-Quelltext
1:
IpcChannel ipc = new IpcChannel();					

und es sollte funktionieren :)

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
King2k7
Hält's aus hier
Beiträge: 6



BeitragVerfasst: Di 03.01.17 15:48 
:D Danke dir für die Info :)