programing

단일 인스턴스 WPF 응용 프로그램을 만드는 올바른 방법은 무엇입니까?

abcjava 2023. 4. 16. 14:16
반응형

단일 인스턴스 WPF 응용 프로그램을 만드는 올바른 방법은 무엇입니까?

의 C# 및 WPF 사용.NET(Windows Forms 또는 콘솔이 아닌) 단일 인스턴스로만 실행할 수 있는 애플리케이션을 만드는 올바른 방법은 무엇입니까?

뮤텍스라고 불리는 신화적인 것과 관련이 있다는 것을 알고 있지만, 멈춰 서서 이것들 중 하나가 무엇인지 설명하는 사람은 좀처럼 찾을 수 없습니다.

또한 코드에서는 이미 실행 중인 인스턴스에 사용자가 두 번째 인스턴스를 시작하려고 했음을 알려야 합니다.또한 명령줄 인수가 존재하는 경우 이를 전달할 수도 있습니다.

다음은 Mutex 솔루션에 관한 매우 좋은 기사입니다.기사에서 설명한 접근법은 두 가지 이유로 유리하다.

첫째, Microsoft에 의존할 필요가 없습니다.VisualBasic 어셈블리.만약 제 프로젝트가 이미 그 어셈블리에 의존하고 있다면, 저는 아마 다른 답변에 나와 있는 접근방식을 사용할 것을 제안할 것입니다.그러나 현재로선 마이크로소프트를 사용하지 않습니다.Visual Basic 어셈블리입니다.프로젝트에 불필요한 의존관계를 추가하지 않는 것이 좋습니다.

둘째, 사용자가 다른 인스턴스를 시작하려고 할 때 응용 프로그램의 기존 인스턴스를 전면에 표시하는 방법을 보여 줍니다.이것은 여기서 설명하는 다른 Mutex 솔루션에서는 다루지 않는 매우 좋은 터치입니다.


갱신하다

2014년 8월 1일 현재, 상기와 링크 한 기사는 아직 유효합니다만, 블로그는 갱신되지 않았습니다.그것은 결국 그것이 사라질지도 모른다는 걱정과 함께, 주창된 해결책이다.나는 후세를 위해 이 기사의 내용을 여기에 재현하고 있다.이 단어는 Sanity Free Coding 블로그 소유자에게만 적용됩니다.

오늘은 어플리케이션 자체의 여러 인스턴스를 실행할 수 없도록 하는 코드를 리팩터링하려고 합니다.

이전에 System을 사용한 적이 있습니다.진단.myapp 인스턴스를 검색하는 프로세스입니다.프로세스 목록에서 exe를 선택합니다.이게 먹히긴 하지만 비용이 많이 들기 때문에 좀 더 깨끗한 걸 원했어요.

뮤텍스를 사용할 수 있다는 것을 알았기 때문에(하지만 한 번도 사용해 본 적이 없음) 코드를 줄이고 생활을 심플하게 하기 시작했습니다.

응용 프로그램 메인 클래스에서 Mutex라는 이름의 스태틱을 만들었습니다.

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

뮤텍스라는 이름을 붙이면 여러 스레드 및 프로세스 간에 동기 스택을 만들 수 있습니다.이것은 바로 제가 찾고 있는 마법입니다.

Mutex.WaitOne에는 대기시간을 지정하는 오버로드가 있습니다.실제로 코드를 동기화할 필요가 없기 때문에(현재 사용 중인지 더 자세히 확인) 두 가지 파라미터로 오버로드를 사용합니다.Mutex.WaitOne(타임 스팬 타임아웃, bool exit Context).입력할 수 있으면 true를 반환하고, 입력할 수 없으면 false를 반환합니다.이 경우 전혀 기다릴 필요가 없습니다.mutex가 사용되고 있는 경우는 생략하고 다음으로 넘어갑니다.그러면 TimeSpan이 통과됩니다.0(대기 0밀리초)을 제로로 하고 exit Context를 true로 설정합니다.이것에 의해, 동기 콘텍스트를 종료하고 나서, 잠금을 취득할 수 있습니다.이것을 사용하여 어플리케이션을 포장합니다.다음과 같은 내부에서 코드를 실행합니다.

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

앱이 실행 중이면 Wait One이 false를 반환하고 메시지 상자가 나타납니다.

메시지 상자를 표시하는 대신 작은 Win32를 사용하여 실행 중인 인스턴스에 누군가가 이미 실행 중임을 잊어버렸음을 알렸습니다(다른 모든 창의 맨 위에 표시).이를 위해 Post Message를 사용하여 모든 창에 커스텀메시지를 브로드캐스트했습니다(커스텀메시지는 실행 중인 어플리케이션에 의해 Register Window Message에 등록되었습니다.즉, 어플리케이션만이 그것이 무엇인지 알 수 있습니다).다음 인스턴스가 종료됩니다.실행 중인 응용 프로그램인스턴스는 해당 알림을 수신하여 처리합니다.그러기 위해 메인 폼에서 WndProc를 오버로드하여 커스텀 알림을 들었습니다.해당 알림을 받은 후 폼의 TopMost 속성을 true로 설정하여 맨 위에 올렸습니다.

