Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Разработка Windows Forms > Один экземпляр приложения, с передачей аргументов


Автор: farad 17.1.2008, 15:23
Поскольку вопрос, «как сделать, чтобы нельзя было запускать более одного экземпляра приложения» возникает с завидной регулярностью, то хочу предложить свое решение данного вопроса. Ваял для себя, но возможно кому-нибудь пригодится еще. Решение позволяет активировать главное окно уже запущенного приложения, при попытке запуска второго экземпляра. При этом происходит передача аргументов командной строки второго экземпляра первому. Например, если приложение поддерживает обработку командной строки, скажем открытие файла определенного типа из проводника, то если при запущенном приложении, щелкнуть по файлу подходящего типа – этот файл будет открыт запущенным экземпляром. Передача аргументов идет через реестр – решил, что это проще всего.
Короче, это базовый класс, от которого нужно наследовать главную форму приложения и перегрузить в своей форме  метод, который осуществляет обработку аргументов командной строки. Если не требуется обработка аргументов, перегружать ничего не надо. Достаточно просто наследовать главную форму от этого класса, и все.. второй экземпляр вашего приложения уже не запустится… 

Код

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Runtime.InteropServices;
namespace forms
{
    public  class SingleHinstForm:Form
    {
        // сообщение которое будем отправлять существующему окну
        // для передачи аргументов
        const int WM_SENDARGS = 0x0400/*WM_USER*/ + 100;
        
        //сюда будем сохранять хендл и аргументы
        //командной строки
        static readonly string RegPath = 
            "Software\\"+ Application.CompanyName +
            "\\" +Application.ProductName+"\\SingleHinst\\";

        const string RegName = "MainWindowHandle";
        const string RegArgsFolder = "CommandLineArguments";
        
        //для контроля
        static Mutex _mutex=null;
        
        [DllImport("User32", CharSet = CharSet.None)]
        static extern long PostMessage(System.IntPtr hWnd,
            int Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("User32.dll", CharSet = CharSet.None)]
        static extern bool SetForegroundWindow(System.IntPtr Handle);

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            FreeMutex();
        }
        //здесь будем ловить сообщения 
        //от других экземпляров приложения
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            //пользователь запустил еще один экземпляр и он
            //от него пришло сообщение
            if (m.Msg == WM_SENDARGS)
            {
                //разворачиваем окно и выводим на передний план
                if (WindowState == FormWindowState.Minimized)
                    WindowState = FormWindowState.Normal;
                Activate();

                //обрабатываем аргументы
                //которые были переданы новому экземпляру
                ArgsProc(GetInsideArgs());
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            //если в системе уже запущен экземпляр формы,
            //то диспозимся
            if (Exist())
            {
                Dispose();
                return;
            }
            base.OnLoad(e);
        }

        //здесь в потомке нужно реализовать обработку аргументов
        //командной строки
        //если обработка не нужна - метод
        //можно исключить
        protected virtual void ArgsProc(string[] args)
        {

        }
        
        //ищем существующий экземпляр формы 
        //если находим -  шлем ему сообщение
        bool Exist()
        {
            bool test;
            _mutex = 
                new Mutex(false, Application.ProductName + ".Mutex", out test);
            if (test)
            {
                SaveHwnd();
                return false;
            }
            else
            {
                _mutex = null;
                IntPtr hwnd = GetExistingHwnd();
                
                //не нашли - можно продолжать выполнение приложения
                if (hwnd == IntPtr.Zero)
                    return false;
                
                //сохраним аргументы командной строки
                SaveArgs();

                //отправим уведомление о том что была попытка запуска
                //второго экземпляра
                PostMessage(hwnd,WM_SENDARGS,IntPtr.Zero,IntPtr.Zero);
                SetForegroundWindow(hwnd);
                return true;
            }
        }

        //сохранение хендла главного окна приложения в реестре
        void SaveHwnd()
        {
            RegistryKey rk = Registry.CurrentUser.CreateSubKey(RegPath);
            if (rk != null)
            {
                rk.SetValue(RegName,this.Handle.ToInt32());
                rk.Close();
            }
        }

