ما هي الطريقة الصحيحة لإنشاء تطبيق WPF لمثيل واحد؟

StackOverflow https://stackoverflow.com/questions/19147

  •  09-06-2019
  •  | 
  •  

سؤال

استخدام C# وWPF ضمن .NET (بدلاً من .NET) نماذج ويندوز أو وحدة التحكم)، ما هي الطريقة الصحيحة لإنشاء تطبيق لا يمكن تشغيله إلا كمثيل واحد؟

أعلم أن الأمر له علاقة بشيء أسطوري يسمى كائن المزامنة (mutex)، ونادرًا ما أجد شخصًا يكلف نفسه عناء التوقف وشرح ما هو أحد هذه الأشياء.

يحتاج الكود أيضًا إلى إعلام المثيل قيد التشغيل بالفعل بأن المستخدم حاول بدء مثيل ثانٍ، وربما أيضًا تمرير أي وسيطات سطر أوامر إن وجدت.

هل كانت مفيدة؟

المحلول

هنا هو جيد جدا شرط فيما يتعلق بحل Mutex.النهج الموصوف في المقالة مفيد لسببين.

أولاً، لا يتطلب الأمر تبعية على تجميع Microsoft.VisualBasic.إذا كان مشروعي يعتمد بالفعل على هذا التجميع، فمن المحتمل أن أؤيد استخدام هذا النهج يظهر في إجابة أخرى.ولكن كما هو الحال، فأنا لا أستخدم تجميع Microsoft.VisualBasic، وأفضل عدم إضافة تبعية غير ضرورية إلى مشروعي.

ثانيًا، توضح المقالة كيفية إحضار المثيل الموجود للتطبيق إلى المقدمة عندما يحاول المستخدم بدء مثيل آخر.هذه لمسة لطيفة جدًا لا تتناولها حلول Mutex الأخرى الموضحة هنا.


تحديث

اعتبارًا من 1/8/2014، لا تزال المقالة التي قمت بربطها أعلاه نشطة، ولكن لم يتم تحديث المدونة منذ فترة.وهذا يجعلني أشعر بالقلق من أنها قد تختفي في نهاية المطاف، ومعها الحل الذي ندعو إليه.أنا أعيد إنتاج محتوى المقال هنا للأجيال القادمة.الكلمات تنتمي فقط إلى صاحب المدونة في التعقل الترميز المجاني.

اليوم أردت أن أعيد إعادة تشكيل بعض التعليمات البرمجية التي تمنع طلبي من تشغيل مثيلات متعددة من نفسه.

في السابق كان لي استخدام System.Diagnostics.Process للبحث عن مثيل myapp.exe في قائمة العملية.بينما يعمل هذا ، فإنه يجلب الكثير من النفقات العامة ، وأردت شيئًا أنظف.

مع العلم أنه يمكنني استخدام Mutex لهذا (ولكن لم أفعل ذلك من قبل) ، شرعت في خفض الكود الخاص بي وتبسيط حياتي.

في فئة تطبيقي الرئيسي قمت بإنشاء اسم ثابت موتيكس:

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

يتيح لنا وجود Mutex المسماة تكديس التزامن عبر مؤشرات ترابط وعمليات متعددة وهو السحر الذي أبحث عنه فقط.

Mutex.WaitOne لديه تحميل زائد يحدد قدرا من الوقت حتى ننتظر.نظرًا لأننا لا نرغب فعليًا في مزامنة التعليمات البرمجية الخاصة بنا (فقط تحقق مما إذا كان قيد الاستخدام حاليًا) نستخدم الحمل الزائد مع معلمتين: Mutex.WaitOne (مهلة الفترة الزمنية، خروج منطقي).انتظر، سيرجع صحيحًا إذا كان قادرًا على الدخول، وخطأ إذا لم يكن قادرًا على الدخول.في هذه الحالة، لا نريد الانتظار على الإطلاق؛إذا تم استخدام Mutex الخاص بنا ، تخطيه ، والمضي قدمًا ، لذلك نمر في TimeSpan.zero (انتظر 0 مللي ثانية) ، وضبط النص على 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 لإخطار مثيل الجري الذي نسي أحدهم أنه كان يعمل بالفعل (من خلال جلب نفسه إلى أعلى جميع النوافذ الأخرى).لتحقيق ذلك استخدمت رسالة بريدية لبث رسالة مخصصة إلى كل نافذة (تم تسجيل الرسالة المخصصة سجلWindowMessageمن خلال تطبيق التشغيل الخاص بي ، مما يعني فقط أن طلبي يعرف ما هو عليه) ثم يخرج مثيلتي الثانية.قد يتلقى مثيل التطبيق قيد التشغيل هذا الإخطار ومعالجته.من أجل القيام بذلك ، أتجاوز WndProc في النموذج الرئيسي والاستماع لإشعار مخصص.عندما تلقيت هذا الإخطار ، قمت بتعيين أفضل خاصية للنموذج على 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 عندما قرأت كتاب كريس سيل.تستخدم هذه الخدعة المنطق المتاح لنا بالفعل في الإطار.لا أعرف عنك، ولكن عندما أتعرف على الأشياء التي يمكنني إعادة استخدامها في إطار العمل، فهذا هو عادةً الطريق الذي أسلكه بدلاً من إعادة اختراع العجلة.ما لم يكن بالطبع لا يفعل كل ما أريد.

