Указатель на другой интерфейс.
От: NEW_DVA  
Дата: 25.06.04 07:05
Оценка:
Всем добрый день.
У меня такой вопрос.
Как получить указатель на другой интерфейс из моего метода с использованием ATL?
Например, реализация моего метода такая:


CComObject<CMyObj> *pCMyObj;
CComPtr<IMyObj> pIObj;

STDMETHODIMP MyClass::GetMyObject(REFIID riid, IUnknown **ppv)
{
    HRESULT hr = S_OK;
    if(riid == IID_IMyObj)
    {
        hr = CComObject<CMyObj>::CreateInstance(&pCMyObj);
        if(SUCCEEDED(hr))
        {
            hr =  pCMyObj->QueryInterface(riid, (void**)&pIObj);
            if(SUCCEEDED(hr))
            {
                pCMyObj->AddRef();
                *ppv = pCMyObj;
            }
        }
    }
    else
        hr = E_NOTIMPL;    
    return hr;
}


Но, почему-то всегда получаю ошибку, после вызова этого метода в клиенте, типа: «Приложение обратилось к объекту из другого потока».
Как правильно построить COM объект и передать на него указатель клиенту в этом случае?

Всем буду, благодарен за помощь.
Спасибо.
Re: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 07:14
Оценка:
Здравствуйте, NEW_DVA, Вы писали:

NEW>
NEW>CComObject<CMyObj> *pCMyObj;
NEW>CComPtr<IMyObj> pIObj;
..
NEW>


Всё хорошо, непонятно зечем переменные глобальные. Или это они просто обозначены так? Ну да на эту ошибку они повлиять не должны были.

NEW>Но, почему-то всегда получаю ошибку, после вызова этого метода в клиенте, типа: «Приложение обратилось к объекту из другого потока».

NEW>Как правильно построить COM объект и передать на него указатель клиенту в этом случае?

А ты потом этот указатель не передаешь в другой поток случайно? Если да, то вот и получаешь.. Если нет, то странно.
Делай что должно, и будь что будет
Re: Указатель на другой интерфейс.
От: Tom Россия http://www.RSDN.ru
Дата: 25.06.04 08:05
Оценка:
Здравствуйте, NEW_DVA, Вы писали:

NEW>Всем добрый день.

NEW>У меня такой вопрос.
NEW>Как получить указатель на другой интерфейс из моего метода с использованием ATL?
NEW>Например, реализация моего метода такая:


NEW>
NEW>CComObject<CMyObj> *pCMyObj;
NEW>CComPtr<IMyObj> pIObj;

NEW>STDMETHODIMP MyClass::GetMyObject(REFIID riid, IUnknown **ppv)
NEW>{
NEW>    HRESULT hr = S_OK;
NEW>    if(riid == IID_IMyObj)
NEW>    {
NEW>        hr = CComObject<CMyObj>::CreateInstance(&pCMyObj);
NEW>        if(SUCCEEDED(hr))
NEW>        {
NEW>            hr =  pCMyObj->QueryInterface(riid, (void**)&pIObj);
NEW>            if(SUCCEEDED(hr))
NEW>            {
NEW>                pCMyObj->AddRef();
NEW>                /* *ppv = pCMyObj; */ hr = pIObj.CopyTo((IMyObj**)ppv);
NEW>            }
NEW>        }
NEW>    }
NEW>    else
NEW>        hr = E_NOTIMPL;    
NEW>    return hr;
NEW>}
NEW>


NEW>Но, почему-то всегда получаю ошибку, после вызова этого метода в клиенте, типа: «Приложение обратилось к объекту из другого потока».

NEW>Как правильно построить COM объект и передать на него указатель клиенту в этом случае?

NEW>Всем буду, благодарен за помощь.

NEW>Спасибо.
Народная мудрось
всем все никому ничего(с).
Re[2]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 08:10
Оценка:
Здравствуйте, Tom, Вы писали:

NEW>>
hr = CComObject<CMyObj>::CreateInstance(&pCMyObj);
if(SUCCEEDED(hr))
{
    hr =  pCMyObj->QueryInterface(riid, (void**)&pIObj);
    if(SUCCEEDED(hr))
    {
        pCMyObj->AddRef();
        /* *ppv = pCMyObj; */ hr = pIObj.CopyTo((IMyObj**)ppv);
    }
}


Объясни, плиз. Код CopyTo (из 3-ки):

    HRESULT CopyTo(T** ppT)
    {
        ATLASSERT(ppT != NULL);
        if (ppT == NULL)
            return E_POINTER;
        *ppT = p;
        if (p)
            p->AddRef();
        return S_OK;
    }


Т.е. добавился один AddRef. Зачем?
Делай что должно, и будь что будет
Re[3]: Указатель на другой интерфейс.
От: Tom Россия http://www.RSDN.ru
Дата: 25.06.04 08:30
Оценка:
Здравствуйте, SergH, Вы писали:

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


NEW>>>
SH>hr = CComObject<CMyObj>::CreateInstance(&pCMyObj);
SH>if(SUCCEEDED(hr))
SH>{
SH>    hr =  pCMyObj->QueryInterface(riid, (void**)&pIObj);
SH>    if(SUCCEEDED(hr))
SH>    {
SH>        /* pCMyObj->AddRef(); */
SH>        /* *ppv = pCMyObj; */ hr = pIObj.CopyTo((IMyObj**)ppv);
SH>    }
SH>}
SH>


SH>Объясни, плиз. Код CopyTo (из 3-ки):


SH>
SH>    HRESULT CopyTo(T** ppT)
SH>    {
SH>        ATLASSERT(ppT != NULL);
SH>        if (ppT == NULL)
SH>            return E_POINTER;
SH>        *ppT = p;
SH>        if (p)
            p->>AddRef();
SH>        return S_OK;
SH>    }
SH>


SH>Т.е. добавился один AddRef. Зачем?


Наверх возвращаем указатель -> должны сделать AddRef.
А у него я просто забыл закаментарить его AddRef.
Сейчас закоментарил
Народная мудрось
всем все никому ничего(с).
Re[4]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 08:33
Оценка:
Здравствуйте, Tom, Вы писали:

Tom>Наверх возвращаем указатель -> должны сделать AddRef.


Так он же его явно делал.

Tom>А у него я просто забыл закаментарить его AddRef.

Tom>Сейчас закоментарил

Ну сейчас значит точно так же, как было (с точки зрения счётчиков ссылок). Хотя с CopyTo, конечно, несколько красивше.
Делай что должно, и будь что будет
Re[2]: Указатель на другой интерфейс.
От: NEW_DVA  
Дата: 25.06.04 08:37
Оценка:
Здравствуйте, Tom, Вы писали:

NEW>>CComObject<CMyObj> *pCMyObj;
NEW>>CComPtr<IMyObj> pIObj;

NEW>>STDMETHODIMP MyClass::GetMyObject(REFIID riid, IUnknown **ppv)
NEW>>{
NEW>>    HRESULT hr = S_OK;
NEW>>    if(riid == IID_IMyObj)
NEW>>    {
NEW>>        hr = CComObject<CMyObj>::CreateInstance(&pCMyObj);
NEW>>        if(SUCCEEDED(hr))
NEW>>        {
NEW>>            hr =  pCMyObj->QueryInterface(riid, (void**)&pIObj);
NEW>>            if(SUCCEEDED(hr))
NEW>>            {
NEW>>                pCMyObj->AddRef();
NEW>>                /* *ppv = pCMyObj; */ hr = pIObj.CopyTo((IMyObj**)ppv);
NEW>>            }
NEW>>        }
NEW>>    }
NEW>>    else
NEW>>        hr = E_NOTIMPL;    
NEW>>    return hr;
NEW>>}
NEW>>