결론은 다음과 같습니다.

  • Program.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs (전면 일부)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}

Mutex 클래스를 사용할 수도 있지만, 인수 등을 전달하기 위해 코드를 구현해야 한다는 것을 곧 알게 될 것입니다.WinForms에서 프로그래밍할 때 Chris Sell의 책을 읽었을 때 요령을 배웠습니다.이 속임수는 프레임워크에서 이미 사용할 수 있는 논리를 사용합니다.당신은 어떨지 모르겠지만, 프레임워크에서 재사용할 수 있는 것을 알게 되면, 대개는 바퀴를 재창조하는 대신 그 방법을 택합니다.물론 그게 내가 원하는 모든 것을 해주지는 않는 한.

WPF에 들어갔을 때 WPF 어플리케이션에서 같은 코드를 사용하는 방법이 떠올랐습니다.이 솔루션은 질문에 따라 고객의 요구를 충족시켜야 합니다.

먼저 어플리케이션클래스를 만들어야 합니다.이 클래스에서는 OnStartup 이벤트를 덮어쓰고 Activate라는 메서드를 만듭니다.이 메서드는 나중에 사용됩니다.

public class SingleInstanceApplication : System.Windows.Application
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        // Call the OnStartup event on our base class
        base.OnStartup(e);

        // Create our MainWindow and show it
        MainWindow window = new MainWindow();
        window.Show();
    }

    public void Activate()
    {
        // Reactivate the main window
        MainWindow.Activate();
    }
}

둘째, 인스턴스를 관리할 수 있는 클래스를 만들어야 합니다.그 전에, Microsoft 의 코드를 재이용합니다.VisualBasic 어셈블리.이 예에서는 C#을 사용하고 있기 때문에 어셈블리를 참조할 필요가 있었습니다.VB를 사용하는 경우.NET, 아무것도 할 필요가 없습니다.이 클래스는 Windows Forms Application Base로 인스턴스 매니저를 상속한 후 속성 및 이벤트를 활용하여 단일 인스턴스를 처리합니다.

public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
    private SingleInstanceApplication _application;
    private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;

    public SingleInstanceManager()
    {
        IsSingleInstance = true;
    }

    protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
    {
        // First time _application is launched
        _commandLine = eventArgs.CommandLine;
        _application = new SingleInstanceApplication();
        _application.Run();
        return false;
    }

    protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
    {
        // Subsequent launches
        base.OnStartupNextInstance(eventArgs);
        _commandLine = eventArgs.CommandLine;
        _application.Activate();
    }
}

기본적으로 VB 비트를 사용하여 단일 인스턴스를 검출하고 그에 따라 처리합니다.OnStartup은 첫 번째 인스턴스가 로드될 때 실행됩니다.OnStartupNextInstance는 응용 프로그램이 다시 실행되면 실행됩니다.보시다시피 event 인수를 통해 명령줄에서 전달된 내용을 확인할 수 있습니다.인스턴스 필드에 값을 설정합니다.여기서 명령줄을 해석하거나 컨스트럭터를 통해 응용 프로그램에 전달하고 Activate 메서드에 호출할 수 있습니다.

셋째, 엔트리 포인트를 작성할 때입니다.일반적인 애플리케이션처럼 애플리케이션을 새로 만드는 것이 아니라 Single Instance Manager를 활용합니다.

public class EntryPoint
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceManager manager = new SingleInstanceManager();
        manager.Run(args);
    }
}

모든 것을 팔로우 할 수 있고, 이 실장을 사용하고, 독자적인 것으로 할 수 있으면 좋겠습니다.

여기서부터.

교차 프로세스 Mutex의 일반적인 용도는 한 번에 프로그램의 인스턴스만 실행할 수 있도록 하는 것입니다.방법은 다음과 같습니다.

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Mutex의 좋은 기능은 ReleaseMutex가 처음 호출되지 않고 응용 프로그램이 종료되면 CLR이 자동으로 Mutex를 릴리스한다는 것입니다.

MSDN에는 실제로 C#과 VB를 위한 샘플애플리케이션이 준비되어 있습니다.http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

