วิธีที่ถูกต้องในการสร้างแอปพลิเคชัน WPF อินสแตนซ์เดียวคืออะไร


657

การใช้ C # และ WPF ภายใต้. NET (แทนที่จะเป็นWindows Formsหรือ console) วิธีที่ถูกต้องในการสร้างแอปพลิเคชั่นที่สามารถเรียกใช้เป็นเพียงอินสแตนซ์เดียวคืออะไร?

ฉันรู้ว่ามันมีบางอย่างเกี่ยวข้องกับบางสิ่งในตำนานที่เรียกว่า mutex ฉันแทบจะไม่สามารถหาคนที่รบกวนและหยุดอธิบายสิ่งเหล่านี้ได้

รหัสจำเป็นต้องแจ้งให้ทราบถึงอินสแตนซ์ที่กำลังทำงานอยู่ซึ่งผู้ใช้พยายามเริ่มต้นครั้งที่สองและอาจส่งผ่านอาร์กิวเมนต์บรรทัดคำสั่งใด ๆ หากมีอยู่


14
CLR จะไม่ปล่อย mutex ที่ไม่ได้เผยแพร่ใด ๆ โดยอัตโนมัติหรือไม่เมื่อแอปพลิเคชันสิ้นสุดลง
Cocowalla

1
@Cocowalla: finalizer ควรกำจัด mutexes ที่ไม่มีการจัดการเว้นแต่จะไม่ทราบว่า mutex นั้นถูกสร้างขึ้นโดยแอพที่มีการจัดการหรือแนบมากับอันที่มีอยู่แล้ว
Ignacio Soler Garcia

การมีแอพของคุณเพียงตัวอย่างเดียวนั้นสมเหตุสมผล แต่การส่งผ่านข้อโต้แย้งไปยังแอพที่มีอยู่แล้วนั้นดูเหมือนจะโง่ไปหน่อย ฉันไม่เห็นเหตุผลที่จะทำเช่นนั้น หากคุณเชื่อมโยงแอปกับนามสกุลไฟล์คุณควรเปิดแอพได้มากเท่าที่ผู้ใช้ต้องการเปิดเอกสาร นั่นเป็นพฤติกรรมมาตรฐานที่ผู้ใช้ทุกคนคาดหวัง
Eric Ouellet

9
@Cocowalla CLR ไม่ได้จัดการทรัพยากรดั้งเดิม อย่างไรก็ตามหากกระบวนการยุติการจัดการทั้งหมดจะได้รับการปลดปล่อยจากระบบ (ระบบปฏิบัติการไม่ใช่ CLR)
IIsspectable

1
ฉันชอบคำตอบของ @huseyint มันใช้คลาส 'SingleInstance.cs' ของ Microsoft ดังนั้นคุณไม่ต้องกังวลกับ Mutexes และ IntPtrs นอกจากนี้ยังไม่มีการพึ่งพา VisualBasic (yuk) ดูcodereview.stackexchange.com/questions/20871/สำหรับข้อมูลเพิ่มเติม ...
Heliac

คำตอบ:


537

นี่เป็นบทความที่ดีมากเกี่ยวกับโซลูชัน Mutex แนวทางที่อธิบายโดยบทความนี้มีข้อดีสองประการ

ครั้งแรกมันไม่จำเป็นต้องพึ่งพาการชุมนุม Microsoft.VisualBasic หากโครงการของฉันแล้วมีการพึ่งพาการชุมนุมว่าผมอาจจะสนับสนุนการใช้วิธีการที่แสดงในคำตอบอื่น แต่ตามที่เป็นอยู่ฉันไม่ได้ใช้แอสเซมบลี Microsoft.VisualBasic และฉันไม่ควรเพิ่มการพึ่งพาที่ไม่จำเป็นในโครงการของฉัน

ประการที่สองบทความแสดงวิธีนำอินสแตนซ์ที่มีอยู่ของแอปพลิเคชันมาสู่เบื้องหน้าเมื่อผู้ใช้พยายามเริ่มอินสแตนซ์อื่น นั่นเป็นเรื่องที่น่ายินดีมากที่โซลูชัน Mutex อื่น ๆ ที่อธิบายไว้ที่นี่ไม่ได้กล่าวถึง


UPDATE

ตั้งแต่วันที่ 8/8/2557 บทความที่ฉันลิงก์ไว้ข้างต้นยังคงทำงานอยู่ แต่บล็อกยังไม่ได้รับการอัปเดตในอีกสักครู่ นั่นทำให้ฉันกังวลว่าในที่สุดมันก็อาจหายไปและด้วยวิธีแก้ปัญหาที่ได้รับการสนับสนุน ฉันทำซ้ำเนื้อหาของบทความที่นี่เพื่อลูกหลาน คำว่าเป็น แต่เพียงผู้เดียวให้กับเจ้าของบล็อกที่สติฟรี Coding

วันนี้ฉันต้องการ refactor รหัสบางอย่างที่ทำให้แอปพลิเคชันของฉันไม่ทำงานหลายอินสแตนซ์ของตัวเอง

ก่อนหน้านี้ฉันเคยใช้System.Diagnostics.Processเพื่อค้นหาอินสแตนซ์ของ myapp.exe ของฉันในรายการกระบวนการ ในขณะที่ใช้งานได้ก็จะทำให้เกิดค่าใช้จ่ายจำนวนมากและฉันต้องการสิ่งที่สะอาด

