在 COM IDL 中如何从 COM 依赖引用枚举?

In COM IDL how to reference enum from COM dependency?

我正在使用 ATL 创建 in-process COM 服务器。在这个 COM 服务器将公开的类型中有一个枚举。此枚举的值需要使用来自此 COM 服务器所依赖的另一个 COM 服务器的值来定义。我已经尝试了几乎所有我能想到的方法来让它工作,但没有真正成功。

这是 COM 服务器依赖项的 IDL。

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;
};

这是尝试使用 Enum1 的 COM 服务器依赖的 IDL(包括我尝试过的一些方法)。

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;
    };
};

所以在类型库(嵌入在 dll 中)上使用 importlib 不起作用,因为它不允许访问 Enum1 的值。

import'ing Type1Lib.idl 不太有效,因为尽管枚举值可用,但它们只能以非限定形式使用,因此尽管 Type2Lib.idl 现在可以编译,但当 Type2Lib_i.h包含在 dllmain.cpp 中,枚举值无法解析为任何内容。另一个问题是import "Type1Lib.idl" 语句导致在Type2Lib_i.h 中添加了“#include "Type1Lib.h"” 行,文件Type1lib.h 不存在并且没有任何原因它应该存在。

这两个问题都有解决方法,但它们并没有真正产生一个整体可行的解决方案。

在 stdafx.h 中使用 no_namespace 导入 Type1Lib 将解决未解析的不合格枚举的问题,但现在您面临着您无法解决的类型名冲突的可能性因为现在你不能使用命名空间。

在 stdafx.h 中导入 Type1Lib 之前,您可以使用 #pragma include_alias 将 #include "Type1Lib.h" 从不存在的 header 重定向到一个确实如此,例如

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

现在一切都建立起来了。问题是你现在有几个讨厌和脆弱的解决方法在等着把一切都搞砸,还有一个我还没有提到的明显问题就是这个。如果您通过 'importlib' 引用您的 COM 依赖项,那么您将得到一个 reference 到您的依赖项,您的 IDL 文件可以使用引用的类型库中的类型,但您不会重新定义任何东西。但是,如果您使用 'import' 导入 idl 文件,那么您基本上拥有的是经过调整以用于 IDL 文件的 'include' 版本。这样做的问题是,使用 'import' 会导致在依赖 IDL 文件中创建类型的重复项,这是您绝对不想看到的。

所以我完全不知所措。我想做的事情看起来非常简单,但尽管尝试了我能想到的一切,但我只看到错误。我迫切需要一些 COM 大师来帮助我走上一条有用的道路。

在尝试了 Hans 的建议并注意到它仍然导致将 Type1Lib 中的 Enum1 定义复制为 Type2Lib 中的新类型后,我做了一些进一步的实验并偶然发现了这种似乎工作得相当优雅的方法。

Type1Lib 的 IDL 与以前相同。 enum typedef 上的 guid 似乎不是必需的,所以我省略了它。

//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;
};

COM 相关的 IDL 有几处更改。

//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;
    };
};

消费库的 stdafx.h header 中还有一个额外的变化。

//stdafx.h

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

那么这是如何工作的?

Type2Lib.idl 现在包含 "import "Type1Lib.idl";" "importlib("Type1Lib.dll");"声明。虽然这种安排意味着 Type2Lib.idl 可以访问枚举类型定义(可以用作参数类型)和枚举值本身,但是它确实重现了我在 OP 中描述的两个问题。

1) "import "Type1Lib.idl";"语句导致在 Type2Lib_i.h 中添加“#include "Type1Lib.h"”行,而 Type1Lib.h 不存在。

2) Type2Lib_i.h 中引用了不合格的枚举值,这些值不会解析为任何内容。

问题 1 由 stdafx.h header 中的“#pragma include_alias”解决。 Type2Lib 已经希望 #import 它正在使用的库以便在其实现中使用它,并且该 #import 语句将生成 Type1Lib.tlh 和 Type1Lib.tli 以启用从 C++ 源代码使用 Type1Lib 的类型。 Type1Lib.tlh 可以替代 Type2Lib_i.h 中的“#include "Type1Lib.h"”行,这就是 #pragma include_alias 的作用。

这留下了 Type1Lib.tlh 在命名空间中声明其类型的问题(我们希望保留该命名空间,因为它是一件非常好的事情),但是 Type2Lib_i.h 引用来自 Type1Lib 的类型是不合格的和所以通常不会使用 Type1Lib.tlh 进行编译。 "cpp_quote("using namespace Type1Lib;")" Type2Lib.idl 中的语句通过使 midl 在生成它时将 using 语句注入 Type2Lib_i.h 来解决此问题。

所以一切正常。它不会在 Type2Lib 中复制 Type1Lib 中的枚举定义,也不会太丑陋。我确实担心在消费 IDL 中使用的枚举值不能由库或类型名称限定,但到目前为止它似乎比我能够设计的任何其他方案都要好。