Как CreateStdDispatch узнает, какой метод вызывать?

я столкнулся с реализацией интерфейса IDispatch. Есть четыре метода, и, к счастью, 3 из них просты:

function TIEEventsSink.GetTypeInfoCount(...): HResult;
{
   Result := E_NOTIMPL;
}

function TIEEventsSink.GetTypeInfo(...): HResult;
{
   Result := E_NOTIMPL;
}

function TIEEventsSink.GetIDsOfNames(...): HResult;
{
   Result := E_NOTIMPL;
}

Это последний способ, Invoke сложный. Здесь я столкнулся с тем, что мне нужно фактически регистрировать DispID и вызывать соответствующий метод; рассортировка параметров из массива вариантов.

function Invoke(  
  dispIdMember: DISPID;
  riid: REFIID;
  lcid: LCID;
  wFlags: WORD;
  var pDispParams: DISPPARAMS;
  var pVarResult: VARIANT;
  var pExcepInfo: EXCEPINFO;
  var puArgErr: DWORD
): HRESULT;

Не желая писать весь утомительный шаблонный код, в котором, я уверен, будут ошибки, я пошел гуглить, а не выполнять какую-либо работу.

я нашел этот фрагмент в документации MSDN IDispatch.Invoke:

Как правило, не следует реализовывать Invoke напрямую.

Отлично! я все равно не хотел его реализовывать! Продолжаем читать:

Вместо этого используйте интерфейс отправки для создания функций CreateStdDispatch и DispInvoke. Дополнительные сведения см. в разделах CreateStdDispatch, DispInvoke, Создание интерфейса IDispatch и Предоставление объектов ActiveX.

Ссылка Создание интерфейса IDispatch гласит:

Вы можете реализовать IDispatch любым из следующих способов:

  • [отрезать]
  • Вызов функции CreateStdDispatch. Этот подход является самым простым, но он не обеспечивает богатой обработки ошибок или нескольких национальных языков.
  • [отрезать]

Отлично, CreateStdDispatch:

Создает стандартную реализацию интерфейса IDispatch с помощью одного вызова функции. Это упрощает предоставление объектов через автоматизацию.

HRESULT CreateStdDispatch(  
  IUnknown FAR*  punkOuter,        
  void FAR*  pvThis,               
  ITypeInfo FAR*  ptinfo,          
  IUnknown FAR* FAR* ppunkStdDisp  
);

я собирался назвать это так:

CreateStdDispatch(
    myUnk,          //Pointer to the object's IUnknown implementation.
    anotherObject,  //Pointer to the object to expose.
    nil             //Pointer to the type information that describes the exposed object (i has no type info)
    dispInterface   //the IUnknown of the object that implements IDispatch for me
);

Чего я не могу понять, так это того, как реализация Windows API CreateStdDispatch знает, какие методы вызывать для моего объекта, тем более что CreateStdDispatch не знает, какой объектно-ориентированный язык я использую, или его соглашения о вызовах.

Как CreateStdDispatch узнает

  • какой метод вызывать для данного dispid?
  • соглашение о вызовах моего языка?
  • как обрабатывать исключения из языка, на котором написан мой объектно-ориентированный объект?

Примечание: у меня нет другого выбора, кроме как реализовать dispinterface; я не определил интерфейс . Я бы хотел, чтобы это была простая ранняя привязка IUnknown, но это не так.


person Ian Boyd    schedule 28.06.2011    source источник
comment
Разве вы не просто вытекаете из TAutoIntfObject или я что-то упустил?   -  person David Heffernan    schedule 28.06.2011
comment
Если это действительно являются вашими реализациями трех других методов, то вам также не нужно реализовывать Invoke, так как ни один потребитель никогда не зайдет достаточно далеко, чтобы вызвать его в любом случае. Потребитель вызовет GetIDsOfNames, чтобы узнать dispid метода или свойства, которое он хочет вызвать, но вы ответите, что он не реализован. Без dispid потребитель не может заполнить первый параметр Invoke. Кроме того, в документации не сказано, что E_NOTIMPL является допустимым результатом GetIDsOfNames.   -  person Rob Kennedy    schedule 29.06.2011
comment
И почему это помечено как delphi, если в вопросе нет ничего о delphi? Delphi никогда не упоминается, и весь показанный код написан на C.   -  person Thorsten Engler    schedule 29.06.2011
comment
@ Роб Кеннеди: Неправда. Это дисп-интерфейс, и человек, звонящий мне (Internet Explorer), уже знает dispIDs, который он хочет вызвать. См. превосходные примеры обработки событий автоматизации от Techvanguard (techvanguards.com/products/eventsinkimp).   -  person Ian Boyd    schedule 29.06.2011
comment
@Thorsten Engler: я изменил примеры кода, чтобы они напоминали Delphi.   -  person Ian Boyd    schedule 29.06.2011


Ответы (2)


Короткий ответ на ваш вопрос: ни CreateStdDispatch(), ни IDispatch реализация, которую она создает, не знают вообще ничего о вызываемых методах.

Объект, который вы возвращаете, просто хранит параметры, которые вы передали в CreateStdDispatch(), и для всех методов IDispatch он только разворачивается и делает соответствующие вызовы ITypeInfo, которые вы ему передали. Вот и все.

Если вы передадите nil вместо ptinfo, как показано в вашем коде, вы получите только E_INVALIDARG, поскольку реализующий объект вообще ничего не может сделать без ITypeInfo, которому можно делегировать всю работу.