รู้ว่าฉันสามารถใช้ mutex สำหรับสิ่งนี้ (แต่ไม่เคยทำมันมาก่อน) ฉันตั้งใจจะตัดรหัสของฉันและทำให้ชีวิตของฉันง่ายขึ้น

ในชั้นเรียนของแอปพลิเคชันหลักของฉันฉันสร้างชื่อMutexแบบคงที่:

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

การมีชื่อ mutex ทำให้เราสามารถทำการซิงโครไนซ์ข้ามหลายเธรดและกระบวนการซึ่งเป็นเพียงความมหัศจรรย์ที่ฉันกำลังมองหา

Mutex.WaitOneมีโอเวอร์โหลดที่ระบุจำนวนเวลาที่เราต้องรอ เนื่องจากเราไม่ได้ต้องการที่จะทำข้อมูลให้ตรงกันรหัสของเรา (เพิ่มเติมเพียงตรวจสอบถ้ามันเป็นที่ใช้ในปัจจุบัน) ที่เราใช้เกินที่มีสองพารามิเตอร์: Mutex.WaitOne (ช่วงเวลาการหมดเวลาบูล exitContext) รอสักครู่ส่งคืนจริงถ้ามันสามารถป้อนและเท็จถ้ามันไม่ได้ ในกรณีนี้เราไม่ต้องการรอเลย หากมีการใช้ mutex ของคุณให้ข้ามและย้ายไปดังนั้นเราจะส่งผ่าน TimeSpan.Zero (รอ 0 มิลลิวินาที) และตั้ง exitContext เป็นจริงเพื่อให้เราสามารถออกจากบริบทการซิงค์ก่อนที่เราจะพยายามล็อคมัน เมื่อใช้สิ่งนี้เราจะห่อโค้ดแอปพลิเคชันของเราให้ทำงานภายในสิ่งนี้:

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 จะส่งคืนค่าเท็จและเราจะได้รับกล่องข้อความ

แทนที่จะแสดงกล่องข้อความฉันเลือกใช้ 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;
    }
}

5
บนพื้นฐานที่ว่าคำตอบนี้ใช้โค้ดน้อยลงและมีไลบรารีน้อยลงและมีการเพิ่มฟังก์ชันการทำงานสูงสุดฉันจะทำให้นี่เป็นคำตอบที่ยอมรับใหม่ หากใครรู้วิธีที่ถูกต้องมากขึ้นที่จะนำแบบฟอร์มขึ้นไปด้านบนโดยใช้ API โปรดเพิ่มข้อมูลนั้น
Nidonocu

11
@BlueRaja คุณเริ่มต้นแอปอินสแตนซ์แรก เมื่อคุณเปิดใช้งานอินสแตนซ์แอปที่สองตรวจพบว่าอินสแตนซ์อื่นกำลังทำงานอยู่และเตรียมการปิดระบบ ก่อนที่จะทำเช่นนั้นจะส่งข้อความดั้งเดิม "SHOWME" ไปที่อินสแตนซ์แรกซึ่งนำอินสแตนซ์แรกไปด้านบน เหตุการณ์ใน. NET ไม่อนุญาตให้มีการสื่อสารข้ามกระบวนการซึ่งเป็นสาเหตุที่ใช้ข้อความดั้งเดิม
Matt Davis

7
มีวิธีส่งบรรทัดคำสั่งจากอินสแตนซ์อื่นหรือไม่?
gyurisc

22
@ ชื่อตัวMutexสร้างต้องการเพียงแค่สตริงดังนั้นคุณสามารถระบุชื่อสตริงใด ๆ ที่คุณต้องการเช่น "นี่คือ Mutex ของฉัน" เนื่องจาก 'Mutex' เป็นวัตถุระบบที่พร้อมใช้งานสำหรับกระบวนการอื่น ๆ โดยทั่วไปคุณต้องการชื่อที่ไม่ซ้ำกันดังนั้นจึงไม่ขัดแย้งกับชื่อ 'Mutex' อื่น ๆ ในระบบเดียวกัน ในบทความสตริงที่ดูเป็นความลับคือ 'Guid' System.Guid.NewGuid()คุณสามารถสร้างโปรแกรมนี้โดยการโทร ในกรณีของบทความผู้ใช้อาจสร้างมันผ่าน Visual Studio ดังที่แสดงที่นี่: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis

6
วิธีการ mutex สันนิษฐานว่าผู้ใช้รายเดียวกันกำลังพยายามเริ่มแอปพลิเคชันอีกครั้งหรือไม่ แน่นอนนำ "อินสแตนซ์ที่มีอยู่ของแอปพลิเคชันไปยังเบื้องหน้า" ไม่สมเหตุสมผลหลังจาก 'ผู้ใช้สวิตช์'
dumbledad

107