عندما انضممت إلى 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 عند إعادة تشغيل التطبيق مرة أخرى.كما ترون، يمكنني الوصول إلى ما تم تمريره في سطر الأوامر من خلال وسيطات الحدث.لقد قمت بتعيين القيمة إلى حقل مثيل.يمكنك تحليل سطر الأوامر هنا، أو يمكنك تمريره إلى التطبيق الخاص بك من خلال المُنشئ واستدعاء طريقة التنشيط.

ثالثًا، حان الوقت لإنشاء 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 ، يحتاج النوع إلى الاستخلاص منه ، وأن يتم استخدامه كشرج بين طريقة نقطة الإدخال الثابتة للتطبيق ، ونوع تطبيق تطبيق WPF.يكتشف Shim عند إطلاق التطبيق لأول مرة ، وعندما تتم محاولة عمليات الإطلاق اللاحقة ، وتحكم العائد على نوع تطبيق WPF لتحديد كيفية معالجة عمليات الإطلاق.

  • بالنسبة لأشخاص C#، فقط خذ نفسًا عميقًا وانسى الأمر بأكمله "لا أريد تضمين VisualBasic DLL".بسبب هذا و ماذا يقول سكوت هانسيلمان وحقيقة أن هذا هو إلى حد كبير الحل الأنظف للمشكلة وقد تم تصميمه من قبل أشخاص يعرفون الكثير عن إطار العمل أكثر مما تعرفه أنت.
  • من وجهة نظر سهولة الاستخدام، فالحقيقة هي أنه إذا كان المستخدم الخاص بك يقوم بتحميل تطبيق وهو مفتوح بالفعل وكنت تعطيه رسالة خطأ مثل 'Another instance of the app is running. Bye' إذن لن يكونوا مستخدمين سعداء جدًا.يجب عليك ببساطة (في تطبيق واجهة المستخدم الرسومية) التبديل إلى هذا التطبيق وتمرير الوسائط المقدمة - أو إذا لم يكن لمعلمات سطر الأوامر أي معنى، فيجب عليك إظهار التطبيق الذي ربما تم تصغيره.

إطار العمل لديه بالفعل دعم لهذا - إنه فقط أن بعض الأغبياء أطلقوا عليه اسم 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 لمثيل واحد.

الرمز تطبيق مثيل واحد لـ C# .NET وهذا هو المرجع للإجابة المميزة بداية رائعة.

ومع ذلك، وجدت أنه لا يتعامل بشكل جيد مع الحالات التي يكون فيها المثيل الموجود بالفعل به مربع حوار مشروط مفتوح، سواء كان مربع الحوار هذا عبارة عن مربع حوار مُدار (مثل نموذج آخر مثل مربع حول)، أو نموذج غير مُدار (مثل OpenFileDialog حتى عند استخدام فئة .NET القياسية).باستخدام الكود الأصلي، يتم تنشيط النموذج الرئيسي، لكن النموذج المشروط يظل غير نشط، وهو ما يبدو غريبًا، بالإضافة إلى أنه يجب على المستخدم النقر عليه لمواصلة استخدام التطبيق.

لذلك، قمت بإنشاء فئة أداة مساعدة SingleInstance للتعامل مع كل هذا تلقائيًا تمامًا لتطبيقات Winforms وWPF.

وينفورمز:

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

وبف:

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

فيما يلي مثال يسمح لك بالحصول على مثيل واحد للتطبيق.عند تحميل أي مثيلات جديدة، فإنها تمرر وسيطاتها إلى المثيل الرئيسي قيد التشغيل.

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