        //получение хендла главного окна ранее запущенного приложения
        IntPtr GetExistingHwnd()
        {
            try
            {

                RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegPath);
                if (rk != null)
                {
                    int ret = (int)rk.GetValue(RegName);
                    rk.Close();
                    return new IntPtr(ret);
                }
            }
            catch { }
            return IntPtr.Zero;
        }
        
        //сохранение аргументов командной строки для передачи
        //уже запущенному приложению.
        //метод можно исключить если не нужна обработка аргументов
        void SaveArgs()
        {
            string[] args=Environment.GetCommandLineArgs();

            RegistryKey rk,subrk;
            rk=subrk= null;

            try
            {
                rk = Registry.CurrentUser.CreateSubKey(RegPath);
                if (rk != null)
                {
                    subrk = rk.CreateSubKey(RegArgsFolder);
                    if (subrk != null)
                    {
                        for (int i = 0; i < args.Length; i++)
                            subrk.SetValue(string.Format("{0}", i), args[i]);
                        subrk.Close();
                        subrk = null;
                    }
                    rk.Close();
                    rk = null;
                }
            }
            finally
            {
                if (subrk != null) subrk.Close();
                if (rk != null) rk.Close();
            }
        }

        //получение аргументов которые были переданы новому 
        //экземпляру приложения
        //можно исключить если не нужна обработка аргументов
        protected string[] GetInsideArgs()
        {
            string[] ret = null;
            RegistryKey rk,subrk;
            rk=subrk=null;
            try
            {
                rk = Registry.CurrentUser.OpenSubKey(RegPath);
                if (rk != null)
                {
                    subrk = rk.OpenSubKey(RegArgsFolder);
                    if (subrk != null)
                    {
                        string[] names = subrk.GetValueNames();
                        if (names.Length > 0)
                        {
                            ret = new string[names.Length];
                            int j = 0;
                            for (int i = 0; i < names.Length; i++)
                            {
                                ret[j++] = (string)subrk.GetValue(names[i]);
                            }
                        }
                        subrk.Close();
                        subrk = null;
                        subrk = rk.OpenSubKey(RegArgsFolder, true);
                        for (int i = 0; i < names.Length; i++)
                        {
                            subrk.DeleteValue(names[i]);
                        }
                        subrk.Close();
                        return ret;
                    }
                }
                return null;
            }
            catch
            {
                return null;
            }
            finally
            {
                if(subrk!=null)subrk.Close();
                if(rk!=null)rk.Close();
            }
        }
        
        
        void FreeMutex()
        {
            if (_mutex == null)
                return;
            _mutex.Close();
            _mutex = null;
            
            Registry.CurrentUser.DeleteSubKey(RegPath+RegArgsFolder,false);
            Registry.CurrentUser.DeleteSubKey(RegPath,false);
        }
    }
}


Автор: Softaz 18.2.2008, 14:07
А если атомы использовать? Все-таки как-то по мне криовато через реестр хранить.

Автор: nettitan 6.5.2008, 22:00
Какие еще атомы?,  smile  плз...

Автор: jonie 6.5.2008, 22:58
определяют стандартно через семафоры.. 

атомы это системные объекты в MSDN примерно там : windowsuserinterface/dataexchange/atoms
суть такая : вы пихаете в этот атом строку командную (заметим что атомов может понадобиться несколько, т.к. они ограничены длиной)....потом шлее сообщение окну, где указываете в одном из параметров DWORD атома. и в первом приложении его зачитываете.

но мне кажется атомы тут тоже не нужны.
1) вы можете использовать IPC (довольно имхо накладно)
2) а просто передавать используя сообщение WM_COPYDATA не проще ли?

на самом деле самая загвоздка передать HWND окна приемника (или вообще какие-то объекты)
но и это можно сделать, думаю, через вышеупомянытый семафор...

в общем как-то так будет работать вплоне даже..
Код

                Semaphore sp = new Semaphore(frm.Handle.ToInt32(), int.MaxValue, "SomeName", out isAlreadyExist);
                if (!isAlreadyExist)
                {
                    Console.WriteLine("already Exist!");
                    Console.WriteLine("HWND={0}", sp.Release());
                }

Автор: source777 6.5.2008, 22:59
ужос. Как можно такие "решения" напоказ выставлять, такое можно только в справочнике "Как не надо делать ни в коем случае" показывать...

Итак, решения данной задачи:
1) SendMessage + WM_COPYDATA (самый быстрый способ)
2) GlobalAddAtom + GlobalFindAtom + GlobalDeleteAtom (периодический способ, на который указал Softaz, мне лично навязываемое данным способом активное ожидание не импонирует)
3) Класс Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase (встроенная .NET-поддержка данного алгоритма, хороша тем, что предельно проста в использовании)

Автор: mr.DUDA 7.5.2008, 09:12
Код
        //для контроля
        static Mutex _mutex=null;

Собственно, вот на этом мьютексе всё и можно было сделать, остальной код и не нужен.  smile 

Автор: nettitan 8.5.2008, 08:40
СПС за инфу, буду пробывать все возможные варианты, дабы найти оптимальный  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)