Как вернуть ошибки из поставщика шаблонов автоматизации пользовательского интерфейса?
Предположим, что я использую шаблон UIA в своем настраиваемом элементе управления. Скажем, TablePattern
. Существующие реализации возвращают null, если что-то пошло не так. Но отлаживать не очень удобно. У меня может быть больше контекста в узле автоматизации. Например, для GetItem(int row, int column)
я могу сказать, что предоставленные аргументы за пределами границ, а не просто возвращают null.
Если я выдаю исключение из peer автоматизации - на стороне клиента UIA я получаю объект TargetInvocationException
из IUIAutomationPatternInstance
без каких-либо подробностей (свойство InnerException равно null).
Есть ли способ заставить МАУ пройти ошибку с некоторой дополнительной информацией со стороны UIA-сервера на стороне UIA-клиента?
UPD: После некоторого исследования и сравнения с примером @SimonMourier, представленным в комментариях, я обнаружил, что TargetInvocationException
был моей ошибкой. Исправлено это здесь.
Теперь я получаю правильный тип исключения, но только стандартное сообщение об исключении. Для IndexOutBoundsException
это "Индекс находился за пределами массива". независимо от того, что я пытался исключить на стороне сервера UIA.
Разница в том, что я пытаюсь вызвать метод UIA не через стандартный управляемый UIAutomationClient, но с моим собственным кодом вплоть до COM-вызова (стандартная управляемая библиотека не поддерживает пользовательские шаблоны МАУ, которые я бы хотел использования). Стандартная библиотека отлично передает сообщения об исключениях. Я попытался отслеживать разницу и нашел следующее:
- Стандартная управляемая библиотека делает вызов P/Invoke через InternallCall здесь с помощью метода, определенного как
private static extern int RawGridPattern_GetItem(SafePatternHandle hobj, int row, int column, out SafeNodeHandle pResult);
. Он возвращает HRESULT, который обрабатывается методом CheckError
посредством вызова Marshal.ThrowExceptionForHR(hr);
. В этот момент появляется исключение с правильным сообщением, которое было выбрано на стороне сервера UIA.
- UIAComWrapper, который я использую, выглядит как бы COM-вызов, определенный в
c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include\UIAutomationClient.idl
как HRESULT GetItem ([in] int row, [in] int column, [out, retval] IUIAutomationElement ** element );
. В моем понимании COM Interop механизм перезаписи возвращаемого значения автоматически проверяет HRESULT, при необходимости выдает исключение и возвращает аргумент out result
в противном случае. Это действительно так, за исключением того, что сообщение об исключении не может быть переведено по какой-либо причине.
Чтобы воспроизвести проблему, вы можете попробовать этот проект. Файлы в папке lib были созданы из этого репозитория. Если ConsoleApplication1 ссылается на библиотеку UIAComWrapper - исключение поставляется с сообщением по умолчанию. Если вы измените ссылку на использование стандартного UIAutomationClient вместо этого - он получает пользовательский.
Ответы
Ответ 1
По умолчанию импортер TLB - или эквивалентные операции пользовательского интерфейса Visual Studio -, который создает сборку Interop.UIAutomationClient
, использует макет подписи "[out, retval]
" вместо использования атрибута Preservesig
(подробнее об этом здесь http://blogs.msdn.com/b/adam_nathan/archive/2003/04/30/56646.aspx).
Так, например, здесь он объявляет IUIAutomationGridPattern
следующим образом (упрощенная версия):
[Guid("414C3CDC-856B-4F5B-8538-3131C6302550"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IUIAutomationGridPattern
{
UIAutomationClient.IUIAutomationElement GetItem(int row, int column);
...
}
вместо этого:
[Guid("414C3CDC-856B-4F5B-8538-3131C6302550")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IUIAutomationGridPattern
{
[PreserveSig]
int GetItem(int row, int column, out UIAutomationClient.IUIAutomationElement element);
...
}
Хотя оба варианта действительны, последний лучше, если вы хотите тщательно обрабатывать исключения. Первая делает магию, которая, к сожалению, преобразует то, что интересно здесь, в нечто менее интересное.
Итак, если вы используете версию Preservesig
, вы можете заменить код в GridItem.cs следующим образом:
public AutomationElement GetItem(int row, int column)
{
try
{
UIAutomationClient.IUIAutomationElement element;
int hr = _pattern.GetItem(row, column, out element);
if (hr != 0)
throw Marshal.GetExceptionForHR(hr); // note this uses COM EXCEPINFO if any
return AutomationElement.Wrap(element).GetUpdatedCache(CacheRequest.Current);
}
catch (System.Runtime.InteropServices.COMException e)
{
Exception newEx; if (Utility.ConvertException(e, out newEx)) { throw newEx; } else { throw; }
}
}
И теперь вы должны увидеть оригинальные исключения.
Итак, чтобы исправить код, вам придется переопределить все задействованные интерфейсы вручную (или здесь http://clrinterop.codeplex.com/releases/view/17579 новый tlbimp который может создавать подписи с PreserveSig - не проверяется). Вам также придется изменить код UIAComWrapper. Довольно много работы впереди.