注:本文适用.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. Asp.Net 操作XML文件的增删改查 利用GridView

    不废话,直接上如何利用Asp.NET操作XML文件,并对其属性进行修改,刚开始的时候,是打算使用JS来控制生成XML文件的,但是最后却是无法创建文件,读取文件则没有使用了 index.aspx 文件 ...

  2. 在vim中使用查找命令查找指定字符串

    要自当前光标位置向上搜索,请使用以下命令:         /pattern   Enter           其中,pattern   表示要搜索的特定字符序列.         要自当前光标位置 ...

  3. spring applicationContext.xml和hibernate.cfg.xml设置

    applicationContext.xml配置 <?xml version="1.0" encoding="UTF-8"?> <beans ...

  4. JAVA FreeMarker工具类

    FreeMarkerUtil.java package pers.kangxu.datautils.utils; import java.io.File; import java.io.StringW ...

  5. 【SAP业务模式】之ICS(三):前台操作

    本片博文开始讲解SAP前台是如何实现ICS业务模式的. 一.VA01开立销售订单 我这里为了方便,创建了一个订单类型ZMIV作为公司间销售的订单类型,其实公司间销售订单跟标准的销售订单是一致的.同时, ...

  6. linux下 lvm 磁盘扩容

    打算给系统装一个oracle,发现磁盘空间不足.在安装系统的时候我选择的是自动分区,系统就会自动以LVM的方式分区.为了保证系统后期的可用性,建议所有新系统安装都采用LVM,之后生产上的设备我也打算这 ...

  7. linux上使用google身份验证器(简版)

    系统:centos6.6 下载google身份验证包google-authenticator-master(其实只是一个.zip文件,在windwos下解压,然后传进linux) #cd /data/ ...

  8. Ubuntu1604下安装Liggghts及CFDEM Coupling

    部分内容参考http://www.linuxdiyf.com/linux/16315.html LIGGGHTS是一款开源的DEM软件,来自于著名的分子动力学软件LAMMPS,目前借助于CFDEM C ...

  9. welcome to my cnblog

    博客园总算开通了,以后就分享自己的东西,和大家交流.

  10. [转]nopCommerce Widgets and How to Create One

    本文转自:https://dzone.com/articles/what-are-nopcommerce-widgets-and-how-to-create-one A widget is a sta ...