Der folgende Code ist meine WCF Named Pipes-Lösung zur Registrierung einer Einzelinstanzanwendung. Es ist schön, weil es auch ein Ereignis auslöst, wenn eine andere Instanz versucht, zu starten, und die Befehlszeile der anderen Instanz erhält.
Es ist auf WPF ausgerichtet, da es die System.Windows.StartupEventHandler
Klasse, aber das kann leicht geändert werden.
Dieser Code erfordert einen Verweis auf PresentationFramework
und System.ServiceModel
.
Verwendung:
class Program
{
static void Main()
{
var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");
if (SingleInstanceManager.VerifySingleInstance(applicationId))
{
SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;
// Start the application
}
}
static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
{
// Do something in response to another instance starting up.
}
}
Quellcode:
/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
/// <summary>
/// Raised when another instance attempts to start up.
/// </summary>
public static event StartupEventHandler OtherInstanceStarted;
/// <summary>
/// Checks to see if this instance is the first instance running on this machine. If it is not, this method will
/// send the main instance this instance's startup information.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
/// <returns>True if this instance is the main instance.</returns>
public static bool VerifySingleInstace(Guid guid)
{
if (!AttemptPublishService(guid))
{
NotifyMainInstance(guid);
return false;
}
return true;
}
/// <summary>
/// Attempts to publish the service.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
/// <returns>True if the service was published successfully.</returns>
private static bool AttemptPublishService(Guid guid)
{
try
{
ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
serviceHost.Open();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Notifies the main instance that this instance is attempting to start up.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
private static void NotifyMainInstance(Guid guid)
{
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
{
ISingleInstance singleInstance = factory.CreateChannel();
singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
}
}
/// <summary>
/// Creates an address to publish/contact the service at based on a globally unique identifier.
/// </summary>
/// <param name="guid">The identifier for the application.</param>
/// <returns>The address to publish/contact the service.</returns>
private static string CreateAddress(Guid guid)
{
return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
}
/// <summary>
/// The interface that describes the single instance service.
/// </summary>
[ServiceContract]
private interface ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
[OperationContract]
void NotifyMainInstance(string[] args);
}
/// <summary>
/// The implementation of the single instance service interface.
/// </summary>
private class SingleInstance : ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
public void NotifyMainInstance(string[] args)
{
if (OtherInstanceStarted != null)
{
Type type = typeof(StartupEventArgs);
ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(argsField != null);
argsField.SetValue(e, args);
OtherInstanceStarted(null, e);
}
}
}
}
17 Stimmen
Gibt die CLR nicht automatisch alle nicht freigegebenen Mutexe frei, wenn die Anwendung ohnehin beendet wird?
2 Stimmen
@Cocowalla: Der Finalisierer sollte die nicht verwalteten Mutexe entsorgen, es sei denn, er kann nicht wissen, ob die Mutex von der verwalteten Anwendung erstellt oder an eine bestehende angehängt wurde.
1 Stimmen
Es ist sinnvoll, nur eine Instanz Ihrer Anwendung zu haben. Aber die Übergabe von Argumenten an eine bereits existierende Anwendung erscheint mir ein wenig albern. Ich kann keinen Grund dafür sehen, dies zu tun. Wenn Sie eine Anwendung mit einer Dateierweiterung verknüpfen, sollten Sie so viele Anwendungen öffnen, wie Benutzer Dokumente öffnen wollen. Das ist das Standardverhalten, das jeder Benutzer erwarten würde.
1 Stimmen
Ich möchte nur eine Korrektur meiner vorherigen Aussage vornehmen. Die Übergabe von Argumenten an eine bestehende Anwendung bedeutet, dass Sie eine MDI (Multi-Document-Interface) durchführen wollen. Ich dachte, dass MDI war eine Art, die Microsoft war drängen aus (Word und Excel sind jetzt SDI). Aber ich habe festgestellt, dass Chrome und IE beide MDI sind. Vielleicht sind wir in Jahren, in denen MDI zurück ist? (Aber ich bevorzuge immer noch SDI gegenüber MDI)
13 Stimmen
@Cocowalla Die CLR verwaltet keine nativen Ressourcen. Wenn ein Prozess jedoch beendet wird, werden alle Handles vom System (dem Betriebssystem, nicht der CLR) freigegeben.
2 Stimmen
Ich bevorzuge die Antwort von @huseyint. Sie verwendet die Microsoft-eigene Klasse 'SingleInstance.cs', so dass man sich keine Gedanken über Mutexe und IntPtrs machen muss. Außerdem besteht keine Abhängigkeit von VisualBasic (igitt). Siehe codereview.stackexchange.com/questions/20871/ für mehr...
1 Stimmen
Ich verwende SingleInstanceApp nuget. nuget.org/packages/SingleInstanceApp Funktioniert perfekt. Benötigt keine Microsoft.VisualBasic-Referenz. Hängt nicht von der App-Version ab (in Microsoft.VisualBasic schon). Die App wird nur durch eine eindeutige Zeichenfolge identifiziert.
0 Stimmen
Es sieht so aus, als ob diese Frage und die Antworten eine Fülle von Informationen enthalten - eine wahre Goldgrube!