C#向無窗口的進(jìn)程發(fā)送消息
注:本文適用.net2.0+的winform程序
一個(gè)winform程序,我希望它不能多開,那么在用戶啟動(dòng)第二個(gè)實(shí)例的時(shí)候,作為第二個(gè)實(shí)例來說,大概可以有這么幾種做法:
1.彈個(gè)窗告知用戶【程序已運(yùn)行】之類,用戶點(diǎn)擊彈窗后,退出自身
2.什么都不做,默默退出自身
3.讓已運(yùn)行的第一個(gè)實(shí)例把它的窗體顯示出來,完了退出自身
顯然第3種做法更地道,實(shí)現(xiàn)該效果的核心問題其實(shí)是:如何顯示指定進(jìn)程的窗口?
首先想到的是調(diào)用ShowWindow、SetForegroundWindow等API,配合使用可以將被遮擋、最小化的窗口前排顯示出來,這也是很多涉及到這種案例的網(wǎng)文介紹的方法,此法的局限在于,目標(biāo)進(jìn)程的主窗口必須存在,準(zhǔn)確說是要有有效的主窗口句柄,表現(xiàn)在訪問Process.MainWindowHandle能得到一個(gè)非IntPtr.Zero的值,即有效的句柄;或者用spy類工具能看到該進(jìn)程下有至少一個(gè)窗口;或者按alt+tab能將它的窗口切換出來。
那如果進(jìn)程沒窗口怎么辦?先說一下什么情況下進(jìn)程會(huì)沒窗口,很簡單,讓Form.Visible=false(或者Form.Hide(),等價(jià)的)就行,此時(shí)窗體就消失了,既不可見,也沒有對(duì)應(yīng)的任務(wù)欄按鈕,alt+tab也切不出來。當(dāng)程序中的所有Form都Hide后,訪問該進(jìn)程的MainWindowHandle會(huì)得到IntPtr.Zero,這就是無窗口進(jìn)程。那什么樣的程序會(huì)這么干,太多了好吧,各種音樂播放器,殺軟什么的,都允許【關(guān)閉/最小化到系統(tǒng)托盤】,在你點(diǎn)叉或者最小化后,窗體就會(huì)隱藏,只留一個(gè)圖標(biāo)在托盤區(qū)。由于這種進(jìn)程的MainWindowHandle拿不到有效句柄,所以上面那些API是用不了的,只能另想辦法。
回到問題【如何顯示指定進(jìn)程的窗口】,如果你的程序不允許關(guān)閉到托盤區(qū),始終存在窗口的話(最小化也是存在),那你愉快的用ShowWindow、SetForegroundWindow等API就好,不用繼續(xù)。但如果你的程序要像播放器殺軟那樣允許用戶隱藏窗口的話,那還得繼續(xù)折騰,此時(shí)問題變成【如何讓無窗口的進(jìn)程顯示窗口】,我的思路是這樣:既然目標(biāo)進(jìn)程沒窗口,我沒辦法純粹用外部手段操作到它的窗體,但因?yàn)槌绦蚴俏易约簩懙?,可不可以來個(gè)里應(yīng)外合,辦了這事。比如向它發(fā)一條特定消息,它在收到該消息后,心領(lǐng)神會(huì),把自己的窗口顯示出來~到時(shí)候榮華富貴享之sorry入戲了。這個(gè)思路主要涉及兩個(gè)問題,怎么發(fā)和怎么收,至于收到后如何前排顯示窗口之類,小case。
怎么發(fā)
SendMessage/PostMessage自然是指不上的,因?yàn)檫@倆貨也是基于窗口的,其實(shí)我一度懷疑走消息這條路是否可行,這涉及到一個(gè)原理問題,就是如果消息一定是只能發(fā)送給窗口的話,那注定此路不通,只能考慮別的進(jìn)程間通信方案。好在了解到PostThreadMessage這個(gè)API,解決了我的問題。該API是向指定線程發(fā)送消息(MSDN文檔在此),這也說明在原理上,消息并非只可以發(fā)給窗口,還可以發(fā)給線程,至于還能不能發(fā)給別的什么東西就不知道了。先看一下發(fā)送語句:
void Main() { ... //向目標(biāo)進(jìn)程的主線程發(fā)送消息 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個(gè)參數(shù)是目標(biāo)線程的ID。注意兩點(diǎn):①此ID是系統(tǒng)全局的線程ID,并非Thread.ManagedThreadId這種“假”ID;②目標(biāo)線程必須存在消息循環(huán)。winform的主線程往往就是UI線程,天然存在消息循環(huán),所以無需考慮這個(gè)問題。第2個(gè)參數(shù)是要發(fā)送的消息ID。我們的目的是發(fā)一條收發(fā)雙方約定的消息,所以這個(gè)消息要夠特別,不能跟系統(tǒng)消息撞衫,所以范圍最好介于0x8001~0xBFFF之間,這是系統(tǒng)留給應(yīng)用程序自用的消息段(WM_APP)。后面?zhèn)z參數(shù)我沒用,你想讓消息更特別一點(diǎn),或想攜帶其它信息的話也可以用上。方法返回true/false分別代表發(fā)送成功/失敗。
另外,目標(biāo)進(jìn)程也許有多個(gè)線程,其中哪個(gè)才是能收消息的主線程我沒有科學(xué)的判斷方法,大膽臆測(cè)就是Process.Threads集合中的第1項(xiàng),這個(gè)猜測(cè)至今工作良好,不管它。若您有科學(xué)判斷法,請(qǐng)告知~謝謝。
怎么收
由于消息是走線程過來的,所以別想著在主窗口的WndProc中去收,再說消息過來的時(shí)候,主窗口存不存在都是個(gè)問題。要用應(yīng)用程序級(jí)別的消息篩選器來收,篩選器是個(gè)實(shí)現(xiàn)System.Windows.Forms.IMessageFilter接口的類(MSDN),該接口只需實(shí)現(xiàn)一個(gè)方法:bool PreFilterMessage(ref Message m),方法邏輯是,如果收到的消息m是你要處理并吃掉的,就返回true,其余消息則返回false放行。整個(gè)篩選器像這樣:
class MsgFilter : IMessageFilter { public bool PreFilterMessage(ref Message m) { if (m.Msg == 0x80F0) { DoSomething(); //顯示窗口或其它事 return true; } return false; } }
事實(shí)上我收到消息后并不是直接做顯示窗口相關(guān)的事,而是引發(fā)一個(gè)事件,主窗體注冊(cè)該事件,在事件處理方法中再寫顯示窗口相關(guān)的代碼。這是設(shè)計(jì)上的考量,與本文主旨無關(guān),不多說。
篩選器寫好后,還得把它添加到一個(gè)地方它才能工作,什么時(shí)候添加就什么時(shí)候才開始發(fā)揮作用,所以最好盡早添加,例如在main的開頭。像這樣:
void Main() { Application.AddMessageFilter(new MsgFilter()); ... }
至此,收發(fā)的問題解決。這實(shí)質(zhì)上是一個(gè)進(jìn)程間通信問題,所以其實(shí)任何進(jìn)程通信手段都可以應(yīng)用在本文的案例,走消息只是其中一種手段。
以上所述是小編給大家介紹的C#向無窗口的進(jìn)程發(fā)送消息的相關(guān)知識(shí),希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- c# 如何實(shí)現(xiàn)不同進(jìn)程之間的通信
- 如何利用c#實(shí)現(xiàn)通用守護(hù)進(jìn)程
- 利用C#守護(hù)Python進(jìn)程的方法
- C# 獲取進(jìn)程退出代碼的實(shí)現(xiàn)示例
- C#結(jié)束Excel進(jìn)程的步驟教學(xué)
- C#獲取所有進(jìn)程的方法
- 利用C#編寫Linux守護(hù)進(jìn)程實(shí)例代碼
- c#進(jìn)程之間對(duì)象傳遞方法
- C#中進(jìn)程的掛起與恢復(fù)
- 詳解C#獲取特定進(jìn)程CPU和內(nèi)存使用率
- C#簡單讀取主機(jī)上所有進(jìn)程的方法
- 簡單掌握Windows中C#啟動(dòng)外部程序進(jìn)程的方法
- 如何在C# 中查找或結(jié)束程序域中的主、子進(jìn)程
相關(guān)文章
C#編寫Windows服務(wù)程序詳細(xì)步驟詳解(圖文)
本文介紹了如何用C#創(chuàng)建、安裝、啟動(dòng)、監(jiān)控、卸載簡單的Windows Service 的內(nèi)容步驟和注意事項(xiàng),需要的朋友可以參考下2017-09-09C#實(shí)現(xiàn)合并多張圖片為GIF動(dòng)態(tài)圖
這篇文章主要為大家詳細(xì)介紹了C#如何將把一張又一張的圖片去拼合成一張GIF動(dòng)態(tài)圖片,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-12-12C# 獲取當(dāng)前總毫秒數(shù)的實(shí)例講解
這篇文章主要介紹了C# 獲取當(dāng)前總毫秒數(shù)的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01C#實(shí)現(xiàn)系統(tǒng)桌面右下角彈框
這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)系統(tǒng)桌面右下角彈框,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2023-01-01WinForm實(shí)現(xiàn)跨進(jìn)程通信的方法
這篇文章主要介紹了WinForm實(shí)現(xiàn)跨進(jìn)程通信的方法,通過一個(gè)WinMessageHelper類實(shí)現(xiàn)這一功能,需要的朋友可以參考下2014-08-08C#事務(wù)處理(Execute Transaction)實(shí)例解析
這篇文章主要介紹了C#事務(wù)處理(Execute Transaction)實(shí)例解析,對(duì)于理解和學(xué)習(xí)事務(wù)處理有一定的幫助,需要的朋友可以參考下2014-08-08基于c#實(shí)現(xiàn)的九九乘法表(簡單實(shí)例)
本文主要分享了基于c#實(shí)現(xiàn)的九九乘法表,代碼簡潔,需要的朋友可以參考下,希望對(duì)大家有所幫助2016-12-12C#獲得MAC地址(網(wǎng)卡序列號(hào))的實(shí)現(xiàn)代碼
這篇文章主要介紹了C#獲得MAC地址的實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-02-02C#使用Win32?Api實(shí)現(xiàn)進(jìn)程注入到wechat的過程
這篇文章主要介紹了C#使用Win32?Api實(shí)現(xiàn)進(jìn)程注入到wechat,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09