단일 인스턴스 검출을 개발하기 위한 가장 일반적이고 신뢰성 높은 기술은 Microsoft 를 사용하는 것입니다.NET Framework 리모트 인프라스트럭처(시스템).리모트)Microsoft 。NET Framework(버전 2.0)에는 필요한 원격 기능을 캡슐화한 Windows Forms Application Base 유형이 포함되어 있습니다.이 타입을 WPF 어플리케이션에 포함시키려면 타입이 WPF 어플리케이션에서 파생되어 어플리케이션 스태틱엔트리 포인트 메서드, Main 및 WPF 어플리케이션타입 사이에서 심으로 사용되어야 합니다.shim은 어플리케이션이 처음 기동된 시점과 그 이후의 기동 시도 시기를 검출하고 WPF 어플리케이션유형을 제어하여 기동 처리 방법을 결정합니다.

  • C#의 경우 사람들은 심호흡을 하고 'I don't want include Visual Basic DLL' 전체를 잊어버립니다.Scott Hanselman이 말한 것 때문에, 그리고 이것이 이 문제에 대한 가장 깨끗한 해결책이며, 당신보다 프레임워크에 대해 더 많이 알고 있는 사람들에 의해 설계되었다는 사실 때문에.
  • 에서 보면, 유저가 때에, 열려 에게 「어플리케이션」, 「어플리케이션」의 에러 됩니다.'Another instance of the app is running. Bye'만 하면 가 없는 .(GUI 어플리케이션에서) 해당 어플리케이션으로 전환하여 제공된 인수를 전달하기만 하면 됩니다.또는 명령줄 파라미터에 의미가 없는 경우에는 어플리케이션을 팝업하여 최소화해야 합니다.

이를 은 단지 DLL이라는 때문입니다.DLL을 사용합니다.Microsoft.VisualBasic그 안에 들어가지 않았다.Microsoft.ApplicationUtils리플렉터를 극복하거나 리플렉터를 엽니다.

힌트: 이 방법을 그대로 사용하고 있으며, 이미 App.xaml에 리소스 등이 포함되어 있는 경우.너도 이걸 봐야 할 거야

이 코드는 메인 메서드로 이동합니다.WPF의 주요 방법에 대한 자세한 내용은 여기를 참조하십시오.

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

방법 2

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

주의: 위의 방법에서는 프로세스/어플리케이션의 이름이 고유하다고 가정합니다.기존 프로세서가 있는지 확인하기 위해 프로세스 이름을 사용하기 때문입니다.따라서 응용 프로그램에 매우 일반적인 이름(메모장)이 있는 경우 위의 접근 방식은 작동하지 않습니다.

대부분의 사용 사례에서 쉽게 사용할 수 있는 일회용 클래스가 있습니다.

다음과 같이 사용합니다.

static void Main()
{
    using (SingleInstanceMutex sim = new SingleInstanceMutex())
    {
        if (sim.IsOtherInstanceRunning)
        {
            Application.Exit();
        }

        // Initialize program here.
    }
}

여기 있습니다.

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}

Mutex 및 IPC 정보를 사용하여 실행 중인 인스턴스에 명령줄 인수를 전달하는 새로운 방법은 WPF Single Instance Application입니다.

코드 C#.표시된 답변의 참조가 되는 NET Single Instance Application은 훌륭한 시작입니다.

그러나 이미 존재하는 인스턴스에서 모달 대화상자가 열려 있는 경우, 해당 대화상자가 관리 대상 대화상자(정보 상자 등 다른 양식)인지 관리 대상 대화상자(표준을 사용하는 경우에도 OpenFileDialog와 같은 관리 대상 대화상자인지)는 잘 처리되지 않습니다.NET 클래스).원래 코드를 사용하면 메인 폼이 활성화되지만, 모달 폼은 활성화되지 않은 채로 있기 때문에 이상하게 보일 수 있으며, 사용자가 클릭해야 앱을 계속 사용할 수 있습니다.

따라서 Winforms 및 WPF 응용 프로그램에서 이 모든 것을 자동으로 처리하기 위해 SingleInstance 유틸리티 클래스를 만들었습니다.

윈폼:

1) 프로그램 클래스를 다음과 같이 수정합니다.

static class Program
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);

    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

2) 다음과 같이 메인창 클래스를 변경합니다.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void WndProc(ref Message m)
    {
        // if needed, the singleton will restore this window
        Program.Singleton.OnWndProc(this, m, true);

        // TODO: handle specific messages here if needed
        base.WndProc(ref m);
    }
}

WPF:

1) 앱 페이지를 다음과 같이 수정합니다(메인 메서드를 재정의할 수 있도록 빌드 액션을 페이지로 설정해야 합니다).

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2) 다음과 같이 메인창 클래스를 변경합니다.

public partial class MainWindow : Window
{
    private HwndSource _source;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        _source = (HwndSource)PresentationSource.FromVisual(this);
        _source.AddHook(HwndSourceHook);
    }

    protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // if needed, the singleton will restore this window
        App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);

        // TODO: handle other specific message
        return IntPtr.Zero;
    }

