C# 实现自定义消息处理
众所周知,委托和事件机制是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”。
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”。
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Diagnostics;
4
using
System.Runtime.InteropServices;
5
using
System.IO;
6
using
System.Reflection;
7
using
System.Windows.Forms;
8
9
namespace
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
}

2

3

4

5

6

7

8

9



10



11

12



13

14

15

16

17



18



19

20

21

22

23



24

25

26

27

28

29



30



31



32

33

34

35

36

37



38

39

40

41

42

43

44

45

46

47



48

49

50

51

52



53

54

55



56

57

58



59

60



61

62



63

64

65

66

67

68

69



70

71

72

73

74



75

76

77

78

79

80



81



82

83

84

85

86



87



88

89

90

91

92



93

94

95

96

97

98

99

该示例程序通过检查进程只允许应用程序运行一个实例,然后自定义消息,通过 SendMessage 向程序的另一个实例发送消息,由
于牵涉到进程间发送数据,如果直接发送则必须使用类型的 Marshal, 而Marshal过的自定义类型在C#中是不允许传递的(除非使用了
Hook)。最简单的方式是直接发送一个整形随机数字,使用这个数字在临时文件夹在建立一个临时文件,写入数据,程序的另一个实例
接收到消息后,读取这个文件获得数据,进行相应的处理,然后临时文件被删除,从而实现了进程间的数据交换。