Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 09:52
Оценка:
Есть сервер . Он в себе содержит некий интерфейс и его реализацию, причем такую, что объект этой реализации создать нельзя (макросы DECLARE_NO_REGISTRY и удален OBJECT_ENTRY_AUTO). Этот сервер создает внутри себя объект этого класса (с помощью CComObject), далее вывешивает событие (через Connection Point), в котором передает указатель на этот самый интерфейс.

Есть клиент. Клиент делает #import либки того самого сервера (соответственно содается некий "ВашИнтерфейсPtr", использующий _com_ptr_t), далее идет подписка на событие с использованием IDispEventImpl. Соответственно, функция обработчик в качестве параметра принимает тот самый "ВашИнтерфейсPtr".

А теперь вот что происходит: при создании объекта делается QueryInterface, в итоге счетчик у объекта становится 1. Потом полученный интерфейс передается в Fire_ВашеСобытие, и там в ::VarianCopy делается AddRef(), как следствие счетчик становится равным двум. По приходу в функцию-обработчик события счетчик остается равным тем же самым двум. Внутри функции ничего, кроме считывания данных из проперти объекта не происходит. А вот при выходе из функции-обработчика счетчик почему-то уменьшается на 1 (осталось 1). Потом при удалении variantarg'a в Fire_ВашеСобытие происходит Release(), счетчик становится 0, объекту хана.
Расследование, рассматривание CallStack'a и прочие шаманства показали, что _com_ptr_t при выходе из функции-обработчика разрушается, и в его деструкторе вызывается Release(). Но AddRef() в этом _com_ptr_t вызван не был, то есть указатель на интерфейс ему скормлен некорректно, как следствие мы теряем одну ссылку и теряем нужный объект.

Происходит ли такое странное поведение по моей вине, или же это очередной "прикол" от M$?..
Заранее спасибо за ответы
Re: Ошибка с подсчетом ссылок
От: AndrewJD США  
Дата: 04.10.06 10:15
Оценка: +2
Здравствуйте, lol4ever, Вы писали:

L>Есть клиент. Клиент делает #import либки того самого сервера (соответственно содается некий "ВашИнтерфейсPtr", использующий _com_ptr_t), далее идет подписка на событие с использованием IDispEventImpl. Соответственно, функция обработчик в качестве параметра принимает тот самый "ВашИнтерфейсPtr".


А почему функция обработчик принимает "ВашИнтерфейсPtr" вместо голого "ВашИнтерфейс" ?
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[2]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 10:53
Оценка:
Здравствуйте, AndrewJD, Вы писали:

AJD>Здравствуйте, lol4ever, Вы писали:


AJD>А почему функция обработчик принимает "ВашИнтерфейсPtr" вместо голого "ВашИнтерфейс" ?


ну, функция могла бы и принимать просто "ВашИнтерфейс"*, но если мне импорт дает возможность использовать сразу умный указатель, то почему бы его не использовать?..
Re[3]: Ошибка с подсчетом ссылок
От: AndrewJD США  
Дата: 04.10.06 11:51
Оценка:
Здравствуйте, lol4ever, Вы писали:

AJD>>А почему функция обработчик принимает "ВашИнтерфейсPtr" вместо голого "ВашИнтерфейс" ?


L>ну, функция могла бы и принимать просто "ВашИнтерфейс"*, но если мне импорт дает возможность использовать сразу умный указатель, то почему бы его не использовать?..



1. Если ты не хранишь этот обьект, то пользы от смарт поинтера никакой.
2. Вызывающая сторона предполагает, что параметром будет поинтер на row интерфейс, а не какой-то класс. Тебе просто повезло, что размер _com_ptr совпал с размером указателя. Но конструктор никто не вызвал. ИМХО, это ожидаемое поведение.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[4]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 12:12
Оценка:
Здравствуйте, AndrewJD, Вы писали:

AJD>1. Если ты не хранишь этот обьект, то пользы от смарт поинтера никакой.