คุณสามารถใช้คลาส Mutex แต่ในไม่ช้าคุณจะพบว่าคุณจะต้องใช้รหัสเพื่อส่งผ่านข้อโต้แย้งและตัวคุณเอง ดีฉันได้เรียนรู้เคล็ดลับการเขียนโปรแกรมในเมื่อ 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 จะทำงานเมื่อมีการเรียกใช้แอปพลิเคชันอีกครั้ง อย่างที่คุณเห็นฉันสามารถไปถึงสิ่งที่ผ่านไปบนบรรทัดคำสั่งผ่านอาร์กิวเมนต์ของเหตุการณ์ ฉันตั้งค่าเป็นเขตข้อมูลอินสแตนซ์ คุณสามารถแยกบรรทัดคำสั่งที่นี่หรือคุณสามารถส่งไปยังแอปพลิเคชันของคุณผ่านตัวสร้างและการเรียกไปยังวิธีการเปิดใช้งาน

ประการที่สามได้เวลาสร้างจุดเริ่มต้นของเราแล้ว แทนที่จะสมัครแอปพลิเคชันใหม่ตามปกติคุณจะใช้ประโยชน์จาก SingleInstanceManager ของเรา

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

ฉันหวังว่าคุณจะสามารถติดตามทุกอย่างและสามารถใช้การดำเนินการนี้และทำให้เป็นของคุณเอง


9
ฉันจะยึดติดกับโซลูชัน mutex เพราะมันไม่เกี่ยวข้องกับรูปแบบ
สตีเว่น Sudit

1
ฉันเคยใช้สิ่งนี้เพราะฉันมีปัญหาเกี่ยวกับวิธีการอื่น ๆ แต่ฉันค่อนข้างแน่ใจว่ามันใช้ระยะไกลภายใต้ประทุน แอพของฉันมีปัญหาที่เกี่ยวข้องสองประการ - ลูกค้าบางคนบอกว่าพยายามโทรหาที่บ้านถึงแม้ว่าพวกเขาจะไม่บอกก็ตาม เมื่อดูอย่างละเอียดยิ่งขึ้นการเชื่อมต่อคือ localhost ถึงกระนั้นพวกเขาไม่รู้ในตอนแรกว่า นอกจากนี้ฉันไม่สามารถใช้การควบคุมระยะไกลเพื่อจุดประสงค์อื่น (ฉันคิดว่า?) เพราะมันถูกใช้เพื่อการนี้แล้ว เมื่อฉันลองใช้วิธี mutex ฉันก็สามารถใช้การควบคุมระยะไกลได้อีกครั้ง
Richard Watson

4
ยกโทษให้ฉัน แต่ถ้าฉันไม่มีอะไรหายไปคุณหลีกเลี่ยงการเขียนโค้ด 3 บรรทัดและคุณใช้เฟรมเวิร์กอีกครั้งเพื่อเขียนโค้ดที่ค่อนข้างหนักเพื่อที่จะทำ ดังนั้นการออมอยู่ที่ไหน
greenoldman

2
เป็นไปได้ทำใน winforms?
แจ็ค

1
หากคุณไม่โทร InitializeComponent () ในอินสแตนซ์ของแอปพลิเคชันคุณจะไม่สามารถแก้ไขทรัพยากร ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick

84

จากที่นี่

การใช้งานร่วมกันสำหรับ 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 โดยอัตโนมัติ


5
ฉันต้องบอกว่าฉันชอบคำตอบนี้มากกว่าคำตอบที่ยอมรับได้เพียงเพราะความจริงที่ว่ามันไม่ได้ขึ้นอยู่กับ WinForms โดยส่วนตัวแล้วการพัฒนาของฉันส่วนใหญ่ย้ายไปที่ WPF และฉันไม่ต้องการดึงไลบรารี WinForm สำหรับสิ่งนี้
Switters

5
ของหลักสูตรที่จะเป็นคำตอบที่เต็มรูปแบบที่คุณต้องยังอธิบายถึงผ่านการขัดแย้งกับอินสแตนซ์อื่น ๆ :)
ไซมอนชัน

@ Jason ดีขอบคุณ! แต่ฉันไม่ชอบที่จะหมดเวลา มันเป็นอัตวิสัยมากและขึ้นอยู่กับตัวแปรมากมาย หากคุณต้องการเปิดใช้งานแอปอื่นให้เริ่มเพียงปล่อย mutex ของคุณเร็วขึ้น .. เช่นทันทีที่ผู้ใช้ยืนยันการปิด
Eric Ouellet

@EricOuellet: ทุกโปรแกรมที่มีแท็บทำเช่นนี้ - Photoshop, Sublime Text, Chrome .... ถ้าคุณมีเหตุผลที่ดีที่จะมีกระบวนการ "หลัก" (บอกว่าคุณมีฐานข้อมูลใน proc สำหรับการตั้งค่า) คุณอาจ ต้องการให้มันแสดง UI ราวกับว่ามันเป็นกระบวนการใหม่เกินไป
Simon Buchan

@Simon คุณพูดถูก ฉันแค่ถามตัวเองเกี่ยวกับสิ่งที่เก่ามาก ... MDI vs SDI (อินเทอร์เฟซเอกสารหลายหน้าเทียบกับอินเตอร์เฟซเอกสารเดี่ยว) เมื่อคุณพูดถึงแท็บคุณหมายถึง MDI ในปี 1998 หนังสือ Microsoft แนะนำให้กำจัดแอพ MDI ทุกตัว Microsoft เปลี่ยน Word, Excel ... เป็น SDI ซึ่งฉันคิดว่าง่ายและดีกว่า ฉันเข้าใจว่า Chrome และอื่น ๆ (ตอนนี้ IE) ต้องการกลับไปที่ MDI ฉัน personnaly (ขึ้นอยู่กับความรู้สึกส่วนตัว) ไม่มีอะไรที่ดีกว่าถ้าจะเปิดแอพใหม่เมื่อเลือกไฟล์ assoc แต่ฉันเข้าใจดีกว่าคำถามที่ถามตอนนี้ ขอบคุณมาก!
Eric Ouellet