유틸리티 클래스는 다음과 같습니다.

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SingleInstanceUtilities
{
    public sealed class SingleInstance
    {
        private const int HWND_BROADCAST = 0xFFFF;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int RegisterWindowMessage(string message);

        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        public SingleInstance(string uniqueName)
        {
            if (uniqueName == null)
                throw new ArgumentNullException("uniqueName");

            Mutex = new Mutex(true, uniqueName);
            Message = RegisterWindowMessage("WM_" + uniqueName);
        }

        public Mutex Mutex { get; private set; }
        public int Message { get; private set; }

        public void RunFirstInstance(Action action)
        {
            RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
        }

        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (WaitForMutext(wParam, lParam))
            {
                try
                {
                    action();
                }
                finally
                {
                    ReleaseMutex();
                }
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return;

            FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
        }

        public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
        {
            if (m == Message)
            {
                if (restorePlacement)
                {
                    WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                    if (placement.IsValid && placement.IsMinimized)
                    {
                        const int SW_SHOWNORMAL = 1;
                        placement.ShowCmd = SW_SHOWNORMAL;
                        placement.SetPlacement(hwnd);
                    }
                }

                if (activate)
                {
                    SetForegroundWindow(hwnd);
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                }
            }
        }

#if WINFORMS // define this for Winforms apps
        public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            if (m == Message)
            {
                if (activate)
                {
                    if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                    {
                        form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    }

                    form.Activate();
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                }
            }
        }

        public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
        }
#endif

        public void ReleaseMutex()
        {
            Mutex.ReleaseMutex();
        }

        public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
        {
            bool b = PrivateWaitForMutext(force);
            if (!b)
            {
                PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
            }
            return b;
        }

        public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
        {
            return WaitForMutext(false, wParam, lParam);
        }

        private bool PrivateWaitForMutext(bool force)
        {
            if (force)
                return true;

            try
            {
                return Mutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                return true;
            }
        }
    }

    // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
    [StructLayout(LayoutKind.Sequential)]
    public struct WindowPlacement
    {
        public int Length { get; set; }
        public int Flags { get; set; }
        public int ShowCmd { get; set; }
        public int MinPositionX { get; set; }
        public int MinPositionY { get; set; }
        public int MaxPositionX { get; set; }
        public int MaxPositionY { get; set; }
        public int NormalPositionLeft { get; set; }
        public int NormalPositionTop { get; set; }
        public int NormalPositionRight { get; set; }
        public int NormalPositionBottom { get; set; }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        private const int SW_SHOWMINIMIZED = 2;

        public bool IsMinimized
        {
            get
            {
                return ShowCmd == SW_SHOWMINIMIZED;
            }
        }

        public bool IsValid
        {
            get
            {
                return Length == Marshal.SizeOf(typeof(WindowPlacement));
            }
        }

        public void SetPlacement(IntPtr windowHandle)
        {
            SetWindowPlacement(windowHandle, ref this);
        }

        public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
        {
            WindowPlacement placement = new WindowPlacement();
            if (windowHandle == IntPtr.Zero)
                return placement;

            placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
            if (!GetWindowPlacement(windowHandle, ref placement))
            {
                if (throwOnError)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                return new WindowPlacement();
            }
            return placement;
        }
    }

    public static class FormUtilities
    {
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetActiveWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentThreadId();

        private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);

        private class ModalWindowUtil
        {
            private const int GW_OWNER = 4;
            private int _maxOwnershipLevel;
            private IntPtr _maxOwnershipHandle;

            private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
            {
                int level = 1;
                if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                {
                    if (level > _maxOwnershipLevel)
                    {
                        _maxOwnershipHandle = hwnd;
                        _maxOwnershipLevel = level;
                    }
                }
                return true;
            }

            private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
            {
                IntPtr o = GetWindow(hwnd, GW_OWNER);
                if (o == IntPtr.Zero)
                    return false;

                if (o == owner)
                    return true;

                level++;
                return IsOwned(owner, o, ref level);
            }

            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd != IntPtr.Zero)
                {
                    SetActiveWindow(hwnd);
                }
            }

            public static IntPtr GetModalWindow(IntPtr owner)
            {
                ModalWindowUtil util = new ModalWindowUtil();
                EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                return util._maxOwnershipHandle; // may be IntPtr.Zero
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            ModalWindowUtil.ActivateWindow(hwnd);
        }

        public static IntPtr GetModalWindow(IntPtr owner)
        {
            return ModalWindowUtil.GetModalWindow(owner);
        }
    }
}

