【C#】給無視窗的進程發送消息

来源:http://www.cnblogs.com/ahdung/archive/2016/05/17/5499570.html
-Advertisement-
Play Games

註:本文適用.net2.0+的winform程式 一個winform程式,我希望它不能多開,那麼在用戶啟動第二個實例的時候,作為第二個實例來說,大概可以有這麼幾種做法: 顯然第3種做法更地道,實現該效果的核心問題其實是:如何顯示指定進程的視窗? 首先想到的是調用ShowWindow、SetForeg ...


註:本文適用.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[0].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());
    ...
}

至此,收發的問題解決。這實質上是一個進程間通信問題,所以其實任何進程通信手段都可以應用在本文的案例,走消息只是其中一種手段。當然對於本文案例,若您有更好的辦法,懇請告知,先行謝過。
-文畢-


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ![圖片來自網路/圖文無關][0] 前言 今天接到領導佈置的一個任務,是之前同事負責的項目。離職了,現在客戶有些地方需要修改,由於我之前參與過,就落在我的頭上了。 然後我就把代碼弄了過來,打開發現其中需要用到水晶報表。(我覺得不好用,不想占用多餘的磁碟空間,就沒有安裝) 想想算了,大不了重新添加一下 ...
  • 一個列表頁面不止是查詢,它也包含了很多業務上功能的實現,這些業務功能的實現的邏輯我稱之為動作。如觸發單擊按鈕刪除數據,更改業務表數據,調用webService,調用WCF介面,彈出新窗體新增、修改、查看數據,根據列表數據顯示行的顏色等等這些我都稱為動作。動作的實現有兩類:第一類編碼實現,第二類利用通 ...
  • 1.項目右鍵-添加 新建項目 重新生成: 引用-新建引用 引入命名空間: ...
  • controller: ,view: 效果圖: ...
  • 資料庫設計 sql語句: ...
  • ABP是“ASP.NET Boilerplate Project (ASP.NET樣板項目)”的簡稱。 ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程式的新起點,它旨在成為一個通用的WEB應用程式框架和項目模板。 ASP.NET Boilerplate 基於DD ...
  • ef
    entityframework作為.net平臺自己的一個orm的框架,之前在項目中也有使用,主要採用了table和model first的方式,此兩種感覺使用上也是大同小異。在項目中經常反應的一個問題源於多個開發團隊共用一個資料庫(3個開發團隊使用同一個資料庫,開發的是一個平臺的各個業務場景),因而 ...
  • 廢話多說 很久之前,我寫過幾篇FastSocket的文章,基本屬於使用的方法,而缺乏對概念的總結講解,而本講就是彌補一下上幾講的不足,將核心的模塊再說說,再談談,再聊聊! 首先FastSocket由Client和Server端組成,我們在進行開發時,可以引用相應的DLL,再加上核心的FastSock ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...