58

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 ซึ่งสรุปฟังก์ชั่นการทำงานของ remoting ที่ต้องการ ในการรวมประเภทนี้ลงในแอปพลิเคชัน WPF ประเภทนั้นจำเป็นต้องได้รับมาจากนั้นและใช้เป็น shim ระหว่างวิธีจุดเข้าใช้งานแบบคงที่, หลักและประเภทแอปพลิเคชันของ WPF shim ตรวจพบเมื่อมีการเปิดตัวแอปพลิเคชั่นเป็นครั้งแรกและเมื่อมีการพยายามเปิดตัวครั้งต่อไปและให้ผลผลิตควบคุมแอปพลิเคชัน WPF เพื่อกำหนดวิธีการประมวลผลการเปิดตัว

  • สำหรับคน C # เพียงแค่หายใจเข้าลึก ๆ และลืมเรื่องทั้งหมด 'ฉันไม่ต้องการรวม VisualBasic DLL' เพราะสิ่งนี้และสิ่งที่Scott Hanselman พูดและความจริงที่ว่าสิ่งนี้เป็นวิธีการแก้ปัญหาที่สะอาดที่สุดและได้รับการออกแบบโดยคนที่รู้เรื่องกรอบมากกว่าที่คุณทำ
  • จากมุมมองการใช้งานความจริงก็คือถ้าผู้ใช้ของคุณกำลังโหลดแอปพลิเคชันและเปิดอยู่แล้วและคุณกำลังให้ข้อความแสดงข้อผิดพลาดเช่น'Another instance of the app is running. Bye'นั้นพวกเขาจะไม่เป็นผู้ใช้ที่มีความสุขมาก คุณเพียงแค่ต้อง (ในแอปพลิเคชัน GUI) สลับไปที่แอปพลิเคชันนั้นและส่งผ่านอาร์กิวเมนต์ที่ให้ไว้ - หรือหากพารามิเตอร์บรรทัดคำสั่งไม่มีความหมายคุณจะต้องเปิดแอปพลิเคชันที่อาจถูกย่อให้เล็กสุด

เฟรมเวิร์กมีการรองรับสิ่งนี้อยู่แล้ว - มันแค่งี่เง่าบางคนที่ชื่อ DLL Microsoft.VisualBasicและมันไม่ได้ใส่เข้าไปMicrosoft.ApplicationUtilsหรืออะไรแบบนั้น รับมากกว่า - หรือเปิดตัวสะท้อนแสง

เคล็ดลับ: หากคุณใช้วิธีการนี้ว่าที่เป็นอยู่และคุณมีอยู่แล้ว app.xaml มีทรัพยากร ฯลฯ คุณจะต้องดูที่นี้มากเกินไป


ขอบคุณที่รวมลิงก์ 'ดูที่นี่ด้วย' นั่นคือสิ่งที่ฉันต้องการ โดยวิธีการแก้ปัญหา # 3 ในลิงค์ของคุณเป็นทางออกที่ดีที่สุด
Eternal21 21

ฉันยังเป็นผู้สนับสนุนในการมอบหมายกรอบและห้องสมุดที่ออกแบบมาเป็นพิเศษเมื่อเป็นไปได้
Eniola

23

รหัสนี้ควรไปที่วิธีการหลัก ดูที่นี่สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการหลักใน 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(...);
    }
}

หมายเหตุ:วิธีการข้างต้นถือว่ากระบวนการ / แอปพลิเคชันของคุณมีชื่อเฉพาะ เพราะมันใช้ชื่อกระบวนการเพื่อค้นหาว่าโปรเซสเซอร์ที่มีอยู่ใด ๆ ดังนั้นหากแอปพลิเคชันของคุณมีชื่อสามัญมาก (เช่น: Notepad) วิธีการด้านบนจะไม่ทำงาน


1
นอกจากนี้สิ่งนี้จะไม่ทำงานหากมีโปรแกรมอื่นทำงานบนคอมพิวเตอร์ของคุณด้วยชื่อเดียวกัน ProcessNameส่งคืนชื่อไฟล์เรียกทำงานลบexeด้วย หากคุณสร้างแอปพลิเคชันที่ชื่อว่า "Notepad" และวินโดว์ notepad กำลังทำงานอยู่มันจะตรวจพบว่าแอปพลิเคชันของคุณทำงานอยู่
Jcl

1
ขอบคุณสำหรับคำตอบนี้ ฉันได้พบคำถามที่คล้ายกันจำนวนมากและคำตอบมักจะซับซ้อนและ / หรือทำให้สับสนฉันพบว่ามันไร้ประโยชน์ อันนี้ (วิธี # 1) ตรงไปตรงมาชัดเจนและที่สำคัญที่สุดมันช่วยให้ฉันเรียกใช้โค้ดของฉัน
ElDoRado1239

20

ฉันมีคลาสแบบใช้ครั้งเดียวซึ่งใช้งานได้ง่ายสำหรับกรณีส่วนใหญ่:

ใช้มันแบบนี้:

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
}

