Возврат умного указателя из dll
От: dleather  
Дата: 05.12.05 19:55
Оценка:
Столкнулся с такой вот проблемой. Есть головной exe и dll. Есть собственный умный указатель, следующего вида:


template <typename T>
class CtSmartLink
{
  private:
    typedef CtValues<T*, int> CtdRefCount; // CtValues - обертка для std::map

    static CtdRefCount * m_pRefCount;      // статический указатель на map, где храним кол-во ссылок

    T * m_pLink; // указатель на свой объект

  protected:
    int AddRef(T * a_p);
    int Release(T * a_p);
    void Destroy(T * a_p);

    void Alloc();
    void Free();

  public:
    CtSmartLink<T> ();
    CtSmartLink<T> (T * a_p);
    CtSmartLink<T> (const CtSmartLink<T> & a_Src);
    CtSmartLink<T> (const CtSmartLink<T> * a_pSrc);

    virtual ~CtSmartLink ();

    CtSmartLink<T> & operator = (T * a_p);
    CtSmartLink<T> & operator = (const CtSmartLink<T> * a_pSrc);
    CtSmartLink<T> & operator = (const CtSmartLink<T> & a_Src);

    T * operator -> (void);
    bool operator ! () const;
};
//==============================================================================

template <typename T>
void CtSmartLink<T>::Alloc()
{
  if (m_pRefCount == NULL) // если еще никто не создал map, то создаем
  {
    m_pRefCount = new CtdRefCount;

    if (m_pRefCount == NULL)
      throw std::invalid_argument("Out of memory. CtSmartLink<T>::Alloc().");
  }
}
//------------------------------------------------------------------------------

