In this post, I’m going to outline the changes we need to make to our custom UI Automation provider to make it connect to the COM API for UI Automation rather than the managed-code API (UIAutomationProviders.dll.) The samples for this part are here.
Before I just start porting, I should explain why you might want to do this. I am setting up for a future post where I want to use some new functionality in UIA that was made available in Windows 7. The UIA classes in the .NET Framework have not yet been updated to include this functionality. This is a dilemma that comes up when we introduce new functionality in the operating system: we rarely get the chance to update wrapper classes in sync with the operating system, since the .NET Framework ships on a different schedule than Windows.
Using wrapper classes creates simplicity for the developer during the process of writing code, and it works well if the wrapped functionality will never change. But when it does change, it leaves the user of the wrapper classes in a pickle.
Moving our sample to use the operating system library (UIAutomationCore.dll) directly gives us the advantage of using all of the new Windows 7 interfaces without needing to wait for wrapper updates. The downside is that our code is going to become slightly more complicated. The code isn’t any longer, but the syntax is a little more involved. There’s nothing wrong with using the wrapper classes, but since I want to use the new stuff, I’m willing to spend some time on the port.
I built my UIA sample for this part based on the sample from this last post. I can use the WinDiff tool from the Platform SDK to see exactly what changed between the old and the new.
To get started, I need a COM/.NET interop library built from the UI Automation type library. You can often do this by just adding a reference to the system DLL in question, but the type library in UIAutomationCore.dll is the Client type library – the one you’d use when writing an automation tool or a screen reader. That makes the client scenario easy, but I really want the Provider type library. This is a three step process:
You can certainly do this by hand. I took a different alternative: I created a separate VC project, “UiaInterop”, to do steps (1) and (2) so that I can do them repeatedly in my build process. The UiaInterop project includes a link to the IDL file from the Platform SDK, which causes MIDL to be invoked automatically. I added a custom post-build step to do the TlbImp and I’m all set.
I need a place to put some P/Invoke statements and constants for items that aren’t in the type library, so I added a new file, NativeMethods.cs. The P/Invoke statements are straightforward to create: I need them for some flat API methods that we call:
public class NativeMethods { [DllImport("UIAutomationCore.dll", EntryPoint = "UiaHostProviderFromHwnd", CharSet = CharSet.Unicode)] public static extern int UiaHostProviderFromHwnd(IntPtr hwnd, [MarshalAs(UnmanagedType.Interface)] out IRawElementProviderSimple provider); [DllImport("UIAutomationCore.dll", EntryPoint = "UiaReturnRawElementProvider", CharSet = CharSet.Unicode)] public static extern IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam, IRawElementProviderSimple el); [DllImport("UIAutomationCore.dll", EntryPoint = "UiaRaiseAutomationEvent", CharSet = CharSet.Unicode)] public static extern int UiaRaiseAutomationEvent(IRawElementProviderSimple el, int eventId); [DllImport("UIAutomationCore.dll", EntryPoint = "UiaRaiseAutomationPropertyChangedEvent", CharSet = CharSet.Unicode)] public static extern int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple el, int propertyId, object oldValue, object newValue); }
These methods are extremely similar to the methods on the AutomationInteropProvider class in the managed-code API, and they do the same job.
I also need a set of constant identifiers for patterns and properties. These are defined in UIAutomationClient.idl, but it seems like a lot of work to import that whole IDL just to get some constants, so I just copied them into NativeMethods.cs:
// Useful constants // Duplicated from UIAutomationClient.idl public class UiaConstants { public const int UIA_InvokePatternId = 10000; public const int UIA_SelectionPatternId = 10001; public const int UIA_ValuePatternId = 10002; public const int UIA_RangeValuePatternId = 10003; public const int UIA_ScrollPatternId = 10004;
And so on. These will replace the AutomationProperty and AutomationPattern classes I was using earlier. They are just integer constants, so they are lightweight to use.
Finally, I need to make some syntactic changes to the main code.
public IRawElementProviderSimple HostRawElementProvider { get { IntPtr hwnd = this.GetWindowHandle(); if (hwnd != IntPtr.Zero) { return AutomationInteropProvider.HostProviderFromHandle(this.GetWindowHandle()); IRawElementProviderSimple hostProvider = null; NativeMethods.UiaHostProviderFromHwnd(this.GetWindowHandle(), out hostProvider); return hostProvider; } else { return null; } } }
This list isn’t intended to be complete, but just a log of what I did. After I changed the references and the using statements, I tried to build and just fixed issues until it compiled. Most of these are easy enough that they can be simple search-and-replaces. Only (5) and (6) required some thought.
Once it compiled, it ran perfectly without further changes. I skipped taking a screenshot this time because it’s identical to the one from Part 6, since the functionality is the same -- which is only appropriate for a refactoring. Now I have the foundation on which I’ll build when I add some Win7 functionality.