|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms.Tests.TestResources;
using Windows.Win32.System.Com;
using Windows.Win32.System.Ole;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
using WMPLib;
namespace System.Windows.Forms.ComponentModel.Com2Interop.Tests;
public unsafe class ComNativeDescriptorTests
{
[StaFact]
public void ComNativeDescriptor_GetProperties_FromIPictureDisp_ComInterop()
{
using Bitmap bitmap = new(10, 20);
object iPictureDisp = IPictureDisp.CreateObjectFromImage(bitmap);
Assert.NotNull(iPictureDisp);
ValidateIPictureDispProperties(iPictureDisp, TypeDescriptor.GetProperties(iPictureDisp));
}
[StaFact]
public void ComNativeDescriptor_GetProperties_FromIPictureDisp_ComWrappers()
{
using Bitmap bitmap = new(10, 20);
using var iPictureDisp = IPictureDisp.CreateFromImage(bitmap);
Assert.False(iPictureDisp.IsNull);
// The runtime needs to be updated to allow ComWrappers through
ComNativeDescriptor descriptor = new();
object comWrapper = UnknownComWrappers.Instance.GetOrCreateObjectForComInstance(iPictureDisp, CreateObjectFlags.None);
ValidateIPictureDispProperties(comWrapper, descriptor.GetProperties(comWrapper));
}
private static void ValidateIPictureDispProperties(object iPictureDisp, PropertyDescriptorCollection properties)
{
Assert.NotNull(properties);
Assert.Equal(5, properties.Count);
var handleProperty = properties["Handle"];
Assert.IsType<Com2PropertyDescriptor>(handleProperty);
Assert.True(handleProperty.IsReadOnly);
Assert.Equal("Misc", handleProperty.Category);
Assert.False(handleProperty.IsLocalizable);
Assert.Equal("Windows.Win32.System.Com.IDispatch+Interface", handleProperty.ComponentType.FullName);
// OLE_HANDLE
Assert.Equal("System.Int32", handleProperty.PropertyType.FullName);
Assert.Equal(DesignerSerializationVisibility.Visible, handleProperty.SerializationVisibility);
Assert.False(handleProperty.CanResetValue(iPictureDisp));
Assert.False(handleProperty.ShouldSerializeValue(iPictureDisp));
Assert.NotEqual(0, handleProperty.GetValue(iPictureDisp));
var converter = (Com2ExtendedTypeConverter)handleProperty.Converter;
Assert.IsAssignableFrom<Int32Converter>(converter.InnerConverter);
var typeProperty = properties["Type"];
Assert.NotNull(typeProperty);
Assert.True(typeProperty.IsReadOnly);
Assert.Equal("Misc", typeProperty.Category);
Assert.False(typeProperty.IsLocalizable);
Assert.Equal("Windows.Win32.System.Com.IDispatch+Interface", typeProperty.ComponentType.FullName);
Assert.Equal("System.Int16", typeProperty.PropertyType.FullName);
Assert.Equal(PICTYPE.PICTYPE_BITMAP, (PICTYPE)(short)typeProperty.GetValue(iPictureDisp));
}
[StaFact]
public void ComNativeDescriptor_GetProperties_FromIPicture()
{
// While we ask for IPicture, the underlying native class also supports IDispatch, so we get support
// in the type descriptor.
using Bitmap bitmap = new(10, 20);
object iPicture = IPicture.CreateObjectFromImage(bitmap);
Assert.NotNull(iPicture);
var properties = TypeDescriptor.GetProperties(iPicture);
Assert.Equal(PICTYPE.PICTYPE_BITMAP, (PICTYPE)(short)properties["Type"].GetValue(iPicture));
}
[StaFact]
public void ComNativeDescriptor_GetProperties_FromActiveXMediaPlayerControl_ComInterop()
{
Guid guid = typeof(WindowsMediaPlayerClass).GUID;
HRESULT hr = PInvokeCore.CoCreateInstance(
in guid,
pUnkOuter: null,
CLSCTX.CLSCTX_INPROC_SERVER,
out IUnknown* mediaPlayerPtr);
Assert.Equal(HRESULT.S_OK, hr);
ComNativeDescriptor descriptor = new();
object mediaPlayer = ComHelpers.GetObjectForIUnknown(mediaPlayerPtr);
ValidateMediaPlayerProperties(mediaPlayer, descriptor.GetProperties(mediaPlayer));
}
[StaFact]
public void ComNativeDescriptor_GetProperties_FromActiveXMediaPlayerControl_ComWrappers()
{
Guid guid = typeof(WindowsMediaPlayerClass).GUID;
ComScope<IUnknown> unknown = new(null);
HRESULT hr = PInvokeCore.CoCreateInstance(
&guid,
null,
CLSCTX.CLSCTX_INPROC_SERVER,
IID.Get<IUnknown>(),
(void**)unknown);
Assert.Equal(HRESULT.S_OK, hr);
object mediaPlayer = UnknownComWrappers.Instance.GetOrCreateObjectForComInstance(unknown, CreateObjectFlags.None);
ComNativeDescriptor descriptor = new();
ValidateMediaPlayerProperties(mediaPlayer, descriptor.GetProperties(mediaPlayer));
}
private static void ValidateMediaPlayerProperties(object mediaPlayer, PropertyDescriptorCollection properties)
{
Assert.Equal(25, properties.Count);
var urlProperty = properties["URL"];
Assert.IsType<Com2PropertyDescriptor>(urlProperty);
Assert.False(urlProperty.IsReadOnly);
Assert.Equal("Misc", urlProperty.Category);
Assert.False(urlProperty.IsLocalizable);
Assert.Equal("Windows.Win32.System.Com.IDispatch+Interface", urlProperty.ComponentType.FullName);
Assert.Equal("System.String", urlProperty.PropertyType.FullName);
Assert.Equal(DesignerSerializationVisibility.Visible, urlProperty.SerializationVisibility);
Assert.False(urlProperty.CanResetValue(mediaPlayer));
Assert.False(urlProperty.ShouldSerializeValue(mediaPlayer));
Assert.Equal(string.Empty, urlProperty.GetValue(mediaPlayer));
urlProperty.SetValue(mediaPlayer, "Movie.mpg");
// This will be the fully qualified path name, which is based on the current directory.
Assert.EndsWith("Movie.mpg", (string)urlProperty.GetValue(mediaPlayer));
var converter = (Com2ExtendedTypeConverter)urlProperty.Converter;
Assert.IsAssignableFrom<StringConverter>(converter.InnerConverter);
}
[WinFormsFact]
public void ComNativeDescriptor_GetProperties_StdAccessible()
{
using Control control = new();
control.CreateControl();
using ComScope<IAccessible> accessible = new(null);
HRESULT hr = PInvoke.CreateStdAccessibleObject(
control.HWND,
(int)OBJECT_IDENTIFIER.OBJID_CLIENT,
IID.Get<IAccessible>(),
accessible);
object comWrapper = UnknownComWrappers.Instance.GetOrCreateObjectForComInstance(accessible, CreateObjectFlags.None);
ComNativeDescriptor descriptor = new();
var properties = descriptor.GetProperties(comWrapper);
Assert.Equal(4, properties.Count);
var accChildCount = properties["accChildCount"];
Assert.True(accChildCount.IsReadOnly);
Assert.Equal(0, accChildCount.GetValue(comWrapper));
var accFocus = properties["accFocus"];
Assert.True(accFocus.IsReadOnly);
// VT_EMPTY - Nothing has focus
Assert.Null(accFocus.GetValue(comWrapper));
}
[StaFact]
public void ComNativeDescriptor_GetProperties_StandardDispatchAccessible_MultiClassInfo()
{
// Hitting the IProvideMultipleClassInfo with our stub object created over a system created IAccessible TypeInfo.
StandardAccessibleObjectWithMultipleClassInfo value = new();
using var accessible = ComHelpers.GetComScope<IAccessible>(value);
object comWrapper = UnknownComWrappers.Instance.GetOrCreateObjectForComInstance(accessible, CreateObjectFlags.None);
ComNativeDescriptor descriptor = new();
var properties = descriptor.GetProperties(comWrapper);
Assert.Equal(4, properties.Count);
var accChildCount = properties["accChildCount"];
Assert.True(accChildCount.IsReadOnly);
Assert.Equal(42, accChildCount.GetValue(comWrapper));
var accFocus = properties["accFocus"];
Assert.True(accFocus.IsReadOnly);
Assert.Equal((int)PInvoke.CHILDID_SELF, accFocus.GetValue(comWrapper));
}
[WinFormsFact(Skip = "Causes test run to abort, must be run manually.")]
public void ComNativeDescriptor_GetProperties_FromSimpleVBControl()
{
if (RuntimeInformation.ProcessArchitecture != Architecture.X86)
{
return;
}
// Not much to see with this control, but it does exercise a fair amount of code.
ComClasses.VisualBasicSimpleControl.CreateInstance(out object vbcontrol).ThrowOnFailure();
var properties = TypeDescriptor.GetProperties(vbcontrol);
Assert.Empty(properties);
var events = TypeDescriptor.GetEvents(vbcontrol);
Assert.Empty(events);
var attributes = TypeDescriptor.GetAttributes(vbcontrol);
Assert.Equal(2, attributes.Count);
BrowsableAttribute browsable = (BrowsableAttribute)attributes[0];
Assert.True(browsable.Browsable);
DesignTimeVisibleAttribute visible = (DesignTimeVisibleAttribute)attributes[1];
Assert.False(visible.Visible);
}
/// <summary>
/// A simple stub that implements IDispatch for IAccessible and also implements IProvideMultipleClassInfo that
/// just forwards to the IDispatch ITypeInfo to exercise the IProvideMultipleClassInfo code path.
/// </summary>
private unsafe class StandardAccessibleObjectWithMultipleClassInfo :
AccessibleDispatch,
IProvideMultipleClassInfo.Interface,
IAccessible.Interface,
IManagedWrapper<IAccessible, IDispatch, IProvideMultipleClassInfo>
{
HRESULT IProvideMultipleClassInfo.Interface.GetClassInfo(ITypeInfo** ppTI) => HRESULT.E_NOTIMPL;
HRESULT IProvideMultipleClassInfo.Interface.GetGUID(uint dwGuidKind, Guid* pGUID) => HRESULT.E_NOTIMPL;
HRESULT IProvideMultipleClassInfo.Interface.GetMultiTypeInfoCount(uint* pcti)
{
if (pcti is null)
{
return HRESULT.E_POINTER;
}
*pcti = 1;
return HRESULT.S_OK;
}
HRESULT IProvideMultipleClassInfo.Interface.GetInfoOfIndex(
uint iti,
MULTICLASSINFO_FLAGS dwFlags,
ITypeInfo** pptiCoClass,
uint* pdwTIFlags,
uint* pcdispidReserved,
Guid* piidPrimary,
Guid* piidSource)
{
if (pptiCoClass is null)
{
return HRESULT.E_POINTER;
}
if (dwFlags != MULTICLASSINFO_FLAGS.MULTICLASSINFO_GETTYPEINFO)
{
return HRESULT.E_NOTIMPL;
}
return ((IDispatch.Interface)this).GetTypeInfo(iti, 0, pptiCoClass);
}
HRESULT IAccessible.Interface.get_accChildCount(int* pcountChildren)
{
if (pcountChildren is null)
{
return HRESULT.E_POINTER;
}
*pcountChildren = 42;
return HRESULT.S_OK;
}
HRESULT IAccessible.Interface.get_accFocus(VARIANT* pvarChild)
{
if (pvarChild is null)
{
return HRESULT.E_POINTER;
}
*pvarChild = (VARIANT)(int)PInvoke.CHILDID_SELF;
return HRESULT.S_OK;
}
HRESULT IProvideClassInfo2.Interface.GetClassInfo(ITypeInfo** ppTI) => HRESULT.E_NOTIMPL;
HRESULT IProvideClassInfo2.Interface.GetGUID(uint dwGuidKind, Guid* pGUID) => HRESULT.E_NOTIMPL;
HRESULT IProvideClassInfo.Interface.GetClassInfo(ITypeInfo** ppTI) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accParent(IDispatch** ppdispParent) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accChild(VARIANT varChild, IDispatch** ppdispChild) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accName(VARIANT varChild, BSTR* pszName) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accValue(VARIANT varChild, BSTR* pszValue) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accDescription(VARIANT varChild, BSTR* pszDescription) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accRole(VARIANT varChild, VARIANT* pvarRole) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accState(VARIANT varChild, VARIANT* pvarState) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accHelp(VARIANT varChild, BSTR* pszHelp) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, int* pidTopic) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accKeyboardShortcut(VARIANT varChild, BSTR* pszKeyboardShortcut) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accSelection(VARIANT* pvarChildren) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.get_accDefaultAction(VARIANT varChild, BSTR* pszDefaultAction) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.accSelect(int flagsSelect, VARIANT varChild) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.accLocation(int* pxLeft, int* pyTop, int* pcxWidth, int* pcyHeight, VARIANT varChild) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.accNavigate(int navDir, VARIANT varStart, VARIANT* pvarEndUpAt) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.accHitTest(int xLeft, int yTop, VARIANT* pvarChild) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.accDoDefaultAction(VARIANT varChild) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.put_accName(VARIANT varChild, BSTR szName) => HRESULT.E_NOTIMPL;
HRESULT IAccessible.Interface.put_accValue(VARIANT varChild, BSTR szValue) => HRESULT.E_NOTIMPL;
}
private class UnknownComWrappers : ComWrappers
{
private UnknownComWrappers() { }
public static ComWrappers Instance { get; } = new UnknownComWrappers();
protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
{
throw new NotImplementedException();
}
protected override object CreateObject(nint externalComObject, CreateObjectFlags flags)
{
return new UnknownClass() { Unknown = externalComObject };
}
protected override void ReleaseObjects(IEnumerable objects)
{
throw new NotImplementedException();
}
private class UnknownClass
{
public nint Unknown { get; set; }
}
}
}
|