如何从UI自动化模式提供程序返回错误?

16
假设我正在自定义控件中实现某些UIA模式,比如TablePattern。现有的实现如果出现任何错误都会返回null。但是这不太方便调试。我可能在自动化对等体中有更多的上下文。例如,对于GetItem(int row, int column),我可能会说提供的参数超出了范围,而不仅仅是返回null。
如果我从自动化对等体中抛出异常-在UIA客户端端,我会从IUIAutomationPatternInstance对象得到TargetInvocationException,但没有任何详细信息(InnerException属性为null)。
有没有一种方法可以使UIA从UIA服务器端传递带有一些附加信息的错误到UIA客户端端?

更新:经过一番调查和与@SimonMourier评论中提供的示例进行比较,我发现TargetInvocationException是我的问题。在这里进行了修复。

现在我得到了正确的异常类型,但只有标准异常消息。对于IndexOutBoundsException,无论我在UIA服务器端尝试放入什么样的异常,它都是“索引超出了数组界限。”

区别在于,我正在尝试不通过标准托管UIAutomationClient而是使用我自己的代码一直到COM调用来调用UIA方法(标准托管库不支持我想要使用的自定义UIA模式)。标准库可以成功传递异常消息。我已经尝试跟踪区别并找到以下内容:

  • 标准托管库通过在此处这里定义的方法通过InternallCall调用P/Invoke,方法定义为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,则会接收自定义消息。

你有检查 SystemAlert 事件(UIA_SystemAlertEventId / 20023)吗? https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx (仅适用于 Windows 8+,它不受标准 .NET UIAutomation dlls 支持,但由 UIAComWrapper 支持,你似乎知道它 :-) - Simon Mourier
@SimonMourier 事件是可能的,但这意味着必须先有人订阅。并且在每次调用之前都应该这样做,以获取此类错误信息。类似地,可以声明独立的UIA属性,返回最后一个错误的详细信息 - 就像GetLastError一样。这不是很吸引人的解决方案(但当然可以实现)。 - Ivan Danilov
@SimonMourier 如果我理解正确,UIA客户端在调用后查看HRESULT,并将其转换为异常(例如“ElementNotAvailableException”)。但是我不明白UIA服务器端如何在那里设置HRESULT - 即使在自动化对等体中抛出“ElementNotAvailableException”,客户端仍然会看到“TargetInvocationException”。也许我做错了什么,但实际上不知道如何调试它。 - Ivan Danilov
事实上,我无法重现你的问题。这里有一个样本WPF UserControl,会引发一个异常,该异常将在示例控制台应用程序服务器中显示:http://pastebin.com/xH7QmHhe - Simon Mourier
@SimonMourier 这是我的问题,异常类型没有正确翻译。但我仍然无法在UIA客户端上获取消息 :( 请参见更新的问题。 P.S.非常抱歉回复延迟了这么久。 - Ivan Danilov
显示剩余2条评论
1个回答

3
The default TLB importer - 或者等效的 Visual Studio UI 操作 - 创建 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's 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; }
        }
    }

现在,您应该可以看到原始异常了。

因此,要修复代码,您将不得不手动重新定义涉及的所有接口(或者这里有一个更新的tlbimp http://clrinterop.codeplex.com/releases/view/17579,可以使用PreserveSig创建签名-未经测试)。您还需要更改UIAComWrapper代码。前方有相当多的工作。


所以,没有办法说服编组器为我完成这项工作。我希望有一些神奇的属性,因为这种情况经常发生 :) 但是谢谢,如果PreserveSig是唯一的方法,我会尝试一下。正如您所看到的,我已经在进行一些原始的tlbimp结果重写,因此添加新属性可能并不像一开始看起来那么痛苦... - Ivan Danilov
有些讽刺的是,为了修复自定义模式,只需要更改三行代码 https://github.com/ivan-danilov/uia-custom-pattern-managed/commit/69226a819a9e316f6f608457891a4641ef5f32e6 但要使常见模式也能同样工作,则需要大幅重写很多代码... 谢谢,它运行正常。将其标记为正确答案。 - Ivan Danilov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接