AJD>2. Вызывающая сторона предполагает, что параметром будет поинтер на row интерфейс, а не какой-то класс. Тебе просто повезло, что размер _com_ptr совпал с размером указателя. Но конструктор никто не вызвал. ИМХО,
это ожидаемое поведение.


согласен, что от смарта пользы никакой, сделан на автомате в тот момент был, но все же...
А насчет данного везения — ох что-то сомневается мне, что так вот повезло с размерами... Моя логика такая:раз вызывается деструктор, то создавался _com_ptr_t. Раз создается _com_ptr_t, значит вызывается конструктор. Раз вызывается конструктор,должен быть аддреф... Ну а потом деструктор...

Решение, в принципе, понятно, но все равно не понятно поведение — почему все работает-та, кроме подстчета ссылок? Про то, что ждется row интерфейс — оно логично, но должен ж он был на меня ругнуцца?
Re[5]: Ошибка с подсчетом ссылок
От: Лазар Бешкенадзе СССР  
Дата: 04.10.06 12:28
Оценка:
Здравствуйте, lol4ever, Вы писали:

L>Здравствуйте, AndrewJD, Вы писали:


AJD>>1. Если ты не хранишь этот обьект, то пользы от смарт поинтера никакой.

AJD>>2. Вызывающая сторона предполагает, что параметром будет поинтер на row интерфейс, а не какой-то класс. Тебе просто повезло, что размер _com_ptr совпал с размером указателя. Но конструктор никто не вызвал. ИМХО,
L>это ожидаемое поведение.

L>согласен, что от смарта пользы никакой, сделан на автомате в тот момент был, но все же...

L>А насчет данного везения — ох что-то сомневается мне, что так вот повезло с размерами... Моя логика такая:раз вызывается деструктор, то создавался _com_ptr_t. Раз создается _com_ptr_t, значит вызывается конструктор. Раз вызывается конструктор,должен быть аддреф...

Я только что изучил comip.h и во всех конструкторах _com_ptr_t нашел AddRef либо QueryInterface.
Противное было бы странно, уж очень интенсивно их пользуют во всем мире.

Но так ли это, что при создании ты делаешь QueryInterface?
Знаешь ли ты, что CComObject::CreateInstance дает объект с нулевым счетчиком?

Лазар
Re[6]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 12:41
Оценка:
ЛБ>Я только что изучил comip.h и во всех конструкторах _com_ptr_t нашел AddRef либо QueryInterface.
ЛБ>Противное было бы странно, уж очень интенсивно их пользуют во всем мире.

ЛБ>Но так ли это, что при создании ты делаешь QueryInterface?

ЛБ>Знаешь ли ты, что CComObject::CreateInstance дает объект с нулевым счетчиком?

ЛБ>Лазар


знаю, поэтому сразу после создания в сервере делаю QueryInterface — и интерфейс пускай лежит, и счетчик = 1, чтобы работать с объектом можно было.
НО потом мне приходится делать дополнительный аддреф, дабы после возврата из события объект жив остался.
Сейчас заменил входной параметр функции-обработчика на просто ВашИнтерфейс*. Все работает, ссылки не теряются, как и следовало ожидать. Но если опять заменю параметр на ВашИнтерфейсPtr — получу потерянную ссылку.

