UE5 打包后 EXE 程序单实例的两种实现方法

UE5 打包后 EXE 程序单实例的两种实现方法

UE5打包后exe程序避免多次打开的两种实现方法

本文整理了UE5打包后防止exe程序多开的两类解决方案,分别为C++代码实现法 (基于引擎底层互斥锁,适合开发阶段集成)和快捷方式脚本法(基于系统脚本检测,适合打包后快速配置),可根据开发需求和使用场景选择。

一、C++代码实现法

该方法通过在UE5工程中添加系统级临界区"锁",实现打包后程序的单实例运行,仅在打包发布版本生效,不影响编辑器开发,核心是利用FWindowsSystemWideCriticalSection创建全局唯一锁,检测到锁已存在时直接关闭新程序实例。

1. 工程前提

创建基于C++的UE5工程 ,工程会自动生成与项目名同名的.h.cpp核心模块文件(示例工程名:ACT)。

2. 头文件(.h)编写

在项目同名头文件中重载模块加载/卸载方法,并声明临界区锁对象,代码如下:

arduino 复制代码
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"

// 继承自FDefaultGameModuleImpl
class ACT: public FDefaultGameModuleImpl
{
public:
    /** IModuleInterface implementation */
    virtual void StartupModule() override; // 重载模块加载方法
    virtual void ShutdownModule() override;// 重载模块卸载方法

private:
    FWindowsSystemWideCriticalSection* Check; // 声明全局临界区"锁"对象
};

3. 源文件(.cpp)编写

在源文件中实现模块加载/卸载的具体逻辑,添加编辑器宏判断、锁创建检测和锁释放操作,注意修改模块名称为项目实际名称,代码如下:

arduino 复制代码
// Fill out your copyright notice in the Description page of Project Settings.
#include "ACT.h"
#include "Modules/ModuleManager.h"
#include "WindowsCriticalSection.h"

// 替换为自身项目名,需与工程名一致,否则模块加载函数不执行
IMPLEMENT_PRIMARY_GAME_MODULE( ACT, ACT, "ACT" );

// 模块加载(程序启动时执行)
void ACT::StartupModule()
{
    // 宏判断:仅打包发布版本执行加锁逻辑,编辑器版本不生效
    #if !WITH_EDITOR
        // 创建全局唯一锁,名称自定义(建议UE5-项目名Game格式)
        Check = new FWindowsSystemWideCriticalSection(TEXT("#UE5-ACTGame"));
        if (Check->IsValid()) // 锁创建成功,正常启动程序
        {
        }
        else // 锁创建失败,说明已有程序实例运行,请求关闭新程序
        {
            FGenericPlatformMisc::RequestExit(true);
        }
    #else
    #endif
}

// 模块卸载(程序关闭时执行)
void ACT::ShutdownModule()
{
    // 锁对象有效时,释放并销毁锁,避免系统资源占用
    if(Check)
    {
        Check->Release(); // 释放临界区锁
        delete Check;     // 销毁锁对象
        Check = nullptr;  // 置空指针,防止野指针
    }
}

4. 核心逻辑说明

  1. 程序启动时,模块加载函数StartupModule会尝试创建指定名称的全局临界区锁;
  2. 若锁已存在(已有程序实例运行),IsValid()返回false,调用FGenericPlatformMisc::RequestExit(true)强制关闭新实例;
  3. 若锁创建成功,程序正常运行;
  4. 程序关闭时,模块卸载函数ShutdownModule自动释放并销毁锁,保证下次启动可正常创建。

二、快捷方式脚本法

该方法无需修改UE5工程代码,通过创建批处理/PowerShell/VBScript脚本检测程序进程或创建系统互斥锁,实现单实例运行,适合打包后快速配置,用户通过点击脚本快捷方式启动程序即可,共提供4种实现方案,各有优劣。

核心使用前提

  1. 将脚本文件放在与UE5打包后的exe文件同目录(或在脚本中填写exe绝对路径);
  2. 对脚本创建桌面快捷方式,后续仅通过该快捷方式启动程序;
  3. 将脚本中的XXX.exe/XXX替换为实际的exe文件名(不含后缀)。

方案一:简易批处理脚本(最基础,存在轻微竞争条件)

优点 :代码简单、易编写;缺点:高频率点击时可能检测失效,无窗口置前功能。

