How does CreateStdDispatch know what method to invoke?
Asked Answered
B

2

5

i'm faced with implementing an IDispatch interface. There are four methods, and fortunately 3 of them are easy:

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

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

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

It's the last method, Invoke that is difficult. Here i am faced with having to actually case the DispID, and call my appropriate method; unmarhsalling parameters from a variant array.

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

Not wanting to have to write all the tedious boilerplate code, that i'm sure will have bugs, i went googling - rather than doing any work.

i found this snippit on the MSDN Documentation of IDispatch.Invoke:

Generally, you should not implement Invoke directly.

Excellent! i didn't want to implement it anyway! Continuing reading:

Instead, use the dispatch interface to create functions CreateStdDispatch and DispInvoke. For details, refer to CreateStdDispatch, DispInvoke, Creating the IDispatch Interface and Exposing ActiveX Objects.

The Creating the IDispatch Interface link says:

You can implement IDispatch by any of the following means:

  • [snip]
  • Calling the CreateStdDispatch function. This approach is the simplest, but it does not provide for rich error handling or multiple national languages.
  • [snip]

Excellent, CreateStdDispatch it is:

Creates a standard implementation of the IDispatch interface through a single function call. This simplifies exposing objects through Automation.

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

i was going to call it as:

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
);

What i cannot figure out is how the Windows API implemention of CreateStdDispatch knows what methods to call on my object - especially since CreateStdDispatch doesn't know what object-oriented language i'm using, or its calling conventions.

How will CreateStdDispatch know

  • what method to call for a given dispid?
  • the calling convention of my language?
  • how to handle exceptions from the language that my object oriented object is written in?

Note: i have no choice but to implement a dispinterface; i didn't define the interface. i wish it was a simple early bound IUnknown, but it tisn't.

Bachelor answered 28/6, 2011 at 15:2 Comment(5)
Don't you just derive from TAutoIntfObject or am I missing something?Prosecute
If those are really your implementations of the other three methods, then you don't really have to implement Invoke, either, since no consumer will ever get far enough to call it anyway. The consumer will call GetIDsOfNames to find out the dispid of the method or property it wants to invoke, but you're going to respond that it's not implemented. Without a dispid, the consumer cannot fill in the first Invoke parameter. Besides, the documentation doesn't say that E_NOTIMPL is even a valid result of GetIDsOfNames.Saxony
And why is this tagged as "delphi" if there isn't anything about delphi in the question? Delphi is never mentioned and all code shown is C.Maidstone
@Rob Kennedy: Not true. It's a dispinterface, and the person calling me (Internet Explorer) already knows the dispIDs it wants to invoke. See Techvanguard's excellent examples of automation event sinking (techvanguards.com/products/eventsinkimp).Bachelor
@Thorsten Engler: i changed the code examples to have a Delphi flair.Bachelor
B
2

The short answer to your question is: neither CreateStdDispatch() nor the IDispatch implementation it creates knows anything at all about the methods to be called.

The object that you get back simply stores the parameters that you passed to CreateStdDispatch(), and for all IDispatch methods it only turns around and makes the corresponding calls on the ITypeInfo that you gave it. That is all.

If you pass nil for ptinfo as shown in your code then you only get E_INVALIDARG, since the implementing object cannot do anything at all without an ITypeInfo to which to delegate all the work.

If you inspect the code for CStdDisp in oleaut32.dll then you will find that it calls API functions like DispInvoke() (which also live in that DLL) instead of invoking the ITypeInfo methods directly, but these functions are all simple wrappers for calls to the ITypeInfo methods, without any further functionality.

In case anyone wonders: neither CreateStdDispatch() nor CStdDisp performs any additional magic; all they do is give you an IDispatch that does whatever the ITypeInfo that you passed in can do. Think of it as a kind of an adapter that allows you to plug an ITypeInfo into an IDispatch socket.

It is true that TAutoIntfObject.Create() needs a type library. However, all that the constructor does is call GetTypeInfoOfGuid() on it in order to get a type info pointer, to which the object then delegates most of the work related to dispatch things.

Borland in their wisdom made the member variable for the type info pointer private, which means that you really need to hand the constructor some type library or other that contains the interface in question, instead of simply writing another constructor or overriding some virtual function. On the other hand it shouldn't be too hard to load the type library via the registry or to dump parts of it to a TLB file. Inspecting a TLB with OleView gives you actual compilable IDL which is often also Borland-compilable RIDL.

CreateStdDispatch() does not know anything about exceptions either. The catching of exceptions thrown from COM methods and their conversion to HRESULT and/or IErrorInfo is compiler magic induced by Delphi's safecall keyword on the implementing method.

The same goes for the translation of HRESULTs to exceptions when calling COM methods specified as safecall in their interface declarations. The compiler simply inserts a call to @CheckAutoResult after every invocation of a safecall method; this function checks the HRESULT and throws EOleSysError if appropriate.