فقط بعض الأفكار:هناك حالات عندما يتطلب الأمر أن يكون مثيل واحد فقط من التطبيق ليس "ضعيفًا" كما قد يعتقد البعض.تطبيقات قواعد البيانات وما إلى ذلك.تكون أكثر صعوبة إذا سمح المرء بمثيلات متعددة من التطبيق لمستخدم واحد للوصول إلى قاعدة بيانات (كما تعلم، كل ذلك تحديث جميع السجلات المفتوحة في مثيلات متعددة من التطبيق على جهاز المستخدم، وما إلى ذلك) .أولاً، بالنسبة لمسألة تضارب الأسماء، لا تستخدم اسمًا يمكن قراءته بواسطة الإنسان - استخدم GUID بدلاً من ذلك، أو حتى أفضل GUID + الاسم القابل للقراءة بواسطة الإنسان.لقد سقطت فرص تضارب الأسماء للتو عن الرادار ولا يهتم Mutex.كما أشار أحد الأشخاص، فإن هجوم DOS سيكون سيئًا، ولكن إذا واجه الشخص الخبيث مشكلة الحصول على اسم كائن المزامنة (mutex) ودمجه في تطبيقه، فأنت هدف إلى حد كبير على أي حال وسيتعين عليك فعل الكثير لحماية نفسك من مجرد العبث باسم كائن المزامنة (mutex).أيضًا ، إذا استخدم أحد متغير:new Mutex(true، "some GUID plus Name"، out AIsFirstInstance)، لديك بالفعل المؤشر الخاص بك حول ما إذا كان Mutex هو المثيل الأول أم لا.

الكثير من الإجابات على مثل هذا السؤال الذي يبدو بسيطًا.فقط لتغيير الأمور قليلاً هنا هو الحل لهذه المشكلة.

قد يكون إنشاء Mutex أمرًا مزعجًا لأن JIT-er يرى أنك تستخدمه فقط لجزء صغير من التعليمات البرمجية الخاصة بك ويريد وضع علامة عليه على أنه جاهز لجمع البيانات المهملة.إنها تريد إلى حد كبير أن تتفوق على ذكائك معتقدًا أنك لن تستخدم Mutex لفترة طويلة.في الواقع، تريد الاحتفاظ بـ Mutex طالما أن التطبيق الخاص بك قيد التشغيل.أفضل طريقة لإخبار جامع القمامة بترك Mutex وحدك هو إخباره بإبقائه على قيد الحياة عبر الأجيال المختلفة لجمع المرآب.مثال:

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

لقد رفعت الفكرة من هذه الصفحة: http://www.ai.uga.edu/~mc/SingleInstance.html

يبدو أن هناك طريقة جيدة حقًا للتعامل مع هذا:

تطبيق WPF لمثيل واحد

يوفر هذا فئة يمكنك إضافتها لإدارة جميع عناصر المزامنة والمراسلة لتبسيط التنفيذ الخاص بك إلى الحد الذي يصبح فيه الأمر تافهًا.

التعليمة البرمجية التالية هي حل توجيهات الإخراج المسماة WCF الخاص بي لتسجيل تطبيق مثيل واحد.إنه أمر رائع لأنه يثير أيضًا حدثًا عندما يحاول مثيل آخر البدء، ويتلقى سطر الأوامر الخاص بالمثيل الآخر.

إنه موجه نحو WPF لأنه يستخدم System.Windows.StartupEventHandler فئة، ولكن هذا يمكن تعديله بسهولة.

يتطلب هذا الرمز إشارة إلى 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) للحماية من "أجهزة النقر النشطة":

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 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 فارغة.في المثال الموضح، تم تكبير النافذة.

انظر إلى الكود التالي.إنه حل رائع وبسيط لمنع مثيلات متعددة لتطبيق 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;

ضعه داخل 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- بيان ووضع "التطبيق قيد التشغيل بالفعل".
قد يكون هذا مفيدًا لشخص ما.

تحديث 2017-01-25. بعد تجربة بعض الأشياء، قررت استخدام VisualBasic.dll فهو أسهل ويعمل بشكل أفضل (على الأقل بالنسبة لي).أترك إجابتي السابقة كمرجع فقط ...

كمرجع فقط، هكذا فعلت دون تمرير الحجج (والتي لا أجد أي سبب للقيام بذلك ...أعني تطبيقًا واحدًا يحتوي على وسيطات يمكن تمريرها من مثيل إلى آخر).إذا كان اقتران الملف مطلوبًا، فيجب إنشاء مثيل للتطبيق (حسب التوقعات القياسية للمستخدمين) لكل مستند.إذا كان عليك تمرير الوسائط إلى التطبيق الموجود، فأعتقد أنني سأستخدم vb dll.

عدم تمرير الوسائط (مجرد تطبيق مثيل واحد)، أفضل عدم تسجيل رسالة نافذة جديدة وعدم تجاوز حلقة الرسالة كما هو محدد في Matt Davis Solution.على الرغم من أن إضافة VisualBasic dll ليس أمرًا كبيرًا، إلا أنني أفضل عدم إضافة مرجع جديد فقط للقيام بتطبيق مثيل واحد.أيضًا، أفضل إنشاء فئة جديدة باستخدام Main بدلاً من استدعاء Shutdown from 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);
                        }
                    }
                }
            }
        }
    }
}