몇 가지 생각:어플리케이션의 인스턴스가1개만 '레임'이 아니어야 하는 경우가 있습니다.데이터베이스 앱 등은 단일 사용자가 데이터베이스에 액세스할 수 있도록 앱의 여러 인스턴스를 허용하는 경우(사용자 머신에서 앱의 여러 인스턴스에서 열려 있는 모든 레코드를 업데이트하는 경우 등) 훨씬 더 어렵습니다.우선 이름 충돌에 대해서는 사람이 읽을 수 있는 이름을 사용하지 말고 GUID를 사용하거나 GUID + 사람이 읽을 수 있는 이름을 사용하는 것이 좋습니다.이름 충돌의 가능성이 레이더에서 사라졌고 뮤텍스는 신경 안 써누군가가 지적했듯이, DOS 공격은 형편없지만, 만약 악의적인 사람이 mutex 이름을 얻어서 그들의 앱에 포함시키는 수고를 했다면, 당신은 어쨌든 거의 목표물이고, 단지 mutex 이름을 조작하는 것 이상의 많은 것을 자신을 보호해야 할 것이다.또한 new Mutex(true, "some GUID plus Name", out AIs First Instance)의 변형을 사용하는 경우 Mutex가 첫 번째 인스턴스인지 여부를 나타내는 표시기가 이미 있습니다.

다음은 단일 응용 프로그램 인스턴스를 사용할 수 있는 예입니다.새로운 인스턴스가 로드되면 해당 인스턴스는 실행 중인 메인인스턴스로 인수를 전달합니다.

public partial class App : Application
{
    private static Mutex SingleMutex;
    public static uint MessageId;

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        IntPtr Result;
        IntPtr SendOk;
        Win32.COPYDATASTRUCT CopyData;
        string[] Args;
        IntPtr CopyDataMem;
        bool AllowMultipleInstances = false;

        Args = Environment.GetCommandLineArgs();

        // TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
        MessageId   = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
        SingleMutex = new Mutex(false, "AppName");

        if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
        {
            new Main();
        }
        else if (Args.Length > 1)
        {
            foreach (Process Proc in Process.GetProcesses())
            {
                SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    2000, out Result);

                if (SendOk == IntPtr.Zero)
                    continue;
                if ((uint)Result != MessageId)
                    continue;

                CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));

                CopyData.dwData = IntPtr.Zero;
                CopyData.cbData = Args[1].Length*2;
                CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);

                Marshal.StructureToPtr(CopyData, CopyDataMem, false);

                Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    5000, out Result);

                Marshal.FreeHGlobal(CopyData.lpData);
                Marshal.FreeHGlobal(CopyDataMem);
            }

            Shutdown(0);
        }
    }
}

public partial class Main : Window
{
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        HwndSource Source;

        Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        Source.AddHook(new HwndSourceHook(Window_Proc));
    }

    private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
    {
        Win32.COPYDATASTRUCT CopyData;
        string Path;

        if (Msg == Win32.WM_COPYDATA)
        {
            CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
            Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);

            if (WindowState == WindowState.Minimized)
            {
                // Restore window from tray
            }

            // Do whatever we want with information

            Activate();
            Focus();
        }

        if (Msg == App.MessageId)
        {
            Handled = true;
            return new IntPtr(App.MessageId);
        }

        return IntPtr.Zero;
    }
}

public class Win32
{
    public const uint WM_COPYDATA = 0x004A;

    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int    cbData;
        public IntPtr lpData;
    }

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL             = 0x0000,
        SMTO_BLOCK              = 0x0001,
        SMTO_ABORTIFHUNG        = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }

    [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern uint RegisterWindowMessage(string lpString);
    [DllImport("user32.dll")]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
        SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}

다음 코드는 단일 인스턴스 응용 프로그램을 등록하기 위한 WCF 명명 파이프 솔루션입니다.또한 다른 인스턴스가 시작을 시도할 때 이벤트를 발생시키고 다른 인스턴스의 명령줄을 수신하므로 좋습니다.

WPF를 .WPFSystem.Windows.StartupEventHandler을 사용하다

에는 이음음음음 a this this requires this this this음 to to to to to to to to to 에 대한 참조가 필요합니다.PresentationFramework , , , , 입니다.System.ServiceModel.

사용방법:

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.
    }
}

소스 코드:

/// <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);
      }
    }
  }
}

그렇게 간단해 보이는 질문에 대한 많은 답변들이 있다.이 문제에 대한 저의 해결방법은 이 문제를 조금이나마 일깨우는 것입니다.

Mutex를 작성하면 JITer는 사용자가 코드의 일부에 대해서만 Mutex를 사용하고 있음을 인식하고 가비지 수집에 사용할 수 있는 것으로 표시하려고 하므로 문제가 발생할 수 있습니다.Mutex를 그렇게 오랫동안 사용하지 않을 거라고 생각하는 당신을 능가하고 싶어합니다.실제로는 어플리케이션이 실행되고 있는 한 이 Mutex를 계속 사용하고 싶다고 생각하고 있습니다.쓰레기 수집가에게 Mutex를 혼자 두라고 말하는 가장 좋은 방법은 차고 수집의 다른 세대에도 불구하고 계속 작동시키라고 말하는 것입니다.예:

