【一起学Rust | Tauri2.0框架】单实例应用程序的深入解析:零漏洞实现与优化实战
文章目录
- 前言
- 一、 单实例应用的意义
- 二、 实现单实例应用的方法
- 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
.
一、 单实例应用的意义
在开发桌面应用时,单实例应用的意义主要体现在以下几个方面:
-
资源管理:某些应用程序需要独占特定的系统资源,例如硬件设备或独特的系统服务。如果允许多个实例运行,可能会导致资源争抢或不可预测的行为。
-
数据一致性:对于需要处理共享数据的应用程序,例如数据库管理工具或配置文件编辑器,防止多个实例同时修改数据可以避免数据冲突和不一致。
-
用户体验:在某些场景下,用户可能不小心多次启动应用程序,导致多个实例运行。通过单实例机制,可以提供更友好的用户体验,例如自动将焦点切换到已有的实例。
-
安全性:对于某些需要严格控制的应用程序,例如金融类软件或敏感数据处理工具,单实例机制可以增强应用的安全性,防止恶意的多实例攻击。
二、 实现单实例应用的方法
在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_EXISTSreturn false;}}}result
}
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();
}
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()
}
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
}
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
}
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
}
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();
}
三、使用Tauri官方提供的插件实现单例程序
1. 安装准备
首先,确保你安装的Rust版本符合条件,该插件要求你的Rust版本大于1.77.2
.
然后就是看你的应用平台是否支持该插件,官方给出以下表格
可以明显看到,只有桌面系统受支持,也就是你的应用只能是在windows
,linux
,macos
上,这个插件才会有用,否则插件是用不了的。
2. 自动安装(推荐)
使用你所选择的包管理器直接安装即可,例如pnpm
安装
pnpm tauri add single-instance
3. 手动安装
首先添加依赖
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-single-instance = "2.0.0"
然后在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");
}
然后运行一下项目就装好插件了
pnpm tauri dev
四、配置单例插件
如果你只是想要简单的实现单实例的话,就以上安装配置就已经能够达到这个效果了,如果你还想要在这个过程中实现其他功能,例如用户启动了另一个程序后提示程序已经启动了
,那么可以接着往下看。
1. init
函数
在配置安装插件时有一个init
函数可以注意一下,也就是
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {// 在这里写代码 ……
}))
插件的 init()
方法接收一个闭包,该闭包在新 App 实例启动时调用,但由插件关闭。 这个闭包有三个参数:
app
:应用程序的 AppHandle ,即应用的句柄,用来操作该程序。args
:用户初始化新实例时传递的参数列表,也就是新打开的程序的传入参数。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");
}
相关文章:

【一起学Rust | Tauri2.0框架】单实例应用程序的深入解析:零漏洞实现与优化实战
文章目录 前言一、 单实例应用的意义二、 实现单实例应用的方法1 Windows下的实现1.1 创建命名Mutex1.2 在Tauri应用中集成Mutex检查 2 macOS下的实现2.1 获取Bundle Identifier2.2 检查是否已经有实例在运行 3 Linux下的实现3.1 获取进程列表3.2 检查是否已经有实例在运行 4 在…...
PhyloSuite v1.2.3安装与使用-生信工具049
PhyloSuite 一个好用的win集成建树平台,官方相关文档视频等做的可好了PhyloSuite (jushengwu.com) 官网 https://github.com/dongzhang0725/PhyloSuite/releases #官网 http://phylosuite.jushengwu.com/dongzhang0725.github.io/installation/ #官方说明文档…...
使用Apache Lucene构建高效的全文搜索服务
使用Apache Lucene构建高效的全文搜索服务 在现代应用程序中,全文搜索功能是不可或缺的一部分。无论是电子商务网站、内容管理系统,还是数据分析平台,快速、准确地搜索大量数据是提升用户体验的关键。Apache Lucene 是一个强大的全文搜索引擎…...
SSH远程登录并执行命令
SSH远程登录并执行命令 1、登录远程服务器2、远程执行命令3、远程执行交互命令4、远程执行脚本5、退出远程SSH连接 SSH是Linux中的远程连接管理工具,可以在本地服务器上通过SSH协议连接到远程服务器,并在远程服务器上执行命令 SSH不仅可以用来登录远程服…...

EasyRTC:支持任意平台设备的嵌入式WebRTC实时音视频通信SDK解决方案
随着互联网技术的飞速发展,实时音视频通信已成为各行各业数字化转型的核心需求之一。无论是远程办公、在线教育、智慧医疗,还是智能安防、直播互动,用户对低延迟、高可靠、跨平台的音视频通信需求日益增长。 一、WebRTC与WebP2P:实…...

