3

我已经使用 c++Builder 2010(组件/导入组件/类型库向导)导入了一个类型库,并试图了解生成的 xxx_TLB.b 和 xxx_OCX.h 文件中定义的类和类型。(问题的一些背景 - 我试图让我的 COM 对象和方法按名称绑定,而不是按 DISPID -请参阅此问题

假设库包含一个类 Foo,我将获得以下 GUID

const GUID IID_IFoo = {0xF1EC45FE, 0x2540, 0x4188,{ 0xAE, 0x14, 0xD8,0x4F, 0x65, 0x6F,0x7A, 0x00} };
const GUID CLSID_Foo = {0xDD1C416D, 0xD8A2, 0x4BBC,{ 0x8E, 0xA8, 0x1A,0x10, 0x77, 0xA4,0x30, 0x0C} };

这对我来说很有意义。还生成了 FooEvents,但我暂时忽略它。我还获得了以下接口和 typedef,每种方法都有两个版本 - 一个“原始”版本和一个带有更友好的包装隐藏的版本HRESULTS

interface DECLSPEC_UUID("{F1EC45FE-2540-4188-AE14-D84F656F7A00}") IFoo;
typedef TComInterface<IFoo, &IID_IFoo> IFooPtr;

interface IFoo  : public IDispatch 
{
  virtual HRESULT STDMETHODCALLTYPE get_Bar(BSTR* Value/*[out,retval]*/) = 0; // [201]
  ...
  BSTR __fastcall get_Bar(void)
  {
    BSTR Value = 0;
    OLECHECK(this->get_Bar((BSTR*)&Value));
    return Value;
  }
}

现在我们来谈谈我无法理解的东西:两个基本公开相同功能但不同的附加类。

template <class T /* IFoo*/ >
class TCOMIFooT : public TComInterface<IFoo>, public TComInterfaceBase<IUnknown>
{
... again, two versions of each method are provided.
}
typedef TCOMIFooT<IFoo> TCOMIFoo;

template<class T>
class IFooDispT : public TAutoDriver<IFoo>
{
... again, two more versions of each method are provided.
}
typedef IFooDispT<IFoo> IFooDisp;

情况变得更糟:在 xxx_OCX.h 文件中,我们发现了这个附加类:-

class PACKAGE TFoo : public Oleserver::TOleServer
{
  IFooPtr m_DefaultIntf;
  _di_IUnknown __fastcall GetDunk();
public:
  ... this tim, just one version of each method
}

因此,对于 Foo 类型的对象,我似乎有四个不同的类来表示它——我应该使用哪一个,什么时候使用?

  IFooPtr f;
  IFooDisp f;
  TCOMIFoo f;
  TFoo *f;

最后,因为IFooDisp f;似乎我可以Bar()通过任何一个调用f->Bar()-f.Bar()它们会以相同的方式工作,还是有一些细微的区别?

4

1 回答 1

3

所以,对于 Foo 类型的对象,我似乎有四个不同的类来表示它

TComInterface是一个包装类,用于为您管理接口指针的引用计数,很像 ATLCComPtrCComQIPtr类。AddRef()这样,您不必Release()手动调用。

包装器TCOMIFoo基本上只是一个 typedef for TComInterface<IFoo>,尽管它也被TCoClassCreator同一个文件中创建的派生类使用。

TAutoDriver是一个用于处理IDispatch和后期绑定的包装类(按名称绑定成员所需要的)。

TOleServer是一个包装类,用于将 COM 对象公开为 VCL 组件,特别是在设计时可以将其放置在TFormTFrameTDataModule对象上。

我应该使用哪一个,何时使用?

我从未TAutoDriver在我的代码中使用过包装器,并且在设计时不使用 COM 对象(因为如果未安装 COM 对象,它们不允许您进行运行时错误处理)。对于大多数代码,我会坚持使用IFooPtror TCOMIFoo,它们基本上是一样的。

最后,对于 IFooDisp f; 看来我可以通过 f->Bar() 和 f.Bar() 调用 Bar() - 它们的工作方式相同,还是有一些细微的区别?

有细微的差别。

f->Bar()直接调用该IFoo::Bar()方法,因此您必须进行自己的HRESULT检查,这使您可以决定发生错误时该怎么办。

f.Bar()调用包装器IFooDisp::Bar()方法,该方法调用该IFoo::Bar()方法并HRESULT为您进行检查,如果发生错误则引发异常。

一般来说,任何通过->操作符调用的方法都会直接调用原始接口,而任何通过.操作符调用的方法都会调用一个包装器方法。

另一个细微的区别是如果Bar()返回一个接口指针。使用->运算符,您必须对结果指针进行自己的引用计数,例如:

ISomeIntf i;
if (SUCCEEDED(f->Bar(&i)))
{
    ...
    i->Release();
}

除非您使用TComInterface变量来接收它:

TComInterface<ISomeIntf> i; // or ISomeIntfPtr
if (SUCCEEDED(f->Bar(&i)))
{
    ...
    // Release()'d automatically
}

相反,使用.运算符,包装器通常会返回 aTComInterface而不是原始接口指针。当接口链接在一起时,这尤其有用,您希望获得一个返回返回...的接口的接口,等等。使用->运算符更灵活,但不如使用.运算符干净,例如:

TComInterface<ISomeIntf> i; // or ISomeIntfPtr
if (SUCCEEDED(f->get_SomeIntf(&i)))
{
    TComInterface<IOtherIntf> j; // or IOtherIntfPtr
    if (SUCCEEDED(i->get_OtherIntf(&j)))
    {
        j->DoSomething();
    }
}

相对:

f.SomeIntf.OtherIntf.DoSomething();
于 2012-12-21T00:13:23.153 回答