熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

.Net平台應用程序唯一運行實例

2022-06-13   來源: .NET編程 

  摘要

  本文闡述了在基於NET平台的應用程序開發中如何實現唯一應用程序運行實例對幾種實現方式進行分析測試比較從而尋找一種合適的處理方式單擊此處才查看本文的示例代碼


概述

   在開發一些應用系統的時候由於程序內在的一些特征系統的某些組成子程序只允許運行一個應用程序實例以保證業務和數據處理安全本文將從實際應用角度來分析其實現原理對三種實現方式進行測試比較從而確定一種合適的實現方法文章的例子使用C#語言進行描述

進程匹配

    對於每一個應用程序運行實例都會包含該實例的一個或多個進程而且在程序運行過程中可能會動態的創建或銷毀進程或者訪問其他現有進程進行通信不難發現在程序最先初始化的那一刻只有一個進程運行而且應用程序進程生命周期最大進程名稱集合是不變的因此在應用程序初始化的時候可以根據進程關鍵信息檢查系統進程列表是否存在同當前初始化進程匹配的進程來確定是否已經運行進程實例
邏輯處理步驟如下
.初始化應用程序啟動程序初始化進程
.訪問系統進程列表根據初始化進程關鍵信息進行匹配查找
.沒有找到匹配進程(這一步是不會發生的因為當前初始化進程也在列表中不過還要看獲取進程列表的實現代碼怎麼寫)繼續初始化進程程序初始化完成運行
.找到第一個匹配進程判斷找到的進程ID是否同初始化進程ID相同
.如果第一個匹配進程ID同初始化進程ID相同則為當前初始化進程繼續查找
.沒有找到第二個匹配進程表明當前運行的是首個實例繼續初始化進程程序初始化完成運行
.找到第二個表明已有一個實例在運行停止當前程序初始化提示已有應用程序運行
.如果找到第一個匹配進程ID不同表明已有一個實例在運行停止當前程序初始化提示已有應用程序運行

     可見上面的邏輯實現中用於進程匹配的信息是關鍵選擇不當功能就無法實現在這個實例中筆者使用了應用程序完全文件名稱作為關鍵信息
在代碼中首先需要引用下面命名空間以調用WinAPI函數
using SystemRuntimeInteropServices;
把實現唯一運行實例功能的類名取為SingleInstance在類前面加static關鍵字為C# 新增的語言特征
public static class SingleInstance {}
使用GetRunningInstance靜態方法獲取應用程序進程實例如果沒有匹配進程返回Null值
public static Process GetRunningInstance()
{
        Process currentProcess = ProcessGetCurrentProcess(); //獲取當前進程
        //獲取當前運行程序完全限定名 
        string currentFileName = currentProcessMainModuleFileName; 
        //獲取進程名為ProcessName的Process數組 
        Process[] processes = ProcessGetProcessesByName(currentProcessProcessName); 
        //遍歷有相同進程名稱正在運行的進程 
        foreach (Process process in processes) 
        { 
                if (processMainModuleFileName == currentFileName) 
                { 
                        if (processId != currentProcessId) //根據進程ID排除當前進程 
                                return process;//返回已運行的進程實例 
                } 
        } 
        return null;
}

接下來調用兩個WinAPI其功能將在包裝方法中描述
[DllImport(Userdll)]
private static extern bool ShowWindowAsync(IntPtr hWnd int cmdShow);
[DllImport(Userdll)]
private static extern bool SetForegroundWindow(IntPtr hWnd);

定義類成員輔助變量
private const int WS_SHOWNORMAL = ;

以上的方法聲明為私有對其進一步包裝HandleRunningInstance靜態方法為獲取應用程序句柄設置應用程序為前台運行並返回bool值
public static bool HandleRunningInstance(Process instance)

        //確保窗口沒有被最小化或最大化 
        ShowWindowAsync(instanceMainWindowHandle WS_SHOWNORMAL); 
        //設置為foreground window 
        return SetForegroundWindow(instanceMainWindowHandle);
}

對上面的方法創建一個重載版本使調用代碼更加簡潔
public static bool HandleRunningInstance()

        Process p = GetRunningInstance(); 
        if (p != null) 
        { 
                HandleRunningInstance(p); 
                return true; 
        } 
        return false;
}

上面的方法實現獲取已經運行的進程實例的句柄並獲取其焦點顯示到前台這個很有用在其他實現方式中也可以用到

