2

In my WPF c# program, I'm trying to implement a custom pattern for Windows UIAutomation, which means I need to import something that knows about IUIAutomationPatternHandler, and the only way I can find to that is to add a COM reference to UIAutomationCore, and I'm not sure which DLL it's actually importing, but what gets included seems wrong.

For example, the IUIAutomationRegistrar.RegisterPattern method that gets imported looks like this:

void RegisterPattern([In] ref UIAutomationPatternInfo pattern,
   out int pPatternId, out int pPatternAvailablePropertyId,
   [In] uint propertyIdCount, out int pPropertyIds,
   [In] uint eventIdCount, out int pEventIds);

but the C++ documentation says this:

HRESULT RegisterPattern(
  [in]  const UIAutomationPatternInfo *pattern,
  [out] PATTERNID                     *pPatternId,
  [out] PROPERTYID                    *pPatternAvailablePropertyId,
  [in]  UINT                          propertyIdCount,
  [out] PROPERTYID                    *pPropertyIds,
  [in]  UINT                          eventIdCount,
  [out] EVENTID                       *pEventIds
);

which is kind of standard COM stuff, where you allocate an array to receive the information, and then pass in the array itself (pPropertyIds) and the size of the array (propertyIdCount) which means the c# import where it thinks pPropertyIds is an "out int" is wrong.

Is adding a COM reference to UIAutomationCore the right way to do this? Or is there a different process that is recommended, to get the correct signature? Or do I just have to do my own manual importing so the parameters are correct?

EDIT

In order to call RegisterPattern, I need a CUIAutomationRegistrar object from the DLL. Normally you just call

var registrar = new UIA.CUIAutomationRegistrar();

and that works, but then in order to call my own version of RegisterPattern(), I need to call InvokeMethod (right?) and when I call

registrar.GetType().GetMethods()
or
registrar.GetType().GetMethod("RegisterPattern")

It claims there's no method named "RegisterPattern", probably because the method is marked

[MethodImpl(MethodImplOptions.InternalCall)]

so it's not actually a "real" method.

EDIT 2

So this works:

var registrar = new UIA.CUIAutomationRegistrar();
MyCopyPaste.IUIAutomationRegistrar myRegistar = (MyCopyPaste.IUIAutomationRegistrar)registrar;

I assume the compiler is actually doing the QueryInterface() behind the scenes, and since MyCopyPaste.IUIAutomationRegistrar has the same UUID as UIA.IUIAutomationRegistrar, the compiler is happy.

But then using 'myRegistrar' is tricky. I modified the RegisterPattern() method in MyCopyPaste.IUIAutomationRegstrar like so:

[MethodImpl(MethodImplOptions.InternalCall)]
void RegisterPattern([In] ref UIAutomationPatternInfo pattern,
    out int pPatternId, out int pPatternAvailablePropertyId,
    [In] uint propertyIdCount,
    [MarshalAs(UnmanagedType.LPArray)] int[] pPropertyIds,
    [In] uint eventIdCount, out int pEventIds);

which I call like this:

int[] propertyIds = new int[10];
myRegistar.RegisterPattern(ref patternInfo,
   out int patternId, out int patternAvailablePropertyId,
   10, propertyIds, 0, out int eventIds);

and it throws a "Value does not fall within the expected range." at runtime.

EDIT 3

Here's my entire copy/paste section:

namespace MyCopyPaste
{
    // [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationType")]
    public enum UIAutomationType
    {
        UIAutomationType_Int = 1,
        UIAutomationType_Bool = 2,
        UIAutomationType_String = 3,
        UIAutomationType_Double = 4,
        UIAutomationType_Point = 5,
        UIAutomationType_Rect = 6,
        UIAutomationType_Element = 7,
        UIAutomationType_Array = 65536,
        UIAutomationType_Out = 131072,
        UIAutomationType_IntArray = 65537,
        UIAutomationType_BoolArray = 65538,
        UIAutomationType_StringArray = 65539,
        UIAutomationType_DoubleArray = 65540,
        UIAutomationType_PointArray = 65541,
        UIAutomationType_RectArray = 65542,
        UIAutomationType_ElementArray = 65543,
        UIAutomationType_OutInt = 131073,
        UIAutomationType_OutBool = 131074,
        UIAutomationType_OutString = 131075,
        UIAutomationType_OutDouble = 131076,
        UIAutomationType_OutPoint = 131077,
        UIAutomationType_OutRect = 131078,
        UIAutomationType_OutElement = 131079,
        UIAutomationType_OutIntArray = 196609,
        UIAutomationType_OutBoolArray = 196610,
        UIAutomationType_OutStringArray = 196611,
        UIAutomationType_OutDoubleArray = 196612,
        UIAutomationType_OutPointArray = 196613,
        UIAutomationType_OutRectArray = 196614,
        UIAutomationType_OutElementArray = 196615
    }

    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    // [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationParameter")]
    public struct UIAutomationParameter
    {
        public UIAutomationType type;

