控制Application 的实例数目

Once you know why you would limit the number of instances of your application, and how to check (and activate) the previous instance , and what are the techniques you can use, you are ready for the final (the best, at least for me) solution...
There can be only few of us (instances of an application)!
Let's say you want your application to be instanced only a limited number of times - not once, as described before, but only twice, for example!

File mapping
The last technique we are to discuss is about file mappings in Windows. Technically, file mapping is the association of a file's contents with a portion of the virtual address space of a process. The system creates an in-memory file mapping object to maintain this association. What file mapping offers is sharing memory between two or more applications - or in our case, sharing memory between two or more application instances. The file mapping functions allow a process to create file mapping objects and file views to easily access and share data.

CheckPrevious.RestoreIfRunning
Let's go from the beginning. To "run-once" enable our application, we'll place all the code required in a separate unit. Your first task is to add an empty code unit to the project. Name (save it as) the unit 'CheckPrevious'.

You'll also need to alter the source code of your project to look like:

program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' ,
  ;



begin
  if not  then 
  begin
    Application.Initialize;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
  end;
end.
 
 

The RestoreIfRunning function accepts two parameters; first parameter is the Handle of the application; the second (default) parameter is the number of allowed instances, of the application, running simultaneously. Since the second parameter is optional, if you want to enable only one instance, the following calls will have the same result:

CheckPrevious.RestoreIfRunning(Application.Handle, 1);

CheckPrevious.RestoreIfRunning(Application.Handle);
 
 

The function returns True if the specified (maximum) number of instances is already running. If this is the case, the last instance is made active and the main form of the application is placed on top of other windows (restored if minimized). The function returns False if the number of running instances is lower that the allowed number, and the new instance gets executed.

So much about the project source, we now move to the code in the CheckPrevious unit. When using file mappings to prevent the second copy of an application to be executed , the following steps can be used:

  1. Create a file mapping object. While calling the create method save your custom data (Application.Handle).
  2. If the function succedes, the result is the handle to the file-mapping object.
  3. If the object existed before, we have a situation where the next instance of the application is trying to start running.
  4. If the object is created and there is no previous mapping, this is the first instance of the application.
  5. When creating the file-mapping from the first instance of an aplication, we have to make sure the Handle of the application is saved within the mapped object.
  6. If the mapping object existed before, meaning the second instance is to be executed, we need stop the process and bring the first instance "to life".
  7. When finished (application end), we must unmap the mapped object and close the handle it returned.
If, on the other hand, you want to control the number of application instances, and make sure only a limited number of instances can run, you'll need to store some more information in the mapping object ... and this is where the power of file-mapping is.

Only two instances, please!
We'll now go step-by-step through the code of the CheckPrevious unit.

First, the unit exposes only one function, RestoreIfRunning, in its interface section (making it public):

unit CheckPrevious;

interface
uses Windows, SysUtils;

function RestoreIfRunning(
                const AppHandle : THandle; 
                MaxInstances : integer = 1) : boolean;


 
 

Since we want to save the number of running instances in the mapped object (along with the last executed application handle), we need to create a record type:

implementation

type
  PInstanceInfo = ^TInstanceInfo;
  TInstanceInfo = packed record
    PreviousHandle : THandle;
    RunCounter : integer;
  end;

 
 

The TInstanceInfo is a record type variable consisting of two fields: PreviousHandle holds the handle to the instance last started; RunCounter holds the number of instances (currently) running.

We'll now look in the RestoreIfRunning function source code:

var
  MappingHandle: THandle;
  InstanceInfo: PInstanceInfo;
  MappingName : string;

  RemoveMe : boolean = True;

function RestoreIfRunning(
             const AppHandle : THandle; 
             MaxInstances : integer = 1) : boolean;
begin
  Result := True;

  MappingName := StringReplace(
                   ParamStr(0),
                   '\',
                   '',
                   [rfReplaceAll, rfIgnoreCase]);

  MappingHandle := CreateFileMapping($FFFFFFFF,
                                     nil,
                                     PAGE_READWRITE,
                                     0,
                                     SizeOf(TInstanceInfo),
                                     PChar(MappingName));
  
  if MappingHandle = 0 then
    RaiseLastOSError
  else
  begin
  
 
 

First, we define several local variables (MappingHandle, ..., RemoveMe). Next we dive into the main source...
Second, and as explained above, when dealing with file-mapping, we first need to create a file mapping object. The CreateFileMapping API call is responsible for this task.
Among other parameters the function requires you to specify the name of the mapping object (MappingName). Since this value must be unique, I've decided to use the full path of the application exe file. Since the MappingName must not contain the backslash character (\) I've used the StringReplace RTL function to remove it from the stirng.
Another important parameter is the size of the object we are to write in the mapped file. The SizeOf function is used to return the number of bytes occupied by a variable of type TInstanceInfo.

