【IT168 专稿】前些天,用VS.NET2008开发一个PDA的项目;这个PDA是基于Windows CE的操作系统;用户有个需求,当这个应用程序一旦开起来,如果他想再点击运行,就提醒他:该程序已经运行;于是本人就建议小兄弟用Mutex类实现,最后他告诉我不可以;用Mutex类去开发一般Windows 程式,会比较容易,但如果拿他去开发Windows CE的程式,就有些说词,本文就是基于这样的背景下展开的.
Mutex单词是“互斥”的意思; 同性相斥,异性相吸(人类活动的多样性,早已打破这一铁定的规律,动物界应该还严格遵守这一约定…呵呵,题外话.);
Mutex更详细的解释,就是mutual exclusion 对象的缩写;用一句计算机的行话来说,一个互斥体就是一个程序对象,它允许多个线程来共享相同的资源;例如对文件的访问,但并不允许同时访问;这在我们使用的过程中能体会到,比如你打开一个文件,你接着再打开一次的时候,它就提醒你,当前的应用程序已经打开,你是否继续等..;当一个程序运行起来的时候,这个互斥体就产生了,且带有唯一的名字;学过操作系统的,都会有这个概念叫互斥;上学的时候,老师举了个很好例子来说明,比如你在火车上,那个厕所就是很好的互斥体;你要用这个厕所,别人就不能用,直到别人使用完,你才能用,即使你闹肚子也不行,^-^..; 你打电话有时候也会遇到这种情况;如:该电话正在忙,请稍候再拨!
闲话少说,那么在大家编程的时候,有时候用户有这样的需求,这个程序只要开启一次,如果有人想继续开启,就提醒他,这个应用程序正在运行;
对Visual Studio.NET开发人员来讲,如果设计一个传统的Window Form程序好像就比较简单,用Mutex很容易解决这样的需求; 如下代码:
[VB.NET]
Imports System.Threading
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'定义Mutex的类
Dim objMutex As Mutex
'得到应用程序的名称
Dim AppName As String = Assembly.GetExecutingAssembly().GetName().Name
'用这个应用程序的名字,去实例化这个Mutex
objMutex = New Mutex(False, AppName)
'判断是否有相同的应用程序在运行;
If objMutex.WaitOne(0, False) = False Then
objMutex.Close()
objMutex = Nothing
MessageBox.Show("这个程序正在运行!")
‘或者为了更通俗些,也可写成下面的代码;
MessageBox.Show("该电话正在忙,请稍候再拨!")
Close() '关闭这个应用程序
Exit Sub
End If
End Sub
这里强调下面的代码:
这里的AppName就很重要,如果你忽略这样的参数,你的上述的函数就失去了原有的意义;
接下来,我们看看开发一个Windows CE的应用程序如何做;作为.NET Framework 的一个子集,.NET Compact Framework只提供了.NET Framework的一部分功能 ,所以特别的Mutex的对象的功能已经完全被弱化.; NET Compact Framework 并不支持这个所谓的互斥体,但幸运的是 Windows CE通过这个coredll.dll动态链接库中的几个函数可以实现这一功能; 有人就问啦,为什么是coredll.dll,而不是其他动态链接库呢?好问题,是因为Windows CE下大部分的API存在于coredll.dll里面,同时DllImport不仅仅支持Win32 API,他可以支持任何native 的DLL的引入。于是有人又要问啦,啥叫”native的DLL的引入?”..呵呵…自己查资料,网上一大堆..
在操作系统创建这个Mutex是必须带有一个特别的命名,这个在文章开头已经讲的比较清楚,就是唯一的暂住证(哈哈..真的有点象..大家好好体会这个”暂住证”..);有了这个暂住证呢,这个Mutex的实际功能才能真正体现;如果你想再冒名创建同样的名字,对不起,系统就警告你;
基本上来讲,如果你创建一个带特别命名的Mutex应用程序,就能确保这个应用程序能唯一地运行在操作系统中;
作为.NET Framework 的一个子集,.NET Compact Framework只提供了.NET Framework的一部分功能,因此有时在实现一些功能时不得不借助于Windows CE API。另外还存在一些第三方的组件或资源,或以动态链接库形式提供,或者已经是COM组件。相对于.NET Compact Framework,它们都属于非托管资源。我们需要一种功能,实现由托管环境访问这些非托管资源。和.NET Framework 一样,平台调用P/Invoke(Platform Invocation Services)提供托管代码调用驻留于 DLL 中的非托管函数的功能。下面是一张平台调用的原理图(From MSDN)。
.NET Compact Framework下,如何做平台调用呢?
下面使用DllImport特征导入Windows CE的API函数MessageBoxW的定义。现举例说明:
<DllImport("coredll.dll", SetLastError := true)> _
public static extern Private Shared function MessageBoxW(ByVal hWnd as IntPtr, ByVal text as String, ByVal caption as String caption, byVal type as uint) as Integer
End Function
End Class
然后可以对它进行调用;
Private sub button1_Click(ByVal sender as object, ByVal e as EventArgs )
Dim Zero as intPtr
APIHelper.MessageBoxW(Zero, "测试MessageBoxW函数",
"API调用", 0)
End sub
然后可以对它进行调用;
Dim Zero as intPtr
APIHelper.MessageBoxW(Zero, "测试MessageBoxW函数",
"API调用", 0)
End sub
可以看到,使用P/Invoke包括声明和调用两个过程,另外还有一个错误处理的过程。通过声明来指定要调用的非托管函数,.NET Compact Framework也是使用DllImport特性来进行声明,包括模块名、函数名及调用约定。与.NET Framework完整版的DllImport特性不同,.NET Compact Framework的一共包括五个公共字段:CallingConvention,CharSet,EntryPoint,PreserveSig和SetLastError。具体各字段的说明可以参考MSDN。
有了上述的内容打底,我们就要用平台调用这样的概念来实现,Windows CE下,这个应用程序单独运行这样的用户需求;下面我创建了一个类,就叫SingleInstance Application;
Imports System.Windows.Forms
‘记得要引用InteropServices这个类
Imports System.Runtime.InteropServices
Imports System.Reflection
Public Class SingleInstanceApplication
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Function CreateMutex(ByVal Attr As IntPtr, ByVal Own As Boolean, ByVal Name As String) As Integer
End Function
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Function ReleaseMutex(ByVal hMutex As IntPtr) As Boolean
End Function
Const ERROR_ALREADY_EXISTS As Long = 183
Public Sub Run(ByVal frm As Form)
Dim name As String = Assembly.GetExecutingAssembly().GetName().Name
Dim mutexHandle As IntPtr = CreateMutex(IntPtr.Zero, True, name)
Dim xError As Long = Marshal.GetLastWin32Error()
If xError <> ERROR_ALREADY_EXISTS Then
Application.Run(frm)
Else
MsgBox("This Application already running now!")
frm.Close()
End If
ReleaseMutex(mutexHandle)
End Sub
End Class
这个类导入了两个native 的函数,一个是创建Mutex,那另一个就是释放Mutex;还有一个简单的公用函数;
这个Run 函数是创建一个带有应用程序名的Mutex,也顺便检查返回的错误代码,去看是否这个mutex已经存在;如果不错在呢,就让这个应用程序运行;
最终会把这个Mutex释放掉;这样的写法是不是很简单,呵呵..
如何引用这个类呢?下面是主程序的入口..
Private oOneInstance As SingleInstanceApplication = Nothing
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
‘实例话这个类,这也是必须地.
oOneInstance = New SingleInstanceApplication()
‘ 调用这个类下面的主函数Run,这更是必须地.
oOneInstance.Run(Me)
End Sub
上述代码全是关于VB.NET写的,有没有用C#写的呢,下面就是啦.
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
namespace Bitmatic
{
static class SingleInstanceApplication
{
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr CreateMutex(IntPtr Attr, bool Own, string Name);
[DllImport("coredll.dll", SetLastError = true)]
public static extern bool ReleaseMutex(IntPtr hMutex);
const long ERROR_ALREADY_EXISTS = 183;
public static void Run(Form frm)
{
string name = Assembly.GetExecutingAssembly().GetName().Name;
IntPtr mutexHandle = CreateMutex(IntPtr.Zero, true, name);
long error = Marshal.GetLastWin32Error();
if (error != ERROR_ALREADY_EXISTS)
Application.Run(frm);
ReleaseMutex(mutexHandle);
}
}
}
[MTAThread]
static void Main()
{
SingleInstanceApplication.Run(new Form1());
}
上述代码在Windows CE(PDA),Windows XP下调试;开发工具是Visual Studio.Net 2008;
最后是感言,非常感谢这么多的网友奉献他们的智慧,让我写这篇文章;更重要的是我在搜索的过程中,没有一篇完整的文章对在.NET Framework 和.NET Compact Framework对这个议题进行探讨;又近阶段有个项目要实现这样的用户需求,故总结归纳之,希大家共勉!