1
อันนี้ค่อนข้างง่ายที่จะทำงาน มันจะไม่ปิดแอปพลิเคชันที่สองจนกว่าฉันจะเปลี่ยน Application.Exit (); เพื่อผลตอบแทนที่ง่าย แต่ยิ่งไปกว่านั้นมันยิ่งใหญ่ แม้ว่าฉันจะยอมรับว่าฉันกำลังมองไปที่โซลูชันก่อนหน้านี้อย่างใกล้ชิดกว่าเนื่องจากใช้อินเทอร์เฟซ blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000

15

หนึ่งใหม่ที่ใช้ Mutex และ IPC สิ่งและยังผ่านอาร์กิวเมนต์บรรทัดคำสั่งใด ๆ เช่นการทำงานเป็นWPF ตัวอย่างเดียวแอพลิเคชัน


ฉันใช้สิ่งนี้กับความสำเร็จที่ยิ่งใหญ่ หากคุณรวม NamedPipes เข้ากับสิ่งนี้คุณสามารถส่งผ่านอาร์กิวเมนต์บรรทัดคำสั่งไปยังแอปพลิเคชันเดิมได้ คลาส 'SingleInstance.cs' เขียนโดย Microsoft ฉันได้เพิ่มลิงก์อื่นไปยังบล็อกของ Arik Poznanski ที่อ่านง่ายกว่าใน CodeProject
Heliac

ลิงก์เสียแล้ว
Mike Lowery

11

รหัสC # .NET Single Instance Applicationที่เป็นข้อมูลอ้างอิงสำหรับคำตอบที่ทำเครื่องหมายไว้เป็นการเริ่มต้นที่ดี

อย่างไรก็ตามฉันพบว่ามันไม่สามารถจัดการกับกรณีได้เป็นอย่างดีเมื่ออินสแตนซ์ที่มีอยู่แล้วมีกล่องโต้ตอบโมดอลเปิดอยู่ไม่ว่ากล่องโต้ตอบนั้นจะมีการจัดการ (เช่นฟอร์มอื่นเช่นกล่องประมาณ) หรือกล่องที่ไม่มีการจัดการ OpenFileDialog แม้เมื่อใช้คลาส NET. มาตรฐาน) ด้วยรหัสเดิมรูปแบบหลักจะถูกเปิดใช้งาน แต่รูปแบบที่เป็นโมฆะยังคงไม่ได้ใช้งานซึ่งดูแปลกรวมทั้งผู้ใช้จะต้องคลิกที่มันเพื่อใช้งานแอปต่อไป

ดังนั้นฉันได้สร้างคลาสยูทิลิตี้ SingleInstance เพื่อจัดการทั้งหมดนี้ค่อนข้างอัตโนมัติสำหรับแอปพลิเคชัน Winforms และ WPF

Winforms :

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) แก้ไขหน้าแอพเช่นนี้ (และตรวจสอบให้แน่ใจว่าคุณได้ตั้งค่าแอ็คชันบิลด์เป็นหน้าเพื่อให้สามารถกำหนดเมธอด Main ใหม่ได้):

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

10

นี่คือตัวอย่างที่ช่วยให้คุณมีอินสแตนซ์เดียวของแอปพลิเคชัน เมื่อมีอินสแตนซ์ใหม่โหลดพวกเขาจะส่งอาร์กิวเมนต์ของตนไปยังอินสแตนซ์หลักที่ทำงานอยู่

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

นี่เป็นตัวอย่างที่ดีของสิ่งที่ฉันจะทำ นาธาน args ทั้งหมดส่งโดยใช้วิธีนี้หรือไม่? ฉันมี 7 หรือมากกว่านั้นในแอปของฉันและฉันคิดว่ารหัสนี้จะใช้งานได้
kevp

1
ในตัวอย่างของฉันส่งเฉพาะอาร์กิวเมนต์แรกเท่านั้น แต่สามารถเปลี่ยนแปลงได้เพื่อให้ส่งทั้งหมด
Nathan Moinvaziri

8

ความคิดบางอย่าง: มีหลายกรณีที่ต้องการให้แอปพลิเคชันอินสแตนซ์เดียวเท่านั้นไม่ใช่ "ขา" เนื่องจากบางคนอาจเชื่อ แอปฐานข้อมูล ฯลฯ นั้นมีขนาดที่ยากขึ้นหากผู้ใช้อนุญาตให้ใช้งานอินสแตนซ์หลายอินสแตนซ์สำหรับผู้ใช้รายเดียวในการเข้าถึงฐานข้อมูล (คุณรู้ทั้งหมดที่อัปเดตระเบียนทั้งหมดที่เปิดอยู่ เครื่อง ฯลฯ ) อันดับแรกสำหรับ "สิ่งชนชื่ออย่าใช้ชื่อที่มนุษย์อ่านได้ - ใช้ GUID แทนหรือดีกว่า GUID + ชื่อมนุษย์ที่มนุษย์อ่านได้โอกาสในการชนชื่อลดลงจากเรดาร์และ Mutex ไม่สนใจ มีคนชี้ให้เห็นว่าการโจมตี DOS จะดูด แต่ถ้าคนที่เป็นอันตรายได้รับปัญหาในการรับชื่อ mutex และรวมไว้ในแอพของพวกเขา คุณค่อนข้างเป็นเป้าหมายอยู่แล้วและจะต้องทำมากกว่านี้เพื่อปกป้องตัวคุณเอง นอกจากนี้หากมีการใช้ตัวแปรของ: Mutex ใหม่ (จริง "บาง GUID พร้อมชื่อ", AIsFirstInstance out), คุณมีตัวบ่งชี้ว่า Mutex เป็นอินสแตนซ์แรกหรือไม่


