1. 程式人生 > >微信PC端多開的祕密

微信PC端多開的祕密

## 微信電腦端也能多開 昨天,偶然從好朋友小林(微信公眾號:小林Coding)處得知,他的電腦居然可以同時上兩個微訊號。 手機端多開微信我知道,像華為、小米等手機系統都對此做了支援,不過在執行Windows系統的電腦上怎麼啟動兩個微信呢,這倒是一下引起了我的好奇。 小林告訴我他是這樣做的,寫了一個批處理: ``` start D:\WeChat\WeChat.exe start D:\WeChat\WeChat.exe ``` 然後直接雙擊批處理檔案,就能啟動兩個微信程序。 我試了一下,果然如此! 隨後我又加了一行,竟然還能啟動3個: ![](https://imgkr.cn-bj.ufileos.com/0e423fbb-cd5c-4dea-a542-ab78bfdedec3.png) 接著我在網路上搜了一下,原來這一招早就被人用過了,看來是我火星了。不過到底為什麼用這種方式就能多開,我倒是很想知道這個迷底。 TIPS:如果對技術分析部分不感興趣,可以跳過直接來到後面的真相部分。 ## 微信的單例模式 正常情況下,直接手動雙擊微信圖示啟動,後面啟動的程序會進行全域性單例模式檢查,如果發現已經存在微信程序,就會直接把對應程序的微信視窗啟用,定位到桌面最前面,隨後自己退出。 但為什麼用上面的方式就能啟動倆呢?我們來一探究竟。 首先,分析一下上面描述的微信單個例項是如何實現的。 做過Windows平臺應用程式開發的朋友可能對此比較熟悉,一般是程序啟動後建立一個全域性唯一名字的互斥體,建立成功則正常啟動,建立失敗則判斷一下是否這個互斥體已經存在。如果已經存在則說明已經有對應程式之前啟動。 帶著這種猜想,用工具**procexp**檢視一下微信程序開啟的所有核心物件,並找到互斥體部分: ![](https://imgkr.cn-bj.ufileos.com/1394b3bd-0eb1-42ce-a2a8-d5375514315a.png) 果然,這其中有一個名字叫 **\_WeChat_App_Instance_Identity_Mutex_Name** 的互斥體,從這個名字可以猜出,這個跟微信的單例模式絕對有關係。 接著,啟動神器APIMonitor,它可以幫你監控指定程序的API呼叫情況,勾選上**CreateMutex**和**GetLastError**這兩個Windows API函式。在已經有微信在執行的情況下,用這個工具再啟動一個微信程序,看一下函式呼叫情況: ![](https://imgkr.cn-bj.ufileos.com/ff9498f0-516f-4764-aa2c-b1477d0efbfe.png) 可以看到,建立這個名字的互斥體後,隨後又呼叫了GetLastError函式,並返回了0x000000b7,檢視手冊,其含義: ![](https://imgkr.cn-bj.ufileos.com/7ed92976-2176-4f75-ad6c-b8725f0c15c9.png) 表示已經存在。 來看一下,這個**CreateMutex**呼叫的堆疊,看看是哪個地方的程式碼在建立這個全域性互斥體: ![](https://imgkr.cn-bj.ufileos.com/9a8f72ed-b3cd-4ece-b305-115a8accd0f4.png) 從堆疊看出,呼叫來自於微信目錄下的一個動態庫**WeChatWin.dll**。具體位置在偏移0x8e271b處的前一條指令。 接下來就要祭出神器中的神器,大名鼎鼎的反彙編軟體IDA,這傢伙支援x86、x64、ARM、MIPS等多種處理器架構和Windows、Linux、Android、MacOS、JVM等多種系統平臺的程式分析。 用IDA開啟這個WeChatWin.dll檔案,並定位到偏移0x8e271b處: ![](https://imgkr.cn-bj.ufileos.com/ff1e09cc-c76e-4884-a24b-990bb07e7823.png) 如上圖所示,建立互斥體的動作,發生在函式sub_108e26d0。 上層是sub_108e2660函式在呼叫它: ![](https://imgkr.cn-bj.ufileos.com/ea79c6ab-fd8c-4e62-8a90-d6f31c55e2bf.png) 上面這張圖反映了建立互斥體後的判斷邏輯: - 如果sub_108e26d0的返回值為0,表示沒有錯誤,當前函式也直接返回0。 - 如果sub_108e26d0的返回值不為0,表示出現了錯誤,則依次判斷**WeChatMainWndForPC**和**WeChatLoginWndForPC**兩個視窗是否存在,如果存在則使用**BringWindowToTop**函式將其置頂彈出。這兩個視窗分別代表的是微信的主介面視窗和登陸介面視窗,如果一個微信例項已經存在,則勢必處於這兩種狀態之一。 問題就出在上面這個判斷中,彙編程式碼看起來有點辣眼睛,咱們F5來還原一下C程式碼(還原效果只能湊合看,能看清楚邏輯就行): ![](https://imgkr.cn-bj.ufileos.com/fb43d32c-290c-4664-94cc-c404dd61fa27.png) 上面圖片的註解已經說明了,函式sub_108e2660的返回值將決定是否啟動微信例項程序,還是直接退出。 ## 真相只有一個 事情到這裡就真相大白了,來總結一下。 微信判斷是否啟動的2個條件: - 如果能成功建立互斥體物件,則啟動微信 - 如果不能建立互斥體: - 如果找到對應視窗,則置頂之,自己退出 - 如果沒有找到,則啟動微信 用虛擬碼來表示一下: ```c if (CreateMutex() == SUCCESS) { 啟動微信 } else { if (FindWindow() == SUCCESS) { 將已有視窗置頂 } else { 啟動微信 } } ``` 而直接使用指令碼啟動的多個程序,雖然作業系統核心層面保證了互斥體的唯一,但由於啟動速度相差不大,相應的視窗還沒有來得及創建出來,導致走入上面的第二個啟動邏輯,從而可以啟動多個例項。 ## 小發現 在分析的過程中,發現了一個有趣的事情: 在WeChatWin.dll中,上面的建立互斥體再上一級函式名字叫**StartWaChat**,也是作為匯出函式被該DLL匯出: ![](https://imgkr.cn-bj.ufileos.com/0897a94e-1ba7-44b3-8d97-c17f154b566a.png) 這裡不知道是故意還是不小心把微信的`WeChat`寫成了`WaChat`,如果是筆誤,這位程式設計師同學看到了趕緊偷偷去改一下吧。 ## 往期TOP5文章 [CPU明明8個核,網絡卡為啥拼命折騰一號核?](https://mp.weixin.qq.com/s/Xf3sbkJFWDQ7bzhKFEmkzA) [因為一個跨域請求,我差點丟了飯碗](https://mp.weixin.qq.com/s/K15IVpe57STOf0SxyLEg9A) [完了!CPU一味求快出事兒了!](https://mp.weixin.qq.com/s/NVIgNj4c7ixkJWsemOt--g) [雜湊表哪家強?幾大程式語言吵起來了!](https://mp.weixin.qq.com/s/erSqe-nItuQPxW4MT3LEVg) [一個HTTP資料包的奇幻之旅](https://mp.weixin.qq.com/s/axPUi-8kHtFQTwgRtyZtGQ) ![](https://imgkr.cn-bj.ufileos.com/4b6d4bd9-3924-4288-a3e9-a31ea0dd045a.png) ![](https://imgkr.cn-bj.ufileos.com/a35abdf1-0fe8-4db2-a574-581069c8726a.png)