Skip to content
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' #21690

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

Comments

@daxian-dbw
Copy link
Contributor

@daxian-dbw daxian-dbw commented May 12, 2017

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

UPDATE (2/6/2020)

With .NET Core 3.1, (comObject as IEnumerable)?.GetEnumerator() works now.
However, casting a comObject to IEnumerator still doesn't work, for the following code will fail:

IEnumerator enumerator = targetValue as IEnumerator;
if (enumerator != null)
{
    enumerable.MoveNext();  // exception: Could not load type 'System.Runtime.InteropServices.ComTypes.IEnumerator' from assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'
    ....
}

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 yizhang82 self-assigned this May 24, 2017
@yizhang82
Copy link

@yizhang82 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
Copy link

@yizhang82 yizhang82 commented May 24, 2017

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

@daxian-dbw
Copy link
Contributor Author

@daxian-dbw 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
Copy link

@yizhang82 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
Copy link

@yizhang82 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
Copy link
Member

@karelz 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
Copy link

@yizhang82 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
Copy link

@mferraricloudsurfers 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.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 2.1.0 milestone Jan 31, 2020
@msftbot msftbot bot locked as resolved and limited conversation to collaborators Dec 23, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.