How can I instantiate a COM class interface generically
Asked Answered
B

2

0

I'm trying to refactor a piece of code and ran out of options I can think off.

This is the original code I had:

        if (WebConfigSettings.ComPartition == null && HttpContext.Current != null)
            Nses = new NSession();
        else
            Nses = (INSession)Marshal.BindToMoniker(string.Format("partition:{0}/new:NuntioServer.NSession", WebConfigSettings.ComPartition));

AND

        if (WebConfigSettings.ComPartition == null && HttpContext.Current != null)
            apses.Wses = new WSession();
        else
            apses.Wses = (IWSession)Marshal.BindToMoniker(string.Format("partition:{0}/new:NuntioServer.WSession", WebConfigSettings.ComPartition));  

And this is how I'm trying to refactor it:
(Yes, in C# you can instantiate an interface.)

    public static TInterface Get<TSubInterface, TInterface>() where TSubInterface: TInterface
    {
        <snip></snip>
        if (!useComPartitions)
            return Activator.CreateInstance<TSubInterface>(); // --> this is not cooperating

        return (TInterface)Marshal.BindToMoniker(.....);
    }

Here's what I already tried:

  1. I tried specifying the new() constraint and then doing a 'new TSubInterface()': this results in a build error: "..must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TSubInterface' in the generic type or method.."

  2. when I use Activator.CreateInstance, I get a runtime exception: "Cannot create an instance of an interface"

  3. when I use Activator.CreateComInstanceFrom("someAssemblyName", "typeName"), I get a compilation error: "Cannot convert expression type 'System.Runtime.Remoting.ObjectHandle' to return type TInterface"

[edit] I was able to make this compile by adding 'where TSubInterface : class, but I'm not sure if that makes sense, since TSubInterface is an interface.
Using CreateComInstanceFrom also doesn't work, because it's trying to find the assembly which is specified in a directory where that dll is not and should not be.

Can I somehow make this compile and run?

Bowlder answered 28/9, 2012 at 10:4 Comment(12)
Which code you use this method?Quagmire
nses = COMUtils.Get<NSession, INSession>() Both NSession and INSession are interfaces.Bowlder
You want it to work just because you can or you intend to use this in production code? If the other then you'd be better off with some inversion of control container to achieve similar thing.Rask
I wouldn't be surprised if whatever magic lets you instantiate an interface via C# (i.e. in your links) doesn't apply when working via reflection. I suspect the compiler translates the linked code into standard class-instantiating IL, but reflection doesn't have this option.Drosophila
I am confused by Both NSession and INSession are interface and you have a constraint NSession : INSession. Are these really meant to be 2 interfaces where one inherits the other? If so, why is the name of the interface in template TClass.Dozy
@ZdeslavVojkovic I agree with you, it is confusing :) Both are interfaces and NSession implements INSession (I can't change those interface names). So yes, I should change TClass to TSubInterface or something similar.Bowlder
@Drosophila that is also what I'm guessing. But I don't want to give up yet :) There has to be a way ;)Bowlder
Well, you could read the class name from the 'CoClass' attribute and instantiate one of those instead?Drosophila
I am afraid that it won't work without reading the metadata and instantiating a class, as most of CreateInstance overloads use the constructors of the specified type, without looking up the 'alias' class. However, I am interested in background of your decision to do it this way. It seems a very bad idea to me.Dozy
ok, I will add some code so that you can see where I started from..Bowlder
@ZdeslavVojkovic it seems you are right..Bowlder
In cases where there seems to be some compiler magic it is often worthwhile to open the code in question in reflector to see how its being done.Necrophilia
E
2

You'll need to focus on the seeming magic of being able to create a class object from an interface name. Let's pick an example that everybody can try. Create a new console application and use Project + Add Reference, Browse tab and select c:\windows\system32\shell32.dll.

Have a look at the interop library that generates with Object Browser. Note how the Shell type is an interface type. Now write this code:

class Program {
    static void Main(string[] args) {
        var shl = new Shell32.Shell();
    }
}

Compile and run ildasm.exe on the .exe file. You'll see:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] class [Interop.Shell32]Shell32.Shell 'shl')
  IL_0000:  nop
  IL_0001:  newobj     instance void [Interop.Shell32]Shell32.ShellClass::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ret
} // end of method Program::Main

Note how the type name got substituted from Shell to ShellClass. The type library importer created that class, it uses the original coclass name and append "Class" to the name. The compiler makes that substitution.

Which is the key, Activator.CreateInstance() is not able to make that same substitution. I don't see an obvious way to have generics make that same substitution, beyond directly using the IFooClass name instead of the interface name. Technically you can retrieve the [CoClass] attribute that the type library importer applied to the interface type.

Endymion answered 28/9, 2012 at 12:41 Comment(1)
Marked yours as correct, because it proposes a solution (use the 'CoClass' attribute) and explains some of the reasons why it's not as easy as I hoped forBowlder
B
0

It can be done by figuring out what's the coClass of that interface and creating an instance of that:

var coClassAttribute = type.GetCustomAttribute<CoClassAttribute>(); // our extension method
return (TSubInterface)Activator.CreateInstance(coClassAttribute.CoClass);

I am not satisfied with this, but it works. (won't mark this as the correct answer)

Bowlder answered 28/9, 2012 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.