1

I have an interface which inherits from another interface, like this:

[
    object,
    uuid(72A6E473-9956-4856-A335-B9169359AACE),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IA : IDispatch
{
    HRESULT MethodA();
}

[
    object,
    uuid(378846D3-7E24-4DAE-B4DF-69AA4B0C1AA9),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IB : IA
{
    HRESULT MethodB();
}

[
    object,
    uuid(4C187526-6809-4A57-A3ED-626E0B36F7DB),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface ICollection : IDispatch
{
    HRESULT GetObject([out, retval] IA** ppValue);
}

The ICollection implementation will return an object which implements IB. That object will provide access to MethodA and MethodB via the dispatch interface. There will be no additional members added at run time.

Is the nonextensible attribute on IA allowed in this case?

Johan
  • 3,039
  • 1
  • 20
  • 15

1 Answers1

1

Is the nonextensible attribute on IA allowed in this case?

Yes, it is.

The [nonextensible] attribute of an interface, translated into the typelib type flag TYPEFLAG_FNONEXTENSIBLE, has the practical effect of telling Visual Basic (6 or for Application) not to look for missing members with the underlying IDispatch.

Dim A As IA
Set A = Obj ' QueryInterface
A.MethodA
' Compilation error
' A.MethodB
' If A.Property Then A.MethodC

Dim B As IB
Set B = Obj ' QueryInterface
B.MethodA
B.MethodB
' Compilation error
' If B.Property Then B.MethodC

Without [nonextensible], Visual Basic would compile the missing method calls and property accesses, if uncommented, as calls to IDispatch::GetIDsOfNames followed by IDispatch::Invoke.

Generally, this is a good thing if you favor compile-time checks, forcing the code to declare the variable as Object or Variant if it needs dynamic lookup. It might be a hassle on expando objects, but those violate one point of IDispatch's contract anyway:

IDispatch::GetIDsOfNames method

Remarks

The member and parameter DISPIDs must remain constant for the lifetime of the object. This allows a client to obtain the DISPIDs once, and cache them for later use.

Caution You cannot use this method to access values that have been added dynamically, such as values added through JavaScript. Instead, use the GetDispID of the IDispatchEx interface. For more information, see the IDispatchEx interface.


EDIT: Here's a sample you can try with Word VBA (Alt+F11), add a macro and paste this text:

Dim Docs As Documents
Set Docs = Application.Documents
Docs.Add
' Compilation error
' Docs.Foo
Dim Doc As Document
Set Doc = Docs(0)
Doc.Activate
Doc.Foo

To try it out, select the menu Debug → Compile Project.

It should compile successfully. Although the (default) interface (for) Document doesn't have a Foo method, it's not [nonextensible] (careful with the double negative), so the call is turned into runtime dispatching.

Now, uncomment Docs.Foo and recompile the project.

It should throw an error, because the (default) interface (for) Documents is [nonextensible] and the call to the missing Foo method is not turned into runtime dispatching.

acelent
  • 7,965
  • 21
  • 39
  • Can you explain this a bit more? I thought an IID was supposed to uniquely identify an interface, so "missing member" ought to be impossible. – M.M Jul 11 '14 at 01:07
  • Remember I'm talking about VB6/VBA, which do a lot of voodoo under the hood. One of its tricks is to fall back to automation on "missing members" unless the interface has the `[nonextensible]` attribute. I've added a Word VBA example which uses the two kinds of interfaces. – acelent Jul 11 '14 at 01:22
  • So all interfaces should be marked `nonextensible` really, except ones that intend to support this runtime adding of members. (Slightly OT but is there actually a problem in practice with adding new members to the end of an interface, without changing the IID?) – M.M Jul 11 '14 at 01:29
  • There is a problem: new clients will try to use the new members whether they're dealing with the new or the old definition of the interface. But note that this has nothing to do with adding members to the interface, or rather, the interface's vtable. It's runtime name dispatching, which may recognize members beyond the ones in the `IDispatch`-based interface you might be using. – acelent Jul 11 '14 at 09:05
  • @MattMcNabb, a rule of COM is: if the interface changes even slightly, even in semantics, it's a new interface, hence a new IID is required. – acelent Jul 11 '14 at 18:02
  • " It's runtime name dispatching, which may recognize members beyond the ones in the IDispatch-based interface you might be using." - are you saying that a dualinterface might have members that are only accessible by dispatch binding, and not by vtable binding? (i.e. their name does not appear in the type library)? If so, are those names also part of the "frozen" interface definition? – M.M Jul 11 '14 at 22:50
  • Yes, but you must explicitly `QueryInterface` for `IDispatch` for generic dispatch. A dual or dispinterface is allowed to only recognize its direct and inherited members. Not all names have to be described somewhere, e.g. an object provided to IE as `window.external` may be anonymous with a static name lookup table (refer to [`CreateDispTypeInfo`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms221237(v=vs.85).aspx), [Dual Interfaces and ATL](http://msdn.microsoft.com/en-us/library/ekfyh289.aspx) or [MFC Automation Servers](http://msdn.microsoft.com/en-us/library/6wx53dax.aspx)). – acelent Jul 12 '14 at 00:01
  • If you use a non-generic `IDispatch`, i.e. a derivate dual interface, you may or may not be able to access more members than described. The implementation of `IDispatch`-derived interfaces as returned by e.g. [`CreateStdDispatch`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms221135(v=vs.85).aspx), or `ITypeInfo::GetIDsOfNames` and `ITypeInfo::Invoke` from [`LoadTypeLib`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms221027(v=vs.85).aspx) definitely won't recognize further members, but a bare C/C++, ATL or MFC implementation might. – acelent Jul 12 '14 at 00:03