在Main函數中調用下面代碼實現單一應用程序實例
Process p = SingleInstanceGetRunningInstance();
if (p != null) //已經有應用程序副本執行

        SingleInstanceHandleRunningInstance(p);
}
else //啟動第一個應用程序

        ApplicationRun(new MainForm());
}

簡潔的調用為
if (SingleInstanceHandleRunningInstance()== false)

        ApplicationRun(new MainForm());
}

   可見在上面的實現過程中由於關鍵信息采用應用程序的完整文件名因此在文件名稱或路徑名稱修改後以上實現就會失效

進程互斥

   在這個實現方式中需要定義一個進程同步基元可以理解為臨界資源該資源只允許一個進程使用根據這一點實現應用程序唯一運行實例就比較簡單了
實現步驟如下
.應用程序初始化訪問該同步基元
.可以訪問說明該同步基元未被使用也就是說沒有應用程序實例運行使用同步基元可以繼續初始化成為第一個運行實例
.不可以訪問說明該同步基元已被使用也就是說已有應用程序實例運行停止當前程序初始化提示已有應用程序運行
.應用程序實例退出釋放同步基元占用

   在代碼中筆者使用SystemThreadingMutex類實現同步基元實現應用程序實例之間互斥功能Mutex默認名字取AssemblyGetEntryAssembly()FullName
在類成員中聲明同步基元
private static Mutex mutex = null;

CreateMutex靜態方法創建應用程序進程Mutex返回創建結果為true表示創建成功false失敗
public static bool CreateMutex()

        return CreateMutex(AssemblyGetEntryAssembly()FullName);
}

實現其重載方法讓用戶可以自定義Mutex名字
public static bool CreateMutex(string name)

        bool result = false; 
        mutex = new Mutex(true name out result); 
        return result;
}

對應的釋放Mutex資源方法為
public static void ReleaseMutex()

        if (mutex != null) 
        { 
                mutexClose(); 
        }
}

在Main函數中調用下面代碼實現單一應用程序實例
if (SingleInstanceCreateMutex())

        ApplicationRun(new MainForm()); 
        SingleInstanceReleaseMutex();
}
else

        MessageBoxShow(程序已經運行!);
}

    可見在上面的實現過程中Mutex名字是同步基元的唯一標識如果剛好有不同的應用程序使用了相同名稱的Mutex那不同的應用程序實例也會出現互斥現象


運行標志

    使用應用程序運行標志簡單來講就是在程序初始化的時候設置一個標志表示程序已運行在程序運行結束的時候刪除該標志
基本步驟如下
.應用程序初始化檢查運行標志是否已經設置
.發現已經設置說明已有應用程序實例運行停止當前程序初始化提示已有應用程序運行 .發現沒有設置說明沒有應用程序實例運行繼續當前程序初始化
.退出應用程序時刪除該運行標志

   對於標志存儲載體可以使用注冊表數據庫或外部文件等這裡的代碼使用外部文件實現對存放標志的文件目錄選擇C:\Documents and Settings\All Users\Application Data也可以是C:\Program Files\Common Files
聲明類成員標志文件名稱變量
private static string runFlagFullname = null;

初始化程序運行標志如果設置成功返回true已經設置返回false設置失敗將拋出異常
public static bool InitRunFlag()

        if (FileExists(RunFlag)) 
        { 
                return false; 
        } 
        using (FileStream fs = new FileStream(RunFlag FileModeCreate)) 
        { 
        }
        return true;
}

釋放初始化程序運行標志如果釋放失敗將拋出異常
public static void DisposeRunFlag()

        if (FileExists(RunFlag)) 
        { 
                FileDelete(RunFlag); 
        }
}

獲取或設置程序運行標志必須符合Windows文件命名規范
public static string RunFlag

        get 
        { 
                if(runFlagFullname == null) 
                { 
                        string assemblyFullName = AssemblyGetEntryAssembly()FullName; 
                        string path = EnvironmentGetFolderPath(EnvironmentSpecialFolderCommonApplicationData); 
                        runFlagFullname = PathCombine(path assemblyFullName); 
                } 
                return runFlagFullname; 
        } 
        set 
        { 
                runFlagFullname = value; 
        }
}

