How to retrieve object pointer from ATL collection

2019-08-18 00:07发布

问题:

I have a collection of objects that is defined as:

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

and I have created several of the CField objects via a series of calls (ignoring hresult):

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

and now I'm trying to retrieve a pointer to an object so that I can call one of it's methods:

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

and it crashes on access violation at the get_Type call. this was changed in response to a responder's post to:

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

but it still crashes when I attempt to trace in the get_Type call.

Here is the preamble to the CField class definition:

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()

Please help!

Note that this question is an offshoot from a previous ATL question In ICollectionOnSTLImpl implementation, can't access m_T or item object's members That more fully describes the collection class. I have replaced the #defines with typedefs.

Vance

回答1:

You can't use QueryInterface() to get a class pointer, only an interface pointer. And you can't type-cast an interface pointer to a class pointer, either. The only safe way to get access to the implementation class is to define a separate private interface that the class implements, and have that interface expose a method that returns the class object's this pointer. For example:

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


回答2:

You query with QueryInterface for ItemInterface/IField, so the pointer you obtain is of this type (supposedly, provided that COM object itself implement the method correctly).

So what you are doing next is an invalid reinterpret_cast from IField to ItemClassPtr/CComObject<CField>*. This does not work: by the line you have exception at your pField has invalid non-NULL pointer there.

Another problem you have here is a bloated reference counting: your raw pointer ppField receives a pointer with incremented counter, I don't see you are releasing it here, I suppose you might be managing reference wrong somewhere else as well. Having other reference counting issues on your code, you might eventually have a very much similar access violation on your code, if your object is already destroyed and you have a call on the pointer to destroyed object.

What is helpful to troubleshoot problems at this point is the call stack at exception position. From current description it is not clear how deep you are inside the get_Type call.

BTW, if you are trying to restore C++ class pointer from interface pointer, it is easy if no marshaling is involved and is likely to be a problem otherwise. To get CField* from IField* in absence marhshaling here you can do this:

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

Note that lifetime of class pointer is dependendent on life time of COM object itself, so it's much safer to do this:

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

This is easy, but assumes that IField you have on your hands is implemented by CField and nothing else. Otherwise, static_cast would succeed and get you a pointer, but it is going to be invalid. Another safer option similar to Remy's but easier to do is the following:

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;

This is simple and reference counter friendly. The COM_INTERFACE_ENTRY above creates a fake interface entry which exposes through QueryInterface a raw C++ pointer. This will return a E_NOINTERFACE error gracefully without crashing in case IField is implemented by something else.



标签: c++ com atl