مساعد النافذة:

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

استخدم حل 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;
            }
        }
    }
}
}

إليك حلًا خفيف الوزن أستخدمه والذي يسمح للتطبيق بإحضار نافذة موجودة بالفعل إلى المقدمة دون اللجوء إلى رسائل Windows المخصصة أو البحث بشكل أعمى عن أسماء العمليات.

[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) محليًا لأنه سيتم التخلص منه تلقائيًا حتى إذا تم إغلاق التطبيق دون الوصول إلى نهاية التطبيق الرئيسي.

يمكنك أيضًا استخدام وقت تشغيل CodeFluent وهي مجموعة مجانية من الأدوات.يوفر أ نسخة واحدة فئة لتنفيذ تطبيق مثيل واحد.

إليك نفس الشيء الذي تم تنفيذه عبر 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();
    }
}

الأساليب المستندة إلى كائن المزامنة المسماة ليست مشتركة بين الأنظمة الأساسية لأن كائنات المزامنة المسماة ليست عالمية في 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.");
            }
        }
    }
}

لا أستطيع العثور على حل قصير هنا لذلك آمل أن يعجب شخص ما بهذا:

تم التحديث بتاريخ 2018-09-20

(بالمناسبة.ضع الكود في مكانك "البرنامج.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;
        }

// رمز اختياري.إذا كنت لا تريد أن يقوم شخص ما بتشغيل ملفك ".exe" باسم مختلف:

        string exeName = AppDomain.CurrentDomain.FriendlyName;
        if (exeName != "the name of you're executable.exe") // If you try that in debug mode, don't forget that u don't use ur normal .exe. Debug uses the .vshost.exe.
        {// You can add here a MessageBox if you want. To point 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 you're executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

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

لقد أضفت طريقة 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);
}

عادة، عندما نقوم بتنفيذ ملف .exe، فإنه في كل مرة يقوم بإنشاء عملية نوافذ منفصلة بمساحة عنوان خاصة بها وموارد وما إلى ذلك.لكننا لا نريد هذه المعايير لأن ذلك سيمنعنا من إنشاء عملية واحدة.يمكن إنشاء تطبيقات مثيل واحد باستخدام Mutex في C# والذي تمت مناقشته في هذه المقالة

علاوة على ذلك، إذا أردنا وضع التطبيق في المقدمة، فيمكننا القيام بذلك باستخدامه

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

عادةً، هذا هو الرمز الذي أستخدمه للمثيل الفردي نماذج ويندوز التطبيقات:

[STAThread]
public static void Main()
{
    String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

    using (Mutex mutex = new Mutex(false, assemblyName))
    {
        if (!mutex.WaitOne(0, false))
        {
            Boolean shownProcess = false;
            Process currentProcess = Process.GetCurrentProcess();

            foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
            {
                if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
                {
                    IntPtr windowHandle = process.MainWindowHandle;

                    if (NativeMethods.IsIconic(windowHandle))
                        NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);

                    NativeMethods.SetForegroundWindow(windowHandle);

                    shownProcess = true;
                }
            }

            if (!shownProcess)
                MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form());
        }
    }
}

حيث المكونات الأصلية هي:

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean IsIconic([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);

public enum ShowWindowCommand : int
{
    Hide                   = 0x0,
    ShowNormal             = 0x1,
    ShowMinimized          = 0x2,
    ShowMaximized          = 0x3,
    ShowNormalNotActive    = 0x4,
    Minimize               = 0x6,
    ShowMinimizedNotActive = 0x7,
    ShowCurrentNotActive   = 0x8,
    Restore                = 0x9,
    ShowDefault            = 0xA,
    ForceMinimize          = 0xB
}

هنا الحل:

Protected Overrides Sub OnStartup(e As StartupEventArgs)
    Const appName As String = "TestApp"
    Dim createdNew As Boolean
    _mutex = New Mutex(True, appName, createdNew)
    If Not createdNew Then
        'app is already running! Exiting the application
        MessageBox.Show("Application is already running.")
        Application.Current.Shutdown()
    End If
    MyBase.OnStartup(e)
End Sub

وهكذا انتهى بي الأمر إلى الاهتمام بهذه المشكلة.لاحظ أن رمز التصحيح لا يزال موجودًا للاختبار.هذا الرمز موجود داخل OnStartup في ملف App.xaml.cs.(وبف)

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

        }

قد يكون لهذا مشاكل لم أكتشفها بعد.إذا واجهت أي شيء فسوف أقوم بتحديث إجابتي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top