微软是如何解决 PC 端程序多开问题的——内部实现

前言

上次,我们通过《引用 Microsoft.VisualBasic 解决程序多开的问题》。

虽然它非常简单,但是仅适用于 WinForm 应用程序,而且还需要引用不常用的Microsoft.VisualBasic类库。

因此,我们决定深挖一下,看看具体是如何实现的。

原理

通过查看WindowsFormsApplicationBaseRun方法实现(代码有删减):

Public Sub Run(commandLine As String())If Not IsSingleInstance ThenDoApplicationModel()Else' This is a Single-Instance applicationDim pipeServer As NamedPipeServerStream = NothingIf TryCreatePipeServer(ApplicationInstanceID, pipeServer) Then' --- This is the first instance of a single-instance application to run.Using pipeServerWaitForClientConnectionsAsync(pipeServer, AddressOf OnStartupNextInstanceMarshallingAdaptor, cancellationToken:=tokenSource.Token)DoApplicationModel()End UsingElseDim awaitable = SendSecondInstanceArgsAsync(ApplicationInstanceID, commandLine, cancellationToken:=tokenSource.Token).ConfigureAwait(False)awaitable.GetAwaiter().GetResult()End IfEnd If 'Single-Instance application
End Sub

可以分析出整个流程如下:

  1. 创建一个NamedPipeServerStream实例

  2. 如果创建成功,则用WaitForClientConnectionsAsync等待第 2 个应用实例进行连接

  3. 如果创建失败,则用SendSecondInstanceArgsAsync向第 1 个应用实例发送数据

NamedPipeServerStream

使用NamedPipeServerStream类可以创建命名管道。

命名管道在管道服务器和一个或多个管道客户端之间提供进程间通信。命名管道可以是单向的,也可以是双向的。它们支持基于消息的通信,并允许多个客户端使用相同的管道名称同时连接到服务器进程。

详细使用说明,请参阅官方文档《使用命名管道进行网络进程间通信》[1]

实现

下面我们用控制台程序进行演示:

const string pipeName = "MyIO";
const PipeOptions NamedPipeOptions = PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly;static async Task Main(string[] args)
{try{using (var pipeServer = new NamedPipeServerStream(pipeName: pipeName,direction: PipeDirection.In,maxNumberOfServerInstances: 1,transmissionMode: PipeTransmissionMode.Byte,options: NamedPipeOptions)){WaitForClientConnectionsAsync(pipeServer,str => Console.WriteLine(str));Console.WriteLine($"start server {args[0]}");Console.ReadKey();}}catch{await SendSecondInstanceArgsAsync(()=> $"call from {args[0]}").ConfigureAwait(false);}
}

需要注意的是,WaitForClientConnectionsAsync不能加await,否则后续代码不能执行。

WaitForClientConnectionsAsync

实现代码如下:

private static async Task WaitForClientConnectionsAsync(NamedPipeServerStream pipeServer, Action<string> callback)
{CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();while (true){await pipeServer.WaitForConnectionAsync(cancellationTokenSource.Token).ConfigureAwait(false);try{const int bufferLength = 1024;var buffer = new byte[bufferLength];using (var stream = new MemoryStream()){while (true){var bytesRead = await pipeServer.ReadAsync(buffer.AsMemory(0, bufferLength), cancellationTokenSource.Token).ConfigureAwait(false);if (bytesRead == 0){break;}stream.Write(buffer, 0, bytesRead);}stream.Seek(0, SeekOrigin.Begin);callback(Encoding.UTF8.GetString(stream.ToArray()));}}finally{pipeServer.Disconnect();}}
}
  • 循环等待客户端连接

  • 读取客户端发送的数据,转换成字符串

  • 调用callback处理字符串,这里是str => Console.WriteLine(str)

  • 断开客户端连接

SendSecondInstanceArgsAsync

实现代码如下:

private static async Task SendSecondInstanceArgsAsync(Func<string> func)
{using (var pipeClient = new NamedPipeClientStream(serverName: ".",pipeName: pipeName,direction: PipeDirection.Out,options: NamedPipeOptions)){CancellationTokenSource cancellationTokenSource2 = new CancellationTokenSource();cancellationTokenSource2.CancelAfter(2500);await pipeClient.ConnectAsync(cancellationTokenSource2.Token).ConfigureAwait(false);await pipeClient.WriteAsync(Encoding.UTF8.GetBytes(func()), cancellationTokenSource2.Token).ConfigureAwait(false);}
}
  • 创建客户端连接本地管道服务

  • 向服务端发送func产生的数据,,这里是()=> $"call from {args[0]}"