Если вы изучите код для CStdDisp в oleaut32.dll, то обнаружите, что он вызывает функции API, такие как DispInvoke() (которые также находятся в этой DLL), вместо прямого вызова методов ITypeInfo, но все эти функции являются простыми оболочками для вызовов ITypeInfo. методы без какой-либо дополнительной функциональности.

На случай, если кому-то интересно: ни CreateStdDispatch(), ни CStdDisp не выполняют никакой дополнительной магии; все, что они делают, это дают вам IDispatch, который делает то же, что и ITypeInfo, который вы передали. Думайте об этом как о своего рода адаптере, который позволяет вам подключать ITypeInfo к IDispatch сокету.

Это правда, что TAutoIntfObject.Create() нужна библиотека типов. Однако все, что делает конструктор, это вызывает для него GetTypeInfoOfGuid(), чтобы получить указатель информации о типе, которому объект затем делегирует большую часть работы, связанной с отправкой вещей.

Borland по своей мудрости сделал переменную-член для указателя информации о типе private, что означает, что вам действительно нужно передать конструктору какую-то библиотеку типов или другую, содержащую рассматриваемый интерфейс, вместо того, чтобы просто писать другой конструктор или переопределять какую-то виртуальную функцию. С другой стороны, не должно быть слишком сложно загрузить библиотеку типов через реестр или выгрузить ее части в файл TLB. Проверка TLB с помощью OleView дает фактически компилируемый IDL, который часто также является RIDL, компилируемым Borland.

CreateStdDispatch() тоже ничего не знает об исключениях. Перехват исключений, генерируемых COM-методами, и их преобразование в HRESULT и/или IErrorInfo — это магия компилятора, вызванная ключевым словом Delphi safecall в реализующем методе.

То же самое касается преобразования HRESULT в исключения при вызове методов COM, указанных как безопасный вызов в их объявлениях интерфейса. Компилятор просто вставляет вызов @CheckAutoResult после каждого вызова метода безопасного вызова; эта функция проверяет HRESULT и при необходимости выдает EOleSysError.

Просто переключите отладчик Delphi в режим дизассемблирования ('CPU view'), чтобы проверить всю ту магию, которую компилятор делает за вас!

person DarthGizka    schedule 21.04.2021

Разве параметр ITypeInfo, переданный в CreateStdDispatch, не раскрывает всю информацию о методе?

Таким образом, вы должны сначала создать информацию о типе, вызвав CreateDispTypeInfo, и передать ее CreateStdDispatch, который затем может использовать информацию о типе, чтобы определить, какой метод вызывать, поскольку CreateDispTypeInfo требует INTERFACEDATA, который содержит всю эту информацию.

Я могу ошибаться, так как у меня нет времени разбираться в этом, но это имело бы для меня смысл. Я изучу это позже и обновлю ответ.

person James Barrass    schedule 28.06.2011
comment
Это точно. Ян, CreateStdDispatch знает все о вашем объекте, потому что вы сообщили эту информацию, когда предоставили ему объект ITypeInfo. Если вы не дали ему один, значит, вы еще не закончили свой проект. - person Rob Kennedy; 29.06.2011
comment
@Ian Почему вы пытаетесь написать свою собственную реализацию IDispatch, когда Delphi поставляется с ней? - person David Heffernan; 29.06.2011
comment
@David Heffernan: Где реализация IDispatch в Delphi? - person Ian Boyd; 29.06.2011
comment
Просто выведите из TAutoIntfObject, как я сказал в комментарии к Q. Я никогда не делал этого сам, но это мое понимание того, как это должно быть сделано. Я думаю, что это правильный путь, но я не уверен на 100%, что понимаю, во что вы стреляете. - person David Heffernan; 29.06.2011
comment
@Rob Kennedy: теперь я вижу, ITypeInfo::AddressOfMember, где я возвращаю адрес методов моего класса. И хотя я не знаю, как получить адрес метода класса, это определенно отвечает на мой вопрос: откуда CreateStdDispatch знает, как вызывать мои методы? Ответ заключается в том, что я даю ему адреса функций (предположительно относительно начала объекта). - person Ian Boyd; 29.06.2011
comment
@David Heffernan: я открываю интерфейс DWebBrowserEvents2 для существующего объекта, чтобы принимать события из Internet Explorer и Windows Shell. К сожалению, DWebBrowserEvents является дисп-интерфейсом. - person Ian Boyd; 29.06.2011
comment
@Ian Разве TAutoIntfObject не поддерживает дисп-интерфейсы? - person David Heffernan; 29.06.2011
comment
@ Дэвид Хеффернан: Да, это так. Dispinterface — это интерфейс, подобный IDispatch, за исключением того, что идентификаторы методов известны во время компиляции. Пока объект, который я выставляю, имеет интерфейс IDispatch, вызывающая сторона может вызвать .Invoke, передав параметры dispid и метода. - person Ian Boyd; 29.06.2011
comment
@Ian Конечно, это ответ на вашу проблему, даже если это не ответ на вопрос, который вы задали. - person David Heffernan; 29.06.2011
comment
@David Hefernan: Проблема с TAutoIntfObject в том, что для этого требуется библиотека типов, которой у меня нет. у меня уже есть код от VCL и от Techvanguards, который я могу скопировать и вставить как свою реализацию IDispatch.Invoke. Но это был не мой вопрос; который задавался вопросом, как CreateStdDispatch мог знать, какие методы вызывать для моего объекта. - person Ian Boyd; 30.06.2011