var m = new Mutex(...);
...
GC.KeepAlive(m);

http://www.ai.uga.edu/~mc/SingleInstance.html 라는 페이지에서 아이디어를 얻었습니다.

이 문제를 해결할 수 있는 정말 좋은 방법이 있는 것 같습니다.

WPF 싱글인스턴스 응용 프로그램

이를 통해 모든 뮤텍스 및 메시징 크러프를 관리하는 클래스를 추가할 수 있으므로 구현이 매우 간단해집니다.

다음 코드를 보세요.이는 WPF 애플리케이션의 여러 인스턴스를 방지하는 매우 간단하고 뛰어난 솔루션입니다.

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process thisProc = Process.GetCurrentProcess();
    if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
    {
        MessageBox.Show("Application running");
        Application.Current.Shutdown();
        return;
    }

    var wLogin = new LoginWindow();

    if (wLogin.ShowDialog() == true)
    {
        var wMain = new Main();
        wMain.WindowState = WindowState.Maximized;
        wMain.Show();
    }
    else
    {
        Application.Current.Shutdown();
    }
}

단, Mutex를 사용하지 않는 간단한 답변:

System.Diagnostics;    
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

the the the .Program.Main().
:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //simple add Diagnostics namespace, and these 3 lines below 
            string thisprocessname = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Sample());
        }
    }
}

해서 더하면 요.MessageBox.Showif-' 실행 중.- '어플리케이션 이미 실행 중'이라고 적습니다.
을 사용하다

이게 제가 쓰는 거예요.프로세스 열거를 조합하여 스위칭을 수행하고 뮤텍스를 사용하여 "액티브 클릭커"로부터 보호합니다.

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }

데일 레이건과 비슷하지만 약간 변형된 간단한 해결책을 찾았습니다.표준 Microsoft Windows Forms Application Base 클래스를 기반으로 필요한 모든 기능을 제공합니다.

먼저 SingleInstanceController 클래스를 만듭니다.이 클래스는 Windows Forms를 사용하는 다른 모든 싱글인스턴스 응용 프로그램에서 사용할 수 있습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;


namespace SingleInstanceController_NET
{
    public class SingleInstanceController
    : WindowsFormsApplicationBase
    {
        public delegate Form CreateMainForm();
        public delegate void StartNextInstanceDelegate(Form mainWindow);
        CreateMainForm formCreation;
        StartNextInstanceDelegate onStartNextInstance;
        public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
        {
            // Set whether the application is single instance
            this.formCreation = formCreation;
            this.onStartNextInstance = onStartNextInstance;
            this.IsSingleInstance = true;

            this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);                      
        }

        void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
        {
            if (onStartNextInstance != null)
            {
                onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
                                                    // for example, by clicking on the exe file.
            }                                       // This code can determine how to re-activate the existing main window of the running application.
        }

        protected override void OnCreateMainForm()
        {
            // Instantiate your main application form
            this.MainForm = formCreation();
        }

        public void Run()
        {
            string[] commandLine = new string[0];
            base.Run(commandLine);
        }
    }
}

그런 다음 프로그램에서 다음과 같이 사용할 수 있습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;

namespace SingleInstance
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static Form CreateForm()
        {
            return new Form1(); // Form1 is used for the main window.
        }

        static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
                                                         // the main window is activated again.
        {
            mainWindow.WindowState = FormWindowState.Maximized;
        }
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);            
            SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
            controller.Run();         
        }
    }
}

프로그램과 Single Instance Controller 모두NET 솔루션은 Microsoft 를 참조해 주세요.[ Visual Basic ]를 선택합니다.사용자가 실행 중인 프로그램을 재시작하려고 할 때 실행 중인 응용 프로그램을 일반 창으로 다시 활성화하는 경우 SingleInstanceController의 두 번째 파라미터는 null일 수 있습니다.이 예에서는 창이 최대화되어 있습니다.

단일 인스턴스 응용 프로그램을 구현하기 위해 이름 있는 뮤텍스를 사용하지 마십시오(적어도 프로덕션 코드에는 사용하지 마십시오).악성코드는 쉽게 DoS(서비스 거부)할 수 있습니다.

2017-01-25 업데이트.몇 가지 시도 끝에 Visual Basic.dll을 선택하기로 했습니다(적어도 저에게는 더 쉽고 더 잘 작동합니다).방금 전 답변은 참고용으로...