Demo

创建多开脚本:

start " " "ConsoleApp1.exe" firstInstancestart " " "ConsoleApp1.exe" secondInstancestart " " "ConsoleApp1.exe" thirdInstance

执行后,我们发现程序只能打开一次。

并且收到了其它多开应用发过来的数据:

5e0b7c1a1ac4c3c1174cb939ca824477.png

结论

使用NamedPipeServerStream相对互斥锁Mutex的实现要复杂。

但是由于可以进行通讯,因此可以做到更灵活的控制。

比如,应用定时启动自己的另一个实例去下载更新,下载完成后通知当前应用提示用户是否更新。

想了解更多内容,请关注我的个人公众号”My IO“

参考资料

[1]

《使用命名管道进行网络进程间通信》: https://docs.microsoft.com/zh-cn/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication?WT.mc_id=DT-MVP-38491

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#常用命名空间

MSDN上的C#.NET Framework类库文档目录树&#xff0c;本人觉得有点不得要领&#xff0c;于是参考搜到的结果简单整理如下&#xff1a; 一、基础命名空间 System 处理内建数据、数学计算、随机数的产生、环境变量、垃圾回收器及一些常见的异常和特征. System.Collections 包含了…

svn之回滚到指定版本

1 问题 我们用svn下载了最新的代码&#xff0c;但是我们需要回退到制定的版本 2 操作 我们先svn showlog 找到对应的版本号 如果用的是svn图形界面客户端 我们点击关联svn项目的鼠标右键&#xff0c;然后有个update to reversion 写上相应的版本即可

番茄工作法—《可以量化的管理学》

6.5.4时间t与番茄工作法 内容提要&#xff1a;番茄工作法划分工作和休息时间&#xff0c;将时间划分为小块&#xff0c;有利于提高工作和休息的效率&#xff0c;提高时间的感知和掌控。 番茄工作法是简单易行的时间管理方法&#xff0c;是由弗朗西斯科西里洛于1992年创立的一种…

【C语言简单说】十二:逻辑运算符

