Delphi中,让程序只运行一次的方法

   公司开发的软件需要对串口进行操作,每次打开软件后程序自动去打开串口 寻找 连接到串口上的设备,但是如果用户不知道打开了两次,那么第二次打开的程序是不能正常使用的,因为对串口的操作时独占的,第一个程序独占了串口的使用权,其他程序无法再使用那一个串口,当然如果PC机器上有两个串口,那第二个程序也是可以用的。为了解决这个 问题 ,必须限制对串口操作的软件只能打开一个。打开软件后用户如果误操作再次想打开该软件,需要提示用户软件已经打开,并让已打开的软件显示在窗口最顶层。

  下面是Delphi版的解决方法。

  (方法一)
  利用互斥对象
  开发过多线程软件的可能都使用过互斥对象,它常被用做线程间同步的 技术 手段。简要的提一下互斥对象:互斥对象把第一次建立它的程序作为主程序,这样只用检测互斥对象是否已经有主程序就判断程序是否已经运行过,这里需要涉及到一个api函数:WaitForSingleObject,该函数的第一个参数为用以检测的互斥对象,第2个参数的表示函数返回结果前的滞留时间,如果改函数返回wait_TimeOut就表明互斥对象已经有了一个主程序。

  注意:以下的 代码 都出现在工程文件中,而不是单元文件中。
  var
   myMutex:HWND;
  begin
    //CreateMutex建立互斥对象,并且给互斥对象起一个唯一的名字。
    myMutex:=CreateMutex(nil,false,'hkOneCopy');
    //程序没有被运行过
    if WaitForSingleObject(myMutex,0)<>wait_TimeOut then
    begin
     Application.Initialize;
     Application.CreateForm(TForm1, Form1);
     Application.Run;
    End;
  End;
  [注释]:

  当应用程序第一次运行的时候,在应用程序中会建立一个互斥对象,名称为'hkOneCopy',然后判断系统中有没有这个互斥对象,如果没有则初始化应用程序。

  下面再完善一下这个程序。

  我们不 希望 程序被多次运行,而是希望如果程序运行过后,再运行这个程序的时候,将已运行的程序做出一些响应,比如说让它变为最上层的活动窗口来提示用户该程序正在运行。为达到这个目的,必须要获得正在运行程序的句柄,然后用一个APISetForeGroundWindow(handle),来使程序的窗口最前并激活。为了得到程序的句柄,要使用windows枚举函数EnumWindows来遍历windows窗口列表,该函数需要一个回调函数作参数,用这个回调函数来对每一个 系统 中的窗口进行调用直到最后一个窗口或回调函数返回false为止[注:关于EnumWindows函数的介绍在篇尾]。只要编写这个回调函数并在其中不断的比较当前遍历到的窗口类名和我们的程序的主窗口类名,以及比较窗口可 执行 文件的名称和我们程序的名称直到找到相同的为止,将这时的窗口句柄保存下来就行了。为获得窗口的类名和句柄,需要一个APIGetClassName,为获得可执行文件的名称,需要APIGetModuleFileName。

  下面是详细代码。

  注意:下面代码在delphi7下运行通过。但是如果窗口最小化后,再次运行程序时,原先已经运行的程序能够被置前并激活但是标题栏的最小化按钮却不能用了。当 尝试 了N中方法后估计是delphi自身TForm类的问题,下面给出一个解决方案:在窗口上放一个ApplicationEvents控件,它管理着应用程序所有的消息。我们在它的OnMessage事件里写上下面的代码:
    if Msg.hwnd=Form1.Handle then
   begin
  //161 是在标题栏按下 鼠标
  //8 是在标题栏的最小化按钮上按下鼠标
     if (Msg.message= 161) and (msg.wParam= 8) then
     begin
       Form1.WindowState:=  wsMinimized;
     end;
   end;

  program MyThreadTest;
  uses
   Windows,
   Forms,
   SysUtils,
   Messages,
   Dialogs,
    Unit1 in 'Unit1.pas' {Form1},
  {$R *.res}
  var
   myMutex,
   FindHid: HWND;
   MoudleName: string;
  function EnumWndProc(hwnd: Thandle; param: Cardinal): bool; stdcall;
  //由于用于api回调函数,请使用windows 传统 的参数传递方式stdcall
  var
    ClassName, WinMoudleName: string;
    WinInstance: THandle;
  begin
    result := true;
    SetLength(ClassName, 100);
    GetClassName(hwnd, pchar(ClassName), length(ClassName)); //获得当前遍历窗口的类名
    ClassName := pchar(ClassName); //在字符串后加结束符,确定字符串结束
    if UpperCase(ClassName) = UpperCase(TForm1.ClassName) then //比较类名
   begin
      WinInstance := GetWindowLong(hwnd, GWL_HINSTANCE); //获得当前遍历窗口的实例
    setlength(WinMoudleName, 100);
    //获得当前遍历窗口的程序文件名
    GetModuleFileName(WinInstance, pchar(WinMoudleName), length(WinMoudleName));
    WinMoudleName := pchar(WinMoudleName);
    WinMoudleName :=ExtractFileName(WinMoudleName);
    //MoudleName为 工程 全局变量,自身程序的文件名
    if UpperCase(WinMoudleName) = UpperCase(MoudleName) then
    begin
        FindHid := hwnd;//FindHid为工程全局变量保存找到的句炳
     result := false; //找到以后就结束遍历
      end;
    end;
  end;
  begin
   // CreateMutex建立互斥对象,并且给互斥对象起一个唯一的名字
    myMutex := CreateMutex(nil, false, 'hkOneCopy');
   if WaitForSingleObject(myMutex, 0) <> wait_TimeOut then 
  //程序没有被运行过
    begin
      Application.Initialize;
      Application.CreateForm(TForm1, Form1);
    Application.Run;
   end else
   begin
    SetLength(MoudleName, 100);
    //获得自己程序文件名
    GetModuleFileName(HInstance, pchar(MoudleName), length(MoudleName));
    MoudleName := pchar(MoudleName);
    MoudleName := ExtractFileName(MoudleName);
    EnumWindows(@EnumWndProc, 0); //调用枚举函数
    if FindHid <> 0 then
    begin
     ShowWindow(FindHid,SW_RESTORE);
     SetForegroundWindow(FindHid);
    end;
   end;
  end.
  [EnumWindows函数使用]:
  EnumWindows 用来列举屏幕上所有顶层窗口。
  MSDN:
  The EnumWindows function enumerates all top-level  windows  on the screen by passing the handle to each window。

  函数形式:
  BOOL EnumWindows(WNDENUMPROC lpEnumFunc, //callback function  
                 LPARAM lParam); //application-defined value
  其中 WNDENUMPROC 是回调函数,回调函数中写自己想做的操作,当调用EnumWindows的时候,每次遇到一个窗口, 系统 就调用一次你的WNDENUMPROC ,然后把窗口句柄传给你。
  EnumWindows 
   函数成功则返回非0值;
   函数失败则返回0值;
   EnumWindowsProc 返回0值,同样导致函数EnumWindows 返回0值。

  另外,该函数不列举子窗口,除了几种拥有WS_CHILD  风格 的系统所属窗口。
  MSDN:
  The EnumWindows function does not enumerate child  windows ,with the exception of a few top-level windows owned by the system that have the WS_CHILD style. 
