Callbacks for IRibbonExtensibility in C++ (Word 2007 B2TR)

D

daniel123

I'm porting a Word COM Add-in that currently runs in Word 2003 / XP over to
Word 2007 B2TR. With some minor tweaking, the add-in runs OK in Word 2007
(with the exception of another weird bug that's not in scope here).

This add-in uses a C++ shim. I've been attempting to implement a custom
ribbon for this add-in, and have only been partially successful. I've gotten
the shim to implement the IRibbonExtensibility interface, and it calls into
the managed .NET (C#) code to retrieve the ribbon XML, and the ribbon is
displayed OK.

But I haven't been able to get a callback to work. I included a callback
method for a button on the COM shim (which would then call into the managed
..NET code to do whatever), but it seems like the callback never gets called.
The methods looks something like the following:

STDMETHOD(ButtonClicked)(Office::IRibbonControl *RibbonControl)
{
return m_pRibbon->MethodGoesHere(....);
}

and the ribbon xml looks something like the following:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
<ribbon>
<tabs>
<tab id="CustomTab" label="My Label">
<group id="SampleGroup" label="Sample Group">
<button id="Button" label="Click Here" size="large"
onAction="ButtonClicked" />
</group >
</tab>
</tabs>
</ribbon>
</customUI>

Note that I'm using Beta 2 Technical Refresh. (Also note that I'm on .NET
1.1, and want to maintain backward compatibility with Word 2003, which is why
I need to stick with the shim approach...)

Is there some other way to get ribbon callbacks to work with unmanaged C++?

Thanks!

Daniel
 
P

Patrick Schmid [MVP]

Your RibbonX looks fine.
STDMETHOD(ButtonClicked)(Office::IRibbonControl *RibbonControl)
{
return m_pRibbon->MethodGoesHere(....);
}
I think here is the problem. According to the RibbonX documentation, the
correct C++ Signature is
HRESULT OnAction([in] IRibbonControl *pControl)

Note that I don't use C++ myself, so I just copied this from the
documentation.

As you aren't reporting seeing any error message, I suspect that you
haven't switched on the display of add-in UI errors? To do so, use Word
Options, Advanced, General section, Show add-in user interface errors.
If you switch that one on, you should see all errors related to your
add-in. That includes RibbonX errors, not found callback function and
any runtime errors in your callbacks.

Patrick Schmid [OneNote MVP]
--------------
http://pschmid.net
***
Office 2007 Beta 2 Technical Refresh (B2TR):
http://pschmid.net/blog/2006/09/18/43
***
Customize Office 2007: http://pschmid.net/office2007/customize
OneNote 2007: http://pschmid.net/office2007/onenote
***
Subscribe to my Office 2007 blog: http://pschmid.net/blog/feed
 
D

daniel123

Thanks. I think the C++ signature may be OK. I'm not too familiar with C++
myself, but I think the signature given in the documentation is for the
interface or type library signature (if I look in the type library for the
interface I'm using, it seems to have the correct definition, as given in the
documentation).

I've attempted to replace the IRibbonExtensibility interface, as suggested
here http://blogs.msdn.com/mshneer/archive/2006/05/31/doclevelribbons.aspx.
In C#, I basically create a separate class library which is registered with
COM, import that into my C++ shim, and use it instead of the real interface.
The interface looks like the following in C#:

[ComVisible(true)]
[Guid("000C0396-0000-0000-C000-000000000046")] // same as for
IRibbonExtensibility
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMyRibbonExtensibility
{
[DispId(1)]
string GetCustomUI(string RibbonID);
[DispId(2)]
void ButtonHandler(IRibbonControl pControl);
[DispId(3)]
void RibbonLoaded(IRibbonUI pRibbon);
}

This works OK with the GetCustomUI method being called (the ribbon shows
up), but neither the ButtonHandler or RibbonLoaded methods are found. The
error message is something like 'The callback function "RibbonLoaded" was not
found.' or when clicking the button, 'The callback function "ButtonHandler"
was not found.

I tweaked around with the interface in different ways to see if there was
anything finicky about the interface method signature (as you had suggested),
and also with the implementation method, to no avail. Word can call the
GetCustomUI method with no problem, but it never seems to be able to find any
of the callback methods.

Maybe I'm making a wrong assumption about how the callbacks work or what
they are looking for, or maybe there's a bug of some sort in the BT2R? Is
there any special thing that I need to register with Word so that it can find
the callbacks? According to information I've read so far, Word ought to be
able to find the callback methods, even if they're not defined in the
IRibbonExtensibility interface (late binding via IDispatch).

For reference, here's some more of my code:

----
ConnectProxy.h:

// ConnectProxy.h : Declaration of the CConnectProxy
#pragma once
#include "resource.h" // main symbols
#include "IntelliSafeFTXShim.h"
// CConnectProxy
class ATL_NO_VTABLE CConnectProxy :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CConnectProxy, &CLSID_ConnectProxy>,
public IDispatchImpl<AddInDesignerObjects::_IDTExtensibility2,
&AddInDesignerObjects::IID__IDTExtensibility2,
&AddInDesignerObjects::LIBID_AddInDesignerObjects,
/* wMajor = */ 1, /* wMinor = */ 0>,
public IDispatchImpl<IntelliSafeFTXRibbon::IMyRibbonExtensibility,
&IntelliSafeFTXRibbon::IID_IMyRibbonExtensibility,
&IntelliSafeFTXRibbon::LIBID_IntelliSafeFTXRibbon,
/* wMajor = */ 1, /* wMinor = */ 0>
{
public:
CConnectProxy()
: m_pConnect(NULL), m_pRibbon(NULL)
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_CONNECTPROXY)

BEGIN_COM_MAP(CConnectProxy)
COM_INTERFACE_ENTRY2(IDispatch, AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY(AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY(IntelliSafeFTXRibbon::IMyRibbonExtensibility)
END_COM_MAP()

DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct();
void FinalRelease();

public:
//IDTExtensibility2 implementation:
STDMETHOD(OnConnection)(IDispatch * Application,
AddInDesignerObjects::ext_ConnectMode ConnectMode, IDispatch *AddInInst,
SAFEARRAY **custom)
{
return m_pConnect->OnConnection(Application, ConnectMode, AddInInst,
custom);
}
STDMETHOD(OnDisconnection)(AddInDesignerObjects::ext_DisconnectMode
RemoveMode, SAFEARRAY **custom )
{
return m_pConnect->OnDisconnection(RemoveMode, custom);
}
STDMETHOD(OnAddInsUpdate)(SAFEARRAY **custom )
{
return m_pConnect->OnAddInsUpdate(custom);
}
STDMETHOD(OnStartupComplete)(SAFEARRAY **custom )
{
return m_pConnect->OnStartupComplete(custom);
}
STDMETHOD(OnBeginShutdown)(SAFEARRAY **custom )
{
return m_pConnect->OnBeginShutdown(custom);
}
//IRibbonExtensibility implementation:
STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR *RibbonXml)
{
return m_pRibbon->GetCustomUI(RibbonID, RibbonXml);
}
STDMETHOD(RibbonLoaded)(Office::IRibbonUI *RibbonUI)
{
return m_pRibbon->RibbonLoaded(RibbonUI);
}
STDMETHOD(ButtonHandler)(Office::IRibbonControl *RibbonControl)
{
return m_pRibbon->ButtonHandler(RibbonControl);
}

protected:
// caches pointer to managed add-in
AddInDesignerObjects::IDTExtensibility2 *m_pConnect;
IntelliSafeFTXRibbon::IMyRibbonExtensibility *m_pRibbon;
};

OBJECT_ENTRY_AUTO(__uuidof(ConnectProxy), CConnectProxy)

-----
ConnectProxy.cpp:

// ConnectProxy.cpp : Implementation of CConnectProxy

#include "stdafx.h"
#include "ConnectProxy.h"
#include "CLRLoader.h"
#include "ShimConfig.h"

using namespace ShimConfig;
using namespace AddInDesignerObjects;

// CConnectProxy

HRESULT CConnectProxy::FinalConstruct()
{
HRESULT hr = S_OK;

// Create an instance of a managed addin
// and fetch the interface pointers
CCLRLoader *pCLRLoader = CCLRLoader::TheInstance();
IfNullGo(pCLRLoader);
IfFailGo(pCLRLoader->CreateInstance(AssemblyName(), ConnectClassName(),
__uuidof(IDTExtensibility2),
(void **)&m_pConnect));

IfFailGo(m_pConnect->QueryInterface(__uuidof(IntelliSafeFTXRibbon::IMyRibbonExtensibility),
(void **)&m_pRibbon));

Error:
return hr;
}

void CConnectProxy::FinalRelease()
{
if (m_pConnect)
m_pConnect->Release();
if (m_pRibbon)
m_pRibbon->Release();
}
---
And the imports in stdafx.h:

// for _AppDomain. Used to communicate with the default app domain from
unmanaged code
#import <mscorlib.tlb> raw_interfaces_only
high_property_prefixes("_get","_put","_putref")

//The following #import imports the MSADDNDR.dl typelib which we need
for IDTExtensibility2.
#import "libid:AC0714F2-3D04-11D1-AE7D-00A0C90F26F4" raw_interfaces_only
named_guids

//The following #import imports the MS Office 12 typelib.
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" raw_interfaces_only
named_guids

//The following #import imports the IntelliSafeFTXRibbon type library.
#import "libid:F3123F7F-81D2-48da-8D55-C7AADFD087B0" raw_interfaces_only
named_guids
 
D

daniel123

I finally was able to get this to work by making the following change:

In ConnectProxy.h, I changed the COM map from

BEGIN_COM_MAP(CConnectProxy)
COM_INTERFACE_ENTRY2(IDispatch, AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY(AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY(IntelliSafeFTXRibbon::IMyRibbonExtensibility)
END_COM_MAP()

to the following

BEGIN_COM_MAP(CConnectProxy)
COM_INTERFACE_ENTRY2(IDispatch, IntelliSafeFTXRibbon::IMyRibbonExtensibility)
COM_INTERFACE_ENTRY(AddInDesignerObjects::IDTExtensibility2)
COM_INTERFACE_ENTRY(IntelliSafeFTXRibbon::IMyRibbonExtensibility)
END_COM_MAP()

In particular, the IDispatch is now taken from IMyRibbonExtensibility
instead of from IDTExtensibility2. And now the callbacks are getting called.

I then tried switching back to using the real interface, but then the
callbacks didn't work again. I guess the fact that callbacks are not found
unless explicitly defined in the interface may really be a bug (as indicated
in the link I mentioned previously at
http://blogs.msdn.com/mshneer/archive/2006/05/31/doclevelribbons.aspx). That
linked also mentioned that using a hacked interface like this is not a good
workaround, so hopefully this bug will be fixed soon!

If anyone has any further light to shed on this issue, please feel free.

Daniel
 
J

Jurciu

Hi Daniel,
I have been trying to solve this problem as well. I have followed the
pattern presented here but still get the message that the callback was not
found.
Could you please post the complete working code here?
Thanks,
Jerzy
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top