`
isiqi
  • 浏览: 16483373 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

基于.Net平台应用程序唯一运行实例实现

阅读更多

在开发一些应用系统的时候,由于程序内在的一些特征,系统的某些组成子程序只允许运行一个应用程序实例,以保证业务和数据处理安全。本文将从实际应用角度来分析其实现原理,对三种实现方式进行测试比较,从而确定一种合适的实现方法。文章的例子使用C#语言进行描述。

进程匹配
对于每一个应用程序运行实例都会包含该实例的一个或多个进程,而且在程序运行过程中可能会动态的创建或销毁进程,或者访问其他现有进程进行通信。不难发现,在程序最先初始化的那一刻只有一个进程运行,而且应用程序进程生命周期最大进程名称集合是不变的。因此,在应用程序初始化的时候,可以根据进程关键信息检查系统进程列表是否存在同当前初始化进程匹配的进程来确定是否已经运行进程实例。
逻辑处理步骤如下,
1.初始化应用程序,启动程序初始化进程;
2.访问系统进程列表,根据初始化进程关键信息进行匹配查找;
3.没有找到匹配进程(这一步是不会发生的,因为当前初始化进程也在列表中,不过还要看获取进程列表的实现代码怎么写),继续初始化进程,程序初始化完成运行。
4.找到第一个匹配进程,判断找到的进程ID是否同初始化进程ID相同;
5.如果第一个匹配进程ID同初始化进程ID相同,则为当前初始化进程,继续查找;
6.没有找到第二个匹配进程,表明当前运行的是首个实例,继续初始化进程,程序初始化完成运行。
7.找到第二个,表明已有一个实例在运行,停止当前程序初始化,提示已有应用程序运行。
8.如果找到第一个匹配进程ID不同,表明已有一个实例在运行,停止当前程序初始化,提示已有应用程序运行。

可见上面的逻辑实现中用于进程匹配的信息是关键,选择不当功能就无法实现。在这个实例中笔者使用了应用程序完全文件名称作为关键信息。
在代码中首先需要引用下面命名空间,以调用WinAPI函数。

using System.Runtime.InteropServices;
//把实现唯一运行实例功能的类名取为SingleInstance,在类前面加static关键字为C# 2.0新增的语言特征。
publicstaticclass SingleInstance
{
//使用GetRunningInstance静态方法获取应用程序进程实例,如果没有匹配进程,返回Null值,
publicstatic Process GetRunningInstance()
{
Process currentProcess = Process.GetCurrentProcess(); //获取当前进程
//获取当前运行程序完全限定名
string currentFileName = currentProcess.MainModule.FileName;
//获取进程名为ProcessName的Process数组。
Process[] processes = Process.GetProcessesByName(currentProcess.ProcessName);
//遍历有相同进程名称正在运行的进程
foreach (Process process in processes)
{
if (process.MainModule.FileName == currentFileName)
{
if (process.Id != currentProcess.Id) //根据进程ID排除当前进程
return process; //返回已运行的进程实例
}
}
returnnull;
}
}
接下来调用两个WinAPI,其功能将在包装方法中描述,
[DllImport("User32.dll")]
privatestaticexternbool ShowWindowAsync(IntPtr hWnd, int cmdShow);
[DllImport("User32.dll")]
privatestaticexternbool SetForegroundWindow(IntPtr hWnd);

定义类成员辅助变量,
privateconstint WS_SHOWNORMAL = 1;
以上的方法声明为私有,对其进一步包装,HandleRunningInstance静态方法为获取应用程序句柄,设置应用程序为前台运行,并返回bool值。
publicstaticbool HandleRunningInstance(Process instance)
{
//确保窗口没有被最小化或最大化
ShowWindowAsync(instance.MainWindowHandle, WS_SHOWNORMAL);
//设置为foreground window
return SetForegroundWindow(instance.MainWindowHandle);
}

对上面的方法创建一个重载版本,使调用代码更加简洁,
publicstaticbool HandleRunningInstance()
{
Process p = GetRunningInstance();
if (p != null)
{
HandleRunningInstance(p);
returntrue;
}
returnfalse;
}

上面的方法实现获取已经运行的进程实例的句柄,并获取其焦点显示到前台,这个很有用,在其他实现方式中也可以用到。

在Main函数中调用下面代码实现单一应用程序实例,

Process p = SingleInstance.GetRunningInstance();
if (p != null) //已经有应用程序副本执行
{
SingleInstance.HandleRunningInstance(p);
}
else//启动第一个应用程序
{
Application.Run(new MainForm());
}

简洁的调用为,

if (SingleInstance.HandleRunningInstance() == false)
{
Application.Run(new MainForm());
}

可见,在上面的实现过程中,由于关键信息采用应用程序的完整文件名,因此在文件名称或路径名称修改后,以上实现就会失效。

进程互斥
在这个实现方式中需要定义一个进程同步基元,可以理解为临界资源,该资源只允许一个进程使用。根据这一点实现应用程序唯一运行实例就比较简单了。
实现步骤如下,
1.应用程序初始化访问该同步基元;
2.可以访问,说明该同步基元未被使用,也就是说没有应用程序实例运行,使用同步基元,可以继续初始化成为第一个运行实例。
3.不可以访问,说明该同步基元已被使用,也就是说已有应用程序实例运行,停止当前程序初始化,提示已有应用程序运行。
4.应用程序实例退出释放同步基元占用。

在代码中笔者使用System.Threading.Mutex类实现同步基元,实现应用程序实例之间互斥功能。Mutex默认名字取Assembly.GetEntryAssembly().FullName。
在类成员中声明同步基元,

privatestatic Mutex mutex = null;

CreateMutex静态方法创建应用程序进程Mutex,返回创建结果为true表示创建成功,false失败。

publicstaticbool CreateMutex()
{
return CreateMutex(Assembly.GetEntryAssembly().FullName);
}

实现其重载方法,让用户可以自定义Mutex名字,
publicstaticbool CreateMutex(string name)
{
bool result = false;
mutex = new Mutex(true, name, out result);
return result;
}

对应的释放Mutex资源方法为,
publicstaticvoid ReleaseMutex()
{
if (mutex != null)
{
mutex.Close();
}
}

在Main函数中调用下面代码实现单一应用程序实例,

if (SingleInstance.CreateMutex())
{
Application.Run(new MainForm());
SingleInstance.ReleaseMutex();
}
else
{
MessageBox.Show("程序已经运行!");
}

可见,在上面的实现过程中,Mutex名字是同步基元的唯一标识,如果刚好有不同的应用程序使用了相同名称的Mutex,那不同的应用程序实例也会出现互斥现象。

运行标志
使用应用程序运行标志简单来讲就是在程序初始化的时候设置一个标志表示程序已运行,在程序运行结束的时候删除该标志。
基本步骤如下,
1.应用程序初始化检查运行标志是否已经设置;
2.发现已经设置,说明已有应用程序实例运行,停止当前程序初始化,提示已有应用程序运行。 3.发现没有设置,说明没有应用程序实例运行,继续当前程序初始化。
4.退出应用程序时删除该运行标志。

对于标志存储载体可以使用注册表、数据库或外部文件等,这里的代码使用外部文件实现。对存放标志的文件目录选择C:\Documents and Settings\All Users\Application Data,也可以是C:\Program Files\Common Files。
声明类成员标志文件名称变量,

privatestaticstring runFlagFullname = null;

初始化程序运行标志,如果设置成功,返回true,已经设置返回false,设置失败将抛出异常,

publicstaticbool InitRunFlag()
{
if (File.Exists(RunFlag))
{
returnfalse;
}
using (FileStream fs = new FileStream(RunFlag, FileMode.Create))
{
}
returntrue;
}

释放初始化程序运行标志,如果释放失败将抛出异常,
publicstaticvoid DisposeRunFlag()
{
if (File.Exists(RunFlag))
{
File.Delete(RunFlag);
}
}

获取或设置程序运行标志,必须符合Windows文件命名规范,
publicstaticstring RunFlag
{
get
{
if (runFlagFullname == null)
{
string assemblyFullName = Assembly.GetEntryAssembly().FullName;
string path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
runFlagFullname = Path.Combine(path, assemblyFullName);
}
return runFlagFullname;
}
set
{
runFlagFullname = value;
}
}

在Main函数中调用下面代码实现单一应用程序实例,
if (SingleInstance.InitRunFlag())
{
Application.Run(new MainForm());
SingleInstance.DisposeRunFlag();
}
else
{
MessageBox.Show("程序已经运行!");
}

可见,在上面的实现过程中,需要访问文件IO,因此有可能会出现异常,对异常需要进行具体处理。如果不同应用程序使用了相同的运行标志,也会出现进程互斥实现中存在的问题。由于运行标志存在外部载体中,如果笔者把启动的应用程序进程实例直接在Windows管理器进程列表中结束或使其产生异常,那设置的运行标志就不会销毁,应用程序就没法再次运行。
分享到:
评论

相关推荐

    基于C#的应用程序单例唯一运行的完美解决方案

    标题“基于C#的应用程序单例唯一运行的完美解决方案”所指的就是如何在C#环境下实现一个应用的单例模式,确保程序在操作系统中只能有一个实例在运行。实现这个目标通常涉及到进程级别的检查和互斥量(Mutex)的使用...

    50个VB.NET初学者参考实例源码.rar

    VB.NET是Microsoft开发的一种面向对象的编程语言,它基于.NET框架,具有丰富的库支持和强大的功能。对于初学者来说,掌握VB.NET的基础知识并能通过实践应用是学习的关键。本资料"50个VB.NET初学者参考实例源码.rar...

    基于.NET的一个小型计算器程序

    .NET是由微软开发的一种全面的开发平台,它包含了运行应用程序所需的各种服务和组件。.NET Framework提供了Common Language Runtime(CLR),这是.NET程序的基础,它负责程序的执行、内存管理、异常处理和安全性等...

    基于.net8的物料管理系统

    1. **ASP.NET Core** - .NET 8继续支持ASP.NET Core,这是一种高性能的开源Web框架,用于构建现代化的、云就绪的Web应用程序。物料管理系统可能使用ASP.NET Core MVC(模型-视图-控制器)或API模式来实现后端逻辑。 ...

    Microsoft .net IL 汇编语言程序设计指南

    2. 安全性:通过检查和修改IL,可以实现代码签名、权限验证等安全性措施,保证.NET应用程序的安全运行。 通过学习《Microsoft .NET IL 汇编语言程序设计指南》,开发者可以深入了解.NET框架的内部工作原理,提升对...

    随机签名.net源程序

    在这个平台上,开发者可以构建分布式、跨平台的应用程序。在本源程序中,我们使用的可能是C#或VB.NET,这两种语言都具有简洁的语法和强大的功能。 二、随机数生成器 随机签名的核心在于随机数生成器。在.NET框架中...

    ASP.NET OCX ACTIVEX 控件 实例

    ASP.NET 是微软公司推出的一种基于.NET Framework的Web应用程序框架,用于构建动态网站、Web应用程序和服务。在本实例中,我们关注的是如何在ASP.NET中使用OCX(ActiveX)控件,这是一种在早期Windows编程中广泛使用...

    .net core 支付包整合开发的一个例子程序

    .NET Core 是微软推出的一种跨平台、高性能的开源框架,用于构建现代云应用程序。在这个例子程序中,我们将关注如何在 .NET Core 中整合支付包,特别是针对支付宝(Alipay)的AopSdk.Core库的使用。这个例子展示了...

    QtSingleApplication实现软件只能启动一次

    在开发桌面应用程序时,有时我们需要确保用户...总之,QtSingleApplication是Qt框架中实现单例应用的有效工具,它简化了跨平台的进程间通信,使得开发者可以专注于应用程序的核心功能,而不必过于关心实例管理的细节。

    VB.NET 源代码学习

    VB.NET是一种基于.NET Framework的编程语言,由微软公司开发,旨在简化Visual Basic 6.0的编程,并且提供更强大的面向对象的特性。这个压缩包文件包含了一系列VB.NET的源代码实例,对于初学者来说是极好的学习资源。...

    .net 计算器源代码!

    如果这个文本文档包含了源代码,开发者可以从中看到类结构、函数定义、事件处理等核心部分,这有助于理解.NET应用程序的设计原则和编程实践。 在这个.NET计算器源代码中,我们可以学习到以下知识点: 1. **Windows...

    C#.NET多线程实例6个(包括多线程基本使用,多线程互斥等全部多线程使用实例)

    在.NET框架中,C#语言提供了强大的多线程支持,使得开发者可以同时执行多个任务,提升程序的执行效率。本文将深入探讨C#.NET多线程的基本使用、线程同步和互斥等关键知识点,结合六个实例进行详细解析。 1. **多...

    基于asp,net的新闻发布系统

    最后,这个新闻发布系统需要在IIS(Internet Information Services)上部署,并进行必要的配置,如连接字符串、应用程序池设置等,以确保系统的正常运行。 通过上述分析,我们可以看到这个基于ASP.NET的新闻发布...

    如何使用离散值参数筛选水晶报表源程序实例,C#.net源代码编写,

    在开发基于C#.NET的应用程序时,我们常常需要与数据打交道,其中水晶报表(Crystal Reports)是一个常用的报表设计工具,能够帮助我们创建复杂的报表并进行数据筛选。本实例将介绍如何在C#.NET环境中,利用离散值...

    asp.net程序开发范例宝典 源码07

    首先,ASP.NET是微软推出的用于构建动态网站、Web应用和Web服务的框架,基于.NET Framework。它提供了丰富的服务器控件、事件驱动模型和自动页面生命周期管理,使得开发者可以更高效地编写网页应用程序。 1. **...

    asp.net课程设计—学生论坛系统,内附论文以及系统,打开可直接运行

    ASP.NET是一种由微软开发的用于构建Web应用程序的框架,它基于.NET Framework,提供了一种高效、易用且功能丰富的环境来开发动态网站、web服务和应用程序。在这个"asp.net课程设计—学生论坛系统"中,我们可以看到一...

    基于ASP.NET的仓库条码管理系统源码.zip

    在ASP.NET框架下,系统采用了Model-View-Controller(MVC)架构模式,这是一种广泛应用于Web开发的设计模式,旨在分离应用程序的业务逻辑、数据处理和用户界面。MVC模式使得代码更易于维护和扩展,提高了开发效率。...

    VB.NET Guid生成器

    在VB.NET编程环境中,Guid(全局唯一标识符)是一种用于创建独特标识的128位数值,常用于数据库记录、对象实例或者网络通信中的唯一识别。Guid生成器是专门用于生成这种唯一标识的工具。本项目提供的“VB.NET Guid...

    一个优秀的ASP.NET(及WinForm)开发人员需要知道

    在 .NET 中,应用程序运行在一个或多个进程中,并可能包含多个线程。 **1.3 Windows EXE 文件与 DLL 文件的区别** EXE 文件是可执行文件,通常用于启动应用程序;DLL 文件是动态链接库文件,可以在多个应用程序...

Global site tag (gtag.js) - Google Analytics