使用举例:

  先声明一个EnumWindowsProc ,比如:
  BOOL CALLBACK EnumWindowsProc_1(HWND hwnd,LPARAM lparam) ;

  然后实现此函数,写入自己想做的事情,比如:
  BOOL CALLBACK EnumWindowsProc_1(HWND hwnd,LPARAM lparam)
  { char lpWinTitle[256];  
    ::GetWindowText(hwnd,lpWinTitle,256-1);  
    CString m_strTitle; 
    m_strTitle.Format("%s",lpWinTitle); 
    if(m_strTitle.Find("Internet Explorer")!=-1)  
    {  AfxMessageBox("这是一个IE窗口!") ; }   
    return TRUE ;
  }

  然后就可以在其他地方调用EnumWindows的时候使用回调函数,比如:
  ::EnumWindows(EnumWindowsProc_1,0) ;
  这样每当遇到IE窗口时,就会进行 提示“这是一个IE窗口!” 的操作。

  方法二:

  不用互斥对象。

  我们可以利用向系统添加全局原子的方法,来防止多个程序实例的运行。全局原子由Windows 系统负责维持,它能保证其中的每个原子都是唯一的,管理其引用计数,并且当该全局原子的引用计数为0时,从 内存 中清除。我们用GlobalAddAtom 函数向全局原子添加一个255个字节以内的字符串,用GlobalFindAtom来 检查 是否已经存在该全局原子,最后在程序结束时用GlobalDeleteAtom函数删除添加的全局原子。示例如下: 
  Uses Windows 
  const iAtom=‘SingleApp’; 
  begin 
   if GlobalFindAtom(iAtom)=0 then 
   begin 
   GlobalAddAtom(iAtom); 
   Application.Initialize; 
   Application.CreateForm(TForm1,Form1); 
   Application.Run; 
   GlobalDeleteAtom(GlobalFindAtom(iAtom)); 
   end 
   else 
   MessageBox(0,‘You can not run a second copy of this App’,‘’,mb_OK); 
  end. 
  利用全局原子的引用计数规则,我们还可以判断当前共运行了该程序的多少个实例: 
  var i:Integer; 
  begin 
   I:=0; 
  while GlobalFindAtom(iAtom)<>0 do 
   begin 
   GlobalDeleteAtom(GlobalFindAtom(iAtom)); 
   i:=i+1; 
   end; 
   ShowMessage(IntToStr(I)); 
  end;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值