Porting our provider to the UIA COM API

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.

Why would you want to use the COM API for a provider, anyway?

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.

The Interop Project

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:

  1. Run the MIDL tool on UIAutomationCore.idl, included in the Windows 7 SDK, to get UIAutomationCore.tlb.
  2. Run the TlbImp tool on UIAutomationCore.tlb to create my interop library, Interop.UIAutomationCore.dll
  3. Add a reference to Interop.UIAutomationCore.dll to my provider sample.

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.

Adding NativeMethods.cs

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.

Changes to the Main Code

Finally, I need to make some syntactic changes to the main code. 

  1. Replacements of “using” statements for System.Windows.Automation and System.Windows.Automation.Provider with a “using” statement for Interop.UIAutomationCore.

  2. Some replacements of .NET types with their equivalents in native code:

    1. System.Windows.Rect –> UiaRect
    2. bool –> int.  This one is unfortunate – the type is BOOL in the IDL file, which turns into an integer in COM interop.
  3. Slight name changes to UIA enums.  For example, NavigationDirection.Parent becomes NavigationDirection.NavigationDirection_Parent.  (You could define your own enums in NativeMethods.cs if this really bothered you, as long as the values matched.)

  4. Property and pattern identifiers move from Identifiers classes (like AutomationElementIdentifiers) to your NativeMethods.cs class.  For example, AutomationElementIdentifiers.NameProperty.Id becomes UiaConstants.NamePropertyId.

  5. The code to return the HostRawElementProvider changed slightly since we have to P/Invoke to the UiaHostProviderFromHwnd function:

             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;
                    }
                }
            }
    
  6. The calls to raise events are now in NativeMethods.cs, so that syntax changes slightly, from RaiseAutomationEvent() to UiaRaiseAutomationEvent(). 

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.