Получение текста из элемента управления в другом приложении с использованием MATLAB

[- Введение -]

У меня есть программное обеспечение (Altair), которое взаимодействует с некоторым измерительным оборудованием. Ограниченный набор функций этого программного обеспечения представлен как API, предоставленный мне производителем в форме его реализации MATLAB (без дополнительной документации). Исходя из предоставленных источников, я знаю, что все коммуникации с этим приложением используют Kernel32.dll или user32.dll (библиотеки Windows API), а точнее следующие методы:

Одна из возможностей, которые мне не хватает в API, - это возможность получить определенный текстовый параметр, который похоронен где-то в этом программном обеспечении. К счастью, настройка появляется внутри TextBox (с неизбираемым текстом) в пределах своего пользовательского интерфейса.

Моя цель - получить внутри MATLAB строку, которая появляется внутри этого отдельного окна без MATLAB.


[- Мои попытки -]

Быстрый поиск в Интернете показал 1, 2 что на самом деле это возможно через API Windows, если для определенного элемента управления (или "окна" ) можно получить HWND (Handle to Window), который содержит требуемый String. A WM_GETTEXT затем отправляется в элемент управления, и теоретически возвращается строка.

Первый шаг, который я предпринял, - проверить, что a HWND можно получить. Это было сделано с помощью утилиты Microsoft Spy ++ (которая по желанию доступна с VS2015). Ниже приведен результат:

Поиск текстового поля в Spy ++

Вышеупомянутая иерархия означает, что 4 thchild класса Static 3 rdchild 1 stchild..... окна "Альтаир" - это то, что я ищу.

В терминах API Windows эти методы оказались полезными для прохождения иерархии окон и получения строки:

  • EnumChildWindows:

    Перечисляет дочерние окна, которые относятся к указанному родительскому окну, передавая дескриптор каждому дочернему окну, в свою очередь, к функции обратного вызова, определенной приложением. EnumChildWindows продолжается до тех пор, пока не будет перечислено последнее дочернее окно или функция обратного вызова не вернет ЛОЖЬ.

    К сожалению, это непригодно, поскольку "Интерфейс общей библиотеки MATLAB не поддерживает библиотечные функции с входами указателей функций". (из документов loadlibrary), который, как раз так, является обязательным вводом EnumChildWindows.

  • FindWindow и FindWindowEx:

    Возвращает дескриптор окна верхнего уровня, имя класса и имя окна которого соответствуют указанным строкам. Эта функция не выполняет поиск дочерних окон. Эта функция не выполняет поиск с учетом регистра.

    Для поиска дочерних окон, начиная с указанного дочернего окна, используйте FindWindowEx.

  • GetWindowText:

    Копирует текст указанной строки заголовка окна (если он есть) в буфер. Если указанное окно является элементом управления, текст элемента управления копируется. Однако GetWindowText не может получить текст элемента управления в другом приложении.

  • SendMessage:

    Отправляет указанное сообщение в окно или окна. Функция SendMessage вызывает оконную процедуру для указанного окна и не возвращается до тех пор, пока оконная процедура не обработает сообщение.

Итак, я решил создать заголовочный файл c, который будет использоваться с 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);   

Вышеприведенная альтернатива рекомендуемому методу здесь, который генерирует заголовок для всех доступных методов API, используя следующее:

[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');

Код MATLAB, который получает меня HWND элемента управления, приведен ниже:

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, который доставит мне строку. В частности, проблема, как я ее понимаю, связана с определением входных параметров для SendMessage:

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

wParam [in]

Тип: WPARAM

Дополнительная информация о конкретном сообщении.

lParam [in]

Тип: LPARAM

Дополнительная информация о конкретном сообщении.

В случае WM_GETTEXT:

WPARAM

Максимальное количество символов, подлежащих копированию, включая завершающий нулевой символ.

Приложения ANSI могут иметь строку в уменьшенном размере буфера (как минимум вдвое меньше, чем значение wParam) из-за преобразования из ANSI в Unicode.

LPARAM

Указатель на буфер, который должен получать текст.

Это дает мне улов: с одной стороны, я должен передать LPARAMS, который согласно typedef в файле заголовка имеет long (что означает, что MATLAB ожидает числовой ввод) но он должен быть указателем на "текстовый буфер", что означает, что я должен, вероятно, передать что-то вроде libpointer​('String').

Как это случилось, другие сталкивались с связанными проблемами в прошлом, и было предложено использовать так называемый MAKELPARAM macro определяется как:

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

..., чтобы создать правильный LPARAMS из другого типа ввода. К сожалению, я не нашел способ помочь мне.

Это может быть непонимание с моей стороны того, как правильно использовать указатели в MATLAB 3, 4 или ограничение MATLAB, с которым я столкнулся. В любом случае, Я спрашиваю, как я могу продолжить вызов SendMessage из MATLAB?

Ответ 1

MATLAB Интерфейс внешних функций позволяет вызывать функции на разных языках, среди которых Java. Как упоминалось в этот ответ, популярная библиотека Java для взаимодействия с Windows API - это Java Native Access (JNA).

Как показано в этот ответ, как 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);
    }
}

Вышеприведенный код импортирует классы, найденные в более старой ветке JNA (в частности, его папку /src/com/sun/jna/). После упаковку как .jar, тогда это можно вызвать из MATLAB с помощью:

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

str затем будет содержать желаемый String. Q.E.F.