Posts: 102
Threads: 30
Joined: Mar 2018
Is there any way to make an executable single launch/singleton, in the sense that it checks if an instance is running already, and if so that the second instance aborts loading?
Posts: 12,074
Threads: 141
Joined: Dec 2002
// script "Singleton exe - simple.cs"
/*/ role exeProgram; ifRunning run; /*/
script.single("unique-mutex-name");
dialog.show("test");
// script "Singleton exe - AppSingleInstance.cs"
/*/ role exeProgram; ifRunning run; /*/
if (script.testing) args = new[] { "test", "args" };
if (AppSingleInstance.AlreadyRunning("unique-mutex-name", args)) {
print.it("already running");
return;
}
var b = new wpfBuilder("Window").WinSize(400);
b.R.AddOkCancel();
b.End();
AppSingleInstance.Notified += a => {
print.it("AppSingleInstance.Notified", a);
b.Window.Activate();
};
if (!b.ShowDialog()) return;
/// <summary>
/// Implements "single instance process" feature.
/// This class will be added to the Au library in next version.
/// </summary>
/// <seealso cref="script.single"/>
public static class AppSingleInstance {
static Mutex _mutex;
static wnd _wNotify;
/// <summary>
/// Detects whether a process of this app is already running.
/// </summary>
/// <param name="mutex">A unique string to use for mutex name (see <see cref="Mutex(bool, string, out bool)"/>). If prefix <c>@"Global\"</c> used, detects processes in all user sessions.</param>
/// <param name="notifyArgs">
/// If not null:
/// <br/>• If already running, sends it to that process, which receives it in <see cref="Notified"/> event.
/// <br/>• Else enables <b>Notified</b> event in this process.
/// </param>
/// <param name="waitMS">Milliseconds to wait until this process can run. No timeout if -1.</param>
/// <returns>True if already running.</returns>
/// <exception cref="InvalidOperationException">This function already called.</exception>
/// <exception cref="Exception">Exceptions of <see cref="Mutex(bool, string, out bool)"/>.</exception>
public static bool AlreadyRunning(string mutex, IEnumerable<string> notifyArgs = null, int waitMS = 0) {
var m = new Mutex(true, mutex, out bool createdNew);
if (null != Interlocked.CompareExchange(ref _mutex, m, null)) { m.Dispose(); throw new InvalidOperationException(); }
if (!createdNew && waitMS != 0) {
try { createdNew = m.WaitOne(waitMS); }
catch (AbandonedMutexException) { createdNew = true; }
}
if (notifyArgs != null) {
if (createdNew) {
WndUtil.RegisterWindowClass(mutex, _WndProc);
_wNotify = WndUtil.CreateMessageOnlyWindow(mutex, "AppSingleInstance");
WndCopyData.EnableReceivingWM_COPYDATA();
} else {
var w = wnd.findFast("AppSingleInstance", mutex, messageOnly: true);
if (!w.Is0) WndCopyData.Send<char>(w, 1, string.Join('\0', notifyArgs));
}
}
return !createdNew;
}
static nint _WndProc(wnd w, int msg, nint wp, nint lp) {
if (msg == api.WM_COPYDATA) {
var x = new WndCopyData(lp);
if (x.DataId == 1) {
Notified?.Invoke(x.GetString().Split('\0'));
}
}
return api.DefWindowProc(w, msg, wp, lp);
}
/// <summary>
/// When <see cref="AlreadyRunning"/> in new process detected that this process is running.
/// Receives <i>notifyArgs</i> passed to it.
/// </summary>
/// <remarks>
/// To enable this event, call <see cref="AlreadyRunning"/> with non-null <i>notifyArgs</i>. The event handler runs in the same thread. The thread must dispatch Windows messages (for example show a window or dialog, or call <see cref="wait.doEvents(int)"/>).
/// </remarks>
public static event Action<string[]> Notified;
unsafe class api : NativeApi {
[DllImport("user32.dll", EntryPoint = "DefWindowProcW")]
internal static extern nint DefWindowProc(wnd hWnd, int Msg, nint wParam, nint lParam);
internal const int WM_COPYDATA = 0x4A;
}
}
Posts: 102
Threads: 30
Joined: Mar 2018
Thanks. That works :-).
I really appreciate this!