使用 MATLAB 从另一个应用程序中的控件获取文本

Getting text from a control in another application, using MATLAB

[ - 简介 - ]

我有软件(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库),更具体地说有以下方法:

我在 API 中缺少的功能之一是能够检索隐藏在该软件某处的特定文本设置。幸运的是,该设置出现在其 UI.

中的 TextBox(带有 non-selectable 文本)内

我的objective是获取,在MATLAB里面,这个separate里面出现的字符串,non-MATLAB window.


[ - 我的尝试 - ]

网上快速搜索发现1,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然后发送到控件,理论上返回字符串。

我采取的第一步是检查是否可以获得 HWND。这是使用 Microsoft Spy++ utility (which is optionally available with VS2015) 完成的。以下是结果:

上面的层级表示第4thchild of class Static of the 3rd child of the 1st child ..... of the window "Altair" 就是我要找的为.

在 Windows API 方面,这些方法似乎对遍历 window 层次结构和获取字符串很有用:

所以我着手创建一个 c header 文件,它将与 MATLAB 的 loadlibrary 一起使用,最后我得到了以下内容 (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);   

以上是 手动 替代建议的值得注意的方法 here,它为所有可用的 API 方法生成 header 使用以下:

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

获取控件 HWND 的 MATLAB 代码如下:

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

[ - 实际问题 - ]

我正在苦苦挣扎的最后一步是调度 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.

WM_GETTEXT的情况下是:

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.

这给了我一个陷阱:一方面,我应该传递一个 LPARAMS,根据 header 文件中的 typedef,它是一个 long(这意味着 MATLAB 需要一个数字输入),但它需要是一个指向 "text buffer" 的指针,这意味着我 可能 传递类似 libpointer​('String').

正好别人运行成相关问题过去,而it was suggested to employ the so-called MAKELPARAM macro定义为:

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

... 为了从另一种类型的输入中创建正确的 LPARAMS。不幸的是,我发现这对我没有任何帮助。

这可能是我对如何在 MATLAB 中正确使用指针的误解3,4,或者是我 运行 对 MATLAB 的限制.无论哪种方式,我都在问如何从 MATLAB 调用 SendMessage

MATLAB 的 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).

this answer 中演示了如何利用 JNA 发送 WM_GETTEXT 消息。针对本题的具体需求进行适配,转化为static方法,需要的Java-JNA代码如下所示:

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

以上代码导入 an older branch of JNA (specifically, its /src/com/sun/jna/ folder). After packaging as a .jar 中的 类,然后可以使用以下命令从 MATLAB 中调用:

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

str 将包含所需的 StringQ.E.F.