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:
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.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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With