참고로, 저는 (그럴 이유를 찾을 수 없는) 변론 없이 이렇게 했습니다.하나의 인스턴스에서 다른 인스턴스로 전달되는 인수를 가진 단일 앱을 말합니다.파일 연결이 필요한 경우 각 문서에 대해 (사용자의 표준 예상에 따라) 앱을 설치해야 합니다.기존 앱에 arg를 전달해야 한다면 vb dll을 사용할 것 같습니다.

args(단일 인스턴스 앱)를 전달하지 않고 Matt Davis 솔루션에서 정의한 메시지 루프를 덮어쓰지 않고 새로운 Window 메시지를 등록하지 않는 것이 좋습니다.Visual Basic dll을 추가하는 것은 큰 문제가 되지 않지만, 단일 인스턴스 앱을 수행하기 위해 새로운 참조를 추가하는 것은 선호하지 않습니다.또한 앱에서 Shutdown을 호출하는 대신 Main을 사용하여 새 클래스를 설치하는 것이 좋습니다.가능한 한 빨리 종료하기 위해 시작 오버라이드.

누구나 좋아했으면 좋겠다고...또는 조금 영감을 줄 것이다:-)

프로젝트 시작 클래스는 'SingleInstanceApp'으로 설정해야 합니다.

public class SingleInstanceApp
{
    [STAThread]
    public static void Main(string[] args)
    {
        Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");

        if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();

            }
            finally
            {
                _mutexSingleInstance.ReleaseMutex();
                _mutexSingleInstance.Close();
            }
        }
        else
        {
            MessageBox.Show("One instance is already running.");

            var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
            {
                if (processes.Length > 1)
                {
                    foreach (var process in processes)
                    {
                        if (process.Id != Process.GetCurrentProcess().Id)
                        {
                            WindowHelper.SetForegroundWindow(process.MainWindowHandle);
                        }
                    }
                }
            }
        }
    }
}

Window 도움말:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace HQ.Util.Unmanaged
{
    public class WindowHelper
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

이름 있는 뮤텍스는 Mono에서는 글로벌하지 않기 때문에 이름 있는 뮤텍스 기반 접근법은 크로스 플랫폼이 아닙니다.프로세스 열거 기반의 접근법에서는 동기화가 이루어지지 않아 잘못된 동작이 발생할 수 있습니다(예를 들어 타이밍에 따라 여러 프로세스가 동시에 시작되는 등 모든 프로세스가 자동으로 종료될 수 있습니다).콘솔 어플리케이션에서는 윈도 시스템 기반의 접근방식은 바람직하지 않습니다.Divin의 답변에 기초하여 구축된 이 솔루션은 다음과 같은 모든 문제를 해결합니다.

using System;
using System.IO;

namespace TestCs
{
    public class Program
    {
        // The app id must be unique. Generate a new guid for your application. 
        public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";

        // The stream is stored globally to ensure that it won't be disposed before the application terminates.
        public static FileStream UniqueInstanceStream;

        public static int Main(string[] args)
        {
            EnsureUniqueInstance();

            // Your code here.

            return 0;
        }

        private static void EnsureUniqueInstance()
        {
            // Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
            string lockDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                "UniqueInstanceApps");
            string lockPath = Path.Combine(lockDir, $"{AppId}.unique");

            Directory.CreateDirectory(lockDir);

            try
            {
                // Create the file with exclusive write access. If this fails, then another process is executing.
                UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);

                // Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
                // (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
                UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
                UniqueInstanceStream.Flush();
            }
            catch
            {
                throw new Exception("Another instance of the application is already running.");
            }
        }
    }
}

[콘솔 및 wpf 어플리케이션의 샘플 코드를 아래에 기재했습니다.]

.createdNew이름 있는 Mutex 인스턴스를 만든 후 변수(아래 예!)를 선택합니다.

" "createdNewfalse가 반환됩니다.

"Your ApplicationNameHere"라는 이름의 Mutex 인스턴스가 시스템 어딘가에서 이미 생성되어 있는 경우

부울createdNewtrue가 반환됩니다.

이것이 시스템상의 첫 번째 Mutex "Your ApplicationNameHere"인 경우.


콘솔 응용 프로그램 - 예:

static Mutex m = null;

static void Main(string[] args)
{
    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        using (m = new Mutex(true, mutexName, out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("instance is alreday running... shutting down !!!");
                Console.Read();
                return; // Exit the application
            }

            // Run your windows forms app here
            Console.WriteLine("Single instance app is running!");
            Console.ReadLine();
        }


    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

WPF - 예:

public partial class App : Application
{
static Mutex m = null;

protected override void OnStartup(StartupEventArgs e)
{

    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        m = new Mutex(true, mutexName, out createdNew);

        if (!createdNew)
        {
            Current.Shutdown(); // Exit the application
        }

    }
    catch (Exception)
    {
        throw;
    }

    base.OnStartup(e);
}


protected override void OnExit(ExitEventArgs e)
{
    if (m != null)
    {
        m.Dispose();
    }
    base.OnExit(e);
}
}

