Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return errors from UI Automation pattern provider?

Suppose I'm implementing some UIA pattern in my custom control. Say, TablePattern. Existing implementations return null if anything went wrong. But it is not very convenient to debug. I might have more of a context in the automation peer. For example, for GetItem(int row, int column) I might say that provided arguments are out of bounds rather than just return null.

If I throw an exception from automation peer - on the UIA client side I get TargetInvocationException from IUIAutomationPatternInstance object without any details (InnerException property is null).

Is there a way to make UIA to pass error with some additional information from UIA-server side to UIA-client side?


UPD: After some investigation and comparison with example @SimonMourier provided in comments I found that TargetInvocationException was my fault. Fixed it here.

Now I'm getting correct exception type, but only a standard exception message. For IndexOutBoundsException it is "Index was outside the bounds of the array." regardless of what I've been trying to put in exception on UIA server side.

The difference is that I'm trying to call UIA method not through standard managed UIAutomationClient, but with my own code all the way down to COM call (standard managed library doesn't support custom UIA patterns which I'd like to use). Standard library passes exception messages just fine. I've tried to track what the difference is and found the following:

  • Standard managed library makes call to P/Invoke through InternallCall here via method defined as private static extern int RawGridPattern_GetItem(SafePatternHandle hobj, int row, int column, out SafeNodeHandle pResult);. It returns HRESULT, which is handled by CheckError method via call to Marshal.ThrowExceptionForHR(hr);. At this point exception with correct message appears as was thrown on UIA server side.
  • UIAComWrapper which I use does seemingly same COM call defined in c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include\UIAutomationClient.idl as HRESULT GetItem ([in] int row, [in] int column, [out, retval] IUIAutomationElement ** element );. To my understanding of COM Interop, rewriting return value mechanism automatically checks HRESULT, throws an exception if necessary and return out result argument otherwise. It really does except that exception message does not get translated for some reason.

To reproduce the issue you can try this project. Files in lib folder were built from this repository. If ConsoleApplication1 references UIAComWrapper library - exception comes with default message. If you change reference to use standard UIAutomationClient instead - it receives custom one.

like image 687
Ivan Danilov Avatar asked Jul 20 '15 13:07

Ivan Danilov


1 Answers

The default TLB importer - or equivalent Visual Studio UI operations - that creates the Interop.UIAutomationClient assembly uses the "[out, retval]" signature layout instead of using Preservesig attribute (more on this here http://blogs.msdn.com/b/adam_nathan/archive/2003/04/30/56646.aspx).

So for example, here it declares IUIAutomationGridPattern like this (simplified version):

[Guid("414C3CDC-856B-4F5B-8538-3131C6302550"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IUIAutomationGridPattern
{
    UIAutomationClient.IUIAutomationElement GetItem(int row, int column);
    ...
}

instead of this:

[Guid("414C3CDC-856B-4F5B-8538-3131C6302550")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IUIAutomationGridPattern
{
    [PreserveSig]
    int GetItem(int row, int column, out UIAutomationClient.IUIAutomationElement element);
    ...
}

Although both are valid, the latter one is better if you want to carefully handle exceptions. The first one does some magic wich unfortunately transforms what's interesting here in something less interesting. So, if you use the PreserveSig version, you can replace the code in GridItem.cs like this:

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

And you should now see original exceptions.

So to fix the code, you'll have to redefine all interfaces involved, manually (or there is here http://clrinterop.codeplex.com/releases/view/17579 a newer tlbimp that can create signatures with PreserveSig - not tested). You will have to change the UIAComWrapper code also. Quite a lot of work ahead.

like image 116
Simon Mourier Avatar answered Oct 23 '22 07:10

Simon Mourier