Спасибо.
Я попробовал ваш вариант, всё ровно выскакивает та же ошибка в клиентском приложении, при вызове метода уже у созданного объекта (IMyObj).
-2147417842 (8001010E) The application called an interface that was
marshalled for a different thread.
Re[3]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 08:43
Оценка:
Здравствуйте, NEW_DVA, Вы писали:

NEW>-2147417842 (8001010E) The application called an interface that was

NEW> marshalled for a different thread.


— а до передачи он работает? Т.е. из GetMyObject его можно вызвать и всё будет хорошо?
— не, ты точно его никуда не передаёшь?
Делай что должно, и будь что будет
Re[2]: Указатель на другой интерфейс.
От: NEW_DVA  
Дата: 25.06.04 08:56
Оценка:
Здравствуйте, SergH, Вы писали:


SH>А ты потом этот указатель не передаешь в другой поток случайно? Если да, то вот и получаешь.. Если нет, то странно.


Нет, не передаю.
Клиент очень простой:

    IMyClass *pIMyClass = NULL;
    IMyObj *obj = NULL;
    int param;
    HRESULT hRes = ::CoCreateInstance(CLSID_MyClass, 
                                    NULL,
                                    CLSCTX_SERVER,
                                    IID_IMyClass,
                                    (void**)&pIMyClass);
    if(SUCCEEDED(hRes))
    {
        hRes = pIMyClass->GetMyObject(IID_IMyObj, (IUnknown**)&obj);
        if(SUCCEEDED(hRes))
        {
            hRes = obj->Method1(&param);/* тут возникает ошибка: -2147417842 (8001010E)    The application called an interface that was
  marshalled for a different thread.*/

            hRes = obj->Release();
        }
            
        pIMyClass->Release();
    
    }

Спасибо.
Re[4]: Указатель на другой интерфейс.
От: NEW_DVA  
Дата: 25.06.04 09:10
Оценка:
Здравствуйте, SergH, Вы писали:


NEW>>-2147417842 (8001010E) The application called an interface that was

NEW>> marshalled for a different thread.


SH>- а до передачи он работает? Т.е. из GetMyObject его можно вызвать и всё будет хорошо?

SH>- не, ты точно его никуда не передаёшь?

1)В методе MyClass::GetMyObject все методы интерфейса IMyObj вызываются нормально, например мы вызываем метод pCMyObj ->AddRef(); (используя CopyTo или не используя).

2) Вот клиент, он очень простой:

    IMyClass *pIMyClass = NULL;
    IMyObj *obj = NULL;
    int param;
    HRESULT hRes = ::CoCreateInstance(CLSID_MyClass, 
                                    NULL,
                                    CLSCTX_SERVER,
                                    IID_IMyClass,
                                    (void**)&pIMyClass);
    if(SUCCEEDED(hRes))
    {
        hRes = pIMyClass->GetMyObject(IID_IMyObj, (IUnknown**)&obj);
        if(SUCCEEDED(hRes))
        {
            hRes = obj->Method1(&param);/* тут возникает ошибка: -2147417842 (8001010E)    The application called an interface that was
  marshalled for a different thread.*/

            hRes = obj->Release();
        }
            
        pIMyClass->Release();
    
    }



Спасибо.
Re[5]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 09:13
Оценка:
Здравствуйте, NEW_DVA, Вы писали:

NEW>1)В методе MyClass::GetMyObject все методы интерфейса IMyObj вызываются нормально, например мы вызываем метод pCMyObj ->AddRef(); (используя CopyTo или не используя).


AddRef не считается. Попробуй что-нибудь более существенное. Или поппробуй AddRef из клиента.
Кстати, что за сервер? exe, dll?
Делай что должно, и будь что будет
Re[6]: Указатель на другой интерфейс.
От: NEW_DVA  
Дата: 25.06.04 09:34
Оценка:
Здравствуйте, SergH, Вы писали:

