Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

[Solved] Как заполнить структуру COMBOBOXINFO с использованием JNA


(Sergey Ivanskoy) #1

Доброго времени суток, уважаемые коллеги.

Прошу помощи по вопросу, над которым бьюсь уже второй день.
Пишу тестирующее приложение, выполняющее функциональные тесты над десктоп приложением. Со временем, надеюсь, данные наработки превратятся в полноценный фреймоврк - обертку над Win32 API, если будет время довести его до ума. Для доступа к Win32 функциям использую JNA (java native access) либу.
Столкнулся со следующей задачей, которая оказалась на практике довольно-таки нетривиальной, - нужно разобрать комбобокс на ListBox, Array ListItem’ов и т.д., а выполнить в Win32 это можно следующими способами:

  • вызовом BOOL GetComboBoxInfo(In HWND hwndCombo, Out PCOMBOBOXINFO pcbi)

  • вызовом BOOL SendMessageA(In hWnd, CB_GETCOMBOBOXINFO, 0, Out PCCOMBOBOXINFO pcbi)

т.е. одним из этих вызовов заполняется структура COMBOBOXINFO, которая и будет содержать заветные HWND списка и т.д.

Ниже отрывки из кода

Описание структуры COMBOBOXINFO ( http://msdn.microsoft.com/en-us/library/bb775798(v=vs.85).aspx ):
COMBOBOXINFO.java

public class COMBOBOXINFO extends Structure
{
public int    cbSize;
public RECT   rcItem;
public RECT   rcButton;
public int    stateButton;
public HWND   hwndCombo;
public HWND   hwndItem;
public HWND   hwndList;

/**
 * Constructs new instance
 */
public COMBOBOXINFO()
{
	super();
	cbSize = size();
}

public List<?> getFieldsList()
{
	return getFields(true);
}

@Override
protected List<?> getFieldOrder() 
{
	return Arrays.asList(new String[] {"cbSize", "hwndCombo", "hwndItem", "hwndList", "rcButton", "rcItem", "stateButton"} );
}

}

Класс WindowsHelper - слой-обертка над Win32 API функциями (вернее, его кусок)

public class WindowHelper 
{
    private static final int CB_GETCOMBOBOXINFO = 0x0164;

    private interface User32Ex extends User32
    {  
	
	User32Ex INSTANCE = (User32Ex)Native.loadLibrary("user32.dll",     User32Ex.class, W32APIOptions.DEFAULT_OPTIONS);

	boolean SendMessageA(HWND hWnd, int msg, int wParam, COMBOBOXINFO cbi);
	
	boolean GetComboBoxInfo(HWND hWnd, COMBOBOXINFO cbi);
    }
    /**
     * 
     * Defines extended interface with additional methods
     * for calling corresponding Win32 functions
     */
     private static final User32Ex _user32Instance = User32Ex.INSTANCE;
    ....................................................

}    

Делаю вызовы следующим способом:

some_function(HWND hWnd /* хендл окна комбобокса */)
{
     COMBOBOXINFO cbi = new COMBOBOXINFO();
     boolean res = _user32Instance.GetComboBoxInfo(hWnd, cbi);
     или
     boolean res = _user32Instance.SendMessageA(hWnd, CB_GETCOMBOBOXINFO, 0, cbi);
}

И проблема в следующем: хотя и передается правильный хендл окна ComboBox, в обоих случаях функции возвращают false и, соответственно, поля структуры не заполнены.

Может кто-то уже сталкивался с подобной проблемой или библиотекой JNA по-больше сможет направить на путь истинный. Копия вопроса на stackoverflow http://stackoverflow.com/questions/25388066/populating-comboboinfo-structure-by-using-jna , но пока без результата :frowning:
Thanks in advance.


(apetrovskiy) #2

Начать надо с GetLastError (в комментах http://msdn.microsoft.com/ru-ru/library/windows/desktop/ms644950(v=vs.85).aspx правильно пишут, что хорошо бы ещё предварительно очистить очередь ошибок). Возможно, WinAPI вовсю ругается на ваши параметры, а вы и не подозреваете.

Я уже редко пользую WinAPI, но что-то мне такое припоминается, что функции, оканчивающиеся на A, относятся больше к Windows 95/98, а на линейки NT-систем правильнее использовать W (мне-то в С# вообще думать об этом не надо, сама система даёт то, что нужно). С другой стороны, статья упоминает только Windows 2000 как старейшую поддерживаемую систему, так что A и W давно уже могут работать одинаково.

А хэндл контрола вы откуда берёте? Ваш код его возвращает или каким-то тулом получаете? Во втором случае может быть ситуация, что вы даёте правильный хэндл, но ваше приложении не имеет достаточно прав (например, тестируемое приложение элевейтится, а ваш код - нет).


(Sergey Ivanskoy) #3

спасибо за наводку, про GetLastError как-то вылетело - безусловно необходимо смотреть, что она говорит.
По поводу A и W - это ANSI и wide (Unicode) версии функций.
Хендл контрола возвращает мой код путем вызова EnumChildWindows и нахождения в списке дочерних окон того, которое мне нужно - здесь без проблем - значение хендла совпадает с тем, что индицирует spy, что дает мне уверенность что я все-таки передаю правильный хендл контрола.


(Sergey Ivanskoy) #4

@apetrovskiy, спасибо большое. с вашей помощью разобрался и получилось то, как вы и предполагали:
GetLastError сказала 5 (ERROR_ACCESS_DENIED) и, соответственно, проблема была в элевейте. Запуск тестирующего приложения под правами администратора решил проблему.
Так зациклился на одном участке кода, что перестал обращать внимание на простые решения и пути, лежащие рядом :smile:


(apetrovskiy) #5

Пожалуйста, не мог пройти мимо такого героического порыва написать аналог AutoIt/UI Automation/etc/etc.

А зачем вам всё это делать из джавы (кроме прокачки скилла)?
Джава же позиционируется как “написано тут, будет работать там и там, а если повезёт, и везде”.
А портировать Win32 обёртки, кроме как на другую версию винды, некуда.

И другое соображение: вот вы окучите стандартные контролы Win32, потом вам надо будет окучить дотнетовские стандартные контролы. Потом devexpress, infragistics, telerik, etc, etc, Т.е. проделать всю ту работу, что проделала команда Майкла Бернстейна в мелкософте в 2006-м году. На самом деле, у них уже даже две UI Automation, одна идёт с .NET, другая - на Nuget’е.

Понятно, что вызывать .NET из Java невыгодно (а писать сразу на C++/C# вы, вероятно, не хотите).
Но есть же ещё MSAA, да и UI Automation для C++. Уже всё (многое: стандартное и часть не стандартного) обёрнуто. Из минусов - работает только в юзерской сессии, потому скринридеры, магнифайеры и т.д. предусматриваются только для юзеров, но не для сервисов.
Особенно интересен в плане переносимости MSAA: работал аж с Windows 95. Считается legacy, но используется до сих пор.


(Sergey Ivanskoy) #6

@apetrovskiy
Джава была выбрана как раз по причине возможности использовать библиотеку JNA, имеющая очень полезную для меня возможность - имитировать, так сказать, настоящий user input.
Приведу маленький пример из практики: у меня в тестируемом приложении есть диалог, который вызывается по нажатию определенной кнопки, вернее даже не вызывается, а при клике на кнопку он просто показывается, т.к. всегда существует на экране, но просто невидим. Как оказалось на практике, с помощью SetCursorPos, а затем посылкой кнопке SendMessage c WM_LBUTTONDOWN(UP) c запакованными координатами курсора, диалог не показывается. Посылать скрытому диалогу WM_SHOWWINDOW тоже некорректно для тестов.
С помощью этой jna-либы возможно симулировать INPUT, который имитирует движения и нажатия мышки, или, например, ввод текста якобы с клавиатуры, а не посылкой WM_SETTEXT со строкой, которую нужно, например, насетить в эдит-бокс. T.e. я делаю скажем MouseMove, затем MouseDown и MouseUp в середине кнопки - курсор передвигается на кнопку, кликает и искомый диалог видим.
Второй профит от такой возможности имитировать user input - это возможность реально симитировать drag’n’drop, что необходимо для моих тестов т.к. в тестируемом приложении одной из главных возможностей использовать UI - это drag’n’drop.
По всей видимости, эта либа тоже является оберткой над всеми этими сообщениями и т.д. (в ее коде пока не лазил), но довольно грамотно сделанная, что показали тесты над моим тестируемом приложении.
И, к сожалению, мне не хватило бы знаний так обернуть мессаджы для имитации юзер-инпут.
На данный момент я не знаю, например, .NET библиотек с такой возможностью и не знаю имеет ли такую возможность UIAtomation (кстати, который тоже рассматривался для использования в виде фреймворка TestStack White)
По поводу окучивания всех стандартных контролов, devexpress, telerik и т.д. пока речи не идет и, возможно, идти не будет (с моей стороны, по крайней мере точно ближайшее время :smile: ) Пока есть несколько оберток только для нужных мне контролов (для более удобного их использования).
Сейчас у меня стоит задача ( моя личная в том числе :smile: ) сделать некий слой, который сможет работать с кастомными контролами, основанными на стандартных, т.е. например возможность заюзать не только Button, а еще какой-нибудь ButtonCustomVasyaPetyaCorporation :smile: + имитация “реальных” нажатий-передвижений мыши\клавиатуры на этих контролах.
По поводу С++ - не рассматриваю как язык для написания таких тестов в принципе. (да и он сейчас, так сказать, немного не такой, которым я баловался, учась в институте несколько лет назад :smile: - новые стандарты и т.д.)
Да, еще по поводу окучивания контролов и добавления возможностей, хочу затем все это выложить на гите, чтобы желающие могли принять участие и опытные люди могли бы поисправлять мои ошибки и улучшить код.
Пока, к сожалению, не могу этого сделать.


(apetrovskiy) #7

Есть неплохой фреймворк для имитации ввода: InputSimulator. Я его использую как дополнительный вариант ввода в моём фреймворке. В применении достаточно прост и удобен.

UI Automation умеет дёргать некоторые события контролов, они не всегда соответствуют тем, чего ожидает программер. К примеру, программер задаёт обработку изменения данных в текстбоксе через TextChanged (почему? да просто потому, что при двойном клике на контрол студия предлагает это событие:)) А UI Automation через ValuePattern активирует, скорее, ModifiedChanged. Обычно это вызывает недовольство программеров - они же честно сделали дабл-клик на контроле и написали код в сгенерённом для них методе (мозг не включается для такой низменной вещи, как написание UI).

Также авторы UI Automation надеются, что кто-нибудь (опытный тимлид, начальство, тестировщики, кастомеры, жажда всё знать и всё делать хорошо) надавит на разработчика в плане использования Accessibility пропертей контролов (та же AccessibilityRole). Ни разу не работал с такими разработчиками, которые до многонедельной серии пинков и увещеваний, сами начинают внедрять Accessibillity в свои продукты.
Так что юзерский инпут (тот же InputSimulator) иногда остаётся наилучшим выбором - только надо контрол на передний план переводить. И не забывать о том, что некоторые драйверы (Vmware Pointing Device, например) не дают работать некоторым функциям, к примеру, SetCursorPos.