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

Impossible to register an instance of activeX object #578

Open
Ducatel opened this Issue Jul 2, 2018 · 13 comments

Comments

2 participants
@Ducatel

Ducatel commented Jul 2, 2018

Hi,

I'm trying to register an instance of an activeX (ATL/COM object) but the register fail.
When I call RegisterInstance, an exception was throw:

The supplied type __ComObject does not implement InteropFactoryX.
Nom du paramètre : serviceType

A Sample which produce this error:

InteropFactoryX interopFactory = getInteropFactoryX(); // The type is API.InteropFactoryX {System.__ComObject}
Container appContainer = new Container();
appContainer.RegisterInstance<InteropFactoryX>(interopFactory); // throw the exception

image

So why I have this error ? There is somethings special to do when we try to store activeX object ?

Thanks in advance for your help

Ps: SimpleInjector version 4.3.0
Ps2: When I wrap the interopFactory in a class, it's working.

class Toto{
    public InteropFactoryX  t;
}
......

InteropFactoryX interopFactory = getInteropFactoryX();
Toto toto = new Toto 
{
   t = interopFactory ;
}
Container appContainer = new Container();
appContainer.RegisterInstance<Toto >(toto ); // it's working
appContainer.GetInstance<Toto >().xxxxx; // it's working
@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jul 2, 2018

Can you post the full stack trace?

@Ducatel

This comment has been minimized.

Ducatel commented Jul 2, 2018

There it is:

à SimpleInjector.Requires.ThrowSuppliedTypeDoesNotInheritFromOrImplement(Type service, Type implementation, String paramName)
à SimpleInjector.Requires.ServiceIsAssignableFromImplementation(Type service, Type implementation, String paramName)
à SimpleInjector.InstanceProducer..ctor(Type serviceType, Registration registration)
à SimpleInjector.Container.AddRegistrationInternal(Type serviceType, Registration registration)
à SimpleInjector.Container.AddRegistration(Type serviceType, Registration registration)
à SimpleInjector.Container.RegisterInstance[TService](TService instance)
à Addin.Main.connectToAPI(Object InteropFactory) dans J:\sources\AddIns\Main.cs:ligne 106
@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jul 2, 2018

Under the covers, Simple Injector uses the Type.IsAssignableFrom method to determine whether or not the implementation type is correct.

This means that, in your case, the following statement returns false:

typeof(InteropFactoryX).IsAssignableFrom(interopFactory.GetType())

I'm unsure how to actually fix this.

@Ducatel

This comment has been minimized.

Ducatel commented Jul 3, 2018

Yes, your are right

typeof(InteropFactoryX).IsAssignableFrom(interopFactory.GetType()) == false

interopFactory.GetType() return

BaseType: {Name = "MarshalByRefObject" FullName = "System.MarshalByRefObject"}
IsCOMObject = true
...

I will search if in can do some tricks on my object to force typeof(InteropFactoryX).IsAssignableFrom(interopFactory.GetType()) == true

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jul 3, 2018

Perhaps the internal validation can be changed, but I'm unsure how to effectively detect that the proxy object is castable to the given base type.

A possible workaround would be to create create a class that wraps the interopFactory while implementing InteropFactoryX. That wrapper than can forward the calls to the wrapped interopFactory. This wrapper can be registered in Simple Injector. For instance:

public class InteropWrapper : InteropFactoryX
{
    public InteropFactoryX Instance { get; set; }

    // Implement all methods as forwards.
    void InteropFactoryX.Method1() => this.Instance.Method1();
}

container.RegisterInstance<InteropFactoryX>(new InteropWrapper { Instance = interopFactory });
@Ducatel

This comment has been minimized.

Ducatel commented Jul 3, 2018

Ok, so I found some solution for managing ComObject.
The first is to use TypeName and do something like

if(obj.GetType().IsCOMObject)
{
    string realComObjectType = Microsoft.VisualBasic.Information.TypeName(obj);
    if( realComObjectType == TypeInTemplate)
       // obj can be added
}

The second is more dirty. Just the way to get the type name is different.
You should define IDispatch interface and use it in the method GetComObjectRealTypeName

