Search code examples
c#com

C# COM visible type: is GUID needed?


I have the following class:

[ComVisible(true)]
[Guid("F8351C66-7F0E-4E38-AE64-9A262202E230")]
[ProgId("CarProject.CarFactory")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class CarFactory
{
    public CarFactory()
    {}

    [DispId(0)]
    public Car CreateCar(int tyres)
    {
         return new Car(tyres);
    }
}

[ComVisible(true)]
[Guid("83f622b9-74f4-4700-9167-52c4ce9e79aa")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Car
{
    [DispId(0)]
    public int NumberOfTyres { get; private set; }

    public Car(int tyres)
    {
        this.NumberOfTyres = tyres;
    }
}

The Car object is created by a factory therefore the COM client uses only the _Car interface autogenerated. Note that there isn't a default constructor so the class cannot be instantiated through COM.

My question is: is the Guid attribute needed? I cannot find its value in the registry.


Solution

  • No, it is not needed since it will never be used. In fact, it is never needed and applying [Guid] is a pretty bad practice.

    There are several other problems with this declaration style, discussing them all requires me to write a book and that is not very practical. It will be a lot easier for you to look at the decompiled type library so you can see what the client compiler sees. Use the Visual Studio Developer Command prompt, generates the type library if you don't have one yet with Tlbexp.exe. Run Oleview.exe, File > View Typelib and select the .tlb file. Highlighting the relevant details you now see:

    importlib("mscorlib.tlb");
    

    This is pretty awkward, the client programmer doesn't just have to add a reference to your type library but also the .NET type library for mscorlib.dll. Stored in the c:\windows\microsoft.net\framework\v4.0.30319 directory. Or v2.0.50727, the older version. Pretty unintuitive and does not often come to a good end. Note how you got in trouble with it in your previous question. Read on to find out why this happened.

    [
      odl,
      uuid(BD7A2C0E-E561-3EBC-8BB7-1C72EE61D5B0),
      hidden,
      dual,
      nonextensible,
      oleautomation,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary171.Car")    
    
    ]
    interface _Car : IDispatch {
        // etc..
    }
    

    This is the auto-generated "class interface", you already now about it. This is the one that the client compiler actually uses to make calls. Note the [uuid] attribute, same as the [Guid] attribute in C#. But it has a pretty random value. It was auto-generated, like the interface was. So using [Guid] in your declaration did not actually accomplish anything at all.

    The [hidden] attribute as well as the leading underscore on the interface name tell an type browser tool (like Object Browser in VS) to hide the declaration. Made it important to use OleView to see the details. Otherwise a quirk that goes back to the kind of languages that don't have support for interfaces, old versions of Visual Basic (like VB6 and VBA) and scripting languages (like VBScript and JavaScript) being the primary examples. Such languages need to emulate them by making the interface look like a class, the only reason why you'd consider exposing the class at all.

    [
      uuid(83F622B9-74F4-4700-9167-52C4CE9E79AA),
      version(1.0),
      noncreatable,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary171.Car")
    ]
    coclass Car {
        [default] interface _Car;
        interface _Object;
    };
    

    Note the [noncreatable] attribute. The type library exporter could see that client code can never create a Car instance since it has no default constructor. That in turn helps Regasm.exe figure out that registering the CLSID for Car is not necessary, that's why you could not find it back in the registry.

    And note the _Object interface. Appeared because every .NET class derives from System.Object. And, likewise, the _Car interface also has the methods of Object (ToString, Equals, GetHashCode, GetType) since Car inherited them. This is how you ended up with the dependency on mscorlib.tlb. They might be useful to the client programmer, but that is not common and you generally certainly don't want to have to document them. The less you expose, the better.


    Long story short, this happened because you used ClassInterfaceType.AutoDual. It is a convenience, but not a very good one and Microsoft strongly discourages it, recommending ClassInterfaceType.AutoDispatch instead. But that's only useful for scripting languages.

    You avoid all this by declaring the ICar interface explicitly instead of letting the type library exporter generate it for you. It needs to be [ComVisible(true)] and the class needs to implement it. Now you can use ClassInterfaceType.None.

    And now you can give the interface a [Guid]. But beware the bad practice this is, COM demands that interfaces are immutable. Once you exposed one in the wild, you can never change it again. Very important, COM has a nasty DLL Hell problem, caused by registration being machine-wide and you in general have no guarantee whatsoever that you can get the client programs recompiled. The crashes this can cause a very hard to diagnose. If you do have to change it then the interface needs a new [Guid] so both the old and the new interface can co-exist. Most trivially done by simply omitting the attribute entirely, the CLR already auto-generates it. Using [Guid] does allow creating interfaces that are binary compatible with an old interface declaration, but that's pretty hard to do correctly.