How does the C# compiler detect COM types?
Asked Answered
P

4

169

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?

Pointdevice answered 7/7, 2009 at 17:1 Comment(9)
You're way ahead of me on this stuff (most stuff), but just for clairification, it sounds like what you are after is dependency injection functionality with a more normalized syntax--would that be accurate?Berryman
Aren't optional parameters nice? IMO, No they are not nice. Microsoft is trying to fix the flaw in Office COM interfaces by adding bloat to C#.Haystack
Nice question (+1). I've noticed the same thing about calling a constructor on an interface. It looks horrendous in code because it'll really confuse any developers not familiar with what's going on. I always wondered what was going on behind the scenes to make it work. Looks like I'll be buying your new book ;-)Bor
Are you creating a type library for your interface? I suspect you need to declare the interface using IDL and then register the resulting type library MIDL outputs in the registry. I also suspect this is just one step on the way to your goal.Lanham
@Mehrdad: Optional parameters are useful beyond COM, of course. You need to be careful with the default values, but between them and named arguments, it's a lot easier to build a usable immutable type.Pointdevice
@jeffamaphone: Nope, I'm not trying to create a type library at all. I'm trying to fake up a COM type so I can see what the C# compiler does without actually having to build any COM types :)Pointdevice
True. Specifically, named parameters can be practically required for interop with some dynamic environments. Sure, without a doubt, it's a useful feature but that doesn't mean it comes for free. It costs simplicity (an explicitly stated design goal). Personally, I think C# is amazing for features the team left off (otherwise, it could have been a C++ clone). C# team is great but a corporate environment can hardly be politics-free. I guess Anders himself wasn't very happy about this as he stated in his PDC'08 talk: "took us ten years to get back to where we were."Haystack
I agree that the team will need to keep a close eye on complexity. The dynamic stuff adds a lot of complexity for little value for most developers, but high value for some developers.Pointdevice
I've seen framework developers beginning to discuss its uses in many places. IMO it's just time until we find a good use for dynamic ... we're just too used to static/strong typing to see why it'd matter outside of COM.Peele
O
146

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 { }
Obstruct answered 7/7, 2009 at 17:22 Comment(2)
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...Pointdevice
+1 for being awesome by writing the accepted answer when Eric Lippert and Jon Skeet also answered ;) No, really, +1 for mentioning CoClass.Reeta
A
61

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.

Antepenult answered 7/7, 2009 at 18:42 Comment(0)
P
36

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

Pointdevice answered 7/7, 2009 at 18:55 Comment(0)
K
27

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!

Kenner answered 7/7, 2009 at 18:56 Comment(1)
thanks, i was trying but i was missing ComImport attribute, but when i go i to source code i was working using F12 only shows CoClass and Guid, why is that?Testify

© 2022 - 2024 — McMap. All rights reserved.