筆者有一陣子接 soho 時,鑑於手邊硬體不夠強大,所以直接跑網咖做開發 ( 聽說在網咖寫程式是很奇怪的一件事 ),然後每次跑網咖的時候都要去 download 四、五樣開發軟體與工具,再安裝,後來覺得很浪費時間,於是自己弄了一份自動安裝包出來。
SendMessage OR PostMessage
自動安裝包的概念是直接拿官網所釋放的安裝檔,主要用 FindWindow、PostMessage 這些 API 進行自動安裝,安裝速度比一般用人工安裝快些,但以這種機制進行會有些缺點出現,關鍵在於 SendMessage 與 PostMessage 之問題。
SendMessage 本身會一直待在 message queue 裡面,等被拿走之後才又繼續跑下個指令;PostMessage 是將訊息放到 queue 裡面就直接閃人不管。一開始在設計這部份程式時用的是 SendMessage,但後來容易發生卡死的情況,於是改用 PostMessage;而 PostMessage 又沒辦法確定到底是不是被正常接收、處理掉,個人認為算是兩難的局面。
由於不想在這個小工具上花費太長的時間,不然就失去當時開發的目的 ( 開發時間大於人工操作時間不就不划算了嗎 ) ,故最後全用 PostMessage 外,還在「適當」的時候加上 Sleep 等待動作,但 Sleep 個人認為是較不智的做法,因該延遲多久,這變數實在是太大的了。
Sample
這裡 Sample 以 Code::Blocks 10.05 ( windows ) with Mingw 為例,但不會放上 main ( 主因是懶得截取專案部份程式碼 )。
xWnd.h / xWnd.cpp 之設計模式,主要仿一些屬 script language ,如 AutoHotkey、AutoIt、按鍵精靈的設計概念,所以沒打算包成 class,也由於是短期開發,所以 xWnd.h / xWnd.cpp 還有很多可擴充的地方 ( 像是 SetText,建議用 SendMessage / PostMessage with WM_TEXT 做,用 SetWindowText 效果沒很好 )。
這個 sample 跳過一個部份 : Toolbar,事實上要對 tool bar 下訊息也行 ( 這方面網路上現有資源似乎較少 ),有機會開發時再放上。
- /**
- * @file xWnd.h
- * @brief declare some functions about AutoSetup
- * @author EdisonX < Edison.Shih >
- * @date 20120307
- */
- #ifndef X_WND_H__
- #define X_WND_H__
- #include <windows.h>
- HWND WaitWnd(char* name, int times=100, DWORD td=50, char* Class=NULL);
- HWND WaitWndEx(HWND hmain, char* name, int times=100, DWORD td=50, char* Class=NULL);
- HWND WaitWndEx(HWND hmain, DWORD ID, int times=100, DWORD td=50);
- void Click(HWND hwnd, DWORD td=50);
- void SetCheck(HWND hwnd, DWORD ID, BOOL Checked, DWORD td=50);
- void ArrowUp(HWND hwnd, DWORD times=1, DWORD td=50);
- void ArrowDown(HWND hwnd, DWORD times=1, DWORD td=50);
- void SendEnter(HWND hwnd, DWORD times=1, DWORD td=50);
- void SetCheck(HWND hwnd, BOOL Checked);
- #endif
- /**
- * @file xWnd.cpp
- * @brief Implement some functions about AutoSetup
- * @author EdisonX < Edison.Shih >
- * @date 20120307
- */
- #include "xWnd.h"
- #include <iostream>
- using namespace std;
- /////////////////////////////////////////////////////
- HWND WaitWnd(char* name, int times,
- DWORD td, char* Class)
- {
- HWND hwnd = FindWindow(Class, name);
- for(int i=0; i<times; ++i){
- hwnd = FindWindow(Class, name);
- if(hwnd!=NULL) return hwnd;
- Sleep(td);
- }
- cout << "can't find " << name << " window\n";
- return NULL;
- }
- /////////////////////////////////////////////////////
- HWND WaitWndEx(HWND hmain, char* name, int times,
- DWORD td, char* Class)
- {
- if(hmain==NULL) {
- cout << " hmain is NULL < from WaitWndEx >\n";
- return NULL;
- }
- HWND hsub = FindWindowEx(hmain,NULL, Class, name);
- for(int i=0; i<times; ++i){
- hsub = FindWindowEx(hmain,NULL, Class, name);
- if(hsub!=NULL) return hsub;
- Sleep(td);
- }
- cout << "can't find " << name << " window\n";
- return NULL;
- }
- /////////////////////////////////////////////////////
- HWND WaitWndEx(HWND hmain, DWORD ID, int times, DWORD td)
- {
- if(hmain==NULL) {
- cout << " hmain is NULL < from WaitWndEx >\n";
- return NULL;
- }
- HWND hsub = GetDlgItem(hmain, ID);
- for(int i=0; i<times; ++i){
- hsub = GetDlgItem(hmain, ID);
- if(hsub!=NULL) return hsub;
- Sleep(td);
- }
- cout << "can't find ID : " <<ID << " \n";
- return NULL;
- }
- /////////////////////////////////////////////////////
- void Click(HWND hwnd, DWORD td)
- {
- if(hwnd==NULL) {
- cout << " hwnd is NULL < from Click >\n";
- return;
- }
- Sleep(td); PostMessage(hwnd, WM_LBUTTONDOWN, NULL, NULL);
- Sleep(td); PostMessage(hwnd, WM_LBUTTONUP, NULL, NULL);
- Sleep(td);
- }
- /////////////////////////////////////////////////////
- void SetCheck(HWND hwnd, DWORD ID, BOOL Checked, DWORD td)
- {
- if(hwnd==NULL) {
- cout << " hwnd is NULL < from SetChect > \n";
- return ;
- }
- CheckDlgButton(hwnd, ID,Checked? BST_CHECKED :BST_UNCHECKED);
- // HWND hsub = GetDlgItem(hwnd, ID);
- // PostMessage(hsub, BM_SETCHECK,Checked? BST_CHECKED :BST_UNCHECKED, 0);
- Sleep(td);
- }
- /////////////////////////////////////////////////////
- void ArrowUp(HWND hwnd, DWORD times, DWORD td)
- {
- if(hwnd==NULL) {
- cout << " hwnd is NULL < from ArrowUp >\n";
- return;
- }
- for(DWORD i=0; i<times; ++i){
- Sleep(td); PostMessage(hwnd, WM_KEYDOWN, VK_UP, NULL);
- Sleep(td); PostMessage(hwnd, WM_KEYUP , VK_UP, NULL);
- }
- Sleep(td);
- }
- /////////////////////////////////////////////////////
- void ArrowDown(HWND hwnd, DWORD times,DWORD td)
- {
- if(hwnd==NULL) {
- cout << " hwnd is NULL < from ArrowDown >\n";
- return;
- }
- for(DWORD i=0; i<times; ++i){
- Sleep(td); PostMessage(hwnd, WM_KEYDOWN, VK_DOWN, NULL);
- Sleep(td); PostMessage(hwnd, WM_KEYUP , VK_DOWN, NULL);
- }
- Sleep(td);
- }
- /////////////////////////////////////////////////////
- void SendEnter(HWND hwnd,DWORD times, DWORD td)
- {
- if(hwnd==NULL) {
- cout << " hwnd is NULL < from SendEnter >\n";
- return;
- }
- for(DWORD i=0; i<times; ++i){
- Sleep(td); PostMessage(hwnd, WM_KEYDOWN, VK_RETURN, NULL);
- Sleep(td); PostMessage(hwnd, WM_KEYUP , VK_RETURN, NULL);
- }
- Sleep(td);
- }
- /**
- * @file AppSetup.h
- * @brief some application software auto setup
- * @author EdisonX < Edison.Shih >
- * @date 20120307
- */
- #ifndef APP_SETUP_H__
- #define APP_SETUP_H__
- void setup_cb1005mingw();
- void setup_devcpp4992();
- void setup_pcman2007();
- void setup_vc2010express();
- void setup_pspad();
- void setup_7z();
- void setup_XNResource();
- void setup_VerySleepy();
- void setup_SourceMonitor();
- #endif
- /**
- * @file setup_cb1005mingw.cpp
- * @brief auto setup code::blocks 10.05 with mingw
- * @author EdisonX < Edison.Shih >
- */
- #include "AppSetup.h"
- #include "xWnd.h"
- void setup_cb1005mingw()
- {
- HWND hwnd, hsub, hsub2;
- WinExec("codeblocks-10.05mingw-setup.exe", SW_SHOW);
- Sleep(2000);
- // [1] "CodeBlocks Setup" (0) , "&Next >" (1)
- hwnd = WaitWnd("CodeBlocks Setup");
- hsub = WaitWndEx(hwnd, 1);
- Click(hsub);
- // [2] "CodeBlocks Setup " (0), I &Agree (1)
- hwnd = WaitWnd("CodeBlocks Setup ");
- hsub = WaitWndEx (hwnd, 1);
- Click(hsub);
- // [3] "CodeBlocks Setup " (0), < "" "#32770" > , Custom (1017) up*4
- // puts("[3]");
- hwnd = WaitWnd("CodeBlocks Setup ");
- hsub = WaitWndEx(hwnd, "", 100, 50, "#32770");
- hsub2 = WaitWndEx(hsub, 1017);
- ArrowUp(hsub2, 4);
- // [4] "CodeBlocks Setup" (0), "&Next >" (1)
- hwnd = WaitWnd("CodeBlocks Setup ");
- hsub = WaitWndEx(hwnd, 1);
- Click(hsub);
- // [5] "CodeBlocks Setup " (0), "&Install" (1)
- hwnd = WaitWnd("CodeBlocks Setup ");
- hsub = WaitWndEx(hwnd, 1);
- Click(hsub);
- // [6] msgbox, "CodeBlocks Setup" (0), 是(&Y) (6) 否(&N) (7)
- hwnd = WaitWnd("CodeBlocks Setup", 500, 1000);
- hsub =WaitWndEx(hwnd, 7);
- Click(hsub);
- // [7] "CodeBlocks Setup " (0), "&Next >" (1)
- hwnd = WaitWnd("CodeBlocks Setup ");
- hsub = WaitWndEx(hwnd, 1);
- Click(hsub);
- // [8] "CodeBlocks Setup " (0), "&Finish" (1)
- hwnd = WaitWnd("CodeBlocks Setup ");
- hsub = WaitWndEx(hwnd, 1);
- Click(hsub);
- }
Win Spy Tools and APIs
原理
這類型 tool 原理大多分兩種,一種是用 WindowFromPoint 去取得 hwnd,再用 GetParent 取得母視窗相關資訊,這也是大多採取的方式。
另一種是較少見 ( 其實筆者沒見過基於這概念的軟體 ) 是用 EnumWindows、EnumChildWindows 去列舉所有視窗與其訊息,較少見的原因是它較難和看到的子控制項目做為結合。個人認為它有存在的必要,原因是極少數 ( 可能會發生於科技廠 release 給 clinet 的 tool ) 應用程式,有些不想讓 client 看到的功能、子控制,故意 create 在 Dialog 可視範圍之外,沒用 EnumChildWindows 根本抓不到。
軟體
WInSpy
筆者用的是早期所下載的 WInSpy ( 圖示是一隻蝴蝶,作者為 Robert Kuster ),它很小型,但它被大多網站判定為高危險的病毒程式。到現在筆者還是較習慣用它,原因是筆者要求截取的資訊必須含有 handle、ID、text、class,且還必須含 parent window 訊息,雖然找不到編好的執行檔,但幸運的是,還是找得到粗略的 source ,同時作者對 Win Spy 留有一些 紀錄 (有講 Inject 技術 ),有興趣可前往查閱,欲下載的話點進去後可選 Download WinSpy - 20 Kb (demo application) ,註明,這系列文章曾在 codeproject 上發表過。
VS
VS 本身也有,以2010 為例,在「開始」->「Microsoft Visual Studio 2010」-> 「Visual Studio Tools」-> Spy++,Alt + F3 就是我們要的功能,但在 2010 裡面它沒辦法顯示 parent 訊息,故筆者認為較不好用 ( 沒記錯的話,早期 VC6 附的 Spy++ 可顯示 parent 訊息 )。
AutoIt Window Info
這是 autoit 這套 script language 額外附的 tools ,有裝 AutoIt 之使用者開啟 IDE (SciTE) 後按下 Ctrl + F6 便可使用,但不知道有沒有提供單獨下載。筆者稱讚它的原因是,它是少數 Win Spy tool 可以抓到 tool bar command ID 的工具 ( 雖然對應沒做得很好 )。
封裝
Auto Setup 寫完後,拿到其他電腦就至少需要兩個檔案,一個是真正的 setup.exe ,一個是 hook.exe。怎解決這問題?
問題敘述大概是這樣,尋問了另一位常寫自動安裝的網友才解決。答案是:把它們全都壓縮在一起 ( 搞了半天是軟體問題 )。
選取兩個要壓縮的執行檔,WinRar 下選擇
1. 一般設定 -> 建立自我解壓縮。
2. 進階設定-> 自解檔選項。
3. 切到「安裝」,解壓縮後執行輸入「hook.exe」。
4. 切到「模式」,暫存模式 勾選「解封裝至暫存資料夾」,安靜模式 勾選「全部隱藏」。
5. 切到「文字與圖示」,我選了一份 icon。
6. 切到「更新」,更新模式選「解壓縮並取代檔案」,覆寫模式選「覆寫所有檔案」。
重新壓好之 exe ,丟到 client 端執行,自動解壓後就會自動執行 hook.exe。當然 Winrar 不是免費軟體,另一免費方案是用 7z ,但 7z 純用 command 要完成這動作比 Winrar 困難許多 , 所以有開發一些 7z 自動解壓工具,筆者這裡沒再深入,有興趣可自行研究。