创建.bat后缀文件,代码如下:

bash 复制代码
@echo off
setlocal

:: 替换为实际的exe文件名(含后缀)
set "EXE_NAME=XXX.exe"
:: exe绝对路径,同目录可直接写%EXE_NAME%
set "EXE_PATH=C:\Users\ASUS\Desktop\Windows\XXX\Binaries\Win64\XXX.exe"

:: 检查进程是否已运行
tasklist /FI "IMAGENAME eq %EXE_NAME%" 2>NUL | find /I "%EXE_NAME%" >NUL
if %ERRORLEVEL% equ 0 (
    echo 程序已在运行中...
    timeout /t 2 /nobreak >NUL
    exit /b
)

:: 进程未运行,启动程序
echo 启动程序...
start "" "%EXE_PATH%"

endlocal

方案二:PowerShell增强批处理(可靠,支持窗口置前)

优点 :检测更稳定,可将已运行的程序窗口前置;缺点:依赖PowerShell环境。

创建.bat后缀文件,代码如下:

bash 复制代码
@echo off
setlocal

:: 替换为实际的exe绝对路径,同目录可直接写XXX.exe
set "EXE_PATH=C:\Users\ASUS\Desktop\Windows\XXX\Binaries\Win64\XXX.exe"

:: PowerShell检测进程,XXX替换为exe文件名(不含后缀)
powershell -Command "if (Get-Process -Name 'XXX' -ErrorAction SilentlyContinue) { exit 1 } else { exit 0 }"
if %ERRORLEVEL% equ 1 (
    echo 程序已在运行中,将已运行的窗口置前...
    powershell -Command "Add-Type -AssemblyName Microsoft.VisualBasic; [Microsoft.VisualBasic.Interaction]::AppActivate('XXX')"
    timeout /t 1 /nobreak >NUL
    exit /b
)

:: 进程未运行,启动程序
echo 启动程序...
start "" "%EXE_PATH%"

endlocal

方案三:互斥锁PowerShell脚本(最可靠,绝对单实例)

优点 :使用系统级互斥锁,彻底避免多开,支持窗口置前;缺点:需创建两个脚本文件,需隐藏命令行窗口。

该方案是推荐方案 ,通过System.Threading.Mutex创建全局互斥锁,保证绝对单实例,分为PowerShell脚本VBScript脚本(用于隐藏命令行窗口)。

步骤1:创建PowerShell脚本(check_and_run.ps1)

创建.ps1后缀文件,代码如下:

php 复制代码
# 隐藏PowerShell控制台窗口
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
$consolePtr = [Console.Window]::GetConsoleWindow()
[Console.Window]::ShowWindow($consolePtr, 0) | Out-Null

# 自定义互斥锁名称,建议项目名_SingleInstance_Mutex格式
$MutexName = "Global\XXX_SingleInstance_Mutex"
# 替换为实际exe文件名,同目录直接写,否则填绝对路径
$ExePath = "XXX.exe"

try {
    # 创建互斥锁
    $Mutex = [System.Threading.Mutex]::new($false, $MutexName)
    # 尝试获取锁,0表示立即检测,失败则说明已有实例
    if (!$Mutex.WaitOne(0)) {
        Write-Host "程序已在运行中..."
        # 窗口置前,XXX替换为exe文件名(不含后缀)
        Add-Type -AssemblyName Microsoft.VisualBasic
        [Microsoft.VisualBasic.Interaction]::AppActivate("XXX")
        Start-Sleep -Seconds 2
        exit
    }

    # 获取锁成功,启动程序
    Write-Host "启动程序..."
    $process = Start-Process -FilePath $ExePath -PassThru
    # 等待程序退出,保证锁正常释放
    $process.WaitForExit()

} finally {
    # 释放并销毁互斥锁,避免资源占用
    if ($Mutex -ne $null) {
        $Mutex.ReleaseMutex()
        $Mutex.Dispose()
    }
}
步骤2:创建VBScript脚本(launch_ps.vbs)

用于隐藏PowerShell的命令行窗口,创建.vbs后缀文件,代码如下:

vbnet 复制代码
' 隐藏窗口启动PowerShell脚本,同目录直接写check_and_run.ps1,否则填绝对路径
CreateObject("WScript.Shell").Run "powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File ""check_and_run.ps1""", 0, False
步骤3:使用方式

