注:本文适用.net2.0+的winform程序

一个winform程序,我希望它不能多开(但是如何防多开不是本文要讲的),那么在用户启动第二个实例的时候,作为第二个实例来说,大概可以有这么几种做法:

  1. 弹个窗告知用户【程序已运行】之类,用户点击弹窗后,退出自身
  2. 什么都不做,默默退出自身
  3. 让已运行的第一个实例把它的窗体显示出来,完了退出自身

显然第3种做法更地道,实现该效果的核心问题其实是:如何显示指定进程的窗口?

首先想到的是调用ShowWindow、SetForegroundWindow等API,配合使用可以将被遮挡、最小化的窗口前排显示出来,这也是很多涉及到这种案例的网文介绍的方法,此法的局限在于,目标进程的主窗口必须存在,准确说是要有有效的主窗口句柄,表现在访问Process.MainWindowHandle能得到一个非IntPtr.Zero的值,即有效的句柄;或者用spy类工具能看到该进程下有至少一个窗口;或者按alt+tab能将它的窗口切换出来。

那如果进程没窗口怎么办?先说一下什么情况下进程会没窗口,很简单,让Form.Visible=false(或者Form.Hide(),等价的)就行,此时窗体就消失了,既不可见,也没有对应的任务栏按钮,alt+tab也切不出来。当程序中的所有Form都Hide后,访问该进程的MainWindowHandle会得到IntPtr.Zero,这就是无窗口进程。那什么样的程序会这么干,太多了好吧,各种音乐播放器,杀软什么的,都允许【关闭/最小化到系统托盘】,在你点叉或者最小化后,窗体就会隐藏,只留一个图标在托盘区。由于这种进程的MainWindowHandle拿不到有效句柄,所以上面那些API是用不了的,只能另想办法。

回到问题【如何显示指定进程的窗口】,如果你的程序不允许关闭到托盘区,始终存在窗口的话(最小化也是存在),那你愉快的用ShowWindow、SetForegroundWindow等API就好,不用继续。但如果你的程序要像播放器杀软那样允许用户隐藏窗口的话,那还得继续折腾,此时问题变成【如何让无窗口的进程显示窗口】,我的思路是这样:既然目标进程没窗口,我没办法纯粹用外部手段操作到它的窗体,但因为程序是我自己写的,可不可以来个里应外合,办了这事。比如向它发一条特定消息,它在收到该消息后,心领神会,把自己的窗口显示出来~到时候荣华富贵享之sorry入戏了。这个思路主要涉及两个问题,怎么发怎么收,至于收到后如何前排显示窗口之类,小case。

怎么发

SendMessage/PostMessage自然是指不上的,因为这俩货也是基于窗口的,其实我一度怀疑走消息这条路是否可行,这涉及到一个原理问题,就是如果消息一定是只能发送给窗口的话,那注定此路不通,只能考虑别的进程间通信方案。好在了解到PostThreadMessage这个API,解决了我的问题。该API是向指定线程发送消息(MSDN文档在此),这也说明在原理上,消息并非只可以发给窗口,还可以发给线程,至于还能不能发给别的什么东西就不知道了。先看一下发送语句:

void Main()
{
...
//向目标进程的主线程发送消息
PostThreadMessage(Process.GetProcessById(pid).Threads[].Id, 0x80F0, IntPtr.Zero, IntPtr.Zero);
...
} [return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostThreadMessage(int threadId, uint msg, IntPtr wParam, IntPtr lParam);

API的第1个参数是目标线程的ID。注意两点:①此ID是系统全局的线程ID,并非Thread.ManagedThreadId这种“假”ID;②目标线程必须存在消息循环。winform的主线程往往就是UI线程,天然存在消息循环,所以无需考虑这个问题。第2个参数是要发送的消息ID。我们的目的是发一条收发双方约定的消息,所以这个消息要够特别,不能跟系统消息撞衫,所以范围最好介于0x8001~0xBFFF之间,这是系统留给应用程序自用的消息段(WM_APP)。后面俩参数我没用,你想让消息更特别一点,或想携带其它信息的话也可以用上。方法返回true/false分别代表发送成功/失败。

另外,目标进程也许有多个线程,其中哪个才是能收消息的主线程我没有科学的判断方法,大胆臆测就是Process.Threads集合中的第1项,这个猜测至今工作良好,不管它。若您有科学判断法,请告知~谢谢。

怎么收

由于消息是走线程过来的,所以别想着在主窗口的WndProc中去收,再说消息过来的时候,主窗口存不存在都是个问题。要用应用程序级别的消息筛选器来收,筛选器是个实现System.Windows.Forms.IMessageFilter接口的类(MSDN),该接口只需实现一个方法:bool PreFilterMessage(ref Message m),方法的逻辑是,如果收到的消息m是你要处理并吃掉的,就返回true,其余消息则返回false放行。整个筛选器像这样:

class MsgFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x80F0)
{
DoSomething(); //显示窗口或其它事
return true;
}
return false;
}
}

事实上我收到消息后并不是直接做显示窗口相关的事,而是引发一个事件,主窗体注册该事件,在事件处理方法中再写显示窗口相关的代码。这是设计上的考量,与本文主旨无关,不多说。