**总觉得今天更两节就好了。 (&#xffe3;.&#xffe3;) ** 困。。。(&#xffe3;.&#xffe3;) \ 今天我们来加深if的使用&#xff0c;我们来讲解运算符&&和||&#xff0c;一个叫做 与&& 一个叫做 或&&&#xff1b;我们先来说 && 我们…

C#遍历一个文件夹下的所有可执行文件

using System; using System.IO;namespace IO操作 {class Program{static void Main(string[] args){Console.WriteLine(File.Exists("C:\IO.txt"));Console.WriteLine(Directory.Exists("C:\"));Console.WriteLine();//获取一个路径下所有可执行文件&…

linux c之把最简单的字符串数据追加写入文件

1 问题 把最简单的字符串数据追加写入文件 2 代码实现 #include <stdio.h> #include <string.h>void write_data_to_file(const char *path, char *str) {FILE *fd fopen(path, "a");if (fd NULL) {printf("fd is NULL and open file fail\n&qu…

Android Json 解析

方法一 使用API解析 json&#xff1a; {"beaconid":"2397","state":"01","userid":"90"} 获取json的方法 private void sendRequestWithHttpClient(){new Thread(new Runnable() {Overridepublic void run() {try…

C#中static void Main(string[ ] args)中的作用及解释

C#中static void Main(string[ ] args)中的作用及解释 static 表示方法是静态的就是说方法在程序被编译的时候就被分配了内存,使用的时候不用生成某个类型的对象,知道程序退出才释放。 void 表示方法没有返回值,就是方法没有renturn 关键字。 main 是方法名,当然这个方法是…

电脑配置java编译报错_java 编译错误

我有两个简单的代码&#xff0c;ClickMe.java代码为&#xff1a;importjava.applet.Applet;importjava.awt.*;importjava.awt.event.*;publicclassClickMeextendsAppletimplementsMouseListener{privateSpo...我有两个简单的代码&#xff0c;ClickMe.java代码为&#xff1a;imp…

正则匹配字符串无匹配不到_实现简单正则表达式匹配

这是facebook的面试题实现包含以下特殊字符的正则表达匹配“.” 匹配任意一个字符“*” 匹配任意多个前面的字符例如给定正则表达“ra.”和字符串“ray”你的函数应该返回true&#xff0c;但是仍用这个正则匹配另一个字符串“raymond”应当返回false给定正则表达“.*at”和字符…

【C语言简单说】十三:逻辑运算符||

&#xff08;﹏&#xff09;~ 更完睡觉。 这一节我们来说说逻辑或||&#xff0c;其实很简单的&#xff0c;既然你们理解了第一个逻辑与&#xff0c;那么逻辑或就没什么难度了。 我们说过逻辑与就像我们的并列关系&#xff0c;例如我们吃了苹果和李子。我们现在的逻辑或呢&a…

C#中元组对象Tuple的使用

简介元组使用泛型来简化类的定义&#xff0c;多用于方法的返回值。在函数需要返回多个类型的时候&#xff0c;就不必使用out , ref等关键字了&#xff0c;直接定义一个Tuple类型&#xff0c;使用起来非常方便例子在我们编程时&#xff0c;比如一个人的信息&#xff0c;我们常常…

linux之通过strings命令查看so里面是否包含****字符串

1 问题 比如我们在一个C文件里面或者C++文件里面添加了日志打印字符串***,但是实际上没打印,我们会怀疑这个打印字符串有没有加到so里面去,那我们有什么办法呢?用strings命令 strings ***.so | grep 关键字 strings命令是在对象文件或者二进制文件中查找可打印的字符串 …

HQ-day2 C#语言基础

今天主要学习了C#的一些语言基础。 主要有&#xff1a;输入、输出。 一丶输入Console.readline(); 输入语句 输出console.write(); 输出语句 Console.writeline(); 输出语句自动换行 注释 CtrlK 然后 按C &#xff0c;注释选中部分 CtrlK 然后 按U&#xff0c; 取消注释 二、…

删除 oracle

C:\app\Administrator\product\11.2.0\client_1\deinstall 用这个批处理文件,会把oracle全部删除,除这个目录本身以外 .另外它不删除服务,即使服务已经被停止(不停止也无法卸载). 采用 sc delete 服务名,删除服务.

程序员应该选择哪种语言来学习更好?应该如何学

还记得自己那年考清华失败&#xff0c;被调剂到中科大软院&#xff0c;当初有几个方向可以选&#xff0c;软件设计、嵌入式、信息安全等等&#xff0c;毫不犹豫地选择了信息安全。为什么选信息安全&#xff1f;这四个字听起来多牛多有感觉&#xff0c;我本科是学物理的&#xf…

C#文件写入操作

using System; using System.IO;namespace IO操作 {class Program{static void Main(string[] args){using(StreamWriter wFile.AppendText("IO.txt")){TextIn("Hello GeoStorm", w);TextIn("Hello World",w);w.Close();}}public static void Te…

【C语言简单说】十三:变量的生命周期

这次我们就来说说生命周期的问题。其实声明周期的意思就是他这个变量的作用范围&#xff0c;啥是作用范围&#xff1f;唔。。。看我举例子吧&#xff0c;意会&#xff0c;意会。。。 首先&#xff0c;我想问一下你们&#xff0c;如果你们校长叫做小明&#xff0c;你们班也有一…

指数分布java_python-numpy-指数分布实例详解

如下所示&#xff1a;# Seed random number generatornp.random.seed(42)# Compute mean no-hitter time: tautau np.mean(nohitter_times)# Draw out of an exponential distribution with parameter tau: inter_nohitter_timeinter_nohitter_time np.random.exponential(ta…

ASP.NET 6 中间件系列 - 条件中间件

这篇文章是 ASP.NET 6 中间件系列文章的第 4 部分。到目前为止&#xff0c;我们已经介绍了 ASP.NET 6 中间件的基础知识&#xff0c;展示了如何创建自定义中间件类&#xff0c;并讨论了中间件执行顺序的重要性。在本系列的最后一部分中&#xff0c;我们将展示在管道中有条件地执…