Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting text from a control in another application, using MATLAB

[ - Introduction - ]

I have software (Altair) that interfaces with some measurement equipment. A limited set of functionalities of this software is exposed as an API, made available to me by the manufacturer in the form of its MATLAB implementation (with no extra documentation). Based on the provided sources, I know that all communications with this application use either Kernel32.dll or user32.dll (Windows API libraries), and more specifically the following methods:

  • RegisterWindowMessage (user32)
  • PostMessage (user32)
  • PeekMessage (user32)
  • GlobalGetAtomName (Kernel32)

One of the features I'm missing in the API is the ability to retrieve a certain textual setting that is buried somewhere in this software. Fortunately, the setting appears inside a TextBox (with non-selectable text) within its UI.

My objective is to obtain, within MATLAB, a string that appears inside this separate, non-MATLAB window.


[ - My attempts - ]

A quick internet search revealed1,2 that this is in fact possible, through the Windows API, if the HWND (Handle to Window) can be obtained for the specific control (or "window") that holds the required String. A WM_GETTEXT is then sent to the control, and the string is theoretically returned.

The first step I took was to check that a HWND is even obtainable. This was done using the Microsoft Spy++ utility (which is optionally available with VS2015). Below is the result:

Finding the text field in Spy++

The above hierarchy means that the 4thchild of class Static of the 3rdchild of the 1stchild ..... of the window "Altair" is what I'm looking for.

In terms of the Windows API, these methods appeared useful for traversing the window hierarchy and obtaining the string:

  • EnumChildWindows:

    Enumerates the child windows that belong to the specified parent window by passing the handle to each child window, in turn, to an application-defined callback function. EnumChildWindows continues until the last child window is enumerated or the callback function returns FALSE.

    Unfortunately, however, this is unusable since "The MATLAB Shared Library interface does not support library functions with function pointer inputs." (from the docs of loadlibrary), which just so happens to be an obligatory input of EnumChildWindows.

  • FindWindow and FindWindowEx:

    Retrieves a handle to the top-level window whose class name and window name match the specified strings. This function does not search child windows. This function does not perform a case-sensitive search.

    To search child windows, beginning with a specified child window, use the FindWindowEx function.

  • GetWindowText:

    Copies the text of the specified window's title bar (if it has one) into a buffer. If the specified window is a control, the text of the control is copied. However, GetWindowText cannot retrieve the text of a control in another application.

  • SendMessage:

    Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message.

So I set out to create a c header file that would be used with MATLAB's loadlibrary, and I've ended up with the following (graphic_hack.h 😉) :

// Windows Data Types: 
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751%28v=vs.85%29.aspx
typedef unsigned int UINT;
typedef UINT WPARAM;
typedef long LPARAM;
typedef long LRESULT;
typedef unsigned long HANDLE;
typedef unsigned long HWND;
typedef unsigned long HICON;
typedef unsigned long HINSTANCE;
typedef int BOOL;
typedef const char *LPCSTR;
typedef char *LPSTR;
typedef char TCHAR;
typedef LPCSTR LPCTSTR;
typedef LPSTR LPTSTR;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef long LONG;
typedef unsigned long ULONG;

#define STDCALL  __stdcall
#define CALLBACK __stdcall

// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633499%28v=vs.85%29.aspx
HWND STDCALL FindWindowA(LPCTSTR,LPCTSTR);               
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633500%28v=vs.85%29.aspx
HWND STDCALL FindWindowExA(HWND,HWND,LPCTSTR,LPCTSTR);
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633520%28v=vs.85%29.aspx
int STDCALL GetWindowTextA(HWND,LPTSTR,int);             
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950%28v=vs.85%29.aspx     
LRESULT STDCALL SendMessageA(HWND,UINT,WPARAM,LPARAM);   

The above is a manual alternative to the noteworthy method suggested here, which generates a header for all available API methods using the following:

[nf,warn] = loadlibrary('user32.dll',...
 'C:\Program Files (x86)\Windows Kits\8.1\Include\um\windows.h',...
'alias','user','includepath','C:\Program Files (x86)\Windows Kits\8.1\Include\um\',...
'includepath','C:\Program Files (x86)\Windows Kits\8.1\Include\shared\',...
'addheader','WinUser','mfilename','user_header');

The MATLAB code that gets me the HWND of the control is given below:

if (libisloaded('gh'))
    unloadlibrary('gh')
end
[~,~]=loadlibrary('user32.dll', 'graphic_hack.h','alias','gh');
javaaddpath(fullfile(pwd,'User32Util.jar'));
%Debug: libfunctionsview gh;
%       libfunctions('gh','-full');
%% Obtain the Altair field handle using various trickery:
hwndAltair = calllib('gh','FindWindowA','Altair',[]); %Find the Altair Window
hTmp(1) = calllib('gh','FindWindowExA',hwndAltair,0,'AfxControlBar70','Capture Manager');
hTmp(2) = calllib('gh','FindWindowExA',hTmp(1),0,'Afx:00400000:48:00000000:01100078:00000000',[]);
hTmp(3) = calllib('gh','FindWindowExA',hTmp(2),0,'SysTabControl32',[]);
hTmp(4) = calllib('gh','FindWindowExA',hTmp(3),0,[],'');
hTmp(5) = calllib('gh','FindWindowExA',hTmp(4),0,'Static',[]);
for k = 1:4
  hTmp(5) = calllib('gh','FindWindowExA',hTmp(4),hTmp(5),'Static',[]);
end

[ - The actual problem- ]

The final step, which I am struggling with, it dispatching the WM_GETTEXT that would get me the string. Specifically, the problem, the way I understand it, has to do with the definition of input parameters for SendMessage:

LRESULT WINAPI SendMessage(
  _In_ HWND   hWnd,
  _In_ UINT   Msg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

wParam [in]

Type: WPARAM

Additional message-specific information.

lParam [in]

Type: LPARAM

Additional message-specific information.

In the case of the WM_GETTEXT are:

wParam

The maximum number of characters to be copied, including the terminating null character.

ANSI applications may have the string in the buffer reduced in size (to a minimum of half that of the wParam value) due to conversion from ANSI to Unicode.

lParam

A pointer to the buffer that is to receive the text.

This presents me with a catch: on the one hand I am supposed to pass an LPARAMS, which according to the typedef in the header file is a long (which means MATLAB expects a numeric input), but it needs to be a pointer to a "text buffer", which means I should probably pass in something like libpointer​('String').

As it happens, others ran into related problems in the past, and it was suggested to employ the so-called MAKELPARAM macro defined as:

#define MAKELPARAM(l, h) ((LPARAM) MAKELONG(l, h))

... in order to create proper LPARAMS from another type of input. Unfortunately, I found no way this could help me.

This may be a misunderstanding on my part of how to properly use pointers in MATLAB3,4, or a limitation of MATLAB which I ran into. Either way, I am asking how can I proceed in calling SendMessage from MATLAB?

like image 934
Dev-iL Avatar asked Dec 21 '15 14:12

Dev-iL


1 Answers

MATLAB's External Functions interface allows calling functions in various languages, among which is Java. As mentioned in this answer, a popular Java library to interface with the Windows API is Java Native Access (JNA).

It is demonstrated in this answer how JNA can be harnessed to send a WM_GETTEXT message. Adapted for the specific needs of this question and transformed into a static method, the required Java-JNA code is shown below:

package hack.graphic.devil

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.LRESULT;
import com.sun.jna.win32.StdCallLibrary;

public class User32Util {
    interface User32 extends StdCallLibrary {
        User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
        int WM_GETTEXT = 0x000D;

        LRESULT SendMessageA(HWND editHwnd, int wmGettext, long l, byte[] lParamStr);
    }

    public static String getStringFromHexHWND(String args0) {
        User32 user32 = User32.INSTANCE;
        HWND target = new HWND(new Pointer(Long.decode(args0)));
        byte[] lParamStr = new byte[512];
        user32.SendMessageA(target, User32.WM_GETTEXT, 512, lParamStr);
        return Native.toString(lParamStr);
    }
}

The above code imports classes found in an older branch of JNA (specifically, its /src/com/sun/jna/ folder). After packaging as a .jar, this can then be called from MATLAB using:

javaaddpath(fullfile(pwd,'User32Util.jar'));
...
str = char(hack.graphic.devil.User32Util.getStringFromHexHWND(['0x' dec2hex(hTmp(5))]));

str will then contain the desired String. Q.E.F.

like image 124
Dev-iL Avatar answered Sep 28 '22 20:09

Dev-iL