【问题标题】:How to retrieve object pointer from ATL collection of objects?如何从 ATL 对象集合中检索对象指针?
【发布时间】:2013-05-11 11:31:59
【问题描述】:

我有一个对象集合,定义为:

typedef IField                  ItemInterface;
typedef CComObject<CField>*     ItemClassPtr;
typedef CAdapt< CComPtr<ItemInterface> > ItemType;
typedef std::vector< ItemType > ContainerType;

我已经通过一系列调用创建了几个 CField 对象(忽略 hresult):

IField* ppField = 0;
hresult = CField::CreateInstance(&ppField);
ItemType spField = ppField;
m_coll.push_back(spField);
ppField->Release();

现在我正在尝试检索指向对象的指针,以便我可以调用其中一个方法:

ItemClassPtr pField;
short type1;
m_coll[index].m_T->QueryInterface( __uuidof(ItemInterface), (void **)&pField ) );
pField->get_Type(&type1);

它在 get_Type 调用时因访问冲突而崩溃。 响应者的帖子已更改为:

short type1;
IField * ppField = m_coll[index].m_T;
CComQIPtr<CField, &__uuidof(IField)> pField = ppField;
pField->get_Type(&type1);

但当我尝试在 get_Type 调用中进行跟踪时,它仍然崩溃。

这是 CField 类定义的序言:

class ATL_NO_VTABLE CField : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CField, &CLSID_Field>,
    public ISupportErrorInfo,
    public IFieldAccess,
    public IDispatchImpl<IField, &IID_IField, &LIBID_SQLite02>
{
    friend class CFields;
    friend class CrecordSet;
public:
    CField();
    ~CField();

DECLARE_REGISTRY_RESOURCEID(IDR_FIELD)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CField)
    COM_INTERFACE_ENTRY(IField)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(ISupportErrorInfo)
    COM_INTERFACE_ENTRY_IID(__uuidof(IField), CField)
END_COM_MAP()

请帮忙!

请注意,此问题是上一个 ATL 问题 In ICollectionOnSTLImpl implementation, can't access m_T or item object's members 的一个分支 这更全面地描述了集合类。我已经用 typedefs 替换了#defines。

万斯