SH>AddRef не считается. Попробуй что-нибудь более существенное. Или поппробуй AddRef из клиента.

SH>Кстати, что за сервер? exe, dll?

Да, уже попробовал все методы вызываются нормально, например IMyObj::Method1(int* param);
Это exe – сервер, точнее сервис.
Все COM классы наследуются от CComObjectRootEx<CComSingleThreadModel>.
Может какой-то другой подход есть к решению этой проблемы, например объект строить как-то по-другому?


Заранее спасибо.
Re[7]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 10:13
Оценка:
Здравствуйте, NEW_DVA, Вы писали:

NEW>Это exe – сервер, точнее сервис.

NEW>Все COM классы наследуются от CComObjectRootEx<CComSingleThreadModel>.

Угу, с этого надо было начинать. В смысле, сразу сказать. А мне спросить...

NEW>Может какой-то другой подход есть к решению этой проблемы, например объект строить как-то по-другому?


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

Я думаю, у тебя где-то не согласованы поточные модели, поэтому глючит маршалинг. Пока не представляю, правда, где...
На всякий случай для прояснения ситуации:

1. Как инициализируется COM в службе? COINIT_APARTMENTTHREADED/COINIT_MULTITHREADED?
2. Что там происходит дальше? В смысле, цикл выборки сообщений или ожидание какого-нибудь события?
3. Как в сервере с потоками? Один, два, много?
4. До кучи — как клиент инициализирует COM?
Делай что должно, и будь что будет
Re[8]: Указатель на другой интерфейс.
От: NEW_DVA  
Дата: 25.06.04 11:10
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Я думаю, у тебя где-то не согласованы поточные модели, поэтому глючит маршалинг. Пока не представляю, правда, где...

SH>На всякий случай для прояснения ситуации:

SH>1. Как инициализируется COM в службе? COINIT_APARTMENTTHREADED/COINIT_MULTITHREADED?

SH>2. Что там происходит дальше? В смысле, цикл выборки сообщений или ожидание какого-нибудь события?
SH>3. Как в сервере с потоками? Один, два, много?
SH>4. До кучи — как клиент инициализирует COM?

1) COM инициализируется в службе следующим образом, в методе:
void CServiceModule::Run()
{
    _Module.dwThreadID = GetCurrentThreadId();

    HRESULT hr = CoInitialize(NULL);
    _ASSERTE(SUCCEEDED(hr));
   hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
            RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IDENTIFY,
            NULL, EOAC_NONE, NULL); /*отключаем Security*/
    _ASSERTE(SUCCEEDED(hr));
    ............................................
    CoUninitialize();
}

Вообще то, я создавал все СОМ объекты как Apartment. Чем в принципе отличаются объекты Apartment и Single я так и не понимаю….(но, это уже другая тема…)
Дальше всё идёт стандартно, всё что генерит Wizard у VisulStudio.
Принудительно я ни где потоки не создаю. Всё упрощенно максимально, чтобы разобраться с этой проблемой.
MIDL простой, для этого примера:
import "oaidl.idl";
import "ocidl.idl";
    [
        object,
        uuid(2B328E42-6B4C-4EF2-87E3-367C8661FA32),
        dual,
        helpstring("IMyClass Interface"),
        pointer_default(unique)
    ]
    interface IMyClass: IDispatch
    {
        [id(1), helpstring("method GetMyObject")] HRESULT GetMyObject([in] REFIID riid, [out, iid_is(riid)] IUnknown **ppv);
    };

