注:本文适用.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. spring注解源码分析--how does autowired works?

    1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...

  2. C#中如何调整图像大小

    在本篇文章中,我将介绍如何在C#中来调整你想要的图像大小.要实现这一目标,我们可以采取以下几个步骤: 1.首先要获取你想要调整大小的图像: string path = Server.MapPath(& ...

  3. 破解SQLServer for Linux预览版的3.5GB内存限制 (UBUNTU篇)

    在上一篇中我提到了如何破解RHEL上SQLServer的内存大小限制,但是Ubuntu上还有一道检查 这篇我将会讲解如何在3.5GB以下内存的Ubuntu中安装和运行SQLServer for Lin ...

  4. 【原】无脑操作:express + MySQL 实现CRUD

    基于node.js的web开发框架express简单方便,很多项目中都在使用.这里结合MySQL数据库,实现最简单的CRUD操作. 开发环境: IDE:WebStorm DB:MySQL ------ ...

  5. Git学习笔记一:新建本地仓库及初始化

    1.百度搜索Git下载安装,直接按默认选项安装即可. 例如:Git-2.7.2-32-bit_setup.1457942412.exe 2.配置Git信息,建立版本仓库 (Alt+PrintScerr ...

  6. 【一起学OpenFOAM】03 OpenFOAM基本使用流程

    OpenFOAM初学者常常对于软件的使用流程感到很迷惑,与其他的具有GUI的CFD软件不同,OpenFOAM的所有操作均为基于文本操作,譬如说里面各种计算模型.计算参数.流程控制参数等,均为通过修改对 ...

  7. PC虚拟现实应用的性能分析与优化:从CPU角度切入

    如今,虚拟现实 (VR) 技术正日益受到欢迎,这主要得益于遵循摩尔定律的技术进步让这一全新体验在技术上成为可能.尽管虚拟现实能给用户带来身临其境般的超凡体验,但相比传统应用,其具有双目渲染.低延迟.高 ...

  8. 跨域问题,前端主动向后台发送cookie

    跨域是什么? 从一个域名的网页访问另一个域名的资源,就会出现跨域.只要协议.端口.域名有一个不同就会出现跨域 例如: 1.协议不同  http://www.baidu.com:80 和 https:/ ...

  9. ASP.NET MVC Model验证(五)

    ASP.NET MVC Model验证(五) 前言 上篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现, 然而在MVC框架中还给我们提供了其它 ...

  10. 【翻译】用AIML实现的Python人工智能聊天机器人

    前言 用python的AIML包很容易就能写一个人工智能聊天机器人. AIML是Artificial Intelligence Markup Language的简写, 但它只是一个简单的XML. 下面 ...