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

Impossible to register an instance of activeX object #578

Closed
Ducatel opened this issue Jul 2, 2018 · 22 comments
Closed

Impossible to register an instance of activeX object #578

Ducatel opened this issue Jul 2, 2018 · 22 comments
Milestone

Comments

@Ducatel
Copy link

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:

// The type is API.InteropFactoryX {System.__ComObject}
InteropFactoryX interopFactory = getInteropFactoryX();
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
Copy link
Collaborator

Can you post the full stack trace?

@Ducatel
Copy link
Author

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Collaborator

Also try the following:

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

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

@Ducatel
Copy link
Author

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
Copy link
Collaborator

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

@Ducatel
Copy link
Author

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Author

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;
            }
        }
    }
}

@Ducatel
Copy link
Author

Ducatel commented Feb 13, 2020

Hi,
There is any news on this subject ?

@dotnetjunkie
Copy link
Collaborator

Hi @Ducatel,

This feature is currently on the backlog, but its still uncertain whether we will add it to the v5.0 release. Don't get your hopes up and workaround accordingly.

@Ducatel
Copy link
Author

Ducatel commented Feb 13, 2020

That what I do, but I have something like 200 objects wrapped and this make my code quite ugly.
So I keep hope :D

@dotnetjunkie
Copy link
Collaborator

I just pushed my changes that fix this bug. Many thanks to your provided Program. That made fixing much easier. You can expect the bug to be released with v5. Unfortunately, I can't give you an ETA on v5, yet.

@Ducatel
Copy link
Author

Ducatel commented May 3, 2020

It's so cool, thanks for the fix ;)
I can wait the v5, don't worry :p

@dotnetjunkie
Copy link
Collaborator

Do note thatonly manually constructed COM objects will be supported. You can't let Simple Injector create your COM object. Would that be a problem?

@dotnetjunkie
Copy link
Collaborator

I'm happy to announce Simple Injector v5 has been released, which now allows registration of COM objects.

@Ducatel
Copy link
Author

Ducatel commented Jun 11, 2020

Youpi , I will test that soon :p

@dotnetjunkie
Copy link
Collaborator

New issue related to COM objects was reported here #986.

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

No branches or pull requests

2 participants