Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the C# compiler detect COM types?

By no means am I an expert in this, but I stumbled recently on what I think you want: the CoClass attribute class.

[System.Runtime.InteropServices.CoClass(typeof(Test))]
public interface Dummy { }

A coclass supplies concrete implementation(s) of one or more interfaces. In COM, such concrete implementations can be written in any programming language that supports COM component development, e.g. Delphi, C++, Visual Basic, etc.

See my answer to a similar question about the Microsoft Speech API, where you're able to "instantiate" the interface SpVoice (but really, you're instantiating SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }

Between you and Michael you've almost got the pieces put together. I think this is how it works. (I didn't write the code, so I might be slightly mis-stating it, but I'm pretty sure this is how it goes.)

If:

  • you are "new"ing an interface type, and
  • the interface type has a known coclass, and
  • you ARE using the "no pia" feature for this interface

then the code is generated as (IPIAINTERFACE)Activator.CreateInstance(Type.GetTypeFromClsid(GUID OF COCLASSTYPE))

If:

  • you are "new"ing an interface type, and
  • the interface type has a known coclass, and
  • you ARE NOT using the "no pia" feature for this interface

then the code is generated as if you'd said "new COCLASSTYPE()".

Jon, feel free to bug me or Sam directly if you have questions about this stuff. FYI, Sam is the expert on this feature.


Okay, this is just to put a bit more flesh on Michael's answer (he's welcome to add it in if he wants to, in which case I'll remove this one).

Looking at the original PIA for Word.Application, there are three types involved (ignoring the events):

[ComImport, TypeLibType(...), Guid("..."), DefaultMember("Name")]
public interface _Application
{
     ...
}

[ComImport, Guid("..."), CoClass(typeof(ApplicationClass))]
public interface Application : _Application
{
}

[ComImport, ClassInterface(...), ComSourceInterfaces("..."), Guid("..."), 
 TypeLibType((short) 2), DefaultMember("Name")]
public class ApplicationClass : _Application, Application
{
}

There are two interfaces for reasons that Eric Lippert talks about in another answer. And there, as you said, is the CoClass - both in terms of the class itself and the attribute on the Application interface.

Now if we use PIA linking in C# 4, some of this is embedded in the resulting binary... but not all of it. An application which just creates an instance of Application ends up with these types:

[ComImport, TypeIdentifier, Guid("..."), CompilerGenerated]
public interface _Application

[ComImport, Guid("..."), CompilerGenerated, TypeIdentifier]
public interface Application : _Application

No ApplicationClass - presumably because that will be loaded dynamically from the real COM type at execution time.

Another interesting thing is the difference in the code between the linked version and the non-linked version. If you decompile the line

Word.Application application = new Word.Application();

in the referenced version it ends up as:

Application application = new ApplicationClass();

whereas in the linked version it ends up as

Application application = (Application) 
    Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("...")));

So it looks like the "real" PIA needs the CoClass attribute, but the linked version doesn't because there isn't a CoClass the compiler can actually reference. It has to do it dynamically.

I might try to fake up a COM interface using this information and see if I can get the compiler to link it...


Just to add a bit of confirmation to Michael's answer:

The following code compiles and runs:

public class Program
{
    public class Foo : IFoo
    {
    }

    [Guid("00000000-0000-0000-0000-000000000000")]
    [CoClass(typeof(Foo))]
    [ComImport]
    public interface IFoo
    {
    }

    static void Main(string[] args)
    {
        IFoo foo = new IFoo();
    }
}

You need both the ComImportAttribute and the GuidAttribute for it to work.

Also note the information when you hover the mouse over the new IFoo(): Intellisense properly picks up on the information: Nice!