How do I convert a method name to a method index for the purpose of INTERFACEINFO?

The IMessage­Filter::Handle­Incoming­Call method describes the incoming call by means of an INTERFACE­INFO structure:

typedef struct tagINTERFACEINFO { 
  IID iid; 
  WORD wMethod; 

The wMethod is a zero-based index of the method within the interface. For example, IUnknown::Query­Interface has index zero, IUnknown::Add­Ref has index one, and IUnknown::Release has index two.

If you want to filter on a method in an interface, you need to know its index. One way of doing this would be to sit and count the methods, but this is error-prone, especially if the interface is still under active development and is not yet set in stone.

C to the rescue.

The IDL compiler spits out a C-compatible structure for the virtual function table, and you can use that structure to derive the method indices. For example:

#if defined(__cplusplus) && !defined(CINTERFACE)
#else   /* C style interface */
    typedef struct IPersistStreamVtbl

        HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
            __RPC__in IPersistStream * This,
            /* [in] */ __RPC__in REFIID riid,
            /* [annotation][iid_is][out] */
            _COM_Outptr_  void **ppvObject);

            __RPC__in IPersistStream * This);

            __RPC__in IPersistStream * This);

            __RPC__in IPersistStream * This,
            /* [out] */ __RPC__out CLSID *pClassID);

            __RPC__in IPersistStream * This);

            __RPC__in IPersistStream * This,
            /* [unique][in] */ __RPC__in_opt IStream *pStm);

            __RPC__in IPersistStream * This,
            /* [unique][in] */ __RPC__in_opt IStream *pStm,
            /* [in] */ BOOL fClearDirty);

            __RPC__in IPersistStream * This,
            /* [out] */ __RPC__out ULARGE_INTEGER *pcbSize);

    } IPersistStreamVtbl;
#endif  /* C style interface */

(You get roughly the same thing if you use the DECLARE_INTERFACE macros.)

After we remove the distractions, the structure is just

    typedef struct IPersistStreamVtbl
        HRESULT (*QueryInterface)(...);
        ULONG (*AddRef)(...);
        ULONG (*Release)(...);
        HRESULT (*GetClassID)(...);
        HRESULT (*IsDirty)(...);
        HRESULT (*Load)(...);
        HRESULT (*Save)(...);
        HRESULT (*GetSizeMax)(...);
    } IPersistStreamVtbl;

From this, we can write a macro which extracts the method index:

// If your compiler supports offsetof, then you can use that
// instead of FIELD_OFFSET.
#define METHOD_OFFSET(itf, method) FIELD_OFFSET(itf##Vtbl, method)

#define METHOD_INDEX(itf, method) \
    ((METHOD_OFFSET(itf, method) - \
      METHOD_OFFSET(itf, QueryInterface)) / sizeof(FARPROC))

The macro works by looking at the position of the method in the vtable and calculating its index relative to Query­Interface, which we know has index zero for all IUnknown-derived COM interfaces.

These macros assume that the size of a pointer-to-function is the same regardless of the prototype, but this assumption is safe to make because it is required by the COM ABI.

Observe that in order to get the C-style interfaces, you must define the CINTERFACE macro before including the header file. (And observe that the C-style interfaces are not available in C++; you must do this in C.)

If the bulk of your program is in C++, you can slip in a single C file to extract the method indices and expose them to the C++ side either through global variables or short functions. Depending on how fancy your link-time code generator is, the global variable or function call might even become eliminated.

Comments (6)
  1. Joshua says:

    #define CINTERFACE gives you the C interface even if coding in C++.

  2. Medinoc says:

    I was surprised when I saw BEGIN_INTERFACE and END_INTERFACE for the first time, because the SDK COM Tutorial Samples didn't use them. I guess the fact that it's defined to nothing on x86 platforms has to do with it.

    (also, +1 to Adam and Joshua)

  3. SpecLad says:

    What kind of compiler doesn't support offsetof?

    [A very old one. -Raymond]
  4. Adam Rosenfield says:

    #if defined(__cplusplus) && !defined(CINTERFACE)


    #else   /* C style interface */

    Wouldn't this give you the C-style interface if you're coding in C *or* if you define the CINTERFACE macro in C++?  The behavior in C shouldn't depend on CINTERFACE, since a C compiler cannot compile the C++ interface.

  5. Joshua says:

    [A very old one. -Raymond]

    I found this in stddef of a 16 bit compiler:

    #define offsetof(type, member) ((size_t)&(((type *)0)->member))

    I personally can't imaging any Win32 compiler missing it.

  6. 640k says:

    Are (latest) plaform/windows sdk officially supported with any other compiler than msvc?

Comments are closed.