16

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.

Ivan Danilov
  • 14,287
  • 6
  • 48
  • 66
  • Have you checked the SystemAlert event (UIA_SystemAlertEventId / 20023)? https://msdn.microsoft.com/en-us/library/windows/desktop/ee671223(v=vs.85).aspx (only for Windows 8+, it's not supported by the standard .NET UIAutomation dlls but by the UIAComWrapper that you seem to know :-) – Simon Mourier Jul 20 '15 at 14:36
  • @SimonMourier events are possible, but it'd mean that somebody should have subscribed there first. And it should be done before every call in order to get such error info. In similar way one could declare standalone UIA property that returns details of the last error - will be something like `GetLastError`. Not very appealing solution (but possible to implement of course). – Ivan Danilov Jul 21 '15 at 11:14
  • Well, there's nothing more to UIA to what's in UIAutomationCore.idl and UIAutomationClient.idl from the windows 8 SDK. There interfaces are not IDispatch interfaces so are not designed to carry extra exception info (EXCEPINFO structure that .NET likes). I think you'll have to come up with a conventional way for defining what are errors (from a UI perspective) and how do they happen. You could also use IAnnotationProvider or IObjectModelProvider which are pretty generic – Simon Mourier Jul 21 '15 at 14:50
  • @SimonMourier if I understand correctly, UIA client-side looks at HRESULT after the call and translates it to an exception if there's appropriate one (e.g. `ElementNotAvailableException`). But I don't understand how UIA server-side sets HRESULTs there - even if in the automation peer I throw `ElementNotAvailableException` - client side still sees `TargetInvocationException`. Maybe I'm doing something wrong, but have no idea how to debug it actually. – Ivan Danilov Jul 22 '15 at 11:02
  • In fact, I can't reproduce your problem. Here is a sample WPF UserControl that throws an exception which is displayed in the sample console app server: http://pastebin.com/xH7QmHhe – Simon Mourier Jul 25 '15 at 10:09
  • Did you try to use your logging (log4net) and throw custom exceptions? – ekostadinov Jul 28 '15 at 08:37
  • @SimonMourier it was my issue that exception type was not translated correctly. But I still can't get the message on UIA client side :( See updated question. P.S. Sorry for very long delay. – Ivan Danilov Sep 27 '15 at 16:24

1 Answers1

3

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.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • So, there's no way to convince marshaller to do this work for me. I was hoping for some magical attribute as it is often the case :) But thanks, if PreserveSig is the only way I will give it a try. As you can see I'm doing a bit of original tlbimp result rewriting already, so adding new attribute might be not so painful as it looks at first... – Ivan Danilov Sep 27 '15 at 22:23
  • There is some irony in the fact that to fix custom patterns it was enough to change three lines of code https://github.com/ivan-danilov/uia-custom-pattern-managed/commit/69226a819a9e316f6f608457891a4641ef5f32e6 but to make common patterns work the same it would require significant rewrite of much code... Thanks, it works. Marking as correct answer. – Ivan Danilov Sep 28 '15 at 01:17