views:

2983

answers:

4

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?

+42  A: 

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 { }
Michael Petrotta
Very interesting - will try it later. The linked PIA types don't have CoClass though. Maybe it's something to do with the linking process - I'll have a look in the original PIA...
Jon Skeet
+1 for being awesome by writing the accepted answer when Eric Lippert and Jon Skeet also answered ;) No, really, +1 for mentioning CoClass.
OregonGhost
+25  A: 

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.

Eric Lippert
I wish I'd refreshed the page before doing a lot of decompiling :) I'm not quite sure how much of this will end up in the book, but it's all good background material.
Jon Skeet
+12  A: 

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

Jon Skeet
Great information. Thanks for the offer to add to my answer, but you have more experience in this area, and I can't really word it any better.
Michael Petrotta
It feels a little strange to answer a question along with Jon Skeet and Eric Lippert, and have mine marked as accepted. Especially when my information comes from just toying with Reflector. You may want to accept this answer.
Michael Petrotta
@Michael: Well, your answer was what I needed to move me further forward, so I think it's only right that I accepted it. It was the answer *I* found most helpful :) Right, now off to write up a blog post with a complete "fake COM" API... (I'll then link to it from the question)
Jon Skeet
+10  A: 

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!

Rasmus Faber