Golang语言特性
1.Go语言的优势 1.1极简单的部署方式 —可以直接编译成机器码。代码可以直接转换为二进制数据,在操作系统上可以直接./去执行。 —不依赖其他库。最终生成的可执行程序是一个静态的二进制文本文件。 —可以直接运行即可部署。 —静态类型语言。编译的时候检查出来隐…...
LangPrompt提示词
LangPrompt提示词 https://github.com/langgptai/LangGPT 学习LangGPT的仓库,帮我创建 一个专门生成LangGPT格式prompt的助手 根据LangGPT的格式规范设计的专业提示词生成助手框架。以下是分步骤的解决方案: 助手角色定义模板 # Role: LangGPT提示词架…...
Java 容器之 List
在 Java 的集合框架中,List 是 Collection 的重要子接口,以其有序、可重复的特点广泛应用于开发中。本文将详细探讨 List 的核心概念、主要实现类(如 ArrayList 和 LinkedList)的底层原理,以及使用中需要注意的常见问题…...

ETL-kettle数据转换使用详解
一、excel转换成mysql 表格就按照我们刚才转换的表格来转换成MySQL数据 在MySQL数据库中创建数据库,这个根据自身情况。我就在现有test库中测试了。 根据以上步骤,新建转换。 构建流程图,选择excel输入和表输出 将两个组件连接起来 双击…...
【容器化】低版本docker拉取ubuntn 22.04镜像启动容器执行apt update提示 NO_PUBKEY 871920D1991BC93C
前置信息 宿主机信息 [root@localhost ~]# cat /etc/os-release NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)" ANSI_COLOR…...

Hive-04之存储格式、SerDe、企业级调优
一、主题 hive表的数据压缩和文件存储格式hive的自定义UDF函数hive的JDBC代码操作hive的SerDe介绍和使用hive的优化 二、要点 1. hive表的文件存储格式 Hive支持的存储数的格式主要有:TEXTFILE(行式存储) 、SEQUENCEFILE(行式存储)、ORC&…...

Makefile、Make和CMake:构建工具的三剑客
目录 1. Makefile 2. Make 3. CMake Makefile、Make、CMake的关系 在软件开发中,构建工具是必不可少的。它们帮助开发者自动化编译、链接和打包的过程,确保代码能够高效地转化为可执行文件。Makefile、Make和CMake是三个常见的构建工具,它…...
The “Rule-of-Zero“ should be followed (s4963)
Most classes should not directly handle resources, but instead, use members that perform resource handling for them: For memory, it can be std::unique_ptr, std::shared_ptr, std::vector…For files, it can be std::ofstream, std::ifstream…… Classes …...
Kotlin语言特性(二):泛型与注解
Kotlin语言特性(二):泛型与注解 一、引言 在上一篇文章中,我们介绍了Kotlin的三大核心特性。本文将深入探讨Kotlin的泛型和注解特性,并与Java进行对比,帮助你更好地理解和运用这些特性。 二、Kotlin泛型 2.1 泛型基础 2.1.1 声明泛型类 // Kotlin泛型类声明 class …...

FunPapers[3]:WWW‘25「快手」生成式回归预测观看时长
Sequence Generation Modeling for Continuous Value Prediction https://arxiv.org/pdf/2412.20211,www 2025. 文章目录 Sequence Generation Modeling for Continuous Value Prediction核心思想1. CVP常规方法是怎么做的?2. 观看时长预测和CVP是如何关…...

并发编程1
JAVA线程回顾 多线程 多个并行的线程来完成个自的任务,优点是程序响应速度更快,程序性能得到提升。 并行执行与并发执行 并发执行就是在单核CPU下,现成实际上是串行执行的,任务调度器将cpu的时间片分给不同的线程使用࿰…...

Hadoop之01:HDFS分布式文件系统
HDFS分布式文件系统 1.目标 理解分布式思想学会使用HDFS的常用命令掌握如何使用java api操作HDFS能独立描述HDFS三大组件namenode、secondarynamenode、datanode的作用理解并独立描述HDFS读写流程HDFS如何解决大量小文件存储问题 2. HDFS 2.1 HDFS是什么 HDFS是Hadoop中的一…...
从源到目标:深度学习中的迁移学习与领域自适应实践
云边有个稻草人-CSDN博客 目录 引言 一、迁移学习概述 1.1 迁移学习的类型 1.2 迁移学习的核心思想 1.3 迁移学习的应用场景 二、领域自适应(Domain Adaptation) 2.1 领域自适应的定义 2.2 领域自适应的挑战 2.3 领域自适应的核心方法 &#…...

WebRTC与PJSIP:呼叫中心系统技术选型指南
助力企业构建高效、灵活的通信解决方案 在数字化时代,呼叫中心系统的技术选型直接影响客户服务效率和业务扩展能力。WebRTC与PJSIP作为两大主流通信技术,各有其核心优势与适用场景。本文从功能、成本、开发门槛等维度为您深度解析,助您精准匹…...

使用IDEA如何隐藏文件或文件夹
选择file -> settings 选择Editor -> File Types ->Ignored Files and Folders (忽略文件和目录) 点击号就可以指定想要隐藏的文件或文件夹...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...