Здравствуйте, lol4ever, Вы писали:
L>Есть клиент. Клиент делает #import либки того самого сервера (соответственно содается некий "ВашИнтерфейсPtr", использующий _com_ptr_t), далее идет подписка на событие с использованием IDispEventImpl. Соответственно, функция обработчик в качестве параметра принимает тот самый "ВашИнтерфейсPtr".
А почему функция обработчик принимает "ВашИнтерфейсPtr" вместо голого "ВашИнтерфейс" ?
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Есть сервер . Он в себе содержит некий интерфейс и его реализацию, причем такую, что объект этой реализации создать нельзя (макросы 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$?..
Заранее спасибо за ответы
Здравствуйте, AndrewJD, Вы писали:
AJD>Здравствуйте, lol4ever, Вы писали:
AJD>А почему функция обработчик принимает "ВашИнтерфейсPtr" вместо голого "ВашИнтерфейс" ?
ну, функция могла бы и принимать просто "ВашИнтерфейс"*, но если мне импорт дает возможность использовать сразу умный указатель, то почему бы его не использовать?..
Здравствуйте, lol4ever, Вы писали:
AJD>>А почему функция обработчик принимает "ВашИнтерфейсPtr" вместо голого "ВашИнтерфейс" ?
L>ну, функция могла бы и принимать просто "ВашИнтерфейс"*, но если мне импорт дает возможность использовать сразу умный указатель, то почему бы его не использовать?..
1. Если ты не хранишь этот обьект, то пользы от смарт поинтера никакой.
2. Вызывающая сторона предполагает, что параметром будет поинтер на row интерфейс, а не какой-то класс. Тебе просто повезло, что размер _com_ptr совпал с размером указателя. Но конструктор никто не вызвал. ИМХО, это ожидаемое поведение.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Здравствуйте, AndrewJD, Вы писали:
AJD>1. Если ты не хранишь этот обьект, то пользы от смарт поинтера никакой. AJD>2. Вызывающая сторона предполагает, что параметром будет поинтер на row интерфейс, а не какой-то класс. Тебе просто повезло, что размер _com_ptr совпал с размером указателя. Но конструктор никто не вызвал. ИМХО,
это ожидаемое поведение.
согласен, что от смарта пользы никакой, сделан на автомате в тот момент был, но все же...
А насчет данного везения — ох что-то сомневается мне, что так вот повезло с размерами... Моя логика такая:раз вызывается деструктор, то создавался _com_ptr_t. Раз создается _com_ptr_t, значит вызывается конструктор. Раз вызывается конструктор,должен быть аддреф... Ну а потом деструктор...
Решение, в принципе, понятно, но все равно не понятно поведение — почему все работает-та, кроме подстчета ссылок? Про то, что ждется row интерфейс — оно логично, но должен ж он был на меня ругнуцца?
Здравствуйте, 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 дает объект с нулевым счетчиком?
ЛБ>Я только что изучил comip.h и во всех конструкторах _com_ptr_t нашел AddRef либо QueryInterface. ЛБ>Противное было бы странно, уж очень интенсивно их пользуют во всем мире.
ЛБ>Но так ли это, что при создании ты делаешь QueryInterface? ЛБ>Знаешь ли ты, что CComObject::CreateInstance дает объект с нулевым счетчиком?
ЛБ>Лазар
знаю, поэтому сразу после создания в сервере делаю QueryInterface — и интерфейс пускай лежит, и счетчик = 1, чтобы работать с объектом можно было.
НО потом мне приходится делать дополнительный аддреф, дабы после возврата из события объект жив остался.
Сейчас заменил входной параметр функции-обработчика на просто ВашИнтерфейс*. Все работает, ссылки не теряются, как и следовало ожидать. Но если опять заменю параметр на ВашИнтерфейсPtr — получу потерянную ссылку.
Соответственно получается, что не используется конструктор... Или как?.. Не понимаю =(
ЛБ>>Я только что изучил comip.h и во всех конструкторах _com_ptr_t нашел AddRef либо QueryInterface. ЛБ>>Противное было бы странно, уж очень интенсивно их пользуют во всем мире.
ЛБ>>Но так ли это, что при создании ты делаешь QueryInterface? ЛБ>>Знаешь ли ты, что CComObject::CreateInstance дает объект с нулевым счетчиком?
ЛБ>>Лазар
L>знаю, поэтому сразу после создания в сервере делаю QueryInterface — и интерфейс пускай лежит, и счетчик = 1, чтобы работать с объектом можно было. L>НО потом мне приходится делать дополнительный аддреф, дабы после возврата из события объект жив остался. L>Сейчас заменил входной параметр функции-обработчика на просто ВашИнтерфейс*.
Я не совсем понял, что здесь имеется ввиду, формальный параметр или фактический аргумент.
L>Все работает, ссылки не теряются, как и следовало ожидать. Но если опять заменю параметр на ВашИнтерфейсPtr — получу потерянную ссылку.
L>Соответственно получается, что не используется конструктор... Или как?.. Не понимаю =(
L>ЗЫ L>могу привести примерный код
L>Решение, в принципе, понятно, но все равно не понятно поведение — почему все работает-та, кроме подстчета ссылок?
Я думаю все работает потому что внутренний поинтер в _com_ptr был проиницилизирован правильным значением.
L>Про то, что ждется row интерфейс — оно логично, но должен ж он был на меня ругнуцца?
Стандартная реализация вычисляет необходимый адрес метода через его смещения, поэтому ни о какой типизации речь не идет. Просто вызывающая сторона положит необходимые параметры (в том виде как они есть в библиотеке типов) в стек и сделает вызов.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
да потому что нельзя указывать в кач. параметров не raw-поинтеры!
размер _com_ptr_t соопадает с размером указателя, который он держит, тк кроме него у _com_ptr_t нет мемберов и виртуальных функций.
Конструктор просто не вызывается. Сделай деструктор _com_ptr_t виртуальным и посмотри, что получится
Здравствуйте, lol4ever, Вы писали:
L>А теперь вот что происходит: при создании объекта делается QueryInterface, в итоге счетчик у объекта становится 1. Потом полученный интерфейс передается в Fire_ВашеСобытие, и там в ::VarianCopy делается AddRef(), как следствие счетчик становится равным двум. По приходу в функцию-обработчик события счетчик остается равным тем же самым двум.
Вот-вот, конструктор _com_ptr_t не вызвался ведь, не так ли?
L>Внутри функции ничего, кроме считывания данных из проперти объекта не происходит. А вот при выходе из функции-обработчика счетчик почему-то уменьшается на 1 (осталось 1).
Не почему-то, а по спецификации __stdcall — вызывающий должен очистить стек вызова.
L>Происходит ли такое странное поведение по моей вине, или же это очередной "прикол" от M$?
Здравствуйте, lol4ever, Вы писали:
L>А теперь вот что происходит: при создании объекта делается QueryInterface, в итоге счетчик у объекта становится 1. Потом полученный интерфейс передается в Fire_ВашеСобытие, и там в ::VarianCopy делается AddRef(), как следствие счетчик становится равным двум. По приходу в функцию-обработчик события счетчик остается равным тем же самым двум. Внутри функции ничего, кроме считывания данных из проперти объекта не происходит. А вот при выходе из функции-обработчика счетчик почему-то уменьшается на 1 (осталось 1). Потом при удалении variantarg'a в Fire_ВашеСобытие происходит Release(),
Стоп. Если ты отдаешь интерфейс по вызову, то ты увеличиваешь счетчик на вызваемой стороне и уменьшаешь на вызывающей. То есть в Fire_ВашеСобытие должен быть нескомпенсированный AddRef.
Теперь клиент (подписка на событие сделана через наследование от IDispEventImpl; был сделан #import библиотеки с rename_namespace("MyLibNameSpace")), функция обработчик:
Этот вариант вызывает ошибку. Ошибка сделана мной, но какая-то она странная — когда вызывается данная функция, то вызывается она через реализацию инвока в IDispEventSimpleImpl (от которого IDispEventImpl наследуется), а там инвок осуществляется динамическим формированием стека при помощи функции DispCallFunc. Стек формируется, размеры _com_ptr_t и интерфейса совпадают, так что стек не рушится. Однако и конструктор _com_ptr_t не вызывается.
Правильно было бы (так и сделал в итоге) нужно сделать так:
Если я где-то не прав, поправьте меня пожалуйста! А так это получается небольшой хак...
Но вот в связи с этим всем возникает вот какой вопрос: берем ворд. Подцепляемся к событиям. Ловим событие OnDocOpen, в котором передается указатель на кокласс. В нашем методе-обработчике в качестве типа параметра ставим вумный указатель — и все работает (правда, подсчитать ссылки в данном случае я не могу). Опять хак?
[snip]
L>Если я где-то не прав, поправьте меня пожалуйста! А так это получается небольшой хак...
Все правильно.
L>Но вот в связи с этим всем возникает вот какой вопрос: берем ворд. Подцепляемся к событиям. Ловим событие OnDocOpen, в котором передается указатель на кокласс. В нашем методе-обработчике в качестве типа параметра ставим вумный указатель — и все работает (правда, подсчитать ссылки в данном случае я не могу). Опять хак?
Ну Office сломать очень трудно, там многоуровневая защита от хакеров.
GS>Стоп. Если ты отдаешь интерфейс по вызову, то ты увеличиваешь счетчик на вызваемой стороне и уменьшаешь на вызывающей. То есть в Fire_ВашеСобытие должен быть нескомпенсированный AddRef.
Интересное кино! Вот сейчас у меня НЕТ нескомпенсированного AddRef'a. И есть тест на C#, в котором автоматически (ну, табами) был создан обработчик данного события. Следуя данной логике, по выходу из обработчика клиент должен был релизнуться, так что ли? Если так, то должны были потеряться данные... А они не теряются — все доходит до сервера, обрабатывается, выдается ответ. Если б у меня был нескомпенсированный AddRef — вышла бы утечка ссылок.. Или я опять не прав?
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]
Вызываемый то бишь... Ну это-то понятно... Но почему тогда шарповый обработчик его не чистит?
Здравствуйте, lol4ever, Вы писали:
L>Но вот в связи с этим всем возникает вот какой вопрос: берем ворд. Подцепляемся к событиям. Ловим событие OnDocOpen, в котором передается указатель на кокласс. В нашем методе-обработчике в качестве типа параметра ставим вумный указатель — и все работает (правда, подсчитать ссылки в данном случае я не могу). Опять хак?
какая разница, какой интерфейс? в текущей реализации размер _com_ptr_t будет всегда совпадать с размером указателя на интерфейс, который он держит.
Еще раз повторяю, сделай деструктор _com_ptr_t виртуальным, и ты получишь AV
Здравствуйте, lol4ever, Вы писали:
L>Вызываемый то бишь... Ну это-то понятно... Но почему тогда шарповый обработчик его не чистит?
Да потому что контракт
[id(1), helpstring("method OnMyEvent")] HRESULT OnMyEvent([in] IMyInterface* pMyInterface);
не предполагает уменьшение количества ссыkок для указателя pMyInterface. И С# его выполняет. Ты сам по собственной инициативе усилил функцию и поэтому потерял одну ссылку.
Здравствуйте, lol4ever, Вы писали:
GS>>Стоп. Если ты отдаешь интерфейс по вызову, то ты увеличиваешь счетчик на вызваемой стороне и уменьшаешь на вызывающей. То есть в Fire_ВашеСобытие должен быть нескомпенсированный AddRef.
L>Интересное кино! Вот сейчас у меня НЕТ нескомпенсированного AddRef'a. И есть тест на C#, в котором автоматически (ну, табами) был создан обработчик данного события. Следуя данной логике, по выходу из обработчика клиент должен был релизнуться, так что ли? Если так, то должны были потеряться данные... А они не теряются — все доходит до сервера, обрабатывается, выдается ответ. Если б у меня был нескомпенсированный AddRef — вышла бы утечка ссылок.. Или я опять не прав?
Нужно смотреть код. Я бы начал смотреть код с Fire_ВашеСобытие — как там интерфейс кладется в параметры.
Vi2>Да потому что контракт Vi2> [id(1), helpstring("method OnMyEvent")] HRESULT OnMyEvent([in] IMyInterface* pMyInterface); Vi2>не предполагает уменьшение количества ссыkок для указателя pMyInterface. И С# его выполняет. Ты сам по собственной инициативе усилил функцию и поэтому потерял одну ссылку.
Понятно... Если отдаешь raw в событии, то и принимай raw в обработчике... Просто странно очень стало, что конструктор не вызвался...
Тогда, если можно,вопрос: так как же это все-таки наиболее корректно должно быть? Имеется ввиду событие, что в нем передавать и как считать ссылки. =)