6

คำตอบมากมายสำหรับคำถามที่ดูเหมือนง่ายมาก เพียงแค่เขย่าบางสิ่งบางอย่างที่นี่เล็กน้อยเป็นทางออกของปัญหานี้

การสร้าง Mutex นั้นอาจลำบากเพราะ JIT-er เห็นคุณใช้มันเพียงส่วนเล็ก ๆ ของรหัสของคุณและต้องการที่จะทำเครื่องหมายว่ามันพร้อมสำหรับการรวบรวมขยะ มันค่อนข้างจะฉลาดเกินกว่าที่คุณคิดว่าคุณจะไม่ใช้ Mutex นั้นนานเท่าไหร่ ในความเป็นจริงคุณต้องการแฮงค์บน Mutex นี้ตราบเท่าที่แอปพลิเคชันของคุณทำงานอยู่ วิธีที่ดีที่สุดในการบอกให้นักสะสมขยะทิ้งคุณ Mutex เพียงอย่างเดียวคือการบอกให้มันมีชีวิตอยู่แม้ว่าจะมีการสะสมโรงรถในรุ่นต่าง ๆ ตัวอย่าง:

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

ฉันยกแนวคิดจากหน้านี้: http://www.ai.uga.edu/~mc/SingleInstance.html


3
การเก็บสำเนาที่ใช้ร่วมกันไว้ในชั้นแอปพลิเคชันนั้นจะง่ายกว่าไหม?
rossisdead

6

ดูเหมือนว่าจะมีวิธีที่ดีในการจัดการสิ่งนี้:

WPF Single Instance Application

นี่เป็นคลาสที่คุณสามารถเพิ่มที่จัดการ mutex และ cruff การส่งข้อความทั้งหมดเพื่อทำให้การใช้งานของคุณง่ายขึ้นจนถึงจุดที่มันไม่สำคัญ


นี่ดูเหมือนจะไม่นำหน้าต่างที่มีอยู่ไปยังเบื้องหน้าเมื่อฉันลองใช้
RandomEngy

6

รหัสต่อไปนี้คือ WCF ของฉันที่มีชื่อว่าโซลูชันท่อเพื่อลงทะเบียนแอปพลิเคชันอินสแตนซ์เดียว มันดีเพราะมันยังเพิ่มเหตุการณ์เมื่ออินสแตนซ์อื่นพยายามเริ่มต้นและรับบรรทัดคำสั่งของอินสแตนซ์อื่น

มันมุ่งไปที่ WPF เพราะใช้System.Windows.StartupEventHandlerคลาส แต่สามารถแก้ไขได้อย่างง่ายดาย

รหัสนี้ต้องมีการอ้างอิงถึงและPresentationFrameworkSystem.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);
      }
    }
  }
}

5

คุณไม่ควรใช้ mutex ที่กำหนดชื่อเพื่อใช้แอปพลิเคชันอินสแตนซ์เดียว (หรืออย่างน้อยก็ไม่ใช่รหัสการผลิต) รหัสที่เป็นอันตรายสามารถทำได้ DoS ( ปฏิเสธการบริการ ) ตูดของคุณ ...


8
"คุณไม่ควรใช้ mutex ที่ตั้งชื่อ" - ไม่ควรพูดไม่เคย หากรหัสที่เป็นอันตรายกำลังทำงานอยู่ในเครื่องของฉันฉันอาจถูกปิดบังไว้แล้ว
Joe

จริงๆแล้วมันไม่จำเป็นต้องเป็นรหัสที่เป็นอันตราย อาจเป็นชื่อที่บังเอิญได้
Matt Davison

ถ้าอย่างนั้นคุณควรทำอย่างไร?
Kevin Berridge

คำถามที่ดีกว่าคือเหตุผลใดที่คุณต้องการพฤติกรรมนั้น อย่าออกแบบแอปของคุณเป็นแอปพลิเคชันอินสแตนซ์เดียว =) ฉันรู้ว่านั่นเป็นคำตอบที่อ่อนแอ แต่จากมุมมองการออกแบบมันเป็นคำตอบที่ถูกต้องเกือบทุกครั้ง โดยไม่รู้ตัวเกี่ยวกับแอปมันยากที่จะพูดมากขึ้น
Matt Davison

2
อย่างน้อยที่สุดใน Windows Mutexes มีการควบคุมการเข้าถึงดังนั้นหนึ่งสามารถเล่นกับวัตถุของคุณ ในการตั้งชื่อการชนกันของตนเองนั่นเป็นสาเหตุที่ UUID / GUID เป็นผู้คิดค้น
NuSkooler

5