여러 인스턴스를 방지하기 위해 솔루션에서 Mutex를 사용합니다.

static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = @"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);

if (!onlyInstance)
{
  MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
  Application.Current.Shutdown();
}

여기에서는 간단한 해결책을 찾을 수 없기 때문에, 누군가가 이것을 마음에 들어 해 주었으면 합니다.

2018-09-20 갱신

이 코드를 입력해 주세요.Program.cs:

using System.Diagnostics;

static void Main()
{
    Process thisProcess = Process.GetCurrentProcess();
    Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
    if (allProcesses.Length > 1)
    {
        // Don't put a MessageBox in here because the user could spam this MessageBox.
        return;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to users that the name got changed and maybe what the name should be or something like that^^ 
        MessageBox.Show("The executable name should be \"the name of your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

뮤텍스 솔루션 사용:

using System;
using System.Windows.Forms;
using System.Threading;

namespace OneAndOnlyOne
{
static class Program
{
    static String _mutexID = " // generate guid"
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Boolean _isNotRunning;
        using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
        {
            if (_isNotRunning)
            {
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("An instance is already running.");
                return;
            }
        }
    }
}
}

Native Methods 클래스에 send Message 메서드를 추가했습니다.

태스크바에 어플리케이션이 표시되지 않으면 포스트메시지 메서드가 기능하지만 sendmessage 메서드를 사용하면 문제가 해결됩니다.

class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}

여기 제가 사용하는 경량 솔루션이 있습니다.이 솔루션은 커스텀 윈도 메시지에 의존하거나 프로세스 이름을 무작정 검색하지 않고 기존 창을 전면에 표시할 수 있습니다.

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

static readonly string guid = "<Application Guid>";

static void Main()
{
    Mutex mutex = null;
    if (!CreateMutex(out mutex))
        return;

    // Application startup code.

    Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}

static bool CreateMutex(out Mutex mutex)
{
    bool createdNew = false;
    mutex = new Mutex(false, guid, out createdNew);

    if (createdNew)
    {
        Process process = Process.GetCurrentProcess();
        string value = process.Id.ToString();

        Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
    }
    else
    {
        string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
        Process process = null;
        int processId = -1;

        if (int.TryParse(value, out processId))
            process = Process.GetProcessById(processId);

        if (process == null || !SetForegroundWindow(process.MainWindowHandle))
            MessageBox.Show("Unable to start application. An instance of this application is already running.");
    }

    return createdNew;
}

편집: mutex 및 createdNew를 정적으로 저장 및 초기화할 수도 있지만 작업이 끝나면 mutex를 명시적으로 폐기/해제해야 합니다.개인적으로는 Main이 종료되지 않고 어플리케이션이 종료되어도 자동으로 폐기되므로 mutex를 로컬로 유지하는 것이 좋습니다.

다음은 Event를 통해 구현된 동일한 내용입니다.

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}

이렇게 해서 저는 이 문제를 해결하게 되었습니다.테스트용 디버깅코드는 아직 들어가 있습니다.이 코드는 App.xaml.cs 파일의 OnStartup 내에 있습니다(WPF).

        // Process already running ? 
        if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
        {

            // Show your error message
            MessageBox.Show("xxx is already running.  \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);

            // This process 
            Process currentProcess = Process.GetCurrentProcess();

            // Get all processes running on the local computer.
            Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);

            // ID of this process... 
            int temp = currentProcess.Id;
            MessageBox.Show("This Process ID:  " + temp.ToString());

            for (int i = 0; i < localAll.Length; i++)
            {
                // Find the other process 
                if (localAll[i].Id != currentProcess.Id)
                {
                    MessageBox.Show("Original Process ID (Switching to):  " + localAll[i].Id.ToString());

                    // Switch to it... 
                    SetForegroundWindow(localAll[i].MainWindowHandle);

                }
            }

            Application.Current.Shutdown();

        }

아직 파악하지 못한 문제가 있을 수 있습니다.어떤 것이라도 발견되면 답변을 갱신하겠습니다.

C# Winforms를 위한 시간 절약 솔루션...

Program.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}

세마포를 사용하여 기존 인스턴스가 이미 실행 중인지, WPF 응용 프로그램에서 동작하는지, TcpListener 및 TcpClient를 사용하여 두 번째 인스턴스에서 이미 실행 중인 첫 번째 인스턴스로 인수를 전달할 수 있는지 여부를 확인하려면 여기서 제안된 솔루션을 확인하십시오.

에서도 동작합니다.NET Core, 만이 아닙니다.NET 프레임워크

언급URL : https://stackoverflow.com/questions/19147/what-is-the-correct-way-to-create-a-single-instance-wpf-application

반응형