Search code examples
c++enumscomidl

In COM IDL how to reference enum from COM dependency?


I am creating an in-process COM server using ATL. Among the types that this COM server will expose is an enum. The values of this enum need to be defined using values from another COM server that this COM server depends on. I've tried pretty much everything I can think of to get this to work with no real success at all.

Here is the IDL for the COM server dependency.

Type1Lib.idl

import "oaidl.idl";
import "ocidl.idl";

[
    uuid(B777544C-77D9-4417-8302-4EAC8272DEDC),
    version(1.0),
]
library Type1Lib
{
   // required system import.
   importlib("stdole2.tlb");

   // A simple enum to be used by another COM server.
   typedef
   [
      uuid(EF82F7A5-3A55-44B9-AD06-201A6D0A6021)
   ]
   enum Enum1
   {
      One,
      Two,
      Three,
      Four,
      Five
   } Enum1;
};

Here is the IDL for the COM server dependent that is trying to use Enum1 (including some of the approaches I've tried).

Type2Lib.idl

// Required system imports.
import "oaidl.idl";
import "ocidl.idl";

// (2) FAIL Enables use of Type1Lib enums in this idl file but only as long as usage
// of those types is totally unqualified. The problem with that is that when a tlh file
// is generated from the type library this idl file creates, that tlh file is malformed
// because the unqualified enum references do not resolve to anything.
import "Type1Lib.idl";

// FAIL Neither of these statements even compiles, Emits error:
// "Native Compiler support only available in C++ compiler".
// The tlh is C++ and apparently cannot be parsed by MIDL.
//import "type1lib.tlh";
//#include "type1lib.tlh"

[
    uuid(D40AC182-8744-42D1-B194-602AEDDC6E7C),
    version(1.0),
]
library Type2Lib
{
   // Required system import.
    importlib("stdole2.tlb");

   // Import Type1Lib without redeclaring the types it contains.
   // (1) FAIL Enables usage of the enum type in Type1Lib, but not the enum values,
   // so that's utterly useless.
//   importlib("Type1Lib.dll");

   typedef
   [
      uuid("0B8D400A-6A8F-44B3-986D-9E099830BB6D")
   ]
   enum Enum2
   {
      A = 0x80000000 + One, // One references an enum value from Type1Lib.
      B,
      C,
      D,
      E
   } Enum2;


   [
       object,
       uuid(F5BA0CB0-B7C7-4483-A3D9-D4B9E39E6269),
       dual,
       nonextensible,
       pointer_default(unique)
   ]
   interface IType2 : IDispatch
   {
      [id(1)] HRESULT Method1([out,retval] LONG* retVal);

      // Partial success. Can reference typedef'ed enum using importlib. Cannot however access the enum values.
      [id(2)] HRESULT Method2([in] enum Enum1 arg1);
   };

    [
        uuid(6179272F-4B34-4EF0-926B-296D3AA73DB7)      
    ]
    dispinterface _IType2Events
    {
        properties:
        methods:
    };

    [
        uuid(75CE545A-D2DA-4EC9-80CF-37531516DFC1)      
    ]
    coclass Type2
    {
        [default] interface IType2;
        [default, source] dispinterface _IType2Events;
    };
};

So using importlib on the typelibrary (embedded in the dll) doesn't work because it doesn't permit access to the values of Enum1.

import'ing Type1Lib.idl doesn't quite work because although the enum values are available they can only be used in unqualified form, so although Type2Lib.idl now compiles, when Type2Lib_i.h is included in dllmain.cpp the enum values do not resolve to anything. Another problem is that the import "Type1Lib.idl" statement causes the addition of an "#include "Type1Lib.h"" line in Type2Lib_i.h, the file Type1lib.h does not exist and there is no reason why it should exist.

There are workarounds for both these problems but they don't really result in a workable solution overall.

Importing Type1Lib with no_namespace in stdafx.h will resolve the issue of the unqualified enums not resolving, but now you are opening yourself up to the possibility of typename clashes that you are unable to work around because now you can't use namespaces.

Prior to importing Type1Lib in stdafx.h you can use a #pragma include_alias to redirect the #include "Type1Lib.h" from a header that doesn't exist to one that does, e.g.

#pragma include_alias("Type1Lib.h", "windows.h")
#import <Type1Lib.dll> no_namespace

Now everything builds. The problem is you now have several nasty and fragile workarounds in play just waiting to screw everything up, and there is still one other glaring issue that I haven't mentioned yet which is this. If you reference your COM dependency via 'importlib' then you get just that, a reference to your dependency, your IDL file can use the types in the referenced typelibrary but you won't be redefining anything. If however you import the idl file using 'import' then what you basically have is a version of 'include' tweaked for use in IDL files. The issue with this is that using 'import' will result in creating duplicates of the types in your dependency IDL file in your dependent IDL file, which you most definitely do not want.

So I'm at a complete loss. What I want to do appears to be pretty straightforward yet despite trying everything I can think of I'm left seeing nothing but wrongness. I desperately need some COM guru to put me on a useful path here.


Solution

  • After trying Hans' suggestion and noting that it still resulted in duplicating the definition of Enum1 from Type1Lib as a new type in Type2Lib, I did some further experimentation and hit upon this approach which appears to work reasonably elegantly.

    The IDL for the Type1Lib is the same as before. The guid on the enum typedef did not appear to be necessary so I elided it.

    //Type1Lib.idl
    
    import "oaidl.idl";
    import "ocidl.idl";
    
    [
        uuid(B777544C-77D9-4417-8302-4EAC8272DEDC),
        version(1.0),
    ]
    library Type1Lib
    {
       // Required system import.
       importlib("stdole2.tlb");
    
       // A simple enum to be used by another COM server.
       typedef enum Enum1
       {
          One,
          Two,
          Three,
          Four,
          Five
       } Enum1;
    };
    

    The IDL for the COM dependent has several changes.

    //Type2Lib.idl
    
    // Required system imports.
    import "oaidl.idl";
    import "ocidl.idl";
    
    cpp_quote("using namespace Type1Lib;")  // Added this.
    import "Type1Lib.idl";        // importing the IDL.
    
    [
       uuid(D40AC182-8744-42D1-B194-602AEDDC6E7C),
       version(1.0),
    ]
    library Type2Lib
    {
       // Required system import.
       importlib("stdole2.tlb");
    
       // Import Type1Lib without redeclaring the types it contains.
       importlib("Type1Lib.dll");   // and importlib as well.
    
       typedef enum Enum2
       {
          A = 0x80000000 + One, // 'One' references an enum value from Type1Lib.
          B,
          C,
          D,
          E
       } Enum2;
    
       [
           object,
           uuid(F5BA0CB0-B7C7-4483-A3D9-D4B9E39E6269),
           dual,
           nonextensible,
           pointer_default(unique)
       ]
       interface IType2 : IDispatch
       {
          [id(1)] HRESULT Method1([out,retval] LONG* retVal);
          [id(2)] HRESULT Method2([in] enum Enum1 arg1);
       };
    
        [
            uuid(6179272F-4B34-4EF0-926B-296D3AA73DB7)      
        ]
        dispinterface _IType2Events
        {
            properties:
            methods:
        };
    
        [
            uuid(75CE545A-D2DA-4EC9-80CF-37531516DFC1)      
        ]
        coclass Type2
        {
            [default] interface IType2;
            [default, source] dispinterface _IType2Events;
        };
    };
    

    And there is an additional change in the consuming library's stdafx.h header.

    //stdafx.h
    
    #pragma include_alias("Type1Lib.h", "obj\Debug\type1lib.tlh")
    #import <Type1Lib.dll>
    

    So how does this work?

    Type2Lib.idl now contains both "import "Type1Lib.idl";" and "importlib("Type1Lib.dll");" statements. While this arrangement means Type2Lib.idl has access to both the enum type definition (which can be used as an argument type) and the enum values themselves, it does however recreate the two problems I described in my OP.

    1) The "import "Type1Lib.idl";" statement causes the addition of an "#include "Type1Lib.h"" line in Type2Lib_i.h and Type1Lib.h does not exist.

    2) Unqualified enum values are referenced in Type2Lib_i.h which do not resolve to anything.

    Issue 1 is addressed by the "#pragma include_alias" in the stdafx.h header. Type2Lib will already wish to #import the library it is consuming in order to utilise it in its implementation, and that #import statement will generate Type1Lib.tlh and Type1Lib.tli to enable use of Type1Lib's types from C++ source. Type1Lib.tlh can serve as a replacement for the "#include "Type1Lib.h"" line in Type2Lib_i.h and that's what the #pragma include_alias does.

    That leaves the issue that Type1Lib.tlh declares its types in a namespace (and we want to keep that namespace since it is a very good thing), but Type2Lib_i.h references types from Type1Lib unqualified and so will not normally compile using Type1Lib.tlh. The "cpp_quote("using namespace Type1Lib;")" statement in Type2Lib.idl resolves this issue by causing midl to inject a using statement into Type2Lib_i.h when it generates it.

    So that all works. It doesn't duplicate the enum definition from Type1Lib in Type2Lib and it isn't too hideous. It does concern me that the enum values when used in the consuming IDL cannot be qualified by either library nor type name, but so far it seems better than any other scheme I've been able to devise.