[
    uuid(DDFBC517-BEA6-4966-B42B-E04676028936),
    version(1.0),
    helpstring("COMTest 1.0 Type Library")
]
library COMTESTLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [
        uuid(6C047820-06E5-429C-898F-D2EAAA892916),
        helpstring("_IMyClassEvents Interface")
    ]
    dispinterface _IMyClassEvents
    {
        properties:
        methods:
        [id(1), helpstring("method MyEvent")] HRESULT MyEvent();
    };
    [
        object,
        uuid(F71C9C04-D55F-4AD9-9C2D-A9EBC94EE2AB),
        dual,
        helpstring("IMyObj Interface"),
        pointer_default(unique)
    ]
    interface IMyObj : IDispatch
    {
        [id(1), helpstring("method Method1")] HRESULT Method1([in, out] int* param);
    };

    [
        uuid(27C7F4D7-BC2E-4ED8-A855-E4D3B6EAD3B2),
        helpstring("IMyClass Class")
    ]
    coclass MyClass
    {
        [default] interface IMyClass;
        [default, source] dispinterface _IMyClassEvents;
    };
    [
        uuid(5374242B-3D5F-44D8-91E4-CFAC135AE020),
        helpstring("MyObj Class")
    ]
    coclass MyObj
    {
        [default] interface IMyObj;
    };
};

ConnectionPoint я пока не трогал, т.е. ни какую реализацию на клиенте ещё не делал.
У клиента, то же инициализация СОМ стандартная, через CoInitialize(NULL).


Спасибо.
Re[9]: Указатель на другой интерфейс.
От: Vi2 Удмуртия http://www.adem.ru
Дата: 25.06.04 11:51
Оценка: 30 (2)
Здравствуйте, NEW_DVA, Вы писали:

NEW> interface IMyClass: IDispatch

NEW> {
NEW> [] HRESULT GetMyObject([in] REFIID riid, [out, iid_is(riid)] IUnknown **ppv);
NEW> };

Вот этому интерфейсу нужна пользовательская прокси/стаб: стандартная прокси/стаб не оказывает влияние riid на возвращаемый интерфейс — возвращается стандартный интерфейс IUnknown, который просто не имеет всех методов интерфейса riid. Так что проще задействовать QueryInterface.


PS
Вот в этом и есть польза void** вместо IUnknown ** в таких случаях.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Re[9]: Указатель на другой интерфейс.
От: Tom Россия http://www.RSDN.ru
Дата: 25.06.04 12:36
Оценка:
Ещё раз весь код сервера и клиенита давай
Народная мудрось
всем все никому ничего(с).
Re[10]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 12:36
Оценка:
Здравствуйте, Vi2, Вы писали:

Vi2>Вот этому интерфейсу нужна пользовательская прокси/стаб: стандартная прокси/стаб не оказывает влияние riid на возвращаемый интерфейс — возвращается стандартный интерфейс IUnknown, который просто не имеет всех методов интерфейса riid. Так что проще задействовать QueryInterface.


О, раз уж появился, объясни мне, что у меня происходит при попытке воспроизвести?

— Если сделать IUnknown**, то ты вроде объяснил, поведение стабильное. Я правильно понял, что просто то, что если что-то прописано в midl-е в качестве интерфейса, то iid_is не работает? Или как?

— Если сделать void** (я тоже сразу про него подумал, только я шёл от соображений, что раз QI работает, так просто повторим его интерфейс), всё собирается, но при создании объекта-сервера (т.е. первый CoCreateInstance в клиенте) возвращается ошибка 0x80020008 — "Неверный тип переменной". Это что, не вышло создать такой прокси что-ли?

А работающий вариант выглядит так: возвращать IUnknown, а QI выполнять на клиенте. Все методы IUnknown вызываются нормально.
Делай что должно, и будь что будет
Re[11]: Указатель на другой интерфейс.
От: Vi2 Удмуртия http://www.adem.ru
Дата: 25.06.04 12:54
Оценка: 6 (1)
Здравствуйте, SergH, Вы писали:

SH>- Если сделать IUnknown**, то ты вроде объяснил, поведение стабильное. Я правильно понял, что просто то, что если что-то прописано в midl-е в качестве интерфейса, то iid_is не работает? Или как?


В библиотеке типа нет места для информации, связанной с iid_is. Там указан базовый тип параметра, а это IUnknown. Вот к нему все и приводится.

