最近在客戶項目上剛好遇到一個問題,項目需求是要獲取某台機床的實時狀態,問題點剛好就在於該機床不是傳統意義上的數控機床,也不是PLC控制器,只有一個上傳下載程式文件的應用程式,上面剛好有幾個按鈕可以大概判斷當前工作狀態,轉眼一想,是否可以實時獲取幾個按鈕的狀態,從而簡單分析下就確定機床加工狀態。 說乾 ...
最近在客戶項目上剛好遇到一個問題,項目需求是要獲取某台機床的實時狀態,問題點剛好就在於該機床不是傳統意義上的數控機床,也不是PLC控制器,只有一個上傳下載程式文件的應用程式,上面剛好有幾個按鈕可以大概判斷當前工作狀態,轉眼一想,是否可以實時獲取幾個按鈕的狀態,從而簡單分析下就確定機床加工狀態。
說乾就乾,開始拿起放下已久的Win32API來試試。思路大概如下:
- 首先,我們知道的是應用程式的進程名稱如:notepad.exe
- 然後,就要通過進程名獲取視窗句柄(HWND)
- 其次,通過視窗句柄遍歷子視窗句柄,通過其獲取相關數據,比如:Button是否被可用、Button的Text、CheckButton是否被選中等等一些列想要的操作。此處我們就抓取記事本內容吧(內容在Edit控制項中)
- 最後,就是實時更新、存儲數據即可,進行後期邏輯處理
獲取進程ID
首先當我們知道進程名,通過進程名獲取進程ID,我們需要用到一個Win32的進程快照模塊:CreateToolhelp32Snapshot 可以通過獲取進程信息為指定的進程、進程使用的堆[HEAP]、模塊[MODULE]、線程建立一個快照。
HANDLE WINAPI CreateToolhelp32Snapshot(
__in DWORD dwFlags,
__in DWORD th32ProcessID
);
參數:
- dwFlags 用來指定“快照”中需要返回的對象,指定快照中包含的系統內容,這個參數能夠使用下列數值(常量)中的一個或多個。
- TH32CS_INHERIT :聲明快照句柄是可繼承的。
- TH32CS_SNAPALL : 在快照中包含系統中所有的進程和線程。
- TH32CS_SNAPHEAPLIST : 在快照中包含在th32ProcessID中指定的進程的所有的堆。
- TH32CS_SNAPMODULE : 在快照中包含在th32ProcessID中指定的進程的所有的模塊。
- TH32CS_SNAPPROCESS : 在快照中包含系統中所有的進程。
- TH32CS_SNAPTHREAD : 在快照中包含系統中所有的線程。
- th32ProcessID 指定將要快照的進程ID。如果該參數為0表示快照當前進程。該參數只有在設置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE後才有效,在其他情況下該參數被忽略,所有的進程都會被快照。
返回值: 調用成功,返回快照的句柄,調用失敗,返回INVALID_HANDLE_VALUE 。
廢話不多說,直接上代碼:
DWORD GetProcessIdFromProcessName(std::string processname)
{
DWORD dwRet = 0;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap != INVALID_HANDLE_VALUE)
{
BOOL bMore = ::Process32First(hProcessSnap, &pe32);
while (bMore)
{
if (boost::iequals(pe32.szExeFile, processname))
{
dwRet = pe32.th32ProcessID;
}
bMore = ::Process32Next(hProcessSnap, &pe32);
}
::CloseHandle(hProcessSnap);
}
return dwRet;
}
調用測試:
std::string str1 = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(str1);
std::cout << dwProcessId << std::endl; //
獲取到了進程,那就進入下一節,獲取視窗句柄。
獲取視窗句柄HWND
通過進程ID獲取視窗句柄,那麼就需要遍歷視窗了,找到符合我們需求的進程所對應的視窗句柄了,這個地方就會用到一個函數:EnumWindows 枚舉所有屏幕上的頂層視窗,並將視窗句柄傳送給應用程式定義的回調函數。
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);
參數:
- lpEnumFunc:指向一個應用程式定義的回調函數指針,請參看EnumWindowsProc。
- lPararm:指定一個傳遞給回調函數的應用程式定義值。
返回值:如果函數成功,返回值為非零;如果函數失敗,返回值為零。若想獲得更多錯誤信息,請調用GetLastError函數。
回調函數:
BOOL CALLBACK EnumWindowsProc(
HWND hwnd,
LPARAM lParam
);
參數:
- hwnd:頂層視窗的句柄
- lparam:應用程式定義的一個值(即EnumWindows中lParam)
返回值:TRUE繼續遍歷,FALSE停止遍歷
註: EnumWindows函數不列舉子視窗。
那麼接下來就是開始獲取視窗句柄了。
首先定一個結構體
該機構體由於標記進程和視窗句柄:
typedef struct tagHWNDINFO
{
DWORD dwProcessId;
HWND hWnd;
} HWNDINFO, *LPHWNDINFO;
枚舉所有視窗
BOOL CALLBACK EnumWindowProc(HWND hWnd, LPARAM lParam)
{
DWORD dwProcId;
GetWindowThreadProcessId(hWnd, &dwProcId);
LPHWNDINFO pInfo = (LPHWNDINFO)lParam;
if (dwProcId == pInfo->dwProcessId)
{
if (GetParent(hWnd) == NULL && IsWindowVisible(hWnd)) //判斷是否頂層視窗並且可見
{
pInfo->hWnd = hWnd;
return FALSE;
}
else
return TRUE;
}
return TRUE;
}
獲取指定進程ID視窗句柄
HWND GetProcessMainWnd(DWORD dwProcessId)//獲取給定進程ID的視窗HWND
{
HWNDINFO wi;
wi.dwProcessId = dwProcessId;
wi.hWnd = NULL;
EnumWindows(EnumWindowProc, (LPARAM)&wi);
return wi.hWnd;
}
調用測試:
std::string processname = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(processname);
std::cout << dwProcessId << std::endl;
if (dwProcessId != 0)
{
HWND hwnd = GetProcessMainWnd(dwProcessId);
if (hwnd != NULL)
{
char WindowTitle[100] = { 0 };
::GetWindowText(hwnd, WindowTitle, 100);
std::cout << WindowTitle << std::endl;
}
}
結果輸出:
11712
無標題 - 記事本
現在已經獲取了記事本主視窗句柄了,下一步就是遍歷子視窗了。
遍歷子視窗
我們的目標是抓取窗體中信息,這時候介紹一個工具,相當的好用Spy++(具體怎麼用,就自己百度了)
現在我們要獲取的就是下麵的Edit框內容。此處我們又需要遍歷子視窗,需用到一個方法EnumChildWindows 枚舉一個父視窗的所有子視窗。
BOOL EnumChildWindows(
HWND hWndParent,
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);
參數:
- hWndParent: 父視窗句柄
- lpEnumFunc: 回調函數的地址
- lParam: 自定義的參數
回調函數如下:
BOOL CALLBACK EnumChildProc(
HWND hwnd,
LPARAM lParam
);
參數:
- hwnd:父視窗指定的一個子視窗句柄
- lParam:EnumChidWindows指定的參數
返回值:如果返回TRUE,則枚舉繼續直到枚舉完成;如果返回FALSE,則將會中止枚舉。
直接亮代碼:
BOOL CALLBACK EnumNotepadChildWindowsProc(HWND hWnd, LPARAM lParam)
{
char szTitle[100] = { 0 };
::GetWindowText(hWnd, szTitle, 100);
long lStyle = GetWindowLong(hWnd, GWL_STYLE);
if (lStyle & ES_MULTILINE)
{
long lineCount = SendMessage(hWnd, EM_GETLINECOUNT, 0,0);
for (int i = 0; i < lineCount; i++)
{
//long chCount = SendMessage(hWnd, EM_LINELENGTH, (WPARAM)i, 0);
char szContent[200] = { 0 };
szContent[0] = 200; //此處註意下,如果不設置EM_GETLINE無法獲取內容
long ret = SendMessage(hWnd, EM_GETLINE, (WPARAM)i, (LPARAM)(LPCSTR)szContent);
if (ret > 0)
{
szContent[ret] = '\0';
std::cout << "line: " << i << ", Content: " << szContent << std::endl;
}
}
}
else
{
std::string title(szTitle);
if (!title.empty())
std::cout << title << std::endl;
}
return true;
}
調用如下:
std::string processname = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(processname);
std::cout << dwProcessId << std::endl;
if (dwProcessId != 0)
{
HWND hwnd = GetProcessMainWnd(dwProcessId);
if (hwnd != NULL)
{
char WindowTitle[100] = { 0 };
::GetWindowText(hwnd, WindowTitle, 100);
std::cout << WindowTitle << std::endl;
EnumChildWindows(hwnd, EnumNotepadChildWindowsProc, NULL);
}
}
結果如下:
line: 0, Content: 第一行 iceman
line: 1, Content: 第二行 Hello
line: 2, Content: 第三行 World
到此,就完成既定目標了