Соответственно получается, что не используется конструктор... Или как?.. Не понимаю =(

ЗЫ
могу привести примерный код
Re[7]: Ошибка с подсчетом ссылок
От: Лазар Бешкенадзе СССР  
Дата: 04.10.06 12:51
Оценка:
Здравствуйте, lol4ever, Вы писали:


ЛБ>>Я только что изучил comip.h и во всех конструкторах _com_ptr_t нашел AddRef либо QueryInterface.

ЛБ>>Противное было бы странно, уж очень интенсивно их пользуют во всем мире.

ЛБ>>Но так ли это, что при создании ты делаешь QueryInterface?

ЛБ>>Знаешь ли ты, что CComObject::CreateInstance дает объект с нулевым счетчиком?

ЛБ>>Лазар


L>знаю, поэтому сразу после создания в сервере делаю QueryInterface — и интерфейс пускай лежит, и счетчик = 1, чтобы работать с объектом можно было.

L>НО потом мне приходится делать дополнительный аддреф, дабы после возврата из события объект жив остался.
L>Сейчас заменил входной параметр функции-обработчика на просто ВашИнтерфейс*.

Я не совсем понял, что здесь имеется ввиду, формальный параметр или фактический аргумент.

L>Все работает, ссылки не теряются, как и следовало ожидать. Но если опять заменю параметр на ВашИнтерфейсPtr — получу потерянную ссылку.


L>Соответственно получается, что не используется конструктор... Или как?.. Не понимаю =(


L>ЗЫ

L>могу привести примерный код

Это, наверно было бы понятнее.

Лазар
Re[5]: Ошибка с подсчетом ссылок
От: AndrewJD США  
Дата: 04.10.06 13:07
Оценка:
Здравствуйте, lol4ever, Вы писали:


L>Решение, в принципе, понятно, но все равно не понятно поведение — почему все работает-та, кроме подстчета ссылок?

Я думаю все работает потому что внутренний поинтер в _com_ptr был проиницилизирован правильным значением.

L>Про то, что ждется row интерфейс — оно логично, но должен ж он был на меня ругнуцца?

Стандартная реализация вычисляет необходимый адрес метода через его смещения, поэтому ни о какой типизации речь не идет. Просто вызывающая сторона положит необходимые параметры (в том виде как они есть в библиотеке типов) в стек и сделает вызов.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[7]: Ошибка с подсчетом ссылок
От: Константин Л.  
Дата: 04.10.06 13:11
Оценка:
Здравствуйте, lol4ever, Вы писали:


[]

да потому что нельзя указывать в кач. параметров не raw-поинтеры!
размер _com_ptr_t соопадает с размером указателя, который он держит, тк кроме него у _com_ptr_t нет мемберов и виртуальных функций.
Конструктор просто не вызывается. Сделай деструктор _com_ptr_t виртуальным и посмотри, что получится
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Ошибка с подсчетом ссылок
От: Vi2 Удмуртия http://www.adem.ru
Дата: 04.10.06 13:19
Оценка:
Здравствуйте, lol4ever, Вы писали:

L>А теперь вот что происходит: при создании объекта делается QueryInterface, в итоге счетчик у объекта становится 1. Потом полученный интерфейс передается в Fire_ВашеСобытие, и там в ::VarianCopy делается AddRef(), как следствие счетчик становится равным двум. По приходу в функцию-обработчик события счетчик остается равным тем же самым двум.


Вот-вот, конструктор _com_ptr_t не вызвался ведь, не так ли?

L>Внутри функции ничего, кроме считывания данных из проперти объекта не происходит. А вот при выходе из функции-обработчика счетчик почему-то уменьшается на 1 (осталось 1).


Не почему-то, а по спецификации __stdcall — вызывающий должен очистить стек вызова.

L>Происходит ли такое странное поведение по моей вине, или же это очередной "прикол" от M$?


По твоей вине.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Re: Ошибка с подсчетом ссылок
От: George Seryakov Россия  
Дата: 04.10.06 13:22
Оценка:
Здравствуйте, lol4ever, Вы писали:

L>А теперь вот что происходит: при создании объекта делается QueryInterface, в итоге счетчик у объекта становится 1. Потом полученный интерфейс передается в Fire_ВашеСобытие, и там в ::VarianCopy делается AddRef(), как следствие счетчик становится равным двум. По приходу в функцию-обработчик события счетчик остается равным тем же самым двум. Внутри функции ничего, кроме считывания данных из проперти объекта не происходит. А вот при выходе из функции-обработчика счетчик почему-то уменьшается на 1 (осталось 1). Потом при удалении variantarg'a в Fire_ВашеСобытие происходит Release(),


Стоп. Если ты отдаешь интерфейс по вызову, то ты увеличиваешь счетчик на вызваемой стороне и уменьшаешь на вызывающей. То есть в Fire_ВашеСобытие должен быть нескомпенсированный AddRef.
GS
Re[8]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 13:43
Оценка:
Сервер, создание объекта:

    CComPtr<IMyInterface> pMyInterface;
    CComObject<CMyInterfaceCoclass>* pMyInterfaceCoclass;

    HRESULT hrCreateInstance = CComObject<CMyInterfaceCoclass>::CreateInstance(& pMyInterfaceCoclass);
    if (FAILED(hrCreateInstance)) return FALSE;

    HRESULT hrQueryInterface = pMyInterfaceCoclass->QueryInterface(IID_IMyInterface, (LPVOID*)&pMyInterface);
    if (FAILED(hrQueryInterface)) return FALSE;

    HRESULT hr = Fire_OnMyEvent(pMyInterface); //собсна генереная функция
    if (FAILED(hr)) return FALSE;


Описание события:

    [id(1), helpstring("method OnMyEvent")] HRESULT OnMyEvent([in] IMyInterface* pMyInterface);



Теперь клиент (подписка на событие сделана через наследование от IDispEventImpl; был сделан #import библиотеки с rename_namespace("MyLibNameSpace")), функция обработчик:

void _stdcall EventHandler(MyLibNameSpace::IMyInterfacePtr pMyInterface)
{
       ...
}


Этот вариант вызывает ошибку. Ошибка сделана мной, но какая-то она странная — когда вызывается данная функция, то вызывается она через реализацию инвока в IDispEventSimpleImpl (от которого IDispEventImpl наследуется), а там инвок осуществляется динамическим формированием стека при помощи функции DispCallFunc. Стек формируется, размеры _com_ptr_t и интерфейса совпадают, так что стек не рушится. Однако и конструктор _com_ptr_t не вызывается.
Правильно было бы (так и сделал в итоге) нужно сделать так:
void _stdcall EventHandler(MyLibNameSpace::IMyInterface* pMyInterface)
{
       ...
}


Если я где-то не прав, поправьте меня пожалуйста! А так это получается небольшой хак...

Но вот в связи с этим всем возникает вот какой вопрос: берем ворд. Подцепляемся к событиям. Ловим событие OnDocOpen, в котором передается указатель на кокласс. В нашем методе-обработчике в качестве типа параметра ставим вумный указатель — и все работает (правда, подсчитать ссылки в данном случае я не могу). Опять хак?
Re[9]: Ошибка с подсчетом ссылок
От: Лазар Бешкенадзе СССР  
Дата: 04.10.06 14:17
Оценка:
Здравствуйте, lol4ever, Вы писали:

[snip]

L>Если я где-то не прав, поправьте меня пожалуйста! А так это получается небольшой хак...


Все правильно.

L>Но вот в связи с этим всем возникает вот какой вопрос: берем ворд. Подцепляемся к событиям. Ловим событие OnDocOpen, в котором передается указатель на кокласс. В нашем методе-обработчике в качестве типа параметра ставим вумный указатель — и все работает (правда, подсчитать ссылки в данном случае я не могу). Опять хак?


Ну Office сломать очень трудно, там многоуровневая защита от хакеров.

Лазар
Re[2]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 14:24
Оценка:
Здравствуйте, George Seryakov, Вы писали:



GS>Стоп. Если ты отдаешь интерфейс по вызову, то ты увеличиваешь счетчик на вызваемой стороне и уменьшаешь на вызывающей. То есть в Fire_ВашеСобытие должен быть нескомпенсированный AddRef.


Интересное кино! Вот сейчас у меня НЕТ нескомпенсированного AddRef'a. И есть тест на C#, в котором автоматически (ну, табами) был создан обработчик данного события. Следуя данной логике, по выходу из обработчика клиент должен был релизнуться, так что ли? Если так, то должны были потеряться данные... А они не теряются — все доходит до сервера, обрабатывается, выдается ответ. Если б у меня был нескомпенсированный AddRef — вышла бы утечка ссылок.. Или я опять не прав?
Re[2]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 04.10.06 14:29
Оценка:
Здравствуйте, Vi2, Вы писали:


Vi2>Не почему-то, а по спецификации __stdcall — вызывающий должен очистить стек вызова.


Стоп стоп стоп.

[msdn]The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.
[/msdn]

Вызываемый то бишь... Ну это-то понятно... Но почему тогда шарповый обработчик его не чистит?
Re[9]: Ошибка с подсчетом ссылок
От: Константин Л.  
Дата: 04.10.06 14:35
Оценка:
Здравствуйте, lol4ever, Вы писали:

L>Но вот в связи с этим всем возникает вот какой вопрос: берем ворд. Подцепляемся к событиям. Ловим событие OnDocOpen, в котором передается указатель на кокласс. В нашем методе-обработчике в качестве типа параметра ставим вумный указатель — и все работает (правда, подсчитать ссылки в данном случае я не могу). Опять хак?


какая разница, какой интерфейс? в текущей реализации размер _com_ptr_t будет всегда совпадать с размером указателя на интерфейс, который он держит.
Еще раз повторяю, сделай деструктор _com_ptr_t виртуальным, и ты получишь AV
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Ошибка с подсчетом ссылок
От: Vi2 Удмуртия http://www.adem.ru
Дата: 04.10.06 14:42
Оценка:
Здравствуйте, lol4ever, Вы писали:

L>Вызываемый то бишь... Ну это-то понятно... Но почему тогда шарповый обработчик его не чистит?


Да потому что контракт
[id(1), helpstring("method OnMyEvent")] HRESULT OnMyEvent([in] IMyInterface* pMyInterface);
не предполагает уменьшение количества ссыkок для указателя pMyInterface. И С# его выполняет. Ты сам по собственной инициативе усилил функцию и поэтому потерял одну ссылку.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Re[3]: Ошибка с подсчетом ссылок
От: George Seryakov Россия  
Дата: 04.10.06 14:46
Оценка:
Здравствуйте, lol4ever, Вы писали:

GS>>Стоп. Если ты отдаешь интерфейс по вызову, то ты увеличиваешь счетчик на вызваемой стороне и уменьшаешь на вызывающей. То есть в Fire_ВашеСобытие должен быть нескомпенсированный AddRef.


L>Интересное кино! Вот сейчас у меня НЕТ нескомпенсированного AddRef'a. И есть тест на C#, в котором автоматически (ну, табами) был создан обработчик данного события. Следуя данной логике, по выходу из обработчика клиент должен был релизнуться, так что ли? Если так, то должны были потеряться данные... А они не теряются — все доходит до сервера, обрабатывается, выдается ответ. Если б у меня был нескомпенсированный AddRef — вышла бы утечка ссылок.. Или я опять не прав?


Нужно смотреть код. Я бы начал смотреть код с Fire_ВашеСобытие — как там интерфейс кладется в параметры.
GS
Re[4]: Ошибка с подсчетом ссылок
От: lol4ever  
Дата: 05.10.06 12:14
Оценка:
GS>Нужно смотреть код. Я бы начал смотреть код с Fire_ВашеСобытие — как там интерфейс кладется в параметры.

код стандартный, генереный мастером:


#pragma once

template<class T>
class CProxy_ICryptoEvents :
    public IConnectionPointImpl<T, &__uuidof(_ICryptoEvents)>
{
public:
    HRESULT Fire_OnMyEvent( IMyInterface * pMyInterface)
    {
        HRESULT hr = S_OK;
        T * pThis = static_cast<T *>(this);
        int cConnections = m_vec.GetSize();

        for (int iConnection = 0; iConnection < cConnections; iConnection++)
        {
            pThis->Lock();
            CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
            pThis->Unlock();

            IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);

            if (pConnection)
            {
                CComVariant avarParams[1];
                avarParams[0] = pEventArgs;
                CComVariant varResult;

                DISPPARAMS params = { avarParams, NULL, 1, 0 };
                hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);
            }
        }
        return hr;
    }
};
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.