        public IntPtr pData;
    }

    [Guid("C03A7FE4-9431-409F-BED8-AE7C2299BC8D")]
    [InterfaceType(1)]
    // [TypeIdentifier]
    public interface IUIAutomationPatternInstance
    {
    }

    [Guid("D97022F3-A947-465E-8B2A-AC4315FA54E8")]
    [InterfaceType(1)]
    // [TypeIdentifier]
    public interface IUIAutomationPatternHandler
    {
        [MethodImpl(MethodImplOptions.InternalCall)]
        void CreateClientWrapper([In][MarshalAs(UnmanagedType.Interface)] IUIAutomationPatternInstance pPatternInstance, [MarshalAs(UnmanagedType.IUnknown)] out object pClientWrapper);

        [MethodImpl(MethodImplOptions.InternalCall)]
        void Dispatch([In][MarshalAs(UnmanagedType.IUnknown)] object pTarget, [In] uint index, [In] ref UIAutomationParameter pParams, [In] uint cParams);
    }

    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    // [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationPatternInfo")]
    public struct UIAutomationPatternInfo
    {
        public Guid guid;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string pProgrammaticName;

        public Guid providerInterfaceId;

        public Guid clientInterfaceId;

        public uint cProperties;

        public IntPtr pProperties;

        public uint cMethods;

        public IntPtr pMethods;

        public uint cEvents;

        public IntPtr pEvents;

        [MarshalAs(UnmanagedType.Interface)]
        public IUIAutomationPatternHandler pPatternHandler;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    // [TypeIdentifier("930299ce-9965-4dec-b0f4-a54848d4b667", "UIA.UIAutomationPropertyInfo")]
    public struct UIAutomationPropertyInfo
    {
        public Guid guid;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string pProgrammaticName;

        public UIAutomationType type;
    }

    [InterfaceType(1)]
    [Guid("8609C4EC-4A1A-4D88-A357-5A66E060E1CF")]
    // [TypeIdentifier]
    [ComImport]
    public interface IUIAutomationRegistrar
    {
        [MethodImpl(MethodImplOptions.InternalCall)]
        void RegisterProperty([In] ref UIAutomationPropertyInfo property, out int propertyId);

        void _VtblGap1_1();

        [MethodImpl(MethodImplOptions.InternalCall)]
        void RegisterPattern([In] ref UIAutomationPatternInfo pattern,
            out int pPatternId, out int pPatternAvailablePropertyId,
            [In] uint propertyIdCount,
            [MarshalAs(UnmanagedType.LPArray)] int[] pPropertyIds,
            [In] uint eventIdCount, out int pEventIds);
    }

    [Guid("8609C4EC-4A1A-4D88-A357-5A66E060E1CF")]
    // [TypeIdentifier]
    public interface CUIAutomationRegistrar : IUIAutomationRegistrar
    {
    }
}

and here's how I'm using it:

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("BFEF93B9-0CAD-4B0F-8FB7-51CFF6891BCD")]      // GUID of IValidatingPattern
public interface IValidatingPattern
{
    static readonly Guid PatternGUID = new(0x66e36682, 0x42f6, 0x4266, 0xaf, 0x27, 0xf, 0x34, 0xfa, 0xe6, 0x58, 0x3e);
    static readonly string PatternName = "Validating";

    static readonly Guid IsValidGUID = new(0x5dbe95c5, 0xb18c, 0x4904, 0xa3, 0xae, 0x57, 0xcb, 0xe4, 0xc2, 0x41, 0x5e);

    public bool IsValid { get; }
}

internal class ValidatingPattern : IValidatingPattern
{
    public bool IsValid
    {
        get
        {
            Debug.WriteLine("Not implemented (yet)");
            throw new NotImplementedException();
        }
    }
}

