M. Johnstone
M. Johnstone

Reputation: 112

How to obtain a COM object type of an array?

Aim: Using reflection and generics pass in a VBA array of custom objects and return the same custom VBA COM object back after processing. Eg in the example removing duplicates from a VBA of any type array passed in as a variant containing an array.

For value types worked as expected returning an array of the same type passed in. See RemoveDuplicates

Issue: For custom VBA objects eg a Person array returns an array of variants containing person objects where expecting a Person array.

To remove duplicates for a custom object arrays RemoveDuplicates2 is used passing in the VBA array and an non-generic IEqualityComparer.

Apart from not returning the custom COM object array required is working as expected removing the duplicates within a Person array according to the IEqualityComparer supplied from VBA.

Note: For now named the RemoveDuplicates overloads in Array.cs uniquely to avoid any potiential issues requiring the knowlegde of expression trees to be investigated later.

SafeArraySingleton.cs

using GSystem = global::System;
using GArray = global::System.Array;
using GType = global::System.Type;
using GMethodInfo = global::System.Reflection.MethodInfo;

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections;
using DotNetLib.System.Collections.Generic;

namespace DotNetLib.System
{
    [ComVisible(true)]
    [Description("Provides methods for creating, manipulating, searching, and sorting safe arrays.")]
    [Guid("37592E87-83CE-4D47-A510-324FE529BE27")]
    [ProgId("DotNetLib.System.SafeArraySingleton")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(ISafeArraySingleton))]
    public class SafeArraySingleton : ISafeArraySingleton
    {
        const string RemoveDuplicatesMethod = "RemoveDuplicates";
        static GType _arrayType = typeof(Array);
        static  GMethodInfo _removeDuplicates = _arrayType.GetMethod("RemoveDuplicates",
                                                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);


        static GMethodInfo _removeDuplicates2 = _arrayType.GetMethod("RemoveDuplicates2",
                                                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
        static GType _genericComparerType = typeof(GenericEqualityComparer<>);

        public SafeArraySingleton() { }

        [return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
        public object RemoveDuplicates([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray)
        {
            if ((inputArray == null) | !(inputArray is GArray))
            {
                throw new ArgumentException("Must be an array.", nameof(inputArray));
            }
            var result = _removeDuplicates.MakeGenericMethod(inputArray.GetType().GetElementType()).Invoke(null, new object[] { inputArray });
            return result;
        }

        [return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
        public object RemoveDuplicates2([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray, IEqualityComparer nonGenericComparer)
        {
            if ((inputArray == null) | !(inputArray is GSystem.Array))
            {
                throw new ArgumentException("Must be an array.", nameof(inputArray));
            }

            GType elementType = inputArray.GetType().GetElementType();

            // Create a generic wrapper for the non-generic comparer using reflection
            var genericComparerType = typeof(GenericEqualityComparer<>);
            var genericType = genericComparerType.MakeGenericType(elementType);
            var genericComparer = Activator.CreateInstance(genericType, nonGenericComparer);

            var result = _removeDuplicates2.MakeGenericMethod(elementType).Invoke(null, new object[] { inputArray, genericComparer });
            return result;
        }

    }
}

ISafeArraySingleton.cs

using GCollections = global::System.Collections;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace DotNetLib.System
{
    [ComVisible(true)]
    [Guid("5B473306-F5E2-4C83-92F7-CEF4A363E2DD")]
    [Description("Provides methods for creating, manipulating, searching, and sorting safe arrays.")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface ISafeArraySingleton
    {
        [Description("Removes duplicates from an array.")]
        [return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
        object RemoveDuplicates([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray);

        [Description("Removes duplicates from an array.")]
        [return: MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)]
        object RemoveDuplicates2([MarshalAs(UnmanagedType.Struct, SafeArraySubType = VarEnum.VT_ARRAY)][In] ref object inputArray, GCollections.IEqualityComparer equalityComparer);

    }
}

From Array.cs

        public static T[] RemoveDuplicates<T>(T[] inputArray)
        {
            if (inputArray == null)
                throw new ArgumentNullException(nameof(inputArray));

            HashSet<T> set = new HashSet<T>(inputArray);
            return set.ToArray();
        }

        public static T[] RemoveDuplicates2<T>(T[] inputArray, IEqualityComparer<T> equalityComparer)
        {
            if (inputArray == null)
                throw new ArgumentNullException(nameof(inputArray));

            HashSet<T> set = new HashSet<T>(inputArray, equalityComparer);
            return set.ToArray();
        }

GenericEqualityComparer.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace DotNetLib.System.Collections.Generic
{
    [ComVisible(false)]
    // Generic wrapper for non-generic IEqualityComparer
    // GenericEqualityComparer<T> is a generic wrapper that delegates to the non-generic comparer.
    public class GenericEqualityComparer<T> : IEqualityComparer<T>
    {
        private readonly IEqualityComparer originalComparer;

        public GenericEqualityComparer(IEqualityComparer originalComparer)
        {
            this.originalComparer = originalComparer ?? throw new ArgumentNullException(nameof(originalComparer));
        }

        public bool Equals(T x, T y)
        {
            return originalComparer.Equals(x, y);
        }

        public int GetHashCode(T obj)
        {
            return originalComparer.GetHashCode(obj);
        }
    }
}

Upvotes: 0

Views: 68

Answers (0)

Related Questions