ดูรหัส folllowing มันเป็นโซลูชันที่ยอดเยี่ยมและใช้งานง่ายเพื่อป้องกันแอปพลิเคชัน 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();
    }
}

4

นี่คือสิ่งที่ฉันใช้ มันรวมการแจงนับกระบวนการเพื่อทำการสลับและ mutex เพื่อป้องกันจาก "clickers ที่ใช้งาน":

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

4

ฉันพบโซลูชันที่ง่ายกว่าคล้ายกับ Dale Ragan's แต่ได้รับการแก้ไขเล็กน้อย มันทำทุกสิ่งที่คุณต้องการจริงและขึ้นอยู่กับคลาส Microsoft WindowsFormsApplicationBase มาตรฐาน

ประการแรกคุณสร้างคลาส 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();         
        }
    }
}

ทั้งโปรแกรมและโซลูชัน SingleInstanceController_NET ​​ควรอ้างอิง Microsoft.VisualBasic หากคุณต้องการเปิดใช้งานแอปพลิเคชันที่กำลังทำงานอีกครั้งเป็นหน้าต่างปกติเมื่อผู้ใช้พยายามรีสตาร์ทโปรแกรมที่กำลังรันพารามิเตอร์ตัวที่สองใน SingleInstanceController อาจเป็นโมฆะ ในตัวอย่างที่กำหนดหน้าต่างถูกขยายให้ใหญ่สุด


4

อัปเดต 2017-01-25 หลังจากลองสิ่งเล็ก ๆ น้อย ๆ ฉันตัดสินใจที่จะไปกับ VisualBasic.dll มันง่ายขึ้นและทำงานได้ดีขึ้น (อย่างน้อยสำหรับฉัน) ฉันให้คำตอบก่อนหน้านี้เป็นข้อมูลอ้างอิง ...

นี่คือวิธีที่ฉันทำโดยไม่ผ่านการขัดแย้ง (ซึ่งฉันไม่สามารถหาเหตุผลที่จะทำเช่นนั้นได้ ... ฉันหมายถึงแอปเดียวที่มีอาร์กิวเมนต์ที่จะถูกส่งผ่านจากอินสแตนซ์หนึ่งไปยังอีกอันหนึ่ง) หากจำเป็นต้องมีการเชื่อมโยงไฟล์แอปควรจะทำการติดตั้งแอป (ต่อความคาดหวังตามมาตรฐานของผู้ใช้) สำหรับแต่ละเอกสาร หากคุณต้องผ่าน args ไปยังแอพที่มีอยู่ฉันคิดว่าฉันจะใช้ vb dll

ไม่ผ่าน args (เพียงแอปอินสแตนซ์เดียว) ฉันไม่ต้องการลงทะเบียนข้อความ Window ใหม่และไม่แทนที่ลูปข้อความตามที่กำหนดไว้ใน Matt Davis Solution แม้ว่าจะไม่ใช่เรื่องใหญ่ที่จะเพิ่ม VisualBasic dll แต่ฉันไม่ต้องการเพิ่มการอ้างอิงใหม่เพียงเพื่อทำแอปอินสแตนซ์เดียว นอกจากนี้ฉันยังชอบที่จะสร้างคลาสใหม่ด้วย Main แทนการปิดการโทรจาก App.Startup override เพื่อให้แน่ใจว่าจะออกโดยเร็วที่สุด

ด้วยความหวังว่าทุกคนจะชอบ ... หรือจะเป็นแรงบันดาลใจเล็กน้อย :-)

ควรกำหนดคลาสเริ่มต้นโครงการเป็น '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);
                        }
                    }
                }
            }
        }
    }
}

WindowHelper:

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

3

แม้ว่าจะไม่ใช้ Mutex คำตอบง่ายๆ:

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

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

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.Showในสถานะ - ifและใส่ "แอปพลิเคชันที่ทำงานอยู่แล้ว"
สิ่งนี้อาจเป็นประโยชน์กับใครบางคน


4
หากทั้งสองกระบวนการเริ่มต้นพร้อมกันพวกเขาทั้งสองอาจเห็นกระบวนการที่ใช้งานอยู่สองกระบวนการและสิ้นสุดด้วยตนเอง
AT

@AT ใช่แล้วนี่เป็นประโยชน์กับแอปที่ทำงานในฐานะผู้ดูแลระบบหรืออย่างอื่น
newbieguy

หากคุณทำสำเนาใบสมัครของคุณและเปลี่ยนชื่อคุณสามารถเรียกใช้ต้นฉบับและสำเนาได้ในเวลาเดียวกัน
Dominique Bijnens

2

วิธีที่ใช้ Named-mutex ไม่ใช่ข้ามแพลตฟอร์มเนื่องจากการตั้งชื่อ mutex นั้นไม่ใช่ global ใน Mono วิธีการที่ใช้การแจงนับกระบวนการไม่มีการซิงโครไนซ์ใด ๆ และอาจส่งผลให้เกิดพฤติกรรมที่ไม่ถูกต้อง (เช่นกระบวนการหลายอย่างที่เริ่มในเวลาเดียวกันอาจทั้งหมดสิ้นสุดด้วยตนเองขึ้นอยู่กับเวลา) วิธีที่ใช้ระบบ Windowing ไม่พึงประสงค์ในโปรแกรมประยุกต์คอนโซล โซลูชันนี้สร้างขึ้นจากคำตอบของ 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.");
            }
        }
    }
}