template <typename T>
void CtSmartLink<T>::Free()
{
  if (m_pRefCount != NULL)
  {
    if (m_pRefCount->Size() < 1) // если в map больше нет элементов, значит он больше не нужен
    {
      delete m_pRefCount;
      m_pRefCount = NULL;
    }
  }
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T>::CtdRefCount * CtSmartLink<T>::m_pRefCount = NULL;

template <typename T>
CtSmartLink<T>::CtSmartLink(T * a_p)
{
  Alloc();

  m_pLink = a_p;
  AddRef(m_pLink);
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T>::CtSmartLink()
{
  Alloc();

  m_pLink = NULL;
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T>::CtSmartLink(const CtSmartLink<T> & a_Src)
{
  Alloc();

  m_pLink = a_Src.m_pLink;
  AddRef(m_pLink);
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T>::CtSmartLink(const CtSmartLink<T> * a_pSrc)
{
  Alloc();

  m_pLink = a_pSrc->m_pLink;
  AddRef(m_pLink);
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T>::~CtSmartLink()
{
  Release(m_pLink);

  Free();
}
//------------------------------------------------------------------------------

template <typename T>
int CtSmartLink<T>::AddRef(T * a_p)
{
  if (m_pRefCount->isSet(a_p))
    (*m_pRefCount)[a_p]++;
  else
    m_pRefCount->Set(a_p, 1);

  size_t nRefCount = (*m_pRefCount)[a_p];

  return nRefCount;
}
//------------------------------------------------------------------------------

template <typename T>
int CtSmartLink<T>::Release(T * a_p)
{
  int nRefCount = -1;

  if (m_pRefCount->isSet(a_p))
  {
    (*m_pRefCount)[a_p]--;
    nRefCount = (*m_pRefCount)[a_p];

    if (nRefCount < 1)
      Destroy(a_p);
  }

  return nRefCount;
}
//------------------------------------------------------------------------------

template <typename T>
void CtSmartLink<T>::Destroy(T * a_p)
{
  if (m_pRefCount->isSet(a_p))
  {
    if (a_p != NULL)
    {
      delete a_p;
      m_pLink = NULL;
    }

    m_pRefCount->Delete(a_p);
  }
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T> & CtSmartLink<T>::operator = (const CtSmartLink<T> * a_pSrc)
{
  m_pRefCount = a_pSrc->m_pRefCount;

  if (m_pLink != a_pSrc->m_pLink)
  {
    Release(m_pLink);
    m_pLink = a_pSrc->m_pLink;
    AddRef(m_pLink);
  }

  return (*this);
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T> & CtSmartLink<T>::operator =(const CtSmartLink<T> & a_Src)
{
  if (m_pLink != a_Src.m_pLink)
  {
    Release(m_pLink);
    m_pLink = a_Src.m_pLink;
    AddRef(m_pLink);
  }

  return (*this);
}
//------------------------------------------------------------------------------

template <typename T>
CtSmartLink<T> & CtSmartLink<T>::operator =(T * a_p)
{
  if (m_pLink != a_p)
  {
    Release(m_pLink);
    m_pLink = a_p;
    AddRef(m_pLink);
  }

  return (*this);
}
//------------------------------------------------------------------------------

template <typename T>
T * CtSmartLink<T>::operator -> (void)
{
  return m_pLink;
}
//------------------------------------------------------------------------------

template <typename T>
bool CtSmartLink<T>::operator ! () const
{
  return (m_pLink == NULL);
}
//------------------------------------------------------------------------------



В dll есть парсер XML, следующего вида:


// класс узел
class CcXMLNode : virtual public iXMLNode
{
  private:
    CtSmartLink<iXMLNode> CreateNode()
    {
      CtSmartLink<iXMLNode> Node = dynamic_cast<iXMLNode *> (new CcXMLNode());
      return Node;
    } 
...

  public:
    virtual ~CcXMLNode();

    virtual CtSmartLink<iXMLNode> GetNode(const nsCommon::String & a_sName)
   {
     ...
     CtSmartLink<iXMLNode> FoundNode = CreateNode(); 
     ...

     return FoundNode;
   }
...
};


class CcXML : virtual public iXML
{
  private:
    CtSmartLink<iXMLNode> m_RootNode; // корневой узел

  public:
    CcXML();
    virtual ~CcXML();

    virtual CtSmartLink<iXMLNode> RootNode();
...
};



В головном exe начинаем это дело использовать:

...
//загрузили dll
...

CcXML XML;

CtSmartLink<iXMLNode> RootNode = XML.RootNode();

// усе нормально нода пришла.

CtSmartLink<iXMLNode> MyNode; // и вдруг нам понадобилось сделать так


И начинаются проблемы... Сработал конструктор по-умолчанию для MyNode, заходим отладчиком и видим, что static CtdRefCount * m_pRefCount у него другой нежели для умного указателя в dll и равен NULL, естественно видя NULL он создает map еще раз.

По окончанию работы вообще происходит странность : Срабатывает деструктор объекта MyNode -> сносит свой map. Затем пошел деструктор CcXML::m_RootNode. И тут выясняется что его map тоже уже НЕТУ! Access при попытке выполнить if (m_pRefCount->isSet(a_p)).

Отсюда вопрос 1: map c количеством ссылок на объеты для CtSmartLink'ов созданных в exe и dll все таки одинаковый или разный ?
вопрос 2: как еще можно решить данную пробему ?

P.S. Кривое решение впринципе есть. Это делать данный map в головном exe, а dll давать на него ссылку.
Re: Возврат умного указателя из dll
От: Кодт Россия  
Дата: 05.12.05 21:20
Оценка:
Здравствуйте, dleather, Вы писали:

D>Столкнулся с такой вот проблемой. Есть головной exe и dll. Есть собственный умный указатель, следующего вида:

D>template <typename T>
D>class CtSmartLink
D>{
D>  private:
D>    typedef CtValues<T*, int> CtdRefCount; // CtValues - обертка для std::map

D>    static CtdRefCount * m_pRefCount;      // статический указатель на map, где храним кол-во ссылок
  .....

Это природное явление — частный случай нарушения One Definition Rule.
Статический член был определён и в dll, и в exe. В результате — тот код, который имеет с ним дело внутри dll, обращается к своему экземпляру, а тот, который в exe — к своему.

D>P.S. Кривое решение впринципе есть. Это делать данный map в головном exe, а dll давать на него ссылку.

"Ну, или так".

А ещё можно было вынести "службу подсчёта ссылок" в отдельную dll, в виде функций
int addref(void* key);
int release(void* key);

и пусть твои шаблоны пользуются этими функциями на здоровье.

Вообще, зачем делать такой странный подсчёт ссылок? Чем не понравился, к примеру, boost::shared_ptr?
Или очень нужна была интрузивность?

Кстати говоря, нужно быть очень аккуратным, когда делаешь внешний счётчик ссылок. Кастинг туда-сюда, и получаешь другое значение указателя на тот же самый объект.
Перекуём баги на фичи!
Re[2]: Возврат умного указателя из dll
От: Аноним  
Дата: 06.12.05 16:00
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Статический член был определён и в dll, и в exe. В результате — тот код, который имеет с ним дело внутри dll, обращается к своему экземпляру, а тот, который в exe — к своему.

Спасибо. Я так и думал. Хотелось уточнить догадку

D>>P.S. Кривое решение впринципе есть. Это делать данный map в головном exe, а dll давать на него ссылку.

К>"Ну, или так".
На самом деле все намного сложнее. CtdRefCount превратился в CtValues<void*, int>.
Есть глобальный пул фабрик классов, в нем просто зарегистрировали фабрику на CtdRefCount, а в Alloc SmartLink'a проверяем есть ли объет, нету тогда создаем его через пул фабрик. Ну и последний SmartLink убивает map если в нем нет элементов.

К>А ещё можно было вынести "службу подсчёта ссылок" в отдельную dll, в виде функций

К>
К>int addref(void* key);
К>int release(void* key);
К>

К>и пусть твои шаблоны пользуются этими функциями на здоровье.
В данном случае это не прокатит. Поскольку эту dll должен кто-то загрузить. А XML-парсер нужен моему CcApllication для чтения настроек при старте. Хм... правда можно эту либу статически прилинковать к exe. Ну да ладно, будет тормозить через фабрику тогда поковыряем.

К>Вообще, зачем делать такой странный подсчёт ссылок? Чем не понравился, к примеру, boost::shared_ptr?

Boost еще к Builder C++ 6 прикрутить надо. Пробывал stl::auto_ptr не прокатил, он убивал объект когда не него еще были другие ссылки. Ведь про другие stl::auto_ptr на тот же объект он ничего не знает и сносит объект, когда помирает сам.

К>Или очень нужна была интрузивность?

Что за зверь "интрузивность" ?

К>Кстати говоря, нужно быть очень аккуратным, когда делаешь внешний счётчик ссылок. Кастинг туда-сюда, и получаешь другое значение указателя на тот же самый объект.

Через фабрику проблем нет, всегда получаем что надо. Ибо ручками никаких кастов не делаем.
Re[3]: Возврат умного указателя из dll
От: Кодт Россия  
Дата: 06.12.05 18:46
Оценка:
Здравствуйте, Аноним, Вы писали:

К>>А ещё можно было вынести "службу подсчёта ссылок" в отдельную dll, в виде функций

К>>и пусть твои шаблоны пользуются этими функциями на здоровье.
А>В данном случае это не прокатит. Поскольку эту dll должен кто-то загрузить. А XML-парсер нужен моему CcApllication для чтения настроек при старте. Хм... правда можно эту либу статически прилинковать к exe. Ну да ладно, будет тормозить через фабрику тогда поковыряем.

Именно так: статически линковать — и к экзе, и к длл, в которой эти шаблоны встречаются.
А чтобы с настройками проекта не возиться, прямо в хедере пишешь #pragma comment(lib)
#ifndef __REFCOUNT_H__
#define __REFCOUNT_H__
//////////////////////////////////////////////////////////
// refcount.h, implemented in refcount.cpp -> refcount.dll

#ifdef REFCOUNT_IMPL
  #define REFCOUNT_API __declspec(dllexport)
#else
  #define REFCOUNT_API __declspec(dllimport)
  #pragma comment(lib, "refcount.lib") // эквивалентно опции линкера /LIB refcount.lib
#endif

namespace refcount {
REFCOUNT_API int addref(void* p);
REFCOUNT_API int decref(void* p);
}

#endif//__REFCOUNT_H__


К>>Вообще, зачем делать такой странный подсчёт ссылок? Чем не понравился, к примеру, boost::shared_ptr?

А>Boost еще к Builder C++ 6 прикрутить надо.

Тююю. А что, не прикручивается? shared_ptr.hpp — это же такой простой зверь.

А>Пробывал stl::auto_ptr не прокатил, он убивал объект когда не него еще были другие ссылки. Ведь про другие stl::auto_ptr на тот же объект он ничего не знает и сносит объект, когда помирает сам.


К>>Или очень нужна была интрузивность?

А>Что за зверь "интрузивность" ?

Интрузивность некоего свойства означает, что свойство присуще объекту. То есть, имея голый указатель на объект, мы получаем доступ к этому свойству.
Неинтрузивность — когда свойство реализуется отдельно от объекта, поэтому мы вынуждены держать связку (объект, носитель свойства данного объекта) и работать только со связками.

Свойство "подсчёт ссылок".
Интрузивные реализации
— счётчик содержится прямо внутри объекта (пример: COM)
— глобальная таблица (та самая refcount.dll)
— идентичность объекта обеспечивается не указателем, а хэндлом; механика подсчёта спрятана за API (пример: объекты ядра и GDI в виндоузе)
Неинтрузивные реализации
— семейство указателей (на один и тот же объект) размещает на куче счётчик, которым также совместно владеет (boost::shared_ptr)
— семейство указателей ссылается друг на друга (linked_ptr Максима Егорушкина)

Свойство "удаление объекта"
Интрузивные реализации
— delete ptr вызывающее виртуальный деструктор
— ptr->Release() — внутри проверяется счётчик ссылок и если он йок, то объект делает delete this
Неинтрузивные реализации
— shared_ptr при конструировании запоминает указатель на функцию checked_delete<Y> того типа, который ему отдан; когда счётчик обнулится, он вызовет именно эту функцию. Не имеет значения, произошло ли это в том же модуле, где объект создан, или в другом (с другим менеджером памяти) — вызовется правильный.
— тому же shared_ptr'у можно подсунуть любую функцию, а не только checked_delete — если объект дОлжно утилизировать иным способом (например, CloseHandle())

К>>Кстати говоря, нужно быть очень аккуратным, когда делаешь внешний счётчик ссылок. Кастинг туда-сюда, и получаешь другое значение указателя на тот же самый объект.

А>Через фабрику проблем нет, всегда получаем что надо. Ибо ручками никаких кастов не делаем.

Ну если гарантируется, что наследование только одиночное — то без проблем.
Перекуём баги на фичи!
Re[4]: Возврат умного указателя из dll
От: dleather  
Дата: 07.12.05 22:08
Оценка:
Здравствуйте, Кодт, Вы писали:

К>А чтобы с настройками проекта не возиться, прямо в хедере пишешь #pragma comment(lib)

К>// refcount.h, implemented in refcount.cpp -> refcount.dll

К>namespace refcount {
К>REFCOUNT_API int addref(void* p);
К>REFCOUNT_API int decref(void* p);
К>}
К>#endif//__REFCOUNT_H__

Принято. Но пока оставим свою реализацию, поскольку не хочется плодить ЕЩЕ одну dll ради подсчета ссылок.

К>>>Вообще, зачем делать такой странный подсчёт ссылок? Чем не понравился, к примеру, boost::shared_ptr?

А>>Boost еще к Builder C++ 6 прикрутить надо.
К>Тююю. А что, не прикручивается? shared_ptr.hpp — это же такой простой зверь.
Каюсь... не увидел что Builder C++ 6 boost поддерживает. boost можно потом прикрутить. Сейчас не хочется разбираться (времени и так много ушло на "разбор полетов"). К тому же boost наверняка делает, что-то похожее (создает глобавльный map ссылок на куче).

К>>>Или очень нужна была интрузивность?

А>>Что за зверь "интрузивность" ?
Спасибо за ликбез. Будем знать.
Re[5]: Возврат умного указателя из dll
От: Кодт Россия  
Дата: 07.12.05 22:57
Оценка:
Здравствуйте, dleather, Вы писали:

D>Каюсь... не увидел что Builder C++ 6 boost поддерживает. boost можно потом прикрутить. Сейчас не хочется разбираться (времени и так много ушло на "разбор полетов"). К тому же boost наверняка делает, что-то похожее (создает глобавльный map ссылок на куче).


shared_ptr размещает счётчики не в map'е, а в куче.
Более того, если даже у dll и exe разные менеджеры кучи (т.е. нельзя в одном модуле сделать p=new T() а в другом delete p) — это не мешает.
Дело в том, что счётчик (detail/shared_count.hpp) — это объект с интрузивным удалением. То есть он сам знает, как себя удалять, и обратится именно к той куче, в которой был размещён.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.