How does the C# compiler detect COM types?

2019-01-01 06:46发布

问题:

EDIT: I\'ve written the results up as a blog post.


The C# compiler treats COM types somewhat magically. For instance, this statement looks normal...

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

... until you realise that Application is an interface. Calling a constructor on an interface? Yoiks! This actually gets translated into a call to Type.GetTypeFromCLSID() and another to Activator.CreateInstance.

Additionally, in C# 4, you can use non-ref arguments for ref parameters, and the compiler just adds a local variable to pass by reference, discarding the results:

// FileName parameter is *really* a ref parameter
app.ActiveDocument.SaveAs(FileName: \"test.doc\");

(Yeah, there are a bunch of arguments missing. Aren\'t optional parameters nice? :)

I\'m trying to investigate the compiler behaviour, and I\'m failing to fake the first part. I can do the second part with no problem:

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

[ComImport, GuidAttribute(\"00012345-0000-0000-0000-000000000011\")]
public interface Dummy
{
    void Foo(ref int x);
}

class Test
{
    static void Main()
    {
        Dummy dummy = null;
        dummy.Foo(10);
    }
}

I\'d like to be able to write:

Dummy dummy = new Dummy();

though. Obviously it\'ll go bang at execution time, but that\'s okay. I\'m just experimenting.

The other attributes added by the compiler for linked COM PIAs (CompilerGenerated and TypeIdentifier) don\'t seem to do the trick... what\'s the magic sauce?

回答1:

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


回答2:

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.



回答3:

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...



回答4:

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!