C#Form程序像C++那样使⽤SendMessage⽅法给窗⼝发送消
息
想实现VC编程的中,像C++ 那样使⽤ SendMessage⽅法给窗⼝发送消息。
于是查⼀下⽂献:
参考:
记录实现步骤如下:
建⽴Note类:
1using System;
2using System.Runtime.InteropServices;
3
4namespace WindowsFormsApp2
5{
6 public class Note
7 {
8 //声明 API 函数
9 [DllImport("User32.dll", EntryPoint = "SendMessage")]
10 private static extern IntPtr SendMessage(int hWnd, int msg, IntPtr wParam, IntPtr lParam);
11
12 [DllImport("User32.dll", EntryPoint = "FindWindow")]
13 private static extern int FindWindow(string lpClassName, string lpWindowName);
14
15 //定义消息常数
16 public const int CUSTOM_MESSAGE = 0X400 + 2;//⾃定义消息
17
18 //向窗体发送消息的函数
19 public void SendMsgToMainForm(int MSG)
20 {
21 int WINDOW_HANDLER = FindWindow(null, "Form1");
22 if (WINDOW_HANDLER == 0)
23 {
24 throw new Exception("Could not find Main window!");
25 }
26 long result = SendMessage(WINDOW_HANDLER, CUSTOM_MESSAGE, new IntPtr(14), IntPtr.Zero).ToInt64();
27
28 }
29 }
30}
建⽴form 程序,内部增加WndProc⽅法,如果将上⾯代码也放在⼀个⼯程⾥⾯,再增加button1_Click⽅法。实现如下
1using System;
2using System.Windows.Forms;
3
4namespace WindowsFormsApp2
5{
6 public partial class Form1 : Form
7 {
8 public Form1()
9 {
10 InitializeComponent();
11 }
12 protected override void WndProc(ref System.Windows.Forms.Message msg)
13 {
14 switch (msg.Msg)
15 {
16 case Note.CUSTOM_MESSAGE: //处理消息
sendmessage 关闭窗口17 {
18 switch (msg.WParam.ToString())
19 {
20 case "11"://对象添加
21 break;
22 case "12"://对象更新
23 break;
24 case "13"://对象删除
25 break;
26 case "14"://与会者信息更新
27 break;
28 }
29 }
30 break;
31 default:
32 base.WndProc(ref msg);//调⽤基类函数处理⾮⾃定义消息。
33 break;
34 }
35 }
36
37 private void button1_Click(object sender, EventArgs e)
38 {
39 Note a = new Note();
40 a.SendMsgToMainForm(Note.CUSTOM_MESSAGE);
41 }
42 }
43}
然后就可以操作在form窗⼝⾥⾯获得消息,发送信息了。
以上测试可以完成发送消息给窗体的⽅法,感觉不错,习惯了我VC发送消息的编程思维。
解说如下:
FindWindow这个函数检索处理顶级窗⼝的类名和窗⼝名称匹配指定的字符串。这个函数不搜索⼦窗⼝。在Win32 API中还有⼀个FindWindowEx,它⾮常适合寻⼦窗⼝。
FindWindow()函数的⽤法。要在C#⾥使⽤该API,写出FindWindow()函数的声明:
[DllImport("coredll.dll", EntryPoint = "FindWindow")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
这个函数有两个参数,第⼀个是要的窗⼝的类,第⼆个是要的窗⼝的标题,是窗体的Text名字,不是name。在搜索的时候不⼀定两者都知道,但⾄少要知道其中的⼀个。有的窗⼝的标题是⽐较容易得到的,如"计算器",所以搜索时应使⽤标题进⾏搜索。但有的软件的标题不是固定的,如"记事本",如果打开的⽂件不同,窗⼝标题也不同,这时使⽤窗⼝类搜索就⽐较⽅便。如果到了满⾜条件的窗⼝,这个函数返回该窗⼝的句柄,否则返回0。
查窗体的功能可以做如下完善
1IntPtr ParenthWnd = new IntPtr(0);
2ParenthWnd = FindWindow(null,"Form1");
3//判断这个窗体是否有效
4 if (ParenthWnd != IntPtr.Zero)
5{
6 MessageBox.Show("到窗⼝");
7}
8else
9 MessageBox.Show("没有到窗⼝");
原作者进⼀步说到:
从上⾯的讨论中可以看出,如果要搜索的外部程序的窗⼝标题⽐较容易得到,问题是⽐较简单的。可如果窗⼝的标题不固定或者根本就没有标题,怎么得到窗⼝的类呢?如果你安装了Visual C++,你可以使⽤其中的Spy,在Spy++中有⼀个FindWindow⼯具,它允许你使⽤⿏标选择窗⼝,然后Spy++会显⽰这个窗⼝的类。
对我来说我当前我这项⽬只是在⼀个⼯程⾥使⽤。
针对窗⼝标题不固定或者没有标题不是问题。
我以为以上就完成了吗?但是,当我想发送string的时候,却仍然是个问题。
于是借鉴了⼀下的⽂章:
来源:
正⽂:
在C#中使⽤SendMessage,原本以为很简单的事,却处处碰壁。
在定义消息时忘记了⽤户可定义消息的边界值,在⽹上⼀阵疯后来发现是const int WM_USER = 0x400。接着是SendMessage 的lParam类型不能决定(默认是IntPtr),我想发送字符串信息,就将它定为了string型,然后通过重写DefWndProc接收⾃定义消息。问题出现:(string) message.lParam提⽰不能转换,然后使⽤另⼀种⽅法:(string)message.GetLParam(typeof(string)),依然有错,提⽰应该重载string的构造函数建⽴string的⽆参构造函数(够狠)。
后来在⽹上见到⼀篇⽂章“C#⽤WM_COPYDATA消息来实现两个进程之间传递数据”,经提⽰想到⼀⽅法:⽤结构体封装string字段,然后传结构体就可以了(结构体有⾃⼰的⽆参构造函数--其实原本想⾃⼰写个⽆参构造函数,却发现在vs2005下⽆法编译通过,提⽰不能够创建结构体的⽆参构造函数)。这样就可以在DefWndProc中通
过 (tagStructStr)message.GetLParam(typeof(tagStructStr)) 将LParam转换为结构体类型,然后取出它的字符串字段就实现了string的传送了。
事还没完,⼀次偶尔碰到有⼈说可以使⽤Marshal转换string。⾃⼰查了下MSDN于是⼀个传送string的另⼀⽅法产⽣:
SendMessage仍然是传string,在DefWndProc中可以⽤ string str = Marshal.PtrToStringAnsi(m.LParam) 将IntPtr类型的LParam转换为string类型,其实还可以使⽤ IntPtr p = Marshal.StringToHGlobalAnsi(s) 将string类型转换为IntPtr类型。
进⼀步深究,发现这其中还有内情:SendMessage使系统API,IntPtr属于⾮托管类型,⽽我们在C#中使⽤的string等类型是托管类型,使⽤Marshal转换其实真正意义是实现了托管与⾮托管类型的转换。看来在C#与⾮托管的C++/C进⾏交互传递数据时,要少不了Marshal的⾝影了。
1//发送string的⽅法如下:
2 string str = "12345667890abcdefghiasdflasdfas.\nHello managed world (from the unmanaged world)!";
3 IntPtr p = Marshal.StringToHGlobalAnsi(str);
4 result = SendMessage(WINDOW_HANDLER, LOGINFORM_MSG, p, new IntPtr(302)).ToInt32();
5
6
7//解析到string消息⽅法如下:
8
9 public const int LOGINFORM_MSG = 0x400;
10 protected override void WndProc(ref System.Windows.Forms.Message msg)
11 {
12 switch (msg.Msg)
13 {
14 case LOGINFORM_MSG: //⾃定义消息
15 switch ((msg.LParam.ToInt32()))
16 {
17 case 301:
18 {
19 MessageBox.Show("收到发来消息:\n" + msg.WParam.ToInt32());
20 }
21 break;
22 case 302:
23 {
24 string stringB = Marshal.PtrToStringAnsi(msg.WParam);
25 MessageBox.Show("收到发来消息:" + msg.LParam.ToInt32() + "消息内容是:\n" + stringB);
26 Marshal.FreeHGlobal(msg.WParam);
27
28 }
29 break;
30
31 }
32 break;
33 }
34 base.WndProc(ref msg); //调⽤基类函数处理⾮⾃定义消息。
35 }
当然还有问题要解决,那就是如何给发送对象呢?
再次总结:
C# Winform窗⼝间消息通知,使⽤Windows API SendMessage⽅法跨进程实现消息发送,重写WndProc⽅法接收消息并消息处理主要使⽤到如下三个⽅法函数:
Control.WndProc(Message) ⽅法 处理 Windows 消息。继承控件应调⽤基类的 WndProc(Message) ⽅法来处理其不处理的所有消息。
protected virtual void WndProc (ref System.Windows.Forms.Message m);
WndProc:主要⽤在拦截并处理系统消息和⾃定义消息
可以重写WndProc函数,来捕捉所有发⽣的窗⼝消息。这样,我们就可以"篡改"传⼊的消息,⽽⼈为的让窗⼝改变⾏为
参数
m
Message
要处理的 Windows Message。
微软给出的标准使⽤例⼦如下:
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, N
ame="FullTrust")] protected override void WndProc(ref Message m)
{
// Listen for operating system messages.
switch (m.Msg)
{
// The WM_ACTIVATEAPP message occurs when the application
// becomes the active application or becomes inactive.
case WM_ACTIVATEAPP:
// The WParam value identifies what is occurring.
appActive = (((int)m.WParam != 0));
// Invalidate to get new text painted.
this.Invalidate();
SendMessage:该函数将指定的消息发送到⼀个或多个窗⼝。此函数为指定的窗⼝调⽤窗⼝程序,直到窗⼝程序处理完消息再返回。该函数是应⽤程序和应⽤程序之间进⾏消息传递的主要⼿段之⼀
函数原型:IntPtr SendMessage(int hWnd, int msg, IntPtr wParam, IntPtr lParam);
参数
hWnd:其窗⼝程序将接收消息的窗⼝的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗⼝,包括⽆效或不可见的⾮⾃⾝拥有的窗⼝、被覆盖的窗⼝和弹出式窗⼝,但消息不被发送到⼦窗⼝
Msg:指定被发送的消息
wParam:指定附加的消息指定信息
IParam:指定附加的消息指定信息
返回值:返回值指定消息处理的结果,依赖于所发送的消息
备注:需要⽤HWND_BROADCAST通信的应⽤程序应当使⽤函数RegisterWindowMessage来为应⽤程序间的通信取得⼀个唯⼀的消息。
FindWindow:函数获得⼀个顶层窗体的句柄,该窗体的类名和窗体名与给定的字符串相匹配。这个函数不查⼦窗体。在查时不区分⼤写和⼩写
函数原型:int FindWindow(string lpClassName, string lpWindowName);
参数
IpClassName :指向⼀个指定了类名的空结束字符串,或⼀个标识类名字符串的成员的指针。假设该參数为⼀个成员,则它必须为前次调⽤theGlobafAddAtom函数产⽣的全局成员。该成员为16位,必须位于IpClassName的低 16位,⾼位必须为 0
IpWindowName:指向⼀个指定了窗体名(窗体标题)的空结束字符串。假设该參数为空,则为全部窗体全匹配
返回值:假设函数成功,返回值为具有指定类名和窗体名的窗体句柄;假设函数失败,返回值为NULL
发现还可以进⼀步学习如下内容:
public IntPtr ClassToIntPtr(ProcessArgs arg,out int length)
{
byte [] temp = SerializationUtility.ToBytes(arg);
length = temp.Length;
IntPtr ptr = Marshal.AllocHGlobal(length);
Marshal.Copy(temp,0,ptr,length);
return ptr;
}
public ProcessArgs IntPtrToClass(IntPtr ptr,int length)
{
byte [] temp = new byte[length];
Marshal.Copy(ptr,temp,0,length);
return SerializationUtility.ToObject(temp) as ProcessArgs;
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论