【问题讨论】:

    标签: c++ com atl


    【解决方案1】:

    您不能使用QueryInterface() 来获取class 指针,只能使用interface 指针。而且您也不能将接口指针类型转换为类指针。访问实现类的唯一安全方法是定义该类实现的单独私有接口,并让该接口公开一个返回类对象的this 指针的方法。例如:

    class CField;
    
    interface DECLSPEC_UUID("...") IFieldAccess : public IUnknown
    {
    public:
        virtual CField* get_ClassPtr() = 0;
    };
    
    class ATL_NO_VTABLE CField : 
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CField, &CLSID_Field>,
        public IDispatchImpl<IField, &IID_IField, &LIBID_SQLite02>,
        public ISupportErrorInfo,
        public IFieldAccess,
    {
        friend class CFields;
        friend class CrecordSet;
    public:
        CField();
        ~CField();
    
    DECLARE_REGISTRY_RESOURCEID(IDR_FIELD)
    
    DECLARE_PROTECT_FINAL_CONSTRUCT()
    
    BEGIN_COM_MAP(CField)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(IField)
        COM_INTERFACE_ENTRY(IFieldAccess)
        COM_INTERFACE_ENTRY(ISupportErrorInfo)
    END_COM_MAP()
    };
    
    CField* CField::get_ClassPtr()
    {
        return this;
    }
    

    .

    CComPtr<IFieldAccess> pFieldAccess;
    CField* pField;
    short type1;
    m_coll[index].m_T->QueryInterface( __uuidof(IFieldAccess), (void **)&pFieldAccess) );
    pField = pFieldAccess->get_ClassPtr();
    pField->get_Type(&type1);
    

    【讨论】:

    • 我不需要实现 IFieldAccess::get_ClassPtr() 吗?
    • CField 类实现了IFieldAccess 接口。 CField::get_ClassPtr() 方法是 IFieldAccess::get_ClassPtr() 的实现。
    • 这就是我的想法,但我得到一个 IFieldAccess::get_ClassPtr(void) not found 链接错误。请注意,我将 _interface 更改为 interface,并且我假设 IFieldAccessPtr 是“IFieldAccess *”。
    • 需要在接口中声明为纯虚拟。我更新了我的答案。
    • 还必须声明 Release 和 AddRef 方法。似乎确实有效。现在的问题是哪个答案最安全和/或最快?
    【解决方案2】:

    你用QueryInterface查询ItemInterface/IField,所以你得到的指针就是这种类型(假设是COM对象本身正确实现了该方法)。

    所以你接下来要做的是从IFieldItemClassPtr/CComObject&lt;CField&gt;* 的无效reinterpret_cast。这不起作用:根据您在pField 有异常的行,那里有无效的非NULL 指针。

    您在这里遇到的另一个问题是引用计数臃肿:您的原始指针 ppField 接收到一个带有递增计数器的指针,我没有看到您在此处释放它,我想您可能在其他地方也错误地管理了引用.如果您的代码存在其他引用计数问题,如果您的对象已经被销毁并且您调用了指向被销毁对象的指针,您最终可能会在代码上遇到非常相似的访问冲突。

    此时有助于解决问题的是异常位置的调用堆栈。从目前的描述来看,您在get_Type 通话中的深度尚不清楚。

    顺便说一句,如果您尝试从接口指针恢复 C++ 类指针,如果不涉及编组,这很容易,否则可能会出现问题。要在没有编组的情况下从IField* 获取CField*,您可以这样做:

    IField* pField = ...
    CField* pNativeField = static_cast<CField*>(pField);
    

    请注意,类指针的生命周期取决于 COM 对象本身的生命周期,因此这样做更安全:

    CComPtr<IField> pField = ...
    CField* pNativeField = static_cast<CField*>((IField*) pField);
    // NOTE: pNativeField is valid until at least pField is released
    

    这很容易,但假设您手头上的IField 是由CField 实现的,没有别的。否则,static_cast 会成功并为您提供一个指针,但它将是无效的。另一个与 Remy 类似但更容易操作的更安全的选择如下:

    class ATL_NO_VTABLE CField :
    // ...
    BEGIN_COM_MAP(CField)
      //...
      COM_INTERFACE_ENTRY_IID(CLSID_Field, CField)
    END_COM_MAP( )
    
    //...
    
    IField* pField = ...
    CComQIPtr<CField, &CLSID_Field> pNativeField = pField;
    

    这很简单并且参考计数器友好。上面的COM_INTERFACE_ENTRY 创建了一个假接口条目,它通过QueryInterface 暴露一个原始C++ 指针。这将优雅地返回 E_NOINTERFACE 错误而不会崩溃,以防 IField 由其他东西实现。

    【讨论】:

    • 首先,我在创建例程中添加了一个 ppField->Release() 以便引用计数器正确。我加了
    • 我将对象访问替换为:IField * ppField = m_coll[index].m_T; CComQIPtr pField = ppField; - pField 仍然指向错误的位置。我将在原始问题中添加 CField 类序言以及您建议的更改。
    • 我查看了您的CField 代码并分别更新了我的答案(最后一个代码片段),您需要将更新复制到您的代码中。
    • 指定 CLSID_Field 似乎可以解决问题。我遇到了另一个问题,但至少我在 get_Type 例程中。谢谢!
    • Remy 的解决方案的问题在于它也是一个 hack。 IFieldAccess 使用不可编组的 COM 类型(原始指针),因此它仅在此解决方案有效的情况下有效 - 没有任何情况下它以任何方式更安全。更多用于 hack 本身的无用代码(您必须声明一个单独的无用类!),速度较慢,没有真正的优势。
    猜你喜欢
    • 1970-01-01
    • 2010-10-09
    • 2010-12-16
    • 1970-01-01
    • 2022-11-13
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    • 2018-08-10
    相关资源
    最近更新 更多