Detailed Explanation of Windows Form Data Grabbing

Posted by caaronbeasley on Wed, 15 May 2019 07:59:20 +0200

Recently, a problem happened to the customer project. The requirement of the project is to obtain the real-time state of a machine tool. The point of the problem is that the machine is not a traditional CNC machine tool or a PLC controller. There is only an application program for uploading and downloading program files. There are just a few buttons on it that can roughly judge the current working state. In a twinkling of an eye, whether it is possible or not. Real-time acquisition of the status of several buttons, so as to determine the machine tool processing status under simple analysis.

Do as you say, and begin to pick up the long-laid down Win32 API to try. The ideas are as follows:

  • First, we know the process name of the application, such as notepad.exe.
  • Then, you get the window handle (HWND) by the process name.
  • Secondly, it traverses the sub-window handle through the window handle to obtain relevant data, such as: whether Button is available, whether Button's Text, whether CheckButton is selected medium, and so on. Here we grab the Notepad content (the content is in the Edit control)
  • Finally, the data can be updated and stored in real time for later logical processing.

Get the process ID

First, when we know the process name and get the process ID through the process name, we need to use a Win32 process snapshot module: CreateToolhelp32Snapshot can create a snapshot for the specified process, the HEAP used by the process, the module [MODULE], and the thread by obtaining the process information.

HANDLE WINAPI CreateToolhelp32Snapshot(
  __in          DWORD dwFlags,
  __in          DWORD th32ProcessID
);

Parameters:

  • dwFlags is used to specify the object to be returned in the snapshot and the system content contained in the snapshot. This parameter can use one or more of the following values (constants).
    1. TH32CS_INHERIT | declares that the snapshot handle is inheritable.
    2. TH32CS_SNAPALL | Includes all processes and threads in the system in the snapshot.
    3. TH32CS_SNAPHEAPLIST | Includes in the snapshot all heaps of the processes specified in the th32ProcessID.
    4. TH32CS_SNAPMODULE | Includes in the snapshot all modules of the process specified in the th32ProcessID.
    5. TH32CS_SNAPPROCESS | Includes all processes in the system in the snapshot.
    6. TH32CS_SNAPTHREAD | Includes all threads in the system in the snapshot.
  • The th32 Process ID specifies the process ID to be snapshot. If this parameter is 0, it represents the snapshot of the current process. This parameter is valid only when TH32CS_SNAPHEAPLIST or TH32CS_SNAPMODULE is set. In other cases, this parameter is ignored and all processes are snapshot.

Return value: The call is successful, the handle of the snapshot is returned, the call fails, and INVALID_HANDLE_VALUE is returned.

Don't talk too much nonsense, go straight to the code:

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;
}

Call the test:

std::string str1 = "notepad.exe";
DWORD dwProcessId = GetProcessIdFromProcessName(str1);
std::cout << dwProcessId << std::endl; //

If you get the process, go to the next section and get the window handle.

Get window handle HWND

To get the window handle through the process ID, you need to traverse the window to find the corresponding window handle for the process that meets our needs. This place will use a function: Enum Windows enumerates the top-level windows on all screens and passes the window handle to the callback function defined by the application.

BOOL EnumWindows(   
    WNDENUMPROC lpEnumFunc,   
    LPARAM lParam 
); 

Parameters:

  • lpEnumFunc: Pointer to a callback function defined by an application, see Enum Windows Proc.
  • lPararm: Specifies an application definition value passed to the callback function.

Return value: If the function succeeds, the return value is non-zero; if the function fails, the return value is zero. To get more error information, call the GetLastError function.

Callback function:
BOOL CALLBACK EnumWindowsProc(  
    HWND hwnd,   
    LPARAM lParam 
);

Parameters:

  • hwnd: Handle to the top window
  • lparam: A value defined by the application (lparam in Enum Windows)
    Return value: TRUE continues traversal, FALSE stops traversal

Note: Enum Windows functions do not list subwindows.

So the next step is to get the window handle.

First, define a structure.

Because of marking processes and window handles, this body:

typedef struct tagHWNDINFO
{
    DWORD   dwProcessId;
    HWND    hWnd;
} HWNDINFO, *LPHWNDINFO;

Enumerate all windows

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))  //Determine whether the top-level window is visible  
        {
            pInfo->hWnd = hWnd; 
            return FALSE;
        }
        else
            return TRUE;
    }

    return TRUE;
}

Gets the ID window handle for the specified process

HWND GetProcessMainWnd(DWORD dwProcessId)//Window HWND to get the given process ID
{
    HWNDINFO wi;
    wi.dwProcessId = dwProcessId;
    wi.hWnd = NULL;
    EnumWindows(EnumWindowProc, (LPARAM)&wi);

    return wi.hWnd;
}

Call the test:

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;
    }
}   

Results Output:

11712
 Untitled - Notepad

Now that you have the main window handle of notepad, the next step is to traverse the sub-windows.

Traversing subwindows

Our goal is to grab the information in the form. At this time, we will introduce a tool, which is quite good at using Spy++ (how to use it, Baidu itself)

Now what we want to get is the following Edit box content. Here we need to traverse the child window again. We need to use a method EnumChildWindows to enumerate all the child windows of a parent window.

BOOL EnumChildWindows(          
    HWND hWndParent,
    WNDENUMPROC lpEnumFunc,
    LPARAM lParam
);

Parameters:

  • hWndParent: parent window handle
  • lpEnumFunc: The address of the callback function
  • lParam: Custom parameters

The callback function is as follows:

BOOL CALLBACK EnumChildProc(          
    HWND hwnd,
    LPARAM lParam
);

Parameters:

  • hwnd: A child window handle specified by the parent window
  • lParam: Parameters specified by EnumChid Windows
    Return value: If TRUE is returned, the enumeration continues until the enumeration is completed; if FALSE is returned, the enumeration will be aborted.

Direct highlight code:

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; //Note here that content cannot be obtained without setting 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;
}

The call is as follows:

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);
    }
}

The results are as follows:

line: 0, Content: First line iceman
 line: 1, Content: Line 2 Hello
 line: 2, Content: Line 3 World

So far, we have accomplished our goals.

Topics: C++ snapshot Windows