【问题标题】:Use Win API to determine if an instance of an executable is already running使用 Win API 确定可执行文件的实例是否已在运行
【发布时间】:2013-08-12 09:50:00
【问题描述】:

我需要确保我的 C++ 应用程序只有 1 个实例正在运行。

如何使用 Win API;

  1. 检索有关我当前应用程序的信息? GetCurrentProcess() 会给我一个关于我的应用程序的 HANDLE,我如何检索有关它的信息

  2. 为用户检索所有正在运行的进程的列表? EnumProcesses() 给出了一个列表,但似乎需要一个预先分配的缓冲区,那么我如何知道当前有多少进程正在运行?

  3. 我需要将我的服务器的 exe 名称与正在运行的进程进行比较,如果发现多个则引发错误

注意:我不能使用任何 boost 库,并且我对使用类似帖子中看到的 mutex 不感兴趣。

【问题讨论】:

  • “我对使用互斥锁不感兴趣”....但是为什么呢?
  • 解释原因需要很长时间,但我需要一个非互斥体解决方案。
  • 然后使用事件,或任何共享​​>对象方法。
  • “如何在不使用巧克力的情况下制作巧克力蛋糕?” - 这……很有挑战性。
  • 嗯,成分替代是关于经验丰富的厨师(烹饪 StackExchange)的一个有效问题,那么为什么不在这里呢? ;) 当然,同样的附带条件也适用:说明根本原因,这样我们的替换就不会遇到同样的问题。

标签: c++ winapi


【解决方案1】:

您可以使用CreateMutex 函数创建一个系统范围的命名互斥锁来表示您的进程是否正在运行。如果进程已经在运行,它将返回ERROR_ALREADY_EXISTS

 (void)::CreateMutex( NULL,
                      TRUE,
                      TEXT( "My_Special_Invokation_Test_Mutex" ) );
 switch ( ::GetLastError() ) {
     case ERROR_SUCCESS:
         // Process was not running already
         break;
     case ERROR_ALREADY_EXISTS:
         // Process is running already
         break;
     default:
         // Error occured, not sure whether process is running already.
         break;
 }

现在,如果您坚持不使用互斥锁,则可以改用CreateFile 函数。确保为dwShareMode 字段传递零以获得独占访问语义,CREATE_NEWdwCreationDisposition 字段(以便您仅在文件不存在时创建文件)和FILE_FLAG_DELETE_ON_CLOSE 为@987654331 @ 参数,以便在您的进程终止后删除文件。像这样的:

LPCTSTR lockFileName = ...;
(void)::CreateFile( lockFileName,
                    GENERIC_READ,
                    0,
                    NULL,
                    CREATE_NEW,
                    FILE_FLAG_DELETE_ON_CLOSE,
                    NULL );
switch ( ::GetLastError() ) {
     case ERROR_SUCCESS:
         // Process was not running already
         break;
     case ERROR_FILE_EXISTS:
         // Process is running already
         break;
     default:
         // Error occured, not sure whether process is running already.
         break;
}

请参阅这篇关于Temporary file generation and usage best practices关于如何安全处理临时文件的文章。

长话短说,当然可以为您的任务使用锁定文件,但我认为很难做到这一点。

【讨论】:

  • 好的,我确实添加了我对使用互斥锁不感兴趣的评论。
  • @MarcusMacWilliam:对不起,我读多了那一点。我现在扩展了我的答案以讨论基于 CreateFile 的解决方案(但请注意,要正确解决这个问题要困难得多)。
【解决方案2】:

纳瓦兹回答的更新版本:-

Handle mutex = CreateMutex (0, 0, "SomeUniqueName");

switch (GetLastError ())
{
case ERROR_ALREADY_EXISTS:
  // app already running
  break;

case ERROR_SUCCESS:
  // first instance
  break;

default:
  // who knows what happened!
  break;
}

这确实存在安全问题,恶意应用程序可能会在您的应用程序启动之前创建一个名为“SomeUniqueName”的互斥锁,从而阻止您的应用程序运行。为了解决这个问题,您可以根据一些常量系统参数(例如 MAC 地址)的散列来命名互斥锁。 MSDN documentation 对单实例应用程序有这样的说法:

如果您使用命名互斥体将应用程序限制为单个实例,则恶意用户可以在您创建此互斥体之前阻止您的应用程序启动。为防止出现这种情况,请创建一个随机命名的互斥体并将名称存储起来,以便只能由授权用户获取。或者,您可以为此目的使用文件。要将您的应用程序限制为每个用户一个实例,请在用户的配置文件目录中创建一个锁定文件。

【讨论】:

  • 再说一次,我对使用互斥锁不感兴趣。
  • 应用程序关闭时是否应该使用 CloseHandle() 关闭互斥体句柄?
  • @MarcusMacWilliam:您当然不想自己关闭互斥体句柄。操作系统将在您的进程关闭时为您执行此操作,因此当您的进程关闭时互斥锁将被破坏 - 这正是您想要的。如果您自己调用CloseHandle,肯定有一个阶段您的进程仍在运行(在CloseHandle 返回之后),但可能的并发调用将认为该进程“未运行”。
【解决方案3】:

由于不需要互斥锁,您可以例如使用文件映射来代替。 CreateFilemapping 的文档说:

如果对象在函数调用之前存在,则函数返回现有对象的句柄(具有当前大小,而不是指定大小),GetLastError 返回 ERROR_ALREADY_EXISTS。 如果函数失败,返回值为NULL。

这导致以下无互斥锁实现:

Handle h = CreateFileMapping(0, 0, PAGE_READONLY, 0, 4096, name);
bool already_running = !!h && (GetLastError() == ERROR_ALREADY_EXISTS);

要么调用成功并且映射已经存在,那么另一个进程已经在运行。

或者,创建了新的映射,或者调用失败。在任何一种情况下,都没有其他进程正在运行。如果调用失败,几乎可以肯定之前可能尝试过的任何其他进程也失败了。由于一旦调用成功,映射就已经存在,两个相同的调用可能成功一次然后失败的唯一可能原因是“没有更多的句柄”,而事实并非如此(嗯,不应该) 发生。无论如何,如果确实发生了这种情况,那么您在其他地方就会遇到更严重的问题。

这可能适用于您选择的每种类型的命名内核对象(即具有CreateOpen 版本的每种类型的内核对象)。

文件映射对象的优势在于,如果您还想做 IPC(例如,将命令行转发到已经运行的实例,然后退出),那么您已经有了一个可以使用的映射(尽管管道也可以做得很好)。

但除此之外,我看不出这个(或任何其他解决方案)如何优于以任何方式使用互斥锁方法。真的,为什么不使用互斥锁呢?这就是他们的目的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-24
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 2016-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多