目錄
介紹
有時,您可能會在某個應用程序中沒有必要兩次啓動主應用程序,但是您可能希望通過傳遞其他命令行參數來再次運行該應用程序。這樣,您可以運行該應用程序,它將打開您的程序,然後您可以再次運行該應用程序,並將其他命令行信息傳遞給已運行的應用程序。例如,當您想讓現有應用程序從命令行打開新文件時,這很有用。因此它像這樣處理:
C:\Foo> MyApp myfile1.txt
C:\Foo> MyApp myfile2.txt myfile3.txt
在這種情況下,MyApp僅在第一次啓動實際的應用程序。第二次,它檢測到它已經在運行,並簡單地將傳入的命令行發送到已經運行的應用程序進行處理。
概念化這個混亂
爲了提供此功能,我們對該應用程序有兩個主要方面。
首先,該應用程序需要檢測它是否已經在運行,並且根據它是否已經在運行,它將執行不同的操作。
其次,我們需要一種使兩個進程進行通信的方法。在這種情況下,主應用程序將等待子序列啓動產生的命令行數據。
第一個方面很容易。我們使用命名的互斥鎖來防止主應用程序代碼兩次啓動。該用戶的所有正在運行的進程都可以看到已命名的互斥鎖。
第二個方面要困難一點,但幸運是也不是很難。我們將使用.NET的命名管道功能來促進主應用程序(服務器)與後續啓動(客戶端)之間的通信。該用戶的所有正在運行的進程也可以看到命名管道。
編碼此混亂
我們使用的代碼在這裏給出,幾乎是完整的。我只是在這裏取消了using指令,以節省一些空間。
class Program
{
static string _AppName= Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().GetName().Name);
static void Main(string[] args)
{
// we need to get our app name so that we can create unique names for our mutex and our pipe
var notAlreadyRunning = true;
// wrap the meat of the application code with a named mutex so it runs only once
using (var mutex = new Mutex(true, _AppName + "Singleton", out notAlreadyRunning))
{
if (notAlreadyRunning)
{
// do additional work here, startup stuff
Console.WriteLine("Running. Press any key to exit...");
// ...
// now process our initial main command line
_ProcessCommandLine(args);
// start the IPC sink.
var srv = new NamedPipeServerStream(_AppName+"IPC", PipeDirection.InOut,1,PipeTransmissionMode.Message,PipeOptions.Asynchronous);
// it's easier to use the AsyncCallback than it is to use Tasks here:
// this can't block, so some form of async is a must
srv.BeginWaitForConnection(new AsyncCallback(_ConnectionHandler), srv);
// block here until exit
Console.ReadKey();
// if this was a windows forms app you would put your "Applicantion.Run(new MyForm());" here
// finally, run any teardown code and exit
srv.Close();
}
else // another instance is running
{
// connect to the main app
var cli = new NamedPipeClientStream(".",_AppName + "IPC",PipeDirection.InOut);
cli.Connect();
var bf = new BinaryFormatter();
// serialize and send the command line
bf.Serialize(cli, args);
cli.Close();
// and exit
}
}
}
static void _ConnectionHandler(IAsyncResult result)
{
var srv = result.AsyncState as NamedPipeServerStream;
srv.EndWaitForConnection(result);
// we're connected, now deserialize the incoming command line
var bf = new BinaryFormatter();
var inargs = bf.Deserialize(srv) as string[];
// process incoming command line
_ProcessCommandLine(inargs);
srv.Close();
srv = new NamedPipeServerStream(_AppName + "IPC", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
srv.BeginWaitForConnection(new AsyncCallback(_ConnectionHandler), srv);
}
static void _ProcessCommandLine(string[] args)
{
// we received some command line
// arguments.
// do actual work here
Console.Write("Command line recieved: ");
for(var i = 0;i<args.Length;++i)
{
Console.Write(args[i]);
Console.Write(" ");
}
Console.WriteLine();
}
}
這裏有很多事情要做。
讓我們從頂部開始,獲取將用於標識應用程序的互斥鎖和命名管道的應用程序名稱。
接下來,我們創建一個命名的互斥體,並將我們的主要應用程序代碼包裝在此塊中。這樣可以防止應用啓動其主要部分兩次。
如果尚未運行,我們要做的第一件事就是運行任何應用程序初始化代碼,然後處理所有命令行參數。然後,我們啓動基於命名管道的IPC服務器,每當我們從該應用程序的後續實例獲得連接時,該服務器便註冊一個回調。我們已經註冊的回調函數讀取傳入的命令行數據並處理傳入的傳入參數。最後,我們阻塞直到退出,在本例中使用Console.ReadKey(),但實際上,您將要創建自己的退出條件。對於Windows窗體應用程序,您可以通過調用Application.Run()來阻塞主應用程序窗體,而不是Console.ReadKey()。
現在,如果它已經在運行,我們所要做的就是打開之前創建的主應用程序實例的命名管道,並序列化命令行數據,將其發送到管道中,然後關閉管道連接並退出。
在_ConnectionHandler() 中,我們處理每個傳入的連接時,從管道讀取任何命令行參數,然後我們實際上重新啓動命名的管道服務器,因爲由於某種原因,它喜歡在結束連接後自行關閉。我尚未確定這是設計使然還是由於某種問題,但此解決方法似乎很好。
在_ProcessCommandLine() args中,我們進行參數處理。在演示中,我們所做的所有工作都是將其輸出到控制檯上,但實際上,您將要做更多的事情,例如基於參數打開文件。