C# WPF实现UIAutomation自定义Pattern时DLL引用与签名异常问题
在WPF C#程序中为Windows UIAutomation实现自定义模式时,需要引入包含IUIAutomationPatternHandler定义的相关类型。目前可查的唯一实现路径是添加UIAutomationCore的COM引用,但存在两个不确定点:一是不清楚该操作实际引入的是哪个DLL,二是IDE自动导入的类型定义存在参数错误。
自动生成的IUIAutomationRegistrar.RegisterPattern方法签名如下:
void RegisterPattern([In] ref UIAutomationPatternInfo pattern, out int pPatternId, out int pPatternAvailablePropertyId, [In] uint propertyIdCount, out int pPropertyIds, [In] uint eventIdCount, out int pEventIds);
而C++官方文档中该方法的签名为:
HRESULT RegisterPattern( [in] const UIAutomationPatternInfo *pattern, [out] PATTERNID *pPatternId, [out] PROPERTYID *pPatternAvailablePropertyId, [in] UINT propertyIdCount, [out] PROPERTYID *pPropertyIds, [in] UINT eventIdCount, [out] EVENTID *pEventIds );
按照标准COM交互逻辑,调用方需要预先分配用于接收返回信息的数组,传入数组本身(pPropertyIds)以及数组长度(propertyIdCount),显然C#自动导入的定义中将pPropertyIds识别为out int类型是错误的。
针对该场景的核心疑问如下:
- 添加UIAutomationCore的COM引用是否是实现该需求的正确方式
- 是否存在其他官方推荐的流程可获取正确的方法签名
- 是否必须手动编写互操作导入定义来修正参数类型
补充1:COM实例反射调用异常
要调用RegisterPattern方法,需要从DLL中获取CUIAutomationRegistrar对象,常规实现中直接调用以下代码即可创建实例:
var registrar = new UIA.CUIAutomationRegistrar();
该代码可以正常运行,但如果要调用自定义修正版本的RegisterPattern(),最初尝试通过反射调用时,执行以下代码:
registrar.GetType().GetMethods() // 或 registrar.GetType().GetMethod("RegisterPattern")
运行结果提示不存在名为RegisterPattern的方法,推测原因是该方法被标记了以下特性:
[MethodImpl(MethodImplOptions.InternalCall)]
因此它并不是常规意义上可通过反射获取的"真实"托管方法。
补充2:自定义互操作接口转换与范围异常
测试发现以下写法可以正常工作:
var registrar = new UIA.CUIAutomationRegistrar(); MyCopyPaste.IUIAutomationRegistrar myRegistar = (MyCopyPaste.IUIAutomationRegistrar)registrar;
推测编译器在后台自动执行了QueryInterface()调用,由于MyCopyPaste.IUIAutomationRegistrar与UIA.IUIAutomationRegistrar的UUID一致,因此类型转换可以通过编译。
但调用myRegistar实例时存在问题。修改后的MyCopyPaste.IUIAutomationRegstrar中RegisterPattern()方法定义如下:
[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);
使用如下代码调用该方法:
int[] propertyIds = new int[10]; myRegistar.RegisterPattern(ref patternInfo, out int patternId, out int patternAvailablePropertyId, 10, propertyIds, 0, out int eventIds);
运行时会抛出 "Value does not fall within the expected range."(值不在预期范围内) 异常。
补充3:手动互操作定义与测试结果
以下是手动编写的全部互操作定义代码:
namespace MyCopyPaste { 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)] public struct UIAutomationParameter { public UIAutomationType type; public IntPtr pData; } [Guid("C03A7FE4-9431-409F-BED8-AE7C2299BC8D")] [InterfaceType(1)] public interface IUIAutomationPatternInstance { } [Guid("D97022F3-A947-465E-8B2A-AC4315FA54E8")] [InterfaceType(1)] 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)] 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)] public struct UIAutomationPropertyInfo { public Guid guid; [MarshalAs(UnmanagedType.LPWStr)] public string pProgrammaticName; public UIAutomationType type; } [InterfaceType(1)] [Guid("8609C4EC-4A1A-4D88-A357-5A66E060E1CF")] [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")] public interface CUIAutomationRegistrar : IUIAutomationRegistrar { } }
对应的调用代码如下:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("BFEF93B9-0CAD-4B0F-8FB7-51CFF6891BCD")] 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, cMethods = 0, cEvents = 0, 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, propertyIds, 0, out int eventIds);
测试发现特殊现象:如果将propertyIdCount参数设置为0,调用可以完美执行,返回patternId=50000、patternAvailablePropertyId=500001,对应代码如下:
myRegistar.RegisterPattern(ref patternInfo, out int patternId, out int patternAvailablePropertyId, 0/* 传0时可以正常运行*/, propertyIds, 0, out int eventIds);
补充4:定义模式方法时的空引用异常
尝试为模式定义单个方法时,代码如下:
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, 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, cMethods = 1, pMethods = (IntPtr)methodsHandle, cEvents = 0, 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, propertyIds, 0, out int eventIds);
执行该代码时会抛出NullReferenceException(空引用)异常。
内容的提问来源于stack exchange,提问作者Betty Crokker

