How can I instantiate a COM class interface generi

2019-09-14 21:10发布

问题:

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?

回答1:

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.



回答2:

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)