2

ฉันใช้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();
}

1

ใช้โซลูชัน mutex:

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

1

ต่อไปนี้เป็นโซลูชันที่มีน้ำหนักเบาที่ฉันใช้ซึ่งอนุญาตให้แอปพลิเคชันนำหน้าต่างที่มีอยู่แล้วไปยังเบื้องหน้าโดยไม่หันไปใช้ข้อความหน้าต่างแบบกำหนดเองหรือค้นหาชื่อกระบวนการแบบสุ่มสี่สุ่มห้า

[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 และสร้างใหม่แบบคงที่ แต่คุณจะต้องกำจัด / เผยแพร่ Mutex อย่างชัดเจนเมื่อคุณทำเสร็จแล้ว โดยส่วนตัวแล้วฉันชอบที่จะรักษา mutex ไว้ที่ Local เพราะมันจะถูกกำจัดโดยอัตโนมัติแม้ว่าแอปพลิเคชันจะปิดลงโดยไม่ถึงจุดสิ้นสุดของ Main



1

ฉันเพิ่มเมธอด sendMessage ในคลาส NativeMethods

เห็นได้ชัดว่าวิธีการ postmessage ใช้งานได้หากแอปพลิเคชันไม่แสดงในทาสก์บาร์ แต่การใช้เมธอด 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);
}

1

นี่คือสิ่งเดียวกันที่ใช้งานผ่านทางกิจกรรม

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

1

[ฉันได้ให้โค้ดตัวอย่างสำหรับแอปพลิเคชันคอนโซลและ wpf ด้านล่าง]

คุณต้องตรวจสอบค่าของcreatedNewตัวแปร (ตัวอย่างด้านล่าง!) หลังจากที่คุณสร้างอินสแตนซ์ Mutex ที่มีชื่อ

บูลีนcreatedNewจะคืนค่าเท็จ:

ถ้าอินสแตนซ์ Mutex ชื่อ "YourApplicationNameHere" ถูกสร้างขึ้นแล้วในระบบบางแห่ง

บูลีนcreatedNewจะคืนค่าจริง:

หากนี่คือ Mutex แรกที่ชื่อว่า "YourApplicationNameHere" บนระบบ


แอปพลิเคชันคอนโซล - ตัวอย่าง:

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

1

โซลูชั่นประหยัดเวลาสำหรับ 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);
        }
    }
}

1

โปรดตรวจสอบวิธีแก้ปัญหาที่เสนอจากที่นี่ซึ่งใช้สัญญาณเพื่อตรวจสอบว่าอินสแตนซ์ที่มีอยู่กำลังทำงานอยู่ทำงานให้กับแอปพลิเคชัน WPF และสามารถส่งผ่านอาร์กิวเมนต์จากอินสแตนซ์ที่สองไปยังอินสแตนซ์แรก

มันทำงานได้กับ. NET Core ไม่เพียง แต่สำหรับ. NET Framework


1

ฉันไม่สามารถหาทางแก้ปัญหาสั้น ๆ ได้ที่นี่ดังนั้นฉันหวังว่าจะมีคนชอบสิ่งนี้:

ปรับปรุง 2561-2552

ใส่รหัสนี้ใน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());
}

นี่จะเป็นการแนะนำสภาพการแข่งขัน ต้องใช้ mutex
georgiosd

1
ไม่มีการรับประกันว่าถ้าคุณหมุนสองอินสแตนซ์ในเวลาเดียวกันกับที่มันใช้งานได้ ชอบอัปเดตตัวแปรจากสองเธรดที่แตกต่างกัน ธุรกิจที่มีความเสี่ยงหากิน ใช้พลังลุค :)
georgiosd

@georgiosd อาฉันเห็นความหมายของคุณ เช่นถ้ามีคนเริ่ม. exe และเปลี่ยนชื่อ ใช่นี่จะเป็นวิธีการเริ่มต้นใหม่อีกครั้ง แต่ปกติแล้ว. exe จะไม่ทำงานหากชื่อเปลี่ยนไป ฉันจะปรับปรุงคำตอบของฉัน ^^ ขอบคุณลุค: D สำหรับการชี้ให้เห็นว่า :)
Deniz

1
ไม่เพียงแค่นั้น @Deniz หากคุณเริ่มต้นสองกระบวนการอย่างรวดเร็วจริง ๆ มีโอกาสที่รายการกระบวนการหรือวิธีการดึงข้อมูลจะดำเนินการในขณะที่ยังคงมีเพียงกระบวนการเดียวเท่านั้นที่ปรากฏ นี้อาจจะเป็นกรณีขอบที่ไม่เกี่ยวข้องกับคุณ แต่นี้เป็นคำถามทั่วไป ...
georgiosd

@georgiosd คุณพิสูจน์ได้ไหม? เพราะ Iv'e ทดสอบมันเพื่อคุณนะฮิฮิ แต่มันเป็นไปไม่ได้สำหรับฉันแม้แต่ "เร็วจริงๆ"! : P ดังนั้นฉันจึงไม่สามารถเข้าใจว่าทำไมคุณถึงเชื่อในสิ่งที่ไม่ใช่กรณีและแม้แต่ไม่ชอบรหัสผู้บริสุทธิ์นี้: D
Deniz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.