首先定义单实例指的是整个操作系统中只运行一个应用程序的实例。当用户运行应用程序时先检查系统是否已经启动该应用程序,若启动则将自动弹出应用程序窗体,否则启动应用程序。OK,这样单实例应用程序的基本功能就出来了。我们分别进行讨论。
一、如何检测系统中是否已经启动该应用程序?
1.使用线程互斥体Mutex
当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。 Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。详见[MSDN]http://msdn.microsoft.com/zh-cn/library/system.threading.mutex.aspx
代码如下:
bool isCreated = false; //是否创建成功
//声明一个互斥体
mutex = new Mutex(true, "63133E79-F9E7-471B-A0BE-9F3CCE4ADFF7", out isCreated);
if (!isCreated) //系统存在该应用程序的实例
{
Process process = GetRunningProcessInstance();
if (process != null)
{
Win32API.SetForegroundProcess(process, MainForm.Title);
}
Application.Exit();
return;
}
2. 使用Process检查是否已经存在应用程序的进程
代码如下:
/// <summary>
/// 根据“进程名称和路径”获取已运行的实例
/// </summary>
/// <param name=" processName ">应用程序的名称,不包含后缀”exe”</param>
/// <returns>实例在运行的进程</returns>
/// <remarks>存在问题:修改了文件名或改变了文件路径,此方法失效。</remarks>
private static Process GetRunningProcessInstance(string processName)
{
Process runningProcess = null;
string fileName = processName+ ".exe";
Process[] processes = Process.GetProcessesByName(processName);
foreach (Process process in processes)
{
if (process.MainModule.ModuleName == fileName)
{
runningProcess = process;
break;
}
}
return runningProcess;
}
若获取到的进程实例不为空则说明应用程序已存在。
3. EventWaitHandle或实现WindowsFormsApplicationBase
EventWaitHandle 类允许线程通过发信号互相通信。通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用Set 方法,以释放一个或多个被阻止的线程。线程可以通过调用 static(在 Visual Basic 中为 Shared)WaitHandle.SignalAndWait 方法,以原子操作的形式向 EventWaitHandle 发出信号,然后在它上面阻止。详见[MSDN] http://msdn.microsoft.com/zh-cn/library/yfk7eby4(v=VS.80).aspx
WindowsFormsApplicationBase提供与当前应用程序相关的属性、方法和事件。WindowsFormsApplicationBase位于Microsoft.VisualBasic.dll中,使用时注意引用该组件。
之所以把这两种方式写一起是因为WindowsFormsApplicationBase判断应用程序是否已运行的方法也是使用的EventWaitHandle。只不过WindowsFormsApplicationBase进行了一些封装,使单实例应用程序更容易实现。对EventWaitHandle和WindowsFormsApplicationBase有疑问的看MSDN,这里不多说了,不喜欢这种方式因为WindowsFormsApplicationBase的代码写的实在太复杂了。不过懒人可以使用。上代码。
调用SingleInstanceManager:
SingleInstanceManager singleInstance = new SingleInstanceManager();
singleInstance.Run(null);
//SingleInstanceManager实现:
public class SingleInstanceManager : WindowsFormsApplicationBase
{
public SingleInstanceManager()
{
//确定此应用程序是否为单实例应用程序。
this.IsSingleInstance = true;
//确定应用程序的主窗体关闭时执行的操作。
this.ShutdownStyle = ShutdownMode.AfterAllFormsClose;
}
protected override bool OnStartup(StartupEventArgs eventArgs)
{
//在程序第一次运行时调用
MainForm form = new MainForm();
Application.Run(form);
//运行后返回FLASE,指示应用程序是否应继续启动。
return false;
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
base.OnStartupNextInstance(eventArgs);
eventArgs.BringToForeground = true;
}
}
注意引用:Microsoft.VisualBasic.dll
二、如何自动弹出应用程序窗体?
对于第3种方法就不说了,稍微修改下代码就可以弹出应用程序窗体了。
无论使用什么方法,要弹出窗体必须获取到已经存在的应用程序的进程,否则一切都是免谈。获取到进程后再通过该进程中找到主窗体的句柄,下面的事情就很简单了。
其实对于方法1只是判断应用程序是否已运行,方法2则可以获取到已运行的应用程序的进程。所以两种方法不管如何判断获取进行都要调用GetRunningProcessInstance()或GetCurrentProcess来获取进程。
最初我的代码是这样的,当然网上有很多代码也都是这样滴,抄一遍吧:
bool createdNew = true;
using (Mutex mutex = new Mutex(true, "MyApplicationName", out createdNew))
{
if (createdNew)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
else
{
Process current = Process.GetCurrentProcess();
foreach (Process process in Process.GetProcessesByName(current.ProcessName))
{
if (process.Id != current.Id)
{
//调API
SetForegroundWindow(process.MainWindowHandle);
break;
}
}
}
最初还不错,当我再次运行程序时会自动弹出窗体。
不久问题就出现了。我的应用程序最小化时隐藏窗体,只在任务栏上显示个图标。这时当我最小化窗体后后,无论怎样点击程序他都不弹出窗体了。跟踪代码发现Hide主窗体后,process.MainWindowHandle值为0。完了,忽略这茬了。窗体隐藏后虽然句柄不会消失,但是系统会认为进程已经没有了主窗体,于是把MainWindowHandle置为0。那就只能问Process的Thread要窗体句柄了。于是我使用EnumThreadWindows枚举每个线程的窗体,然后判断该窗体是否为需要的窗体,我通过窗体的Title来判断的,有好的方法请告诉我。OK!问题解决,哈哈!上代码( ⊙ o ⊙ )!
Win32API:
/// <summary>
/// 该委托是一个与EnumWindows或EnumDesktopWindows一起使用的应用程序定义的回调函数。它接收顶层窗口句柄。
/// WNDENUMPROC定义一个指向这个回调函数的指针。EnumWindowsProc是应用程序定义函数名的位置标志符。
/// </summary>
/// <param name="hwnd">顶层窗口句柄。</param>
/// <param name="lParam">指定在EnumWIndowsh或EnumDesktopWindows中的应用程序定义值。</param>
/// <returns>为继续列表,回调函数必须返回TRUE;若停止列表,它必须返回FALSE</returns>
public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
/// <summary>
/// 该函数枚举所有与一个线程相关联的非子窗口,办法是先将句柄传送给每一个窗口,随后传送给应用程序定义的回调函数。
/// EnumThreadWindows函数继续直到所有窗口枚举完为止或回调函数返回FALSE为止。
/// 要枚举一个特定窗口的所有子窗口,使用EnumChildWindows函数。
/// </summary>
/// <param name="dwThreadId">标识将被列举窗口的线程。</param>
/// <param name="lpfn">指向一个应用程序定义的回调函数指针,请参看EnumThreadWndProc。</param>
/// <param name="lParam">指定一个传递给回调函数的应用程序定义值。</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool EnumThreadWindows(int dwThreadId, EnumWindowsProc lpfn, IntPtr lParam);
/// <summary>
/// 该函数获得给定窗口的可视状态。
/// </summary>
/// <param name="hWnd">被测试窗口的句柄</param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
/// <summary>
/// 窗口是否已最小化
/// </summary>
/// <param name="hWnd">窗口句柄</param>
/// <returns>非零表示成功,零表示失败</returns>
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern bool IsIconic(IntPtr hWnd);
/// <summary>
/// 将窗口设为系统的前台窗口
/// </summary>
/// <param name="hWnd">窗口句柄</param>
/// <returns>非零表示成功,零表示失败</returns>
[System.Runtime.InteropServices.DllImport("User32.dll")]
public static extern int SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
static extern IntPtr SetActiveWindow(IntPtr hWnd);
#region - 激活进程指定标题的窗口 -
/// <summary>
/// 设置进程指定标题的窗口为前台窗口
/// </summary>
/// <param name="process">进程</param>
/// <param name="caption">窗口的标题</param>
public static void SetForegroundProcess(Process process, string caption)
{
IntPtr handler = process.MainWindowHandle;
if (handler == IntPtr.Zero)
{
EunmProcessWindows(process, caption);
}
else
{
bool isVisible = IsWindowVisible(handler);
if (isVisible)
{
bool isIcon = IsIconic(handler);
// 窗口是否已最小化
if (isIcon)
{
ShowWindow(handler, SWCmd.SW_SHOW);
// 将窗口设为前台窗口
SetForegroundWindow(handler);
}
else
{
// 将窗口设为前台窗口
SetForegroundWindow(handler);
}
}
else
{
// 显示窗口
ShowWindowAsync(handler, CmdShow.SW_RESTORE);
}
}
}
private static void EunmProcessWindows(Process process, string caption)
{
//窗体Title
IntPtr pCaption = Marshal.StringToHGlobalAnsi(caption);
foreach (ProcessThread thread in process.Threads)
{
int tid = thread.Id;
bool flag = EnumThreadWindows(tid, new EnumWindowsProc(EnumWindowsCallBack), pCaption);
if (!flag)
{
return;
}
}
Marshal.FreeHGlobal(pCaption);
}
//枚举Windows时的回调函数
private static bool EnumWindowsCallBack(IntPtr handle, IntPtr lparam)
{
string caption = Marshal.PtrToStringAnsi(lparam); //窗体标题
IntPtr pStr = Marshal.AllocHGlobal(512);
GetWindowText(handle, pStr, 512);
string str = Marshal.PtrToStringAuto(pStr);
Marshal.FreeHGlobal(pStr);
if (string.IsNullOrEmpty(str))
{
return true;
}
else
{
if (str.Equals(caption, StringComparison.OrdinalIgnoreCase))
{
ShowWindow(handle, SWCmd.SW_SHOW);
SetActiveWindow(handle);
SetForegroundWindow(handle);
return false;
}
return true;
}
}
#endregion
调用:
static class Program
{
private static Mutex mutex;
const string uniqueName = "63133E79-F9E7-471B-A0BE-9F3CCE4ADFF7";
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
#region 单实例
bool isCreated = false; //是否已运行了该程序
//声明一个互斥体
using( mutex = new Mutex(true, uniqueName, out isCreated))
{
if (!isCreated)
{
Process process = GetRunningProcessInstance();
if (process != null)
Win32API.SetForegroundProcess(process, “主窗体标题”);
Environment.Exit(0);
return;
}
}
#endregion
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}