Search code examples
comidlmidl

Is inheritance allowed on a COM interface which has the nonextensible attribute?


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?


Solution

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