UIAutomationPatternInfo patternInfo = new()
{
   guid = IValidatingPattern.PatternGUID,
   pProgrammaticName = IValidatingPattern.PatternName,
   providerInterfaceId = typeof(IValidatingPattern).GUID,
   cProperties = 0, // (uint)properties.Length,
   // struct UIAutomationPropertyInfo *pProperties;
   cMethods = 0,
   // struct UIAutomationMethodInfo   *pMethods;
   cEvents = 0,
   // struct UIAutomationEventInfo    *pEvents;
   pPatternHandler = Handler
};
var registrar = new UIA.CUIAutomationRegistrar();
IUIAutomationRegistrar myRegistar = (IUIAutomationRegistrar)registrar;
int[] propertyIds = new int[10];
myRegistar.RegisterPattern(ref patternInfo,
   out int patternId, out int patternAvailablePropertyId,
   10/*(uint)properties.Length*/, propertyIds, 0, out int eventIds);

Note that if I change the 'propertyIdCount' to zero, the call works perfectly and returns patternId=50000 and patternAvailablePropertyId=500001:

myRegistar.RegisterPattern(ref patternInfo,
   out int patternId, out int patternAvailablePropertyId,
   0/* <-- WORKS WHEN ZERO*/, propertyIds, 0, out int eventIds);

EDIT 4

I also tried defining a single method:

UIAutomationType[] methodOutParamTypes = { UIAutomationType.UIAutomationType_Bool };
GCHandle methodOutParamTypesHandle = GCHandle.Alloc(methodOutParamTypes);

string[] methodOutParamNames = { "IsValid" };
GCHandle methodOutParamNamesHandle = GCHandle.Alloc(methodOutParamNames);

UIAutomationMethodInfo getIsValidMethod = new()
{
   pProgrammaticName = "get_IsValid",
   doSetFocus = 0,      // int
   cInParameters = 0,
   cOutParameters = 1,
   pParameterTypes = (IntPtr)methodOutParamTypesHandle,
   pParameterNames = (IntPtr)methodOutParamNamesHandle
};

UIAutomationMethodInfo[] methods = { getIsValidMethod };
GCHandle methodsHandle = GCHandle.Alloc(methods);

UIAutomationPatternInfo patternInfo = new()
{
   guid = IValidatingPattern.PatternGUID,
   pProgrammaticName = IValidatingPattern.PatternName,
   providerInterfaceId = typeof(IValidatingPattern).GUID,
   cProperties = 0, // (uint)properties.Length,
   // struct UIAutomationPropertyInfo *pProperties;
   cMethods = 1,
   pMethods = (IntPtr)methodsHandle,           // struct UIAutomationMethodInfo   *pMethods;
   cEvents = 0,
   // struct UIAutomationEventInfo    *pEvents;
   pPatternHandler = Handler
};

var registrar = new UIA.CUIAutomationRegistrar();
MyCopyPaste.IUIAutomationRegistrar myRegistar = (MyCopyPaste.IUIAutomationRegistrar)registrar;

int[] propertyIds = new int[10];
myRegistar.RegisterPattern(ref patternInfo,
   out int patternId, out int patternAvailablePropertyId,
   0/*(uint)properties.Length*/, propertyIds,
   0, out int eventIds);

That throws a NullReferenceException.

Betty Crokker
  • 3,001
  • 6
  • 34
  • 68
  • This type library is authored for use in C/C++ clients, that makes the parameter type ambiguous. Could be an int passed by reference, could be an array of int. The .net type library importer always guesses at byref int. A practical way to work around the problem is decompiling the interop library with a tool like ILSpy or Reflector and copy/pasting the generated code. And fix the parameter type, I think you need [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] int[]. – Hans Passant Jun 13 '22 at 21:34
  • @HansPassant I'm having -zero- luck getting any of my copy/paste to work. Can you read my EDIT and see if you can figure out where I'm going wrong? – Betty Crokker Jun 13 '22 at 23:33
  • No idea why that's a problem, no need for GetMethods(). Did you remove the COM reference? You could cast to (UIA.IUIAutomationRegistrar) explicitly. – Hans Passant Jun 14 '22 at 00:59
  • I can't see your code, not much point in keeping it a secret. Note that the last parameter is an array as well. – Hans Passant Jun 14 '22 at 14:21
  • It's a lot of code, but I've added it as EDIT 3 now. Yes, the last parameter is an array as well, but if I pass zero for the count, it doesn't even look at the associated array parameter. – Betty Crokker Jun 14 '22 at 14:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245601/discussion-between-betty-crokker-and-hans-passant). – Betty Crokker Jun 14 '22 at 14:32
  • It surely is unhappy about the propertyIds array content, all zeros, use RegisterProperty() to get valid property IDs. Signing off, good luck with it. – Hans Passant Jun 14 '22 at 14:43

0 Answers0