Simply switch the Delphi debugger to disassembly ('CPU view') to inspect all the magic that the compiler does for you!

Branle answered 21/4, 2021 at 19:4 Comment(0)
C
5

Doesn't the ITypeInfo parameter passed into CreateStdDispatch expose all of the method information?

So you'd create type info first calling CreateDispTypeInfo and pass that through to CreateStdDispatch which can then use the type information to work out which method to call since CreateDispTypeInfo requires INTERFACEDATA which contains all this information

I could be way wrong since I don't have time to look into it but that would make sense to me. I'll investigate this later and update the answer.

Conceivable answered 28/6, 2011 at 16:43 Comment(10)
This is exactly it. Ian, CreateStdDispatch knows all that stuff about your object because you told it that information when you gave it an ITypeInfo object. If you didn't give it one, then you're not finished with your project yet.Saxony
@Ian Why are you trying to write your own implementation of IDispatch when Delphi comes with one?Prosecute
@David Heffernan: Where is Delphi's implementation of IDispatch?Bachelor
Just derive from TAutoIntfObject like I said in comment to Q. I've never done it myself but that's my understanding of how it should be done. I think it's the way to go but I'm not 100% sure I understand what you are shooting at.Prosecute
@Rob Kennedy: i see it now, ITypeInfo::AddressOfMember, where i return the address of my class's methods. And although i don't know how to get the address of a class method, it certainly answers my question, "How does CreateStdDispatch" know how to call my methods?" The answer is that i give it the addresses of the functions (presumably relative to the start of the object).Bachelor
@David Heffernan: i am exposing the DWebBrowserEvents2 interface on an existing object to sink events from Internet Explorer and the Windows Shell. Unfortunately DWebBrowserEvents is a dispinterface.Bachelor
@Ian Doesn't TAutoIntfObject support dispinterfaces?Prosecute
@David Heffernan: Yes it does. A dispinterface is an IDispatch-like interface, except that the ID's of the methods are known at compile time. As long as the object i'm exposing has an IDispatch interface, the caller can call .Invoke, passing the dispid and the method parameters.Bachelor
@Ian Surely that's the answer to your problem, even if it's not the answer to the question you asked.Prosecute
@David Hefernan: Problem with TAutoIntfObject is that it requires a type library, which i don't have. i already have code from VCL and from Techvanguards that i can copy-paste as my implementation of IDispatch.Invoke. But that wasn't my question; which was wondering how CreateStdDispatch could possibly know what methods to call on my object.Bachelor
B
2

The short answer to your question is: neither CreateStdDispatch() nor the IDispatch implementation it creates knows anything at all about the methods to be called.

The object that you get back simply stores the parameters that you passed to CreateStdDispatch(), and for all IDispatch methods it only turns around and makes the corresponding calls on the ITypeInfo that you gave it. That is all.

If you pass nil for ptinfo as shown in your code then you only get E_INVALIDARG, since the implementing object cannot do anything at all without an ITypeInfo to which to delegate all the work.

If you inspect the code for CStdDisp in oleaut32.dll then you will find that it calls API functions like DispInvoke() (which also live in that DLL) instead of invoking the ITypeInfo methods directly, but these functions are all simple wrappers for calls to the ITypeInfo methods, without any further functionality.

In case anyone wonders: neither CreateStdDispatch() nor CStdDisp performs any additional magic; all they do is give you an IDispatch that does whatever the ITypeInfo that you passed in can do. Think of it as a kind of an adapter that allows you to plug an ITypeInfo into an IDispatch socket.

It is true that TAutoIntfObject.Create() needs a type library. However, all that the constructor does is call GetTypeInfoOfGuid() on it in order to get a type info pointer, to which the object then delegates most of the work related to dispatch things.

Borland in their wisdom made the member variable for the type info pointer private, which means that you really need to hand the constructor some type library or other that contains the interface in question, instead of simply writing another constructor or overriding some virtual function. On the other hand it shouldn't be too hard to load the type library via the registry or to dump parts of it to a TLB file. Inspecting a TLB with OleView gives you actual compilable IDL which is often also Borland-compilable RIDL.

CreateStdDispatch() does not know anything about exceptions either. The catching of exceptions thrown from COM methods and their conversion to HRESULT and/or IErrorInfo is compiler magic induced by Delphi's safecall keyword on the implementing method.

The same goes for the translation of HRESULTs to exceptions when calling COM methods specified as safecall in their interface declarations. The compiler simply inserts a call to @CheckAutoResult after every invocation of a safecall method; this function checks the HRESULT and throws EOleSysError if appropriate.

Simply switch the Delphi debugger to disassembly ('CPU view') to inspect all the magic that the compiler does for you!

Branle answered 21/4, 2021 at 19:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.