首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【一起学Rust | Tauri2.0框架】单实例应用程序的深入解析:零漏洞实现与优化实战

  • 25-03-07 23:00
  • 4120
  • 11636
blog.csdn.net

文章目录

  • 前言
  • 一、 单实例应用的意义
  • 二、 实现单实例应用的方法
    • 1 Windows下的实现
      • 1.1 创建命名Mutex
      • 1.2 在Tauri应用中集成Mutex检查
    • 2 macOS下的实现
      • 2.1 获取Bundle Identifier
      • 2.2 检查是否已经有实例在运行
    • 3 Linux下的实现
      • 3.1 获取进程列表
      • 3.2 检查是否已经有实例在运行
    • 4 在Tauri应用中集成单实例检查
  • 三、使用Tauri官方提供的插件实现单例程序
    • 1. 安装准备
    • 2. 自动安装(推荐)
    • 3. 手动安装
  • 四、配置单例插件
    • 1. `init`函数
    • 2. 新打开程序提示例子


前言

随着跨平台应用开发的需求不断增加,Tauri2.0框架凭借其高性能和跨平台的特性,成为了开发者们的热门选择。然而,在开发桌面应用时,如何确保应用程序只能运行一个实例是一个常见的需求。例如,某些应用程序需要独占系统资源,或者需要避免用户误操作导致的数据冲突。今天,我们将探讨如何在Tauri2.0框架下,使用Rust语言实现单实例应用程序的功能。

本文将详细介绍在不同操作系统(Windows、macOS、Linux)下实现单实例应用的方法,并提供完整的代码示例。通过本文,你将了解到如何在Tauri2.0应用启动时检查是否已经有实例在运行,并采取相应的措施,例如提示用户或将参数传递给已有的实例。

最后再为你介绍Tauri官方为我们实现这种需求提供的一种捷径,从而不用去管理互斥体,而是简单的插件配置就能得到相同的结果,这也是为什么要写本文的原因。这就是Tauri插件 —— Single Instance.


一、 单实例应用的意义

在开发桌面应用时,单实例应用的意义主要体现在以下几个方面:

  1. 资源管理:某些应用程序需要独占特定的系统资源,例如硬件设备或独特的系统服务。如果允许多个实例运行,可能会导致资源争抢或不可预测的行为。

  2. 数据一致性:对于需要处理共享数据的应用程序,例如数据库管理工具或配置文件编辑器,防止多个实例同时修改数据可以避免数据冲突和不一致。

  3. 用户体验:在某些场景下,用户可能不小心多次启动应用程序,导致多个实例运行。通过单实例机制,可以提供更友好的用户体验,例如自动将焦点切换到已有的实例。

  4. 安全性:对于某些需要严格控制的应用程序,例如金融类软件或敏感数据处理工具,单实例机制可以增强应用的安全性,防止恶意的多实例攻击。

二、 实现单实例应用的方法

在Tauri2.0框架下实现单实例应用,我们需要在应用启动时检查是否已经有一个实例在运行。如果有,则采取相应的措施,例如提示用户或将参数传递给已有的实例。

1 Windows下的实现

在Windows平台下,可以通过创建一个命名的Mutex(互斥量)来实现单实例检查。Mutex是Windows提供的一种同步机制,可以用于跨进程的同步和互斥控制。

1.1 创建命名Mutex

在Windows下,我们可以通过调用CreateMutexW函数创建一个命名的Mutex。如果Mutex已经存在,则表示已经有一个实例在运行。

use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use winapi::shared::minwindef::DWORD;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::synchapi::CreateMutexW;

fn create_mutex(name: &str) -> bool {
    let name = OsStr::new(name).encode_wide().chain(Some(0)).collect::<Vec<u16>>();
    unsafe {
        CreateMutexW(name.as_ptr(), false as DWORD, None) as DWORD
    } != 0
}