public static string GetComObjectRealTypeName(object comObj)
        {

            if (comObj == null)
                return String.Empty;

            if (!Marshal.IsComObject(comObj))
                //The specified object is not a COM object
                return String.Empty;

            IDispatch dispatch = comObj as IDispatch;
            if (dispatch == null)
                //The specified COM object doesn't support getting type information
                return String.Empty;

            System.Runtime.InteropServices.ComTypes.ITypeInfo typeInfo = null;
            try
            {
                try
                {
                    // obtain the ITypeInfo interface from the object
                    dispatch.GetTypeInfo(0, 0, out typeInfo);
                }
                catch (Exception ex)
                {
                    //Cannot get the ITypeInfo interface for the specified COM object
                    return String.Empty;
                }

                string typeName = "";
                string documentation, helpFile;
                int helpContext = -1;

                try
                {
                    //retrieves the documentation string for the specified type description 
                    typeInfo.GetDocumentation(-1, out typeName, out documentation,
                        out helpContext, out helpFile);
                }
                catch (Exception ex)
                {
                    // Cannot extract ITypeInfo information
                    return String.Empty;
                }
                return typeName;
            }
            catch (Exception ex)
            {
                // Unexpected error
                return String.Empty;
            }
            finally
            {
                if (typeInfo != null) Marshal.ReleaseComObject(typeInfo);
            }
        }
    

    /// <summary>
    /// Exposes objects, methods and properties to programming tools and other
    /// applications that support Automation.
    /// </summary>
    [ComImport()]
    [Guid("00020400-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IDispatch
    {
        [PreserveSig]
        int GetTypeInfoCount(out int Count);

        [PreserveSig]
        int GetTypeInfo(
            [MarshalAs(UnmanagedType.U4)] int iTInfo,
            [MarshalAs(UnmanagedType.U4)] int lcid,
            out System.Runtime.InteropServices.ComTypes.ITypeInfo typeInfo);

        [PreserveSig]
        int GetIDsOfNames(
            ref Guid riid,
            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)]
            string[] rgsNames,
            int cNames,
            int lcid,
            [MarshalAs(UnmanagedType.LPArray)] int[] rgDispId);

        [PreserveSig]
        int Invoke(
            int dispIdMember,
            ref Guid riid,
            uint lcid,
            ushort wFlags,
            ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams,
            out object pVarResult,
            ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
            IntPtr[] pArgErr);
    }

ps: your solution is very hard to use and maintain. I have somethings like 50 Com objects to store is SimpleInjector 😄

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jul 3, 2018

Also try the following:

container.RegisterSingleton<InteropFactoryX>(() => interopFactory);

That will circumvent the IsAssignableFrom validation, because this is done by the compiler.

@Ducatel

This comment has been minimized.

Ducatel commented Jul 3, 2018

This is working but there is an exception when I call Verify()
message:

The configuration is invalid. Creating the instance for type InteropFactoryX failed. The registered delegate for type InteropFactoryX threw an exception. Les types des arguments ne correspondent pas.

Stacktrace:

à SimpleInjector.InstanceProducer.VerifyExpressionBuilding()
à SimpleInjector.Container.VerifyThatAllExpressionsCanBeBuilt(InstanceProducer[] producersToVerify)
à SimpleInjector.Container.VerifyThatAllExpressionsCanBeBuilt()
à SimpleInjector.Container.VerifyInternal(Boolean suppressLifestyleMismatchVerification)
à SimpleInjector.Container.Verify(VerificationOption option)
à SimpleInjector.Container.Verify()
à Addin.Main.connectTAPI(Object interopFactory) dans J:\sources\Main.cs:ligne 119

And if I drop the Verify, another exception is throw when I do the get

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jul 3, 2018

Do you have simple example for me that I can use to reproduce the issue locally, and possibly include as a unit test?

@Ducatel

This comment has been minimized.

Ducatel commented Jul 3, 2018

I thinks the problem should be the same we Excel Com object for example.
I will try to create a solution which reproduce this error.

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jul 3, 2018

Ideally, I would use an example that doesn't require an out-of-process object to be created, since the unit tests must be able to run on AppFayor—not just on a developer's machine.

@Ducatel

This comment has been minimized.

Ducatel commented Jul 3, 2018

You can try to get an activeX object on internet explorer.
It should be present on all windows computer

@Ducatel

This comment has been minimized.

Ducatel commented Jul 3, 2018

Ok, so you can found in the archive a minimal example
ConsoleApp1.zip

it's pretty simple, just a console app with ref on Microsoft Internet Controls and inclusion of SimpleInjector package.

The code is:

using SimpleInjector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Container c = new Container();
                SHDocVw.InternetExplorer IE = new SHDocVw.InternetExplorer();


                c.RegisterInstance(IE);
                c.Verify();
                var test = c.GetInstance<SHDocVw.InternetExplorer>();
                test.ToolBar = 0;
            }
            catch(Exception e)
            {
                throw e;
            }
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment