New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeLoadException is thrown when calling 'GetEnumerator()' on a COM object that has been successfully casted to 'IEnumerable' #19731

Closed
daxian-dbw opened this Issue May 12, 2017 · 8 comments

Comments

Projects
None yet
5 participants
@daxian-dbw
Contributor

daxian-dbw commented May 12, 2017

This is happening with netcoreapp2.0 version 2.0.0-preview1-002106-00, on windows 10 desktop.

Summary

I get the ShellWindows object by calling Shell.Application.Windows() method with C#. The ShellWindows object is enumerable, and it can be successfully cast to IEnumerable. After that, when I call GetEnumerator() on the casted IEnumerable object, a TypeLoadException is thrown:

Could not load type 'System.Runtime.InteropServices.ComTypes.IEnumerable' from assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'

Will the type System.Runtime.InteropServices.ComTypes.IEnumerable be added to .NET Core 2.0?

Repro

using System;
using System.Collections;
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;

namespace COMTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Guid clsId;
            int result = CLSIDFromProgID("Shell.Application", out clsId);
            Type type = Marshal.GetTypeFromCLSID(clsId);
            if (type == null) { Console.WriteLine("Failed to get type from CLSID"); return; }

            object obj = Activator.CreateInstance(type);
            int dispId = GetDispId(obj, "Windows");
            if (dispId == -1) { return; }

            object windows = Invoke(obj as IDispatch, dispId);

            IEnumerable enumerable = windows as IEnumerable;
            if (enumerable == null) { Console.WriteLine("Cannot cast COM object to IEnumerable"); return; }

            try
            {
                var enumerator = enumerable.GetEnumerator();
                if (enumerator == null) { Console.WriteLine("Cannot get enumerator"); return; }
                if (enumerator.MoveNext())
                {
                    Console.WriteLine("MoveNext runs successfully");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception caught from 'GetEnumerator()': {0}", ex.Message);
                Console.WriteLine("StackTrace:\n{0}", ex.StackTrace);

                if (ex.InnerException != null)
                {
                    Console.WriteLine("\nInnerException: {0}", ex.InnerException.Message);
                    Console.WriteLine("StackTrace:\n{0}", ex.InnerException.StackTrace);
                }
            }
        }
        
        static int GetDispId(object rcw, string methodName)
        {
            IDispatch dispatchObject = rcw as IDispatch;
            if (dispatchObject == null)
            {
                Console.WriteLine("Passed-in argument is not a IDispatch object");
                return -1;
            }


            int[] dispIds = new int[1];
            Guid emtpyRiid = Guid.Empty;
            dispatchObject.GetIDsOfNames(
                emtpyRiid,
                new string[] { methodName },
                1,
                0,
                dispIds);

            if (dispIds[0] == -1)
            {
                Console.WriteLine("Method name {0} cannot be recognized.", methodName);
            }

            return dispIds[0];
        }

        static object Invoke(IDispatch target, int dispId)
        {
            if (target == null) { Console.WriteLine("Cannot cast target to IDispatch."); return null; }

            IntPtr variantArgArray = IntPtr.Zero, dispIdArray = IntPtr.Zero, tmpVariants = IntPtr.Zero;
            int argCount = 0;

            var paramArray = new ComTypes.DISPPARAMS[1];
            paramArray[0].rgvarg = variantArgArray;
            paramArray[0].cArgs = argCount;
            paramArray[0].cNamedArgs = 0;
            paramArray[0].rgdispidNamedArgs = IntPtr.Zero;

            ComTypes.EXCEPINFO info = default(ComTypes.EXCEPINFO);
            object result = null;

            try
            {
                uint puArgErrNotUsed = 0;
                target.Invoke(dispId, new Guid(), 0x0409, ComTypes.INVOKEKIND.INVOKE_FUNC, paramArray, out result, out info, out puArgErrNotUsed);
            }
            catch (Exception ex)
            {
                Console.WriteLine("IDispatch.Invoke failed: {0}", ex.Message);
            }

            return result;
        }

        [DllImport("ole32.dll")]
        internal static extern int CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] string lpszProgID, out Guid pclsid);
    }
    
    [Guid("00020400-0000-0000-c000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport]
    internal interface IDispatch
    {
        [PreserveSig]
        int GetTypeInfoCount(out int info);

        [PreserveSig]
        int GetTypeInfo(int iTInfo, int lcid, out ComTypes.ITypeInfo ppTInfo);

        void GetIDsOfNames(
            [MarshalAs(UnmanagedType.LPStruct)] Guid iid,
            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] rgszNames,
            int cNames,
            int lcid,
            [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4)] int[] rgDispId);

        void Invoke(
            int dispIdMember,
            [MarshalAs(UnmanagedType.LPStruct)] Guid iid,
            int lcid,
            ComTypes.INVOKEKIND wFlags,
            [In, Out] [MarshalAs(UnmanagedType.LPArray)] ComTypes.DISPPARAMS[] paramArray,
            out object pVarResult,
            out ComTypes.EXCEPINFO pExcepInfo,
            out uint puArgErr);
    }
}

Expected Result

GetEnumerator() is successful, and then MoveNext() works too.

Actual Result

PS:73> dotnet run
Exception caught from 'GetEnumerator()': Could not load type 'System.Runtime.InteropServices.ComTypes.IEnumerable' from assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.
StackTrace:
   at System.Collections.IEnumerable.GetEnumerator()
   at COMTest.Program.Main(String[] args)

Environment

PS:74> dotnet --info
.NET Command Line Tools (2.0.0-preview1-005952)

Product Information:
 Version:            2.0.0-preview1-005952
 Commit SHA-1 hash:  356e309f17

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.15063
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   ...\AppData\Local\Microsoft\dotnet\sdk\2.0.0-preview1-005952\

Microsoft .NET Core Shared Framework Host

  Version  : 2.0.0-preview1-002106-00
  Build    : 86fe9816c8ba782241c441c3228c665e393c3ef3
@yizhang82

This comment has been minimized.

Contributor

yizhang82 commented May 24, 2017

@daxian-dbw Thanks for reporting this issue. As previously discussed, CoreCLR doesn't support IDispatch (and that's why you are implementing your own IDispatch). IEnumerable.GetEnumerator uses IDispatch.Invoke(DISPID_NEWENUM) under the hood (that's the convention for IDispatch objects to support enumeration). You need to write your own enumeration code to support that pattern in CoreCLR. Based on what you already have (IDispatch and VARIANT support), it should be pretty straight-forward. I'll be happy to help out offline if you need any help.

@yizhang82

This comment has been minimized.

Contributor

yizhang82 commented May 24, 2017

I have a PR out to make the exception better: dotnet/coreclr#11865

@daxian-dbw

This comment has been minimized.

Contributor

daxian-dbw commented May 24, 2017

@yizhang82 thanks for the taking a look. We have been using the pattern comObj as IEnumerable to check if a COM object is enumerable (see below code). Can we trust this pattern to continue working in .NET Core?

IEnumerable enumerable = comObj as IEnumerable;
if (enumerable != null) {
    // Try get an enumerator
}
@yizhang82

This comment has been minimized.

Contributor

yizhang82 commented May 25, 2017

The casting will keep working (mainly I want to avoid confusion because IEnumerable casting has too many possible scenarios) but IEnumerable.GetEnumerator will throw.

@yizhang82

This comment has been minimized.

Contributor

yizhang82 commented Jun 10, 2017

After PR dotnet/coreclr#11865, exception error message is now:

Exception caught from 'GetEnumerator()': IDispatch and IDispatchEx are not supported
StackTrace:
at System.Collections.IEnumerable.GetEnumerator()
at COMTest.Program.Main(String[] args)

@yizhang82 yizhang82 closed this Jun 10, 2017

@karelz

This comment has been minimized.

Member

karelz commented Jun 10, 2017

@yizhang82 the PR is only in master, hence in 2.1. Please either reopen this issue to track porting into rel/2.0.0 branch, or change milestone of this issue appropriately to 2.1. Thanks!

@yizhang82 yizhang82 modified the milestones: 2.1.0, 2.0.0 Jun 10, 2017

@yizhang82

This comment has been minimized.

Contributor

yizhang82 commented Jun 10, 2017

This is a simple fix for better exception and not needed in 2.0. Moved to 2.0.0 2.1.0

@mferraricloudsurfers

This comment has been minimized.

mferraricloudsurfers commented May 29, 2018

@yizhang82 I downloaded .NET Core 2.1 RC and i have the mentioned exception.

Exception caught from 'GetEnumerator()': IDispatch and IDispatchEx are not supported
StackTrace:
at System.Collections.IEnumerable.GetEnumerator()
at COMTest.Program.Main(String[] args)

I don't understand if exists a workaround to fix this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment