Not long ago, when trying to use C# to solve the problem of multi-opening of PC-side programs, I found that the WinForm program of VB.NET provides a very simple implementation method:
No code required, just a tick.
I was intrigued by how it was implemented, so I explored it. Today, through this article, I will introduce to you how Microsoft solves the problem of multi-opening of PC-side programs, and how to achieve the same function in C#.
Principle 1 - WindowsFormsApplicationBase base class
Compile a VB.NET WinForm program, decompile the source code, and find the entry Main method:
The entry class MyApplication inherits from the WindowsFormsApplicationBase base class, and actually executes the Run method of the base class. The Run method uses the IsSingleInstance property internally to determine whether to enable a single instance application:
The IsSingleInstance property is assigned in the constructor of the entry class:
Realize 1
By referencing the NuGet package Microsoft.VisualBasic, we can also inherit the WindowsFormsApplicationBase base class in C#, assign values to the IsSingleInstance property, and implement a single instance application:
class Program : WindowsFormsApplicationBase
{
public Program()
{
IsSingleInstance = true;
}
protected override void OnCreateMainForm()
{
MainForm = new Form1();
}
[STAThread]
static void Main(string[] args)
{
new Program().Run(args);
}
}
Although the above implementation is very simple, it is only suitable for WinForm applications, and also needs to refer to the Microsoft.VisualBasic class library.
So, we decided to dig a little deeper to see how it was implemented.
Principle 2 - Named Pipes
By looking at the Run method implementation of WindowsFormsApplicationBase (the code is abridged):
Public Sub Run(commandLine As String())
If Not IsSingleInstance Then
DoApplicationModel()
Else
' This is a Single-Instance application
Dim pipeServer As NamedPipeServerStream = Nothing
If TryCreatePipeServer(ApplicationInstanceID, pipeServer) Then
' --- This is the first instance of a single-instance application to run.
Using pipeServer
WaitForClientConnectionsAsync(pipeServer, AddressOf OnStartupNextInstanceMarshallingAdaptor, cancellationToken:=tokenSource.Token)
DoApplicationModel()
End Using
Else
Dim awaitable = SendSecondInstanceArgsAsync(ApplicationInstanceID, commandLine, cancellationToken:=tokenSource.Token).ConfigureAwait(False)
awaitable.GetAwaiter().GetResult()
End If
End If 'Single-Instance application
End Sub
It can be analyzed that the internal implementation principle of Microsoft to solve the problem of multi-opening of PC-side programs is as follows:
- Create a NamedPipeServerStream named pipe server instance
- If the creation is successful, use WaitForClientConnectionsAsync to wait for the second application instance to connect
- If the creation fails, use SendSecondInstanceArgsAsync to send data to the first application instance
Named pipes provide interprocess communication between a pipe server and one or more pipe clients. Named pipes can be unidirectional or bidirectional. They support message-based communication and allow multiple clients to connect to the server process simultaneously using the same pipe name.
For detailed usage instructions, please refer to the official document "Using Named Pipes for Network Interprocess Communication" [1]
realization 2
Below, we use the console program to demonstrate how to implement a single instance application:
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);
}
}
It should be noted that WaitForClientConnectionsAsync cannot add await, otherwise the subsequent code cannot be executed.
▌WaitForClientConnectionsAsync method implementation
The implementation code is as follows:
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();
}
}
}
▌SendSecondInstanceArgsAsync method implementation
The implementation code is as follows:
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);
}
}
Demo
Create a multi-open script:
start " " "ConsoleApp1.exe" firstInstance
start " " "ConsoleApp1.exe" secondInstance
start " " "ConsoleApp1.exe" thirdInstance
After execution, we found that the program can only be opened once. And received data from other multi-development applications:
Microsoft Most Valuable Professional (MVP)
The Microsoft Most Valuable Professional is a global award given to third-party technology professionals by Microsoft Corporation. For 29 years, technology community leaders around the world have received this award for sharing their expertise and experience in technology communities both online and offline.
MVPs are a carefully selected team of experts who represent the most skilled and intelligent people, passionate and helpful experts who are deeply invested in the community. MVP is committed to helping others and maximizing the use of Microsoft technologies by Microsoft technical community users by speaking, forum Q&A, creating websites, writing blogs, sharing videos, open source projects, organizing conferences, etc.
For more details, please visit the official website:
https://mvp.microsoft.com/en-us
Long press to identify the QR code and follow Microsoft China MSDN
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。