Comment envoyer un message d'une instance d'une application gérée à une autre?
-
06-07-2019 - |
Question
J'ai une application WinForms où, s'il y a déjà une instance en cours d'exécution, & amp; l'utilisateur essaie d'en créer un autre, je l'arrête en vérifiant par un mutex avant d'appeler Application.Run (). Cette partie fonctionne très bien. Ce que je voudrais faire est de transmettre un message de la nouvelle instance de l'application (avec un élément de données sous forme de chaîne) à l'instance existante avant de tuer le nouveau processus.
J'ai essayé d'appeler PostMessage et je reçois le message sur l'application en cours d'exécution, mais la chaîne que je transmets dans le lparam échoue (oui, j'ai vérifié pour passer une bonne chaîne à commencer avec). Comment puis-je le faire au mieux?
static class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool PostMessage(int hhwnd, uint msg, IntPtr wparam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint RegisterWindowMessage(string lpString);
private const int HWND_BROADCAST = 0xffff;
static uint _wmJLPC = unchecked((uint)-1);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
_wmJLPC = RegisterWindowMessage("JumpListProjectClicked");
if (_wmJLPC == 0)
{
throw new Exception(string.Format("Error registering window message: \"{0}\"", Marshal.GetLastWin32Error().ToString()));
}
bool onlyInstance = false;
Mutex mutex = new Mutex(true, "b73fd756-ac15-49c4-8a9a-45e1c2488599_ProjectTracker", out onlyInstance);
if (!onlyInstance) {
ProcessArguments();
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
GC.KeepAlive(mutex);
}
internal static void ProcessArguments()
{
if (Environment.GetCommandLineArgs().Length > 1)
{
IntPtr param = Marshal.StringToHGlobalAuto(Environment.GetCommandLineArgs()[1]);
PostMessage(HWND_BROADCAST, _wmJLPC, IntPtr.Zero, param);
}
}
}
Ailleurs, sous ma forme ...
protected override void WndProc(ref Message m)
{
try
{
if (m.Msg == _wmJLPC)
{
// always returns an empty string
string param = Marshal.PtrToStringAnsi(m.LParam);
// UI code omitted
}
}
catch (Exception ex)
{
HandleException(ex);
}
base.WndProc(ref m);
}
La solution
Greg,
Le pointeur non géré créé par StringToHGlobalAuto est uniquement valide dans l'espace de processus qui l'a créé. La mémoire référencée n’est pas accessible depuis l’autre processus.
Pour transmettre des données d'une application à une autre, utilisez SendMessage () avec le message WM_COPYDATA.
Scott
Autres conseils
Vérifiez à nouveau votre code. Vous utilisez StringToHGlobalAuto pour créer la chaîne (aboutissant probablement à Unicode). Ensuite, vous appelez PtrToStringAnsi, qui n’utilise pas unicode.
Si cette solution ne fonctionne pas, vous avez plusieurs options. Vous pouvez en savoir plus en recherchant IPC (InterProcess Communication.)
Depuis que c'était rapide et facile, j'ai utilisé une méthode pour créer un fichier bien connu avec le contenu nécessaire. Vous verrouillez l'utilisation de ce fichier avec des événements nommés et vous en informez le propriétaire. app que le fichier a changé en définissant un autre événement nommé. Le " propriétaire " vérifie occasionnellement l'événement ou lance un thread de travail pour le surveiller.
Encore une fois, IPC a de nombreuses variantes, si ces idées ne fonctionnent pas, continuez à chercher.
Voici un moyen simple. Je n'ai pas exécuté le code mais vous avez une idée
class Program
{
static Thread listenThread;
static void Main(string[] args)
{
try
{
using (Mutex mutex = new Mutex(true, "my mutex"))
{
listenThread = new Thread(Listen);
listenThread.IsBackground = true;
listenThread.Start();
}
}
catch (ApplicationException)
{
using (Mutex mutex = Mutex.OpenExisting("my mutex"))
{
mutex.WaitOne();
try
{
using (NamedPipeClientStream client = new NamedPipeClientStream("some pipe"))
{
using (StreamWriter writer = new StreamWriter(client))
{
writer.WriteLine("SomeMessage");
}
}
}
finally
{
mutex.ReleaseMutex();
}
}
}
}
static void Listen()
{
using (NamedPipeServerStream server = new NamedPipeServerStream("some pipe"))
{
using (StreamReader reader = new StreamReader(server))
{
for (; ; )
{
server.WaitForConnection();
string message = reader.ReadLine();
//Dispatch the message, probably onto the thread your form
// was contructed on with Form.BeginInvoke
}
}
}
}
De nos jours, il existe des méthodes beaucoup plus simples et modernes de communication entre processus. en particulier, consultez WCF.
Bien que je reconnaisse qu’il existe une petite courbe d’apprentissage. Une fois que vous avez compris, c'est vraiment très facile. Vous pouvez même tout faire par programme afin de ne pas avoir à vous soucier des confusions de configuration.
Il semble qu'il manque un exemple complet. J'ai un peu lutté pour obtenir un exemple de travail en cours d'exécution. Il s’agit donc de mon implémentation minimale de l’application à instance unique lorsqu’il est exécuté plusieurs fois et transmet le message (premier argument de ligne de commande) à la première instance. Si quelqu'un comme moi a besoin d'un exemple complet, c'est un bon début:
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
namespace MutexApp
{
class Program
{
private const string PIPE_NAME = "MY_PIPE"; // Name of pipe
private const string MUTEX_NAME = "MY_MUTEX"; // Mutex name
static void Main(string[] args)
{
string message = "NO MESSAGE";
if (args.Length > 0) // If we have a parameter to the .exe get it
message = args[0];
bool firstinstance = false;
Mutex mutex = new Mutex(true, MUTEX_NAME, out firstinstance);
if (firstinstance) // We are the first instance of this process
{
Console.WriteLine("First instance started");
Console.WriteLine("Message: " + message);
Console.WriteLine("Waiting for messages (ctrl+c to break)...");
while (true) { ProcessNextClient(); } // Unfinite loop that listens for messages by new clients
}
else // This process is already running, parse message to the running instance and exit
{
{
try
{
using (NamedPipeClientStream client = new NamedPipeClientStream(PIPE_NAME)) // Create connection to pipe
{
client.Connect(5000); // Maximum wait 5 seconds
using (StreamWriter writer = new StreamWriter(client))
{
writer.WriteLine(message); // Write command line parameter to the first instance
}
}
} catch (Exception ex)
{
Console.WriteLine("Error: "+ex.Message);
}
}
}
mutex.Dispose();
}
private static void ProcessNextClient()
{
try
{
NamedPipeServerStream pipeStream = new NamedPipeServerStream(PIPE_NAME, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances); // Create Server pipe and listen for clients
pipeStream.WaitForConnection(); // Wait for client connection
using (StreamReader reader = new StreamReader(pipeStream)) // Read message from pipe stream
{
string message = reader.ReadLine();
Console.WriteLine("At " + DateTime.Now.ToLongTimeString()+": " + message); // Print message on screen
}
}
catch (Exception e)
{
Console.WriteLine("Error: " + ex.Message);
}
}
}
}