在Main函數中調用下面代碼實現單一應用程序實例
if (SingleInstanceInitRunFlag())

        ApplicationRun(new MainForm()); 
        SingleInstanceDisposeRunFlag();
}
else

        MessageBoxShow(程序已經運行!);
}

   可見在上面的實現過程中需要訪問文件IO因此有可能會出現異常對異常需要進行具體處理如果不同應用程序使用了相同的運行標志也會出現進程互斥實現中存在的問題由於運行標志存在外部載體中如果筆者把啟動的應用程序進程實例直接在Windows管理器進程列表中結束或使其產生異常那設置的運行標志就不會銷毀應用程序就沒法再次運行

功能測試

  這一節對上面的三個功能進行測試以分析之間的區別功能測試類別包括下面五類
.本地系統同一應用程序目錄
.本地系統同一應用程序修改運行文件名稱使兩次運行名稱不同
.本地系統兩次運行程序目錄不同不修改文件名稱
.本地系統不同會話用戶登錄啟動應用程序
.遠程計算機程序訪問啟動應用程序(一個程序在遠程另一個在本地)

   根據代碼實現細節不同對測試的結果可能會有所不同這裡的測試結果以筆者上面幾節中實現的代碼為准為了測試簡單化通過給應用程序傳入測試參數決定使用哪種方式入口函數調用代碼為
[STAThread]
static void Main(string[] args)

        if (argsLength == ) //沒有傳送參數 
        { 
                Process p = SingleInstanceGetRunningInstance(); 
                if (p != null) //已經有應用程序副本執行 
                        SingleInstanceHandleRunningInstance(p); 
                else //啟動第一個應用程序 
                        ApplicationRun(new MainForm()); 
        } 
        else //有多個參數 
        { 
                switch (args[]ToLower()) 
                { 
                        case api
                                if (SingleInstanceHandleRunningInstance() == false) 
                                        ApplicationRun(new MainForm()); 
                                break; 
                        case mutex
                                if (argsLength >= ) //參數中傳入互斥體名稱 
                                { 
                                        if ( SingleInstanceCreateMutex(args[]) ) 
                                        { 
                                                ApplicationRun(new MainForm()); 
                                                SingleInstanceReleaseMutex(); 
                                        } 
                                        else 
                                                //調用SingleInstanceHandleRunningInstance()方法顯示到前台 
                                                MessageBoxShow(程序已經運行!); 
                                } 
                                else 
                                { 
                                        if (SingleInstanceCreateMutex()) 
                                        { 
                                                ApplicationRun(new MainForm()); 
                                                SingleInstanceReleaseMutex(); 
                                        } 
                                        else 
                                                //調用SingleInstanceHandleRunningInstance()方法顯示到前台 
                                                MessageBoxShow(程序已經運行!); 
                                } 
                                break; 
                        case flag://使用該方式需要在程序退出時調用 
                                if (argsLength >= ) //參數中傳入運行標志文件名稱 
                                        SingleInstanceRunFlag = args[]; 
                                try 
                                {
                                        if (SingleInstanceInitRunFlag()) 
                                        { 
                                                ApplicationRun(new MainForm()); 
                                                SingleInstanceDisposeRunFlag(); 
                                        } 
                                        else 
                                                //調用SingleInstanceHandleRunningInstance()方法顯示到前台 
                                                MessageBoxShow(程序已經運行!); 
                                } 
                                catch (Exception ex) 
                                { 
                                        MessageBoxShow(exToString()); 
                                } 
                                break; 
                        default: 
                                MessageBoxShow(應用程序參數設置失敗); 
                                break; 
                } 
        }
}

運行CMD命令行
第一種調用為 WindowsApplicationexe –api 或 WindowsApplicationexe
第二種調用為 WindowsApplicationexe –mutex 或WindowsApplicationexe –mutex {FAECfBDE}
第三種調用為 WindowsApplicationexe –flag 或WindowsApplicationexe –flag c:\zhzuo

測試結果

匹配/互斥/標志 同一目錄 修改名稱 不同目錄 不同用戶 遠程訪問 同一目錄 O/O/O         修改名稱   X/O/O       不同目錄     X/O/O     不同用戶       #/X/O   遠程訪問         X/O/O
備注O 表示成功X – 表示失敗# 程序第二個運行沒有反應

   針對遠程訪問的測試需要在系統管理工具的NET Framework Configuration中進行設置授權該局域網路徑允許訪問否則會拋出SystemSecuritySecurityException異常根據測試結果可見三種實現方式適用范圍不同理想的實現是結合他們的優點進行多點判斷

更多資源
    關於NET平台應用的開發更多的技術文章可以訪問 對於本文的建議或意見可在網站上留言


From:http://tw.wingwit.com/Article/program/net/201311/11652.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.