眾所周知,委托和事件機制是C#應用程序的一個很重要的方面。
Microsoft 的 BCL 類庫對Windows的控件進行了幾乎全面的封裝,使應用程序 開發人員甚至不用了解消息循環就能寫出相樣的程序。
然而,甚至Windows UI編程到了 WPF 時代,消息機制仍然占據著舉足輕重的 作用。可以這麼說,沒有消息循環就沒有Windows。(當然WPF很大程度上是對D3D 的封裝,它本身並不是基於Win32的消息循環, 但是Micorsoft仍然在WPF中提供了 對Win32消息機制的支持)。
BCL2.0 的System.Windows.Forms 命名空間的控件則完全是對 Win32 控件的 進一步封裝,因此C#2.0本身也是基於Win32消息循環機制的,在編寫C#桌面應用 時,在一些地方,甚至還必須借助於消息,否則可能無法實現一些功能,或者很 難實現。比如Aplication類提供了對添加 AddMessageFilter 的支持,Control類 放出了幾個類似於 ProcessCmdKey 的虛方法,另外,Control類還把 WndProc 實 現為虛擬的,以便控件開發人員能夠方便地重寫。
廢話不多說了,考慮這樣一種情況:一個應用程序關聯一種文件類型,雙擊這 樣類型的文件,則使用該應用程序打開這個文檔。這很容易,實現,程序把文件 (可能是多個)當作命令行參數讀取,現在假設這是一個多文檔應用程序,如果 應用程序正在運行,那麼一般希望用這個已經存在的程序來打開這些文件,而不 是重新啟用一個新的程序實例。這在Windows應用中很常見,比如用Visual Studio 打開程序源文件,用IE7打開另外一個Html文檔(當然這個可以配置是啟 用新實例還是用已有實例打開它)等。假設這個軟件是用Win32或者VC開發的,那 很容易實現,查找已經存在的實例(進程),如果找到,則進一步查找它的主窗 口,並向其發送消息,傳送文件列表,讓它去處理,當前例程則結束。主窗口則 要處理這個自定義的消息,但是如果使用的是C#,你可能犯傻了,怎麼辦呢?
實際上,上述方法在C#應用程序中仍然適用,你仍然可以在C#中自定義消息。 下面演示這一設想。假設最後生成的可執行程序叫 “MyApplication.exe”。
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Runtime.InteropServices;
5using System.IO;
6using System.Reflection;
7using System.Windows.Forms;
8
9namespace MyApplication {
10 internal static class Program {
11 [STAThread]
12 private static void Main(string[] args) {
13 Application.EnableVisualStyles();
14 Application.SetCompatibleTextRenderingDefault (false);
15
16 List<string> fileList = new List<string>();
17 foreach (string filename in args) {
18 if (File.Exists(filename)) {
19 fileList.Add(filename);
20 }
21 }
22
23 if (! SingleInstanceHelper.OpenFilesInPreviousInstance(fileList.ToArray())) {
24 Application.Run(new MyWindow());
25 }
26 }
27 }
28
29 public class MyWindow : Form {
30 protected override void WndProc(ref Message msg) {
31 if (!SingleInstanceHelper.PreFilterMessage(ref msg, this)) {
32 base.WndProc(ref msg);
33 }
34 }
35 }
36
37 internal static class SingleInstanceHelper {
38 private static readonly int CUSTOM_MESSAGE = 0X400 + 2;
39 private static readonly int RESULT_FILES_HANDLED = 2;
40
41 [DllImport("user32.dll")]
42 private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
43
44 [DllImport("user32.dll")]
45 private static extern IntPtr SetForegroundWindow (IntPtr hWnd);
46
47 public static bool OpenFilesInPreviousInstance (string[] fileList) {
48 int currentProcessId = Process.GetCurrentProcess ().Id;
49 string currentFile = Assembly.GetEntryAssembly ().Location;
50 int number = new Random().Next();
51 string fileName = Path.Combine(Path.GetTempPath (), "sd" + number + ".tmp");
52 try {
53 File.WriteAllLines(fileName, fileList);
54 List<IntPtr> alternatives = new List<IntPtr>();
55 foreach (Process p in Process.GetProcessesByName("MyApplication")) {
56 if (p.Id == currentProcessId) continue;
57
58 if (string.Equals(currentFile, p.MainModule.FileName, StringComparison.CurrentCultureIgnoreCase)) {
59 IntPtr hWnd = p.MainWindowHandle;
60 if (hWnd != IntPtr.Zero) {
61 long result = SendMessage(hWnd, CUSTOM_MESSAGE, new IntPtr(number), IntPtr.Zero).ToInt64();
62 if (result == RESULT_FILES_HANDLED) {
63 return true;
64 }
65 }
66 }
67 }
68 return false;
69 } finally {
70 File.Delete(fileName);
71 }
72 }
73
74 internal static bool PreFilterMessage(ref Message m, Form mainForm) {
75 if (m.Msg != CUSTOM_MESSAGE)
76 return false;
77
78 long fileNumber = m.WParam.ToInt64();
79 m.Result = new IntPtr(RESULT_FILES_HANDLED);
80 try {
81 MethodInvoker invoker = delegate {
82 SetForegroundWindow(mainForm.Handle) ;
83 };
84 mainForm.Invoke(invoker);
85 string tempFileName = Path.Combine (Path.GetTempPath(), "sd" + fileNumber + ".tmp");
86 foreach (string file in File.ReadAllLines (tempFileName)) {
87 invoker = delegate {
88 // Open the file
89 };
90 mainForm.Invoke(invoker);
91 }
92 } catch (Exception) {
93 return false;
94 }
95
96 return true;
97 }
98 }
99}
該示例程序通過檢查進程只允許應用程序運行一個實例,然後自定義消息,通 過 SendMessage 向程序的另一個實例發送消息,由於牽涉到進程間發送數據,如 果直接發送則必須使用類型的 Marshal, 而Marshal過的自定義類型在C#中是不允 許傳遞的(除非使用了Hook)。最簡單的方式是直接發送一個整形隨機數字,使 用這個數字在臨時文件夾在建立一個臨時文件,寫入數據,程序的另一個實例接 收到消息後,讀取這個文件獲得數據,進行相應的處理,然後臨時文件被刪除, 從而實現了進程間的數據交換。