筛选器写好后,还得把它添加到一个地方它才能工作,什么时候添加就什么时候才开始发挥作用,所以最好尽早添加,例如在main的开头。像这样:

void Main()
{
Application.AddMessageFilter(new MsgFilter());
...
}

至此,收发的问题解决。这实质上是一个进程间通信问题,所以其实任何进程通信手段都可以应用在本文的案例,走消息只是其中一种手段。当然对于本文案例,若您有更好的办法,恳请告知,先行谢过。
-文毕-

【C#】给无窗口的进程发送消息的更多相关文章

  1. 【C#】无损转换Image为Icon 【C#】组件发布:MessageTip,轻快型消息提示窗 【C#】给无窗口的进程发送消息 【手记】WebBrowser响应页面中的blank开新窗口及window.close关闭本窗体 【手记】调用Process.EnterDebugMode引发异常:并非所有引用的特权或组都分配给呼叫方 【C#】DataRowState演变备忘

    [C#]无损转换Image为Icon 如题,市面上常见的方法是: var handle = bmp.GetHicon(); //得到图标句柄 return Icon.FromHandle(handle ...

  2. smbcontrol - 向smbd或nmbd进程发送消息

    总览smbcontrol [ -i ] smbcontrol [ 目标 ] [ 消息类型 ] [ 参数 ] 描述这个工具是是Samba组件的一部分. smbcontrol是个很小的程序,用它可以向系统 ...

  3. DelphiXE8FMX工程实现无边框托动(发送消息)

    1.引用单元 uses Winapi.Windows, FMX.Platform.Win, Winapi.Messages; 2.发送消息 //发送系统消息SendMessage(FmxHandleT ...

  4. 跨进程发送消息数据(发送WM_COPYDATA消息,够简单的)

    1 //1.发送窗体 2 procedure TForm2.Button1Click(Sender: TObject); 3 var 4 h: HWND; 5 Size: Integer; 6 Cop ...

  5. Android为TV端助力:EventBus跨进程发送消息

    单一app内的用法 如果你在单一app内进行多进程开发,那么只需要做以下三步: Step 1 在gradle文件中加入下面的依赖:   dependencies {   compile 'xiaofe ...

  6. C# 向指定的进程发送消息

    public static class ProcessExtensions { // Messages const int WM_KEYDOWN = 0x100; const int WM_KEYUP ...

  7. [转] C#中发送消息给指定的窗口,以及接收消息

    原文C#中发送消息给指定的窗口,以及接收消息 public class Note { //声明 API 函数 [DllImport("User32.dll", EntryPoint ...

  8. 在向server发送请求时发生传输级错误。 (provider: 共享内存提供程序, error: 0 - 管道的还有一端上无不论什么进程。

    作者:卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/36425825 SQL Server 2008 + VS2010 用C#编写的代 ...

  9. 深入delphi编程理解之消息(六)无窗口单元消息的创建、接受及dispatch模式编程

    一.程序界面 二.程序代码 (一).主界面代码 //========================================================================== ...

随机推荐

  1. mybatis_个人总结

    在使用mybatis框架开发数据访问层的过程中,我在这段时间遇到很多细节问题困住我,在这里我来分享一下我遇到的坑,希望能帮到大家. 一.mybatis动态代理方式开发的规范: 1.注意在mybatis ...

  2. 利用Select2优化@Html.ListBoxFor显示,学会用MultiSelectList

    最近需要用到多选框,Asp.Net MVC自带的@Html.ListBox或@Html.ListBoxFor的显示效果太差,于是找到了Select2进行优化,并正式了解了多选框的操作方法. 首先介绍多 ...

  3. JSP 标准标签库(JSTL)

    JSP 标准标签库(JSTL) JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能. JSTL支持通用的.结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签, ...

  4. 6_Win7下Chrome主页被流氓网站hao123.com劫持后的解决方法。

    今天安装了一个PDF阅读器,免费的,你懂的,结果自己安装的时候没有将默认的选项取消,就被hao123流氓网站劫持啦. 说实话某免费PDF阅读器还算好的,有一个可以供你选择的项.不想某些软件直接就默认选 ...

  5. Html 制作相册

    本文主要讲述采用Html5+jQuery+CSS 制作相册的小小记录. 主要功能点: Html5进行布局 调用jQuery(借用官网的一句话:The Write Less, Do More)极大的简化 ...

  6. Android 死锁和重入锁

    死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...

  7. 用Kotlin创建第一个Android项目(KAD 01)

    原文标题:Create your first Android project using Kotlin (KAD 01) 作者:Antonio Leiva 时间:Nov 21, 2016 原文链接:h ...

  8. Linux基础介绍【第八篇】

    Linux网络基础 网线 568A 568B 线序:橙白橙 绿白蓝 蓝白绿 棕白棕 交换机.路由器 交换机:DLINK.H3C.CISCO 交换机(Switch)是一种用于电信号转发的网络设备.它可以 ...

  9. C++虚函数和函数指针一起使用

    C++虚函数和函数指针一起使用,写起来有点麻烦. 下面贴出一份示例代码,可作参考.(需要支持C++11编译) #include <stdio.h> #include <list> ...

  10. C# 云端-让http自动跳转到https链接

    在项目的web.config下面加上下面的配置: <rewrite> <rules> <clear /> <rule name="Redirect ...