直接对launch_ps.vbs创建桌面快捷方式,可自定义快捷方式图标,点击该快捷方式启动程序即可。

方案四:VBScript脚本(兼容性最好,适合老系统)

优点 :纯VBScript编写,兼容Windows老系统(如Win7),无需依赖其他环境;缺点:功能简单,无窗口置前。

创建.vbs后缀文件,代码如下:

vbscript 复制代码
' XXX_Launcher.vbs
Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\.\root\cimv2")
' 替换为实际exe文件名(含后缀)
Set processes = wmi.ExecQuery("SELECT * FROM Win32_Process WHERE Name='XXX.exe'")

If processes.Count > 0 Then
    ' 程序已运行,弹出提示框,2秒后自动关闭
    Set shell = CreateObject("WScript.Shell")
    shell.Popup "程序已在运行中!", 2, "提示", 64
Else
    ' 程序未运行,启动程序,替换为exe绝对路径
    Set shell = CreateObject("WScript.Shell")
    shell.Run """C:\Users\ASUS\Desktop\Windows\XXX\Binaries\Win64\XXX.exe""", 1, False
End If

4种脚本方案对比

方案 核心优点 核心缺点 适用场景
方案一 代码最简单、易修改 存在竞争条件,高频率点击可能失效 测试环境、对稳定性要求低的场景
方案二 检测稳定,支持窗口置前 依赖PowerShell环境 主流Windows系统(Win10/11),需要窗口前置功能
方案三 系统互斥锁,绝对单实例,支持窗口置前 需创建两个脚本文件 生产环境、对单实例要求严格的场景(推荐
方案四 兼容性最好,支持老系统,无命令行窗口 无窗口置前,功能简单 Windows老系统(Win7及以下)

三、两种方法整体对比与选型建议

1. 整体对比

实现方法 开发侵入性 生效范围 配置复杂度 稳定性 适用阶段
C++代码法 需修改UE5工程代码 仅打包发布版本,不影响编辑器 中等(需编写C++代码) 极高(引擎底层实现) 开发阶段、需集成到程序本身
快捷方式脚本法 无侵入,不修改工程代码 仅通过脚本快捷方式启动时生效 低(直接编写脚本,无需开发) 高(方案三接近绝对稳定) 打包后、快速配置,或无C++开发环境的场景

2. 选型建议

  1. 开发阶段/商业项目 :选择C++代码法,将单实例逻辑集成到程序本身,避免用户绕开脚本直接启动exe,安全性和稳定性更高;
  2. 测试阶段/快速配置/无C++环境 :选择快捷方式脚本法(方案三) ,无需修改工程,快速实现单实例,满足日常使用需求;
  3. 老系统兼容 :选择快捷方式脚本法(方案四) ,适配Win7等低版本Windows系统。
相关推荐
tedcloud12318 小时前
UI-TARS-desktop部署教程:构建AI桌面自动化系统
服务器·前端·人工智能·ui·自动化·github
UXbot21 小时前
AI原型设计工具如何支持团队协作与快速迭代
前端·交互·个人开发·ai编程·原型模式
智者知已应修善业21 小时前
【51单片机89C51及74LS273、74LS244组成】2022-5-28
c++·经验分享·笔记·算法·51单片机
ZC跨境爬虫21 小时前
跟着MDN学HTML_day_48:(Node接口)
前端·javascript·ui·html·音视频
PieroPc1 天前
CAMWATCH — 局域网摄像头监控系统 Fastapi + html
前端·python·html·fastapi·监控
Byron Loong1 天前
【c++】为什么有了dll和.h,还需要包含lib
java·开发语言·c++
巴巴博一1 天前
2026 最新:Trae / Cursor 一键接入 taste-skill 完整教程(让 AI 前端告别“AI 味”)
前端·ai·ai编程
kyriewen1 天前
半夜三点线上崩了,AI替我背了锅——用AI排错,五分钟定位三年老bug
前端·javascript·ai编程
坚果派·白晓明1 天前
【鸿蒙PC三方库移植适配框架解读系列】第一篇:Lycium C/C++ 三方库适配 — 概述与环境配置
c语言·开发语言·c++·harmonyos·开源鸿蒙·三方库·c/c++三方库
kyriewen1 天前
我让 AI 当了 24 小时全年无休的“毒舌考官”
前端·ci/cd·ai编程