SH>- Если сделать void** (я тоже сразу про него подумал, только я шёл от соображений, что раз QI работает, так просто повторим его интерфейс), всё собирается, но при создании объекта-сервера (т.е. первый CoCreateInstance в клиенте) возвращается ошибка 0x80020008 — "Неверный тип переменной". Это что, не вышло создать такой прокси что-ли?


Уже не помню, что тут должно быть. Экспериментировал давным-давно, на практике не приходится пользоваться, так что все позабылось. Осталось ощущение, что тип void является типом ограниченного использования и вызывает некоторую диагностику.

SH>А работающий вариант выглядит так: возвращать IUnknown, а QI выполнять на клиенте. Все методы IUnknown вызываются нормально.


Для возрата интерфейсов ЭТОГО же объекта нет нужды придумывать такие функции. Если объект работает в качестве фабрики ДРУГИХ объектов, то лучше реализовать стандартный IServiceProvider с более богатыми возможностями.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Re[12]: Указатель на другой интерфейс.
От: SergH Россия  
Дата: 25.06.04 13:16
Оценка:
Здравствуйте, Vi2, Вы писали:

Vi2>В библиотеке типа нет места для информации, связанной с iid_is. Там указан базовый тип параметра, а это IUnknown. Вот к нему все и приводится.


А где же он тогда нужен? Или только в автоматически генерируемом custom-proxy?

Vi2>Уже не помню, что тут должно быть. Экспериментировал давным-давно, на практике не приходится пользоваться, так что все позабылось. Осталось ощущение, что тип void является типом ограниченного использования и вызывает некоторую диагностику.


Я думал, dual интерфейс с ним вообще не соберётся.. А он собрался (tlb есть, и там void**, я проверил), но не создался..

Vi2>Для возрата интерфейсов ЭТОГО же объекта нет нужды придумывать такие функции. Если объект работает в качестве фабрики ДРУГИХ объектов, то лучше реализовать стандартный IServiceProvider с более богатыми возможностями.


Так, извини, я опять не понял..
Возврат чужих заданных заранее — просто возвращаем.
Возврат своих интерфейсов заданных заранее — просто возвращаем.
Возврат своих интерфейсов по IID — QI.
Возврат чужих по IID — а почему IServiceProvider, чем он лучше самопального решения? .
Делай что должно, и будь что будет
Re[13]: Указатель на другой интерфейс.
От: Vi2 Удмуртия http://www.adem.ru
Дата: 25.06.04 13:41
Оценка: 6 (1)
Здравствуйте, SergH, Вы писали:

SH>А где же он тогда нужен? Или только в автоматически генерируемом custom-proxy?


Не знаю.

SH>Я думал, dual интерфейс с ним вообще не соберётся.. А он собрался (tlb есть, и там void**, я проверил), но не создался..


Он собраться может, т.к. VT_VOID описывает данные именно void. Но при разборе параметров при построении прокси/стаб должна появляться ошибка. Потому как неизвестно как маршаллить данные VT_VOID и VT_PTR (и, может быть, еще один VT_PTR).

SH>Так, извини, я опять не понял..

SH>Возврат чужих заданных заранее — просто возвращаем.
SH>Возврат своих интерфейсов заданных заранее — просто возвращаем.

Заданные заранее вроде бы как и не рассматриваются здесь вовсе. Иначе смысла в параметре riid нет.

SH>Возврат своих интерфейсов по IID — QI.

SH>Возврат чужих по IID — а почему IServiceProvider, чем он лучше самопального решения? .

Вот ты когда пользуешься QI, ты смотришь на тип интерфейса, с помощью которого ты запрашиваешь новый интерфейс? Нет. Главное чтобы объект был соответствующий. Так и с помощью IServiceProvider можно у любого объекта приложения запрашивать построение других объектов. Если, конечно, реализовано.
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! © КВН НГУ
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.