Finally, we check the result of the CreateFileMapping function: if the function fails and the return value is 0, we have an error situation (it might be that MappingName string has a backslash in it, for example). If all went well, we need to check for an existing mapped object:

    if GetLastError <> ERROR_ALREADY_EXISTS then
    begin
      InstanceInfo := MapViewOfFile(MappingHandle,
                                    FILE_MAP_ALL_ACCESS,
                                    0,
                                    0,
                                    SizeOf(TInstanceInfo));

      InstanceInfo^.PreviousHandle := AppHandle;
      InstanceInfo^.RunCounter := 1;

      Result := False;
    end
  
 
 

So, if the object did not exist before the function call, the GetLastError API function is different from the ERROR_ALREADY_EXISTS (it's a NO_ERROR) and we can be sure this is the first instance of this application executed.

The second file-mapping function, MapViewOfFile, comes handy now. The MapViewOfFile function returns a pointer to the file view. By using this pointer an application can read data from the file and write data to the file.
In our case, if this is the first instance of the application, we assign AppHandle (Application.Handle) to the PreviousHandle field of the InstanceInfo variable; we also set the RunCounter field to one (1).

And now, the interesting part. If the mapping object already exists, we have an "application already running" situation:

    else //already runing
    begin
      MappingHandle := OpenFileMapping(
                                FILE_MAP_ALL_ACCESS, 
                                False, 
                                PChar(MappingName));
      if MappingHandle <> 0 then
      begin
        InstanceInfo := MapViewOfFile(MappingHandle,
                                      FILE_MAP_ALL_ACCESS,
                                      0,
                                      0,
                                      SizeOf(TInstanceInfo));

        if InstanceInfo^.RunCounter >= MaxInstances then
        begin
          RemoveMe := False;

          if IsIconic(InstanceInfo^.PreviousHandle) then
            ShowWindow(InstanceInfo^.PreviousHandle, SW_RESTORE);
          SetForegroundWindow(InstanceInfo^.PreviousHandle);
        end
        else
        begin
          InstanceInfo^.PreviousHandle := AppHandle;
          InstanceInfo^.RunCounter := 1 + InstanceInfo^.RunCounter;

          Result := False;
        end
      end;
    end;
  end;
end; 

 
 

So, if there already is a mapped object with the same name, we have a situation of an already running instance. This is where the third mapping function, OpenFileMapping, opens a mapped object. In case of a no-error situation, we call the MapViewOfFile to get our hands on the information inside the mapped object.

Now, if we have run out of the allowed instances (MaxInstances), we make sure the last instance gets restored and made active. If RunCounter is smaller then MaxInstances we simply set new value(s) for the InstanceInfo variable.

Note: the RemoveMe boolean variable is used to make sure we have the correct instance counter - in case of an application termination.

Finally, when the application ("this" instance) ends, we need to make sure that the view of the mapped file is unmapped; and the memory is freed. This is done in the finalization section of the unit (executed when the application terminates)

initialization


finalization
  
  if RemoveMe then
  begin
    MappingHandle := OpenFileMapping(
                        FILE_MAP_ALL_ACCESS, 
                        False, 
                        PChar(MappingName));
    if MappingHandle <> 0 then
    begin
      InstanceInfo := MapViewOfFile(MappingHandle,
                                  FILE_MAP_ALL_ACCESS,
                                  0,
                                  0,
                                  SizeOf(TInstanceInfo));

      InstanceInfo^.RunCounter := -1 + InstanceInfo^.RunCounter;
    end
    else
      RaiseLastOSError;
  end;

  if Assigned(InstanceInfo) then UnmapViewOfFile(InstanceInfo);
  if MappingHandle <> 0 then CloseHandle(MappingHandle);

end. 
 
 

And we are done! That's all folks. You now have the ultimate source to run-once (or twice, or...) enable your applications.

If you don't understanding something from the code, or if you simply want to add your view of the whole problem, I encourage you to post your questions and problems on the Delphi Programming Forum.

Tickling your imagination..
Stop for a moment! Take a look at the InstanceInfo record. We can store *anything* we want inside it. How about letting the next instance run only after a specified time period: store a TDateTime value with the current ( Now function) date and time, and prevent the instance from running if "enough" time has not passed after the previous instance was executed. Enything else? No problem, just make sure you handle the "situation" properly.
"I'll be happier with a component to drop on a form"
Yes, in some situations it's quite easier to simply drop a component on a form and forget about all the hard coding that needs to be done if you want to limit the number of your application instances.
Ok, you've won; in the next part of this article I'll make a component from this code, till then explore the source and learn!

On the last page of this article, you'll find the entire source...

转载于:https://www.cnblogs.com/forrestsun/articles/198331.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值