[C#] 단일 인스턴스 WPF 애플리케이션을 작성하는 올바른 방법은 무엇입니까?

Windows Forms 또는 콘솔이 아닌 .NET에서 C # 및 WPF 를 사용하여 단일 인스턴스로만 실행할 수있는 응용 프로그램을 만드는 올바른 방법은 무엇입니까?

나는 그것이 뮤텍스라는 신화적인 것과 관련이 있다는 것을 알고 있습니다. 드물게 중지하고 이들 중 하나가 무엇인지 설명하는 사람을 찾을 수는 없습니다.

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



답변

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

먼저 Microsoft.VisualBasic 어셈블리에 대한 종속성이 필요하지 않습니다. 내 프로젝트가 이미 해당 어셈블리에 종속되어 있다면 다른 답변에 표시된 접근법을 사용하여 옹호 할 것입니다 . 그러나 그대로 Microsoft.VisualBasic 어셈블리를 사용하지 않고 프로젝트에 불필요한 종속성을 추가하지 않습니다.

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


최신 정보

2014 년 8 월 1 일 현재, 위에 링크 된 기사는 여전히 활성화되어 있지만 블로그는 한동안 업데이트되지 않았습니다. 그것은 결국 그것이 사라지고 옹호 된 해결책이 될지 걱정합니다. 나는 후손을 위해 기사의 내용을 재현하고 있습니다. 단어는 Sanity Free Coding 의 블로그 소유자에게만 속합니다 .

오늘 나는 내 응용 프로그램이 여러 인스턴스를 실행하지 못하게하는 코드를 리팩토링하고 싶었습니다.

이전에는 System.Diagnostics.Process 를 사용 하여 프로세스 목록에서 myapp.exe의 인스턴스를 검색했습니다. 이것이 작동하는 동안 많은 오버 헤드가 발생하고 더 깨끗한 것을 원했습니다.

나는 이것을 위해 뮤텍스를 사용할 수 있다는 것을 알았지 만 (이전에 한 번도 해본 적이 없다) 나는 코드를 줄이고 내 인생을 단순화하기 시작했다.

내 응용 프로그램 main 클래스에서 Mutex 라는 정적을 만들었습니다 .

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

명명 된 뮤텍스를 사용하면 여러 스레드와 프로세스에서 동기화를 스택 할 수 있습니다. 이것은 내가 찾고있는 마술 일뿐입니다.

Mutex.WaitOne 에는 대기 시간을 지정하는 과부하가 있습니다. 실제로 코드를 동기화하고 싶지 않기 때문에 (현재 사용 중인지 확인하십시오) Mutex.WaitOne (Timespan timeout, bool exitContext) 매개 변수와 함께 오버로드를 사용합니다 . 기다릴 수 있으면 true를 입력하고 입력 할 수 없으면 false를 리턴합니다. 이 경우, 우리는 전혀 기다리기를 원하지 않습니다. 우리의 뮤텍스가 사용 중이라면 그것을 건너 뛰고 계속 진행하여 TimeSpan.Zero (0 밀리 초 대기)를 전달하고 exitContext를 true로 설정하여 잠금 컨텍스트를 얻기 전에 동기화 컨텍스트를 종료하십시오. 이것을 사용하여 Application.Run 코드를 다음과 같이 묶습니다.

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

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

메시지 상자를 표시하는 대신 작은 Win32를 사용하여 실행중인 인스턴스에 이미 실행 중임을 잊었다는 것을 알리는 것을 선택했습니다 (다른 모든 창의 맨 위로 가져옴). 이것을 달성하기 위해 PostMessage 를 사용 하여 모든 창에 사용자 정의 메시지를 브로드 캐스트했습니다 (사용자 정의 메시지는
실행중인 응용 프로그램에 의해 RegisterWindowMessage등록 되었습니다. 즉, 내 응용 프로그램만이 무엇인지 알 수 있습니다). 두 번째 인스턴스가 종료됩니다. 실행중인 응용 프로그램 인스턴스는 해당 알림을 수신하여 처리합니다. 이를 위해 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 클래스를 사용할 수는 있지만 인수와 그 자체를 전달하기 위해 코드를 구현해야한다는 것을 곧 알게 될 것입니다. 글쎄, Chris Sell의 책을 읽을 때 WinForms에서 프로그래밍 할 때의 트릭을 배웠습니다. . 이 트릭은 프레임 워크에서 이미 사용 가능한 로직을 사용합니다. 나는 당신에 대해 모른다. 그러나 내가 물건에 대해 배울 때 나는 프레임 워크에서 재사용 할 수있다. 그것은 일반적으로 바퀴를 재발 명하는 대신 내가 취하는 길이다. 물론 내가 원하는 모든 것을하지는 않습니다.

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을 사용하는 경우 별도의 작업을 수행 할 필요가 없습니다. 우리가 사용할 클래스는 WindowsFormsApplicationBase이며 인스턴스 관리자를 상속 한 다음 속성과 이벤트를 활용하여 단일 인스턴스를 처리합니다.

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가 시작됩니다. 보시다시피, 이벤트 인수를 통해 명령 행에 전달 된 내용을 얻을 수 있습니다. 값을 인스턴스 필드로 설정했습니다. 여기서 명령 줄을 구문 분석하거나 생성자와 Activate 메서드 호출을 통해 명령 줄을 응용 프로그램에 전달할 수 있습니다.

셋째, EntryPoint를 만들 차례입니다. 평소처럼 응용 프로그램을 새로 고치는 대신 SingleInstanceManager를 활용할 것입니다.

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 원격 인프라 (System.Remoting)를 사용하는 것입니다. Microsoft .NET Framework (버전 2.0)에는 필수 원격 기능을 캡슐화하는 WindowsFormsApplicationBase 유형이 포함되어 있습니다. 이 유형을 WPF 응용 프로그램에 통합하려면 유형을 파생해야하며 응용 프로그램 정적 진입 점 방법 Main과 WPF 응용 프로그램 응용 프로그램 유형 사이의 shim으로 사용해야합니다. shim은 응용 프로그램이 처음 시작될 때와 후속 실행이 시도 될 때를 감지하고 WPF 응용 프로그램 유형을 제어하여 시작 처리 방법을 결정합니다.

  • C # 사람들은 숨을 크게 쉬고 ‘VisualBasic DLL을 포함하고 싶지 않습니다’라는 전체를 잊어 버립니다. 이것Scott Hanselman의 말 때문에 이 꽤 많은 문제에 깨끗한 솔루션 및 더 많은 프레임 워크에 대한 당신보다 많이 알고 사람들에 의해 설계되어 있다는 사실.
  • 유용성 관점에서 볼 때 사용자가 응용 프로그램을로드하고 있고 이미 열려 있고 오류 메시지가 표시 'Another instance of the app is running. Bye'되면 매우 행복한 사용자가되지 않을 것입니다. GUI 응용 프로그램에서 해당 응용 프로그램으로 전환하고 제공된 인수를 전달해야합니다. 또는 명령 줄 매개 변수에 의미가없는 경우 최소화 된 응용 프로그램을 팝업해야합니다.

프레임 워크는 이미 이것을 지원합니다. 일부 바보는 DLL이라는 이름을 가지고 있으며 Microsoft.VisualBasic삽입되지 않았습니다.Microsoft.ApplicationUtils 그와 같은 것에 . 그것을 극복하십시오-또는 Reflector를여십시오.

팁 :이 방법을 그대로 사용하고 이미 리소스 등이있는 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 단일 인스턴스 애플리케이션 입니다.