fn is_single_instance(name: &str) -> bool {
    let result = create_mutex(name);
    if result {
        // 如果Mutex已经存在,则表示已经有一个实例在运行
        unsafe {
            if GetLastError() == 183 { // ERROR_ALREADY_EXISTS
                return false;
            }
        }
    }
    result
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

1.2 在Tauri应用中集成Mutex检查

在Tauri应用的主函数中,我们可以调用上述函数来检查是否已经有一个实例在运行。如果已经有实例运行,则可以提示用户并退出。

fn main() {
    let instance_name = "MyTauriApp";
    if !is_single_instance(instance_name) {
        // 如果已经有一个实例在运行,则提示用户并退出
        println!("An instance of {} is already running.", instance_name);
        std::process::exit(1);
    }

    // 启动Tauri应用
    tauri::run();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2 macOS下的实现

在macOS平台下,可以通过BUNDLE_IDENTIFIER来实现单实例检查。macOS提供了LSOpenURLsWithRole函数,可以用于检查是否已经有一个应用程序在运行。

2.1 获取Bundle Identifier

在macOS下,每个应用程序都有一个唯一的Bundle Identifier,可以通过Info.plist文件配置。

use std::process::Command;

fn get_bundle_identifier() -> String {
    let output = Command::new("osascript")
        .arg("-e")
        .arg("id of app \"System Events\"")
        .output()
        .expect("failed to execute osascript");
    
    String::from_utf8(output.stdout).unwrap()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.2 检查是否已经有实例在运行

通过调用LSOpenURLsWithRole函数,我们可以检查是否已经有一个实例在运行。如果有,则返回true,否则返回false。

use std::os::raw::c_char;

extern crate libc;

fn is_single_instance(bundle_id: &str) -> bool {
    let mut psi: libc::PROCESSENTRY32 = unsafe { std::mem::zeroed() };
    let snapshot = unsafe { libc::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
    
    if snapshot == 0 {
        return false;
    }

    psi.dwSize = std::mem::size_of::<libc::PROCESSENTRY32>() as DWORD;
    while unsafe { Process32Next(snapshot, &mut psi) } != 0 {
        if let Some(name) = unsafe { CStr::from_ptr(psi.szExeFile.as_ptr() as *const c_char) }.to_str() {
            if name == bundle_id {
                return true;
            }
        }
    }

    unsafe { CloseHandle(snapshot) };
    false
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3 Linux下的实现

在Linux平台下,可以通过检查进程名或使用套接字来实现单实例检查。这里我们将演示如何通过检查进程名来实现单实例检查。

3.1 获取进程列表

通过调用/proc文件系统,我们可以获取当前运行的所有进程,并检查是否有相同的进程名。

use std::fs;
use std::path::Path;

fn get_process_list() -> Vec<String> {
    let mut processes = Vec::new();
    for entry in fs::read_dir("/proc").unwrap() {
        let entry = entry.unwrap();
        let path = entry.path();
        if path.is_dir() {
            if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                if name.chars().all(char::is_digit) {
                    processes.push(name.to_string());
                }
            }
        }
    }
    processes
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.2 检查是否已经有实例在运行

通过遍历所有进程,并检查是否有相同的进程名来判断是否已经有实例在运行。

fn is_single_instance(process_name: &str) -> bool {
    let processes = get_process_list();
    for pid in processes {
        let exe_path = format!("/proc/{}/exe", pid);
        let exe_link = Path::new(&exe_path);
        if exe_link.exists() {
            if let Some(exe_path) = exe_link.canonicalize().ok() {
                if exe_path.file_name().and_then(|n| n.to_str()) == Some(process_name) {
                    return true;
                }
            }
        }
    }
    false
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

4 在Tauri应用中集成单实例检查

在Tauri应用的主函数中,我们可以根据不同的平台调用相应的单实例检查函数。

fn main() {
    #[cfg(target_os = "windows")]
    {
        let instance_name = "MyTauriApp";
        if !is_single_instance(instance_name) {
            println!("An instance of {} is already running.", instance_name);
            std::process::exit(1);
        }
    }

    #[cfg(target_os = "macos")]
    {
        let bundle_id = get_bundle_identifier();
        if is_single_instance(&bundle_id) {
            println!("An instance of {} is already running.", bundle_id);
            std::process::exit(1);
        }
    }

    #[cfg(target_os = "linux")]
    {
        let process_name = "my_tauri_app";
        if is_single_instance(process_name) {
            println!("An instance of {} is already running.", process_name);
            std::process::exit(1);
        }
    }

    tauri::run();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

三、使用Tauri官方提供的插件实现单例程序

1. 安装准备

首先,确保你安装的Rust版本符合条件,该插件要求你的Rust版本大于1.77.2.

然后就是看你的应用平台是否支持该插件,官方给出以下表格

在这里插入图片描述
可以明显看到,只有桌面系统受支持,也就是你的应用只能是在windows,linux,macos上,这个插件才会有用,否则插件是用不了的。

2. 自动安装(推荐)

使用你所选择的包管理器直接安装即可,例如pnpm安装

pnpm tauri add single-instance
  • 1

3. 手动安装

首先添加依赖

# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-single-instance = "2.0.0"
  • 1
  • 2
  • 3

然后在tauri启动的时候添加插件

pub fn run() {
    tauri::Builder::default()
    // 就是下面这行
        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {})) 
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后运行一下项目就装好插件了

pnpm tauri dev
  • 1

四、配置单例插件

如果你只是想要简单的实现单实例的话,就以上安装配置就已经能够达到这个效果了,如果你还想要在这个过程中实现其他功能,例如用户启动了另一个程序后提示程序已经启动了,那么可以接着往下看。

1. init函数

在配置安装插件时有一个init函数可以注意一下,也就是

.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
  // 在这里写代码 ……
}))
  • 1
  • 2
  • 3

插件的 init() 方法接收一个闭包,该闭包在新 App 实例启动时调用,但由插件关闭。 这个闭包有三个参数:

  1. app:应用程序的 AppHandle ,即应用的句柄,用来操作该程序。
  2. args:用户初始化新实例时传递的参数列表,也就是新打开的程序的传入参数。
  3. cwd:当前工作目录表示启动新应用程序实例的目录,也就是另一个程序在哪个目录打开的。

2. 新打开程序提示例子

注意,这部分逻辑你可以自己实现,这只是个官方给的例子。

use tauri::{AppHandle, Manager};

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            let _ = show_window(app);
        }))
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

fn show_window(app: &AppHandle) {
    let windows = app.webview_windows();

    windows
        .values()
        .next()
        .expect("Sorry, no window found")
        .set_focus()
        .expect("Can't Bring Window to Focus");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
共同探讨互联网知识
微信名片
注:本文转载自blog.csdn.net的广龙宇的文章"https://blog.csdn.net/weixin_47754149/article/details/145709030"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

103
后端
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top