1

The System.Runtime.InteropServices.ClassInterfaceType Enumeration in .NET has an AutoDispatch value and an AutoDual value, but no AutoUnknown value. Why doesn't it, and has anyone already come up with a fairly automated way of accomplishing it, so that I don't have to reinvent the wheel?

For a bit more background, here are the three values in the enum currently and what they do:

  • ClassInterfaceType.None does not create a class interface for you. This is Microsoft's recommended value. With no class interface you have to create your own interface with whatever members you intend, and make your class implement it. You can then use the System.Runtime.InteropServices.ClassInterfaceAttribute attribute on your interface to make it a dispinterface (supporting late bound clients), an unknown interface (supporting early bound clients), or dual (supporting both early and late bound clients).
  • ClassInterfaceType.AutoDispatch creates a class interface derived from IDispatch with no explicit members. With no explicit members it can only support late-bound callers.
  • ClassInterfaceType.AutoDual creates a class interface derived from IDispatch that does have explicit members for all that class's public non-static members as well as all those of its base class and any implemented interfaces. Microsoft strongly discourages using this value "because of the versioning limitations" resulting from the fact that if any of the class's members change, the class interface will change as well. (So callers who don't re-bind after the class has changed could end up calling the wrong methods or passing incorrect parameters.)

So what I'm wondering is why there isn't a value called ClassInterfaceType.AutoUnknown which would create a class interface derived from IUnknown that explicitly declares the members of your class (not the base class or other interfaces it implements).

While we're at it, I would also expect there to be something like ClassInterfaceType.AutoDualDirect that would be similar to AutoDual except instead of exposing all members from the base class and all implemented interfaces it would only expose the class's direct members, since the other members could be retrieved through the other COM interfaces.

So what's the story here, what am I missing? I know Microsoft says it recommends against using AutoDual because of the "versioning challenges," and that those concerns would apply to some degree to AutoUnknown too; but despite that recommendation they still have AutoDual. Why wouldn't they also have AutoUnknown?

A word on those "versioning challenges": the most dangerous aspects of those concerns only apply to late-bound callers. AutoUnknown would generate an IUnknown-derived interface, not an IDispatch-derived interface, so we only have to concern ourselves with versioning issues that early-bound clients could have. And since the IID of any auto-generated class interface will change anytime the class's public non-static members change, an early-bound client would not be able to make a "bad call" to existing members, it would simply fail to QueryInterface() for the class interface. Still a failure, but a manageable one, not a crash or anything. This doesn't even break the COM rule of never allowing an interface to change; it's not changing, it's becoming a new interface (new IID). Indeed, it is practically no different from the IUnknown half of the AutoDual mechanism (minus the derived members). In fact, it suffers fewer "versioning challenges" than AutoDual because AutoDual has all the late-bound challenges as well.

So again I have to ask: why does AutoDual exist if AutoUnknown does not!

To give some actual examples, consider the following C# code:

using System.Runtime.InteropServices;

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class F
{
    public int foo()
    {
        return 5;
    }
}

generates the following idl:

[
  uuid(1F3A7DE1-99A1-37D4-943E-1BF5CFDF7DFA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")
]
coclass F {
    [default] interface _F;
    interface _Object;
};

[
  odl,
  uuid(3D0A1144-1C0B-3877-BE45-AD8318898790),
  hidden,
  dual,
  nonextensible,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")    

]
interface _F : IDispatch {
    [id(00000000), propget,
      custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
    HRESULT ToString([out, retval] BSTR* pRetVal);
    [id(0x60020001)]
    HRESULT Equals(
                    [in] VARIANT obj, 
                    [out, retval] VARIANT_BOOL* pRetVal);
    [id(0x60020002)]
    HRESULT GetHashCode([out, retval] long* pRetVal);
    [id(0x60020003)]
    HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x60020004)]
    HRESULT foo([out, retval] long* pRetVal);
};

That's fine, but I would propose that AutoUnknown would helpfully generate this idl instead:

[
  uuid(1F3A7DE1-99A1-37D4-943E-1BF5CFDF7DFA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")
]
coclass F {
    [default] interface _F;
    interface _Object;
};

[
  odl,
  uuid(BC84F393-DACC-353F-8DBE-F27CB2FB4757),
  version(1.0),
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "_F")    

]
interface _F : IUnknown {
    HRESULT _stdcall foo([out, retval] long* pRetVal);
};

If there's no way to do this automatically now, I'll be writing something that uses reflection to do it, so I'd appreciate any recommendations on that side of things too. For example, are there any attributes that I need to check for on methods? On parameters? I dug into the IL of System.Runtime.InteropServices.TypeLibConverter to see how it does it, but unfortunately all the goodies are in the private internalcall nConvertAssemblyToTypeLib() function which I can't get at.

Thanks!

bob
  • 452
  • 4
  • 11
  • 1
    You fundamentally misunderstand how COM works, starting with the meaning of [ClassInterfaceType]. Explaining it requires a book, there are many available. – Hans Passant Mar 18 '13 at 17:05
  • I understand quite well how COM works. Always more to learn of course, thank you for the observation that there are many books. Anyway, to reiterate my question: I am going to need `IUnknown`-derived interfaces based entirely on the public non-static members of my classes, so I was hoping for some ideas from the community on auto-generating them so that I don't have to hand write them upon each change. I'm open to any and all constructive ideas, including alternate ways of disallowing late-bound clients without hand-writing the interfaces. Thank you. – bob Mar 18 '13 at 18:05
  • "I am going to need `IUnknown`-derived interfaces based entirely on the public non-static members of my classes" - exactly what `AutoDual` provides. – Zdeslav Vojkovic Mar 18 '13 at 20:58
  • For each class, `AutoDual` generates an `IDispatch`-derived interface that includes (with dispids) the public non-static members of not just the class but also of the base class and of each interface my class or my base class (and its base class, and so on) implements. What a mess! – bob Mar 19 '13 at 00:23
  • Also @ZdeslavVojkovic one reason I don't want my base class members to show up in my interface is that the base class could change its own members. Suddenly my whole class interface (including its IID) has changed when my class's actual members haven't changed at all. If I were able to auto-generate *just* my class's own interface (and let my ancestor classes' interfaces take care of themselves) then I wouldn't have to worry every time I build my assembly that I'm suddenly not going to be callable by my clients. – bob Mar 19 '13 at 01:11
  • well, I quoted you from 18:05 and showed that AutoDual provides that - you didn't mention any tlb bloat (which is, IMO, a straw man, anyway). WRT to "mess", it is mess only if you decide to ignore the fact that the whole purpose of `ClassInterface` is exactly to expose the interface of the CLASS to COM clients - so you are actually complaining that the feature does what what it is designed for and not something else. If you want to have proper interfaces, use interfaces. – Zdeslav Vojkovic Mar 19 '13 at 06:52
  • The mess, @ZdeslavVojkovic, is not just from the bloat; it's also, as I've said, from the dangerous fact that AutoDual will pick up the changes of my ancestor classes. And I've been clear from the beginning that I'm looking for an automated way to generate an interface with the members of just my class, "not the base class or other interfaces it implements". If `ClassInterface` can't do this, that's fine, but I'm still looking for an existing way (maybe another attribute, something like `ClassSpecInterface`?) to do it, or constructive advice on pitfalls of doing it myself. Thanks! – bob Mar 19 '13 at 13:02

1 Answers1

0

The real question is what do you try to achieve?

From ClassInterface docs: "Indicates the type of class interface to be generated for a class exposed to COM, if an interface is generated at all."

The idea behind ClassInterface is to make available the programmatic interface of the class to COM clients - this is why you see full interface, including base class methods. The goal is not to provide a view to a subset of the class functionality. That's what interfaces are for (and ClassInterfaceType.None)

Providing a class interface without late binding capability (i.e. IDispatch) is something you shouldn't really do (and ClassInterface luckily doesn't enable it) as it is directly depending on the layout of your class to function properly. This means that neither the class nor its base classes should ever change. That is something which is hard to guarantee and why this is in general bad idea, besides the fact that it goes against whole philosophy of COM. Early-bound clients are tightly coupled to vtable layout of the interface - every change in the layout would break it. Late-bound clients don't exhibit this problem, as methods are resolved by name and they can handle missing method properly.

Using AutoDual already provides the early-bound access to the methods if you really need it, so I don't see why would you want to reinvent the wheel.

Zdeslav Vojkovic
  • 14,391
  • 32
  • 45
  • I want to support ONLY early-bound clients precisely because late-bound clients suffer the severe risks of version incompatibility, i.e. binary mismatches leading to crashing, while early-bound clients are required to QueryInterface() to get the interface they need, so with the IID having changed (because the interface changed) they would get E_NOINTERFACE which they can handle gracefully. As long as the IID changes when the interface changes, I see no reason a class interface without late binding capability shouldn't be provided. – bob Mar 18 '13 at 17:54
  • You got it backwards. Early-bound clients suffer binary mismatches as they are coupled to vtable layout, not late-bound - they just return an `DISP_E_MEMBERNOTFOUND` (or `DISP_E_BADPARAMCOUNT`/`DISP_E_BADVARTYPE`) in case of mismatch. However, `AutoDual` is fine for early-bound clients so just use that and you are ready to go. You are trying to use a feature to do something it was not designed to do. – Zdeslav Vojkovic Mar 18 '13 at 20:56
  • I'll concede that a properly written late-bound client will be safe from binary mismatches too, because it won't have cached interface details (such as dispids and parameter counts). I still don't see any reason here for `AutoUnknown` not to exist: I simply don't want to support late-bound clients; I don't want my interface to derive from `IDispatch`, I don't want the bloat in my type library. Without an answer here yet I'm proceeding with my own auto-generation for now, but would love to hear more ideas. Thanks (to you and all) for your insights so far! – bob Mar 19 '13 at 00:18