c++winapicodeblocks

Code::Blocks 20.03 - 64 bit Compiler error


I cannot compile the winapi code below using Code::Blocks 20.03 - 64 bit Compiler and get the following error messages:

#include <tchar.h>
#include <windows.h>
#include <commctrl.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <objidl.h>

LPNMTREEVIEW nmtv = (LPNMTREEVIEW)lParam;
TVITEM item = nmtv->itemNew;
LPSHELLFOLDER lpsf;

if(SUCCEEDED(SHGetDesktopFolder(&lpsf))) {
   LPITEMIDLIST lpidl = (LPITEMIDLIST) item.lParam;
   char m1[MAX_PATH];

   if(SHGetPathFromIDList(lpidl, m1)) {
      if(lpidl->mkid.cb) {
         LPSHELLFOLDER lpsfSub;
         if(SUCCEEDED(lpsf->lpVtbl->BindToObject(lpsf, lpidl, NULL, &IID_IShellFolder,(void **)&lpsfSub))) {
            FillTreeEx(lpsfSub, lpidl, item.hItem);
            lpsfSub->lpVtbl->Release(lpsfSub);
         }
      }
   }
   lpsf->lpVtbl->Release(lpsf);
}
error: 'IShellFolder' {aka 'struct IShellFolder'} has no member named 'lpVtbl'
error: 'IMalloc' {aka 'struct IMalloc'} has no member named 'lpVtbl'
error: 'IEnumIDList' {aka 'struct IEnumIDList'} has no member named 'lpVtbl'

Any suggestion?

I have tried default GNU GCC Compiler and msys64 mingw compiler. Nothing changed.


Solution

  • A diagnostic like

    error: 'IMalloc' {aka 'struct IMalloc'} has no member named 'lpVtbl'
    

    is issued when trying to access a COM object via its C-style interface but compiling the client code as C++. The solution is to either compile the code as C, or place

    #define CINTERFACE
    

    ahead of any #include directives that pull in COM interface definitions. The latter makes the C-style interface available when compiling as C++ (in exchange for the more convenient C++-style interface).

    When choosing to go through the C-style interface you can optionally enable helper macros via

    #define COBJMACROS
    

    that remove the need to spell out the interface pointer twice. Assuming that you have an IMalloc* pMalloc you can then write

    void* p = IMalloc_Alloc(pMalloc, 4096);
    

    instead of

    void* p = pMalloc->lpVtbl->Alloc(pMalloc, 4096);
    //        ~~~~~~~                ~~~~~~~
    

    All of this is only needed if you have a requirement to utilize the C-style interface. If you don't, just go with the C++ interface and benefit from a vastly superior developer experience. To make an informed decision here's a bit of background information.


    At the ABI a COM interface consists of a pointer to an array of function pointers. The function pointer array comprises the interface's functionality and is populated by concrete implementations ("COM objects"). Functions are called by index using a call-contract predefined by the interface. This design1 allows COM objects to be authored and consumed across a wide array of programming languages.

    A consequence of COM's reduced set of requirements is that it is incredibly hard and error-prone to directly access the ABI. Instead, interfaces are generally transformed into something that's easier to use for any given programming language. For C and C++ this is conventionally done by the MIDL Compiler.

    The Windows SDK comes with pre-transformed C and C++ header files for its COM interfaces. In case of the IMalloc interface the code in objidlbase.h roughly looks like this:

    #if defined(__cplusplus) && !defined(CINTERFACE)
        
        struct IMalloc : public IUnknown
        {
            virtual void* Alloc(SIZE_T cb) = 0;
            // ...
        };
        
    #else   /* C style interface */
    
        typedef struct IMallocVtbl
        {
            HRESULT (*QueryInterface)(IMalloc* This, REFIID riid, void **ppvObject);
            ULONG (*AddRef)(IMalloc* This);
            ULONG (*Release)(IMalloc* This);
            
            void* (*Alloc)(IMalloc* This, SIZE_T cb);
            // ...
        } IMallocVtbl;
    
        struct IMalloc
        {
            IMallocVtbl* lpVtbl;
        };
    
    #endif
    

    At the bottom is the C-style IMalloc interface with its v-table pointer. This represents the raw binary layout of the interface, but the pointer is already typed so we don't have to cast our way back from what is essentially a void**. The IMallocVtbl is the function pointer "array" translated into something more accessible. It is represented as a struct with a series of (named and typed) members holding the function pointers. It has the same binary layout2 as an array of void*s, but greatly helps writing code: Instead of having to index into the lpVtbl array, casting the pointer to the correct function pointer type and performing the call, the compiler does all the tricky parts for us.

    At the top is the C++ representation that looks wildly different: The lpVtbl is gone, the base interface (IUnknown) methods are no longer spelled out, and the functions no longer take an interface pointer. Instead, they appear as if they were member functions of the IMalloc class. This transformation squarely relies on a C++ compiler modeling virtual functions (and inheritance) identical to COM's interface layout requirements3.

    Mind you, all of these transformations are purely syntactic. The object code generated is identical between the C and C++ interfaces. For example, when calling Alloc(), the code generated will look up the pointer at index 3 in the v-table and perform a function call through that pointer.

    This hopefully explains why there are seemingly different versions for the same interface and helps you decide which one to use.


    1 The Component Object Model explains this in much more detail.

    2 Technically, this is compiler-dependent, but any compiler targeting Windows will have to agree on this.

    3 MSVC does this. I don't know how well the myriad of assumptions are met by GCC.