0

I have a Visual Studio add-in, which contains a scripting engine implemented in C++. The add-in can only communicate with Visual Studio using IDispatch interfaces. I am in the process of upgrading it from VS 2005 to VS 2010.

The add-in makes a series of IDispatch::Invoke() calls equivalent to this Visual Basic:

control = commandBar.Controls.Add(MsoControlType.msoControlButton)
control.Caption = "My button"
control.FaceId = 59

In VS 2005, this used to work. But in VS 2010 it doesn't. GetIDsOfNames() returns DISP_E_UNKNOWNNAME for "FaceId".

Note that "Caption" (which succeeds) is a property of CommandBarControl, and "FaceId" (which fails) is a property of the CommandBarButton subclass. The classname for the button's IDispatch* is CommandBarControl. So I think I need to downcast the CommandBarControl IDispatch* to a CommandBarButton IDispatch* somehow.

In Visual Basic I could write:

button = DirectCast(control, CommandBarButton)
button.FaceId = 59

But I don't know what DirectCast() does internally. If I did I'd probably be close to solving this.

Thanks

Chungzuwalla
  • 1,038
  • 6
  • 17
  • the joys of multiple dispatch interfaces – M.M Sep 22 '14 at 02:00
  • Thanks Matt... Multiple IDispatch interfaces, really?? Wouldn't that mean MS violated their own rules that an object can have only one IDispatch interface? My understanding is: "only one IDispatch is allowed (using QueryInterface with IDispatch should always returns the same interface)" (http://www.codeguru.com/cpp/com-tech/atl/atl/article.php/c47/Multiple-Dispatch-Interfaces-in-ATL.htm) – Chungzuwalla Sep 22 '14 at 05:25
  • Multiple dispatch interfaces are permitted, but some languages can't access them. That's why the advice is sometimes given to not use multiple dual interfaces. In C++ for example, you specify the IID with your interface request, so you can have an `IDispatch *` but you can request `IID_IFoo`. If the object has `IFoo` as a dual interface then this works. – M.M Sep 22 '14 at 05:30
  • (I don't know if this has anything to do with your problem) – M.M Sep 22 '14 at 05:31
  • I expect I could QI the IDispatch for ICommandBarButton and provide compiled-in hooks to let the script call it directly, but then I'd need to do that for every other class the script might want to use. Isn't IDispatch meant to ensure a scripting engine doesn't need this compiled-in knowledge? – Chungzuwalla Sep 22 '14 at 06:07
  • The VB samples I posted above come from here: http://www.mztools.com/articles/2012/MZ2012015.aspx This suggests VB can access CommandBarButton properties after DirectCast(), so unless VB has compiled-in knowledge of every MS class, it must be somehow resolving these things dynamically at runtime. Which is exactly what I want to do... – Chungzuwalla Sep 22 '14 at 06:08
  • it's hard to say what IDispatch is "meant" to do, it started off as a hack and grew from there. I can't help any more with your problem , sorry. – M.M Sep 22 '14 at 06:09
  • No problem Matt, thanks for your input so far, type libraries look kind of promising (if convoluted), so I'll have a dig around in there. It's a bit like looking for a fleck of gold in a bottomless mine without a lantern. – Chungzuwalla Sep 22 '14 at 07:01

1 Answers1

0

Just answering my own question here... bleah.

First I discovered that if I query the IDispatch for ICommandBarButton, then query that for IDispatch, I get a different IDispatch (at a different address) which recognises the ICommandBarButton properties. That left me with how to find the IID of some arbitrary interface name given to me by the script (in this case, the string "CommandBarButton").

But after more experimenting, it looks like I can downcast an IDispatch to the most derived class by simply querying for IUnknown, then querying that for IDispatch again. No other IIDs or trickery needed.

In the situation I described in the question, The second IDispatch* returned is a different address to the first, and is of type _CommandBarButton (note the underscore) where the original was of type ICommandBarControl. _CommandBarButton seems to have all the functionality of CommandBarButton. (See http://technet.microsoft.com/en-us/microsoft.visualstudio.commandbars.commandbarbutton%28v=vs.90%29)

I now include this code in my routine which returns an IDispatch object to the script engine:

/*
 * Query for IUnknown, then for IDispatch again.  This APPEARS to return
 * an IDispatch for the most derived class, thus exposing all methods and
 * properties, and in the process returns a different IDispatch to the
 * original (multiple dispatch interfaces on the same object).
 *
 * For example, calling ICommandBarControls.Add(msoControlButton) returns
 * an IDispatch for the CommandBarControl base class, not the
 * CommandBarButton class.  After casting IDispatch to IUnknown and back,
 * we get an IDispatch for _CommandBarButton, which appears to be almost,
 * but not quite, a CommandBarButton.  That is, CommandBarButton inherits
 * just about every one of its properties and methods from
 * _CommandBarButton -- certainly all the ones we're interested in anyway.
 */
HRESULT hr;
IUnknown *unknown;
hr = dispatch->QueryInterface(IID_IUnknown, (void**)&unknown);
if (hr == S_OK)
{
    IDispatch *dispatch2 = NULL;
    unknown->QueryInterface(IID_IDispatch, (void**)&dispatch2);
    if (hr == S_OK)
    {
        dispatch->Release();
        dispatch = dispatch2;
    }
    unknown->Release();
}
Chungzuwalla
  • 1,038
  • 6
  • 17
  • PS: it's not clear to me whether this shows that the VS 2010 model is breaking QueyInterface rules. The rules (http://msdn.microsoft.com/en-us/library/aa910647.aspx) say that the set of interfaces on an object "It must be reflexive — if a client holding a pointer to one interface queries successfully for another, a query through the obtained pointer for the first interface must succeed." Which I suppose is not saying that the pointer to the first interface must be THE SAME as the initial pointer -- only that the call succeeds, ie a valid interface is returned. – Chungzuwalla Sep 24 '14 at 00:48