VSTO Addin Bidirectional communication - c#

I'm looking into the possibility of having a instance of an VSTO Word Add-in communicate with an instance of a VSTO Excel Add-in.
So far i've found an article from Microsoft on what I thought was going to be the solution:
https://learn.microsoft.com/en-us/visualstudio/vsto/calling-code-in-vsto-add-ins-from-other-office-solutions?view=vs-2022&tabs=csharp
However, when one iterates over the Globals.ThisAddIn.Application.COMAddIns collection, I can only see the current Office product (Word/Excel/PowerPoint/Outlook) installed Addin's.
I have a central project that all of the add-in's share, this includes the shared interface. Each add-in implements that interface and are decorated with attributes as per the above guide.
public interface IInterAddinCommsService
{
void Notify();
}
[ComVisible(true)]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
public class WordAddinNotificationService : StandardOleMarshalObject, IInterAddinCommsService
{
public void Notify()
{
}
}
[ComVisible(true)]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
class PowerPointAddinNotificationService : StandardOleMarshalObject, IInterAddinCommsService
{
public void Notify()
{
}
}
And here is my method trying to retrieve the com objects and calling the implemenetd method
public void OnAction(Office.IRibbonControl control, bool IsPressed)
{
//// Notify other addin's
var addins = Globals.ThisAddIn.Application.COMAddIns;
foreach (Office.COMAddIn addin in addins)
{
var service = addin.Object as IInterAddinCommsService;
if (service == null) continue; // skip if object doesn't implement IInterAddinCommsService
service.Notify();
}
}
Any help would be greatly appreciated.

In addition to implementing the interface you need to override the RequestComAddInAutomationService method which returns an object in your add-in that can be used by other solutions. For a code example that demonstrates how to override the RequestComAddInAutomationService method, see Walkthrough: Calling Code in a VSTO Add-in from VBA. For example, that is how it should like:
private WordAddinNotificationService utilities;
protected override object RequestComAddInAutomationService()
{
if (utilities == null)
utilities = new WordAddinNotificationService();
return utilities;
}
Note, the object that you return must be public, it must be visible to COM, and it must expose the IDispatch interface. If the object you return does not meet these requirements, the Visual Studio Tools for Office runtime will throw an InvalidCastException after it calls your implementation.

Related

How to force sub class to implement a static method

I understand this is only possible with a workaround. But why?
I want to add plugin support to my app. So I designed an abstract class that all future plugins will need to implement. Every plugin must implement a GetVersion() method like in this example code:
public abstract class Plugin
{
public abstract int GetVersion();
}
public class MyPlugin : Plugin
{
public override int GetVersion()
{
return 1;
}
}
This of course works perfectly as long as I instantiate the plugin before calling the GetVersion() method.
But if I want to get the version number of the plugin before creating an instance of it? To check compatibility for example?
public class Program
{
public Program()
{
if (MyPlugin.GetVersion() > 1)
{
PluginLoader.Load(new MyPlugin());
}
}
}
Although it might not answer directly your question "WHY" I think below solution might be usefull in your scenario:
Use assembly version attribute:
Assembly thisAssem = typeof(MyPlugin).Assembly;
AssemblyName thisAssemName = thisAssem.GetName();
Version ver = thisAssemName.Version;
It never can be done by C# because a static method cannot be implemented in derived classes.
Like the workaround, you can create a static factory to create the instance.
public abstract class Plugin
{
public abstract int GetVersion();
}
public class FactoryPlugin<T> where T : Plugin, new()
{
public static int GetVersion()
{
return new T().GetVersion();
}
}
public class Program
{
public Program()
{
if (FactoryPlugin<MyPlugin>.GetVersion() > 1)
{
}
}
}
Consider using the Factory pattern in a way similar to what a COM class factory does. You create two classes, your useful class, and a class factory class. Your class factory class implements IPluginFactory. You package it with your Plugin. The plugin factory has vary simple methods, but one of them allows your Plugin to be created. It's close to what #ThierryV showed, but without static methods. So the process is:
Use whatever you are planning to use to store and instantiate your plugins, but instead of instantiating a plugin, you instantiate the appropriate Plugin Factory
You can have the Plugin factory do what ever you want -- get detailed information about the plugin, allow instantiation of the latest version or a particular version of the plugin - go to town
But, eventually, you use an instance of the factory to instantiate your Plugin.
This is a good place to start: What exactly is a Class Factory?, but Don Box's Essential COM book is where I learned all this stuff, a long time ago in a place far away.

Can I implement a managed COM-visible interface in unmanaged code?

I have a class implemented in C# that I want to use from a native application. The C# class has a dependency described by an interface, which is exepected to be delivered by the code instantiating the class. I would like to realize this interface in the native application and pass it to the C# object via COM. Strongly simplified, the C# code looks like this:
[ComVisible(true)]
[Guid("910E8445-7A62-403F-BAEE-17AB0C169CA8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IComWidget
{
void SetClient(IComWidgetClient client);
void DoStuff();
}
[ComVisible(true)]
[Guid("850F3EBB-CD18-4E16-881F-50B50DD5AEB0")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IComWidgetClient
{
int GetValue();
}
[ComVisible(true)]
[Guid("86B9EC33-6CDF-438F-9A67-57D009723027")]
[ClassInterface(ClassInterfaceType.None)]
public class ComWidget : IComWidget
{
private IComWidgetClient m_Client;
public void DoStuff()
{
var i = m_Client.GetValue();
Debug.WriteLine("value was {0}", i);
}
public void SetClient(IComWidgetClient client)
{
m_Client = client;
}
}
The native application loads the COM library as a side-by-side assembly with a proper manifest, and implements the IComWidgetClient interface. It seems to work, but when running automated tests on the system several tests fail with an unhandled ExecutionEngineExecption. The way it fails (the test process is aborted) smells like some kind of corruption during garbage collection.
I think I may be able to write a managed c++ unit test that mimics the steps that lead to the error. At least it fails the same way. The test looks like this:
[TestMethod]
void TestStuff()
{
IComWidgetPtr sut = NULL;
NativeClient* client = NULL;
try
{
sut = IComWidgetPtr(__uuidof(ComWidget));
client = new NativeClient();
IComWidgetClient* pvObject;
client->QueryInterface(IID_IComWidgetClient, (void**)&pvObject);
sut->SetClient(pvObject);
sut->Release();
GC::Collect();
GC::WaitForPendingFinalizers();
Assert::IsTrue(true); // If we get this far, everything went OK...
}
finally
{
sut = NULL;
delete client;
client = NULL;
}
};
Where NativeClient is a simple native object implementing IComWidgetClient
public class NativeClient: IComWidgetClient
{
...
}
What goes wrong? Is what I am trying to do at all possible?
Full source code can be found here:
https://drive.google.com/file/d/0B-D57qCpESa5MnpZUXZRN2pyNnc/view?usp=sharing

Why my implementation of IDocHostUIHandler is ignored

I have created derived control from WebBrowser control that has own IDocHostUIHandler implementation following idea from CreateWebBrowserSiteBase method:
The WebBrowser.WebBrowserSite class provides default implementations
of the OLE IDocHostUIHandler interface. You can provide your own
implementation of this interface or implement any other WebBrowser
ActiveX control interface in order to customize the behavior of the
control.
The problem that is not working. My code looks next way:
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
public MyBrowser(){}
protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
{
var manager = new NewWebBrowserSite(this);
return manager;
}
protected class NewWebBrowserSite : WebBrowserSite,
UnsafeNativeMethods.IDocHostUIHandler
{
private MyBrowser host;
public NewWebBrowserSite(MyBrowser h)
: base(h)
{
this.host = h;
}
int UnsafeNativeMethods.IDocHostUIHandler.ShowContextMenu(int dwID, NativeMethods.POINT pt, object pcmdtReserved, object pdispReserved)
{
MyBrowser wb = (MyBrowser)this.host;
// other code
}
// rest of IDocHostUIHandler methods
}
My questions are:
Do I have to implement other interfaces to make it working;
Is that by design, I have read some post that it is related to a bug in .net framework implementation of WebBrowser
I know that is possible to go ICustomDoc.SetUIHandler way, but it is not what I am looking for.
I was having idea at some point to give up with c# and do that with unmanaged code. Is that the way?
I've just dealt with exactly the same problem: how to provide a custom implementation of IDocHostUIHandler to WinForms WebBrowser control. The problem is that the base class WebBrowserSite has already implemented its own version of IDocHostUIHandler (which is an internal interface, so it's not possible to explicitly re-implement it in the derived class NewWebBrowserSite). However, in theory it should not be a problem to implement another C# interface with the same GIID and methods layout (because that's all the COM client - the underlying WebBrowser ActiveX Control - cares about in this particular case).
Unfortunately, it was not possible until .NET 4.0. Luckily, now it is, by means of the new ICustomQueryInterface feature:
protected class NewWebBrowserSite : WebBrowserSite,
UnsafeNativeMethods.IDocHostUIHandler
ICustomQueryInterface
{
private MyBrowser host;
public NewWebBrowserSite(MyBrowser h): base(h)
{
this.host = h;
}
int UnsafeNativeMethods.IDocHostUIHandler.ShowContextMenu(int dwID, NativeMethods.POINT pt, object pcmdtReserved, object pdispReserved)
{
MyBrowser wb = (MyBrowser)this.host;
// other code
}
// rest of IDocHostUIHandler methods
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
if (iid == typeof(UnsafeNativeMethods.IDocHostUIHandler).GUID)
{
ppv = Marshal.GetComInterfaceForObject(this, typeof(UnsafeNativeMethods.IDocHostUIHandler), CustomQueryInterfaceMode.Ignore);
}
else
{
ppv = IntPtr.Zero;
return CustomQueryInterfaceResult.NotHandled;
}
return CustomQueryInterfaceResult.Handled;
}
}
You can't simply override the interfaces implemented by a class. If the methods for IDocHostUIHandler are not marked as virtual, you can't replace them.
The fact that the interface is defined in UnsafeNativeMethods is also a clue that you probably shouldn't be messing with it unless you have a very good idea of what you're doing.

Any events supported in ExcelDna?

I want to initialize a couple of variables on Excel Dna .dll gets loaded in the
Excel?
Excel-DNA will check whether your add has a public class that implements the interface ExcelDna.Integration.IExcelAddIn, and if so will run the AutoOpen method when the add-in is loaded, and the AutoClose method if the user removes the add-in from the add-ins list.
So you'd have something like:
public class MyAddIn : IExcelAddIn
{
public void AutoOpen()
{
// Do your initialization here...
}
public void AutoClose()
{
}
}

How Can I Create My C# Com Class From JavaScript Using ActiveXObject

I wrote C# class to COM but I could not use it from JavaScript. Example
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ICommandsEvents))]
[ProgId("Scripting.Commands")]
public class Commands : ICommands
{
public Commands()
{
}
public int CreateChannel(string channelName)
{
return 0;
}
public int GetChannelID(string channelName)
{
return CreateChannel(channelName);
}
public event ChannelEventsHandler OnChannelEvents;
}
[ComVisible(false)]
public delegate void ChannelEventsHandler(string a);
[ComVisible(true)]
[Guid("E2147768-8BA8-400b-8602-A1FDC31E6AA5")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICommands
{
[DispId(5)]
int CreateChannel(string channelName);
[DispId(6)]
int GetChannelID(string channelName);
}
[ComVisible(true)]
[Guid("22316373-A8DF-4ace-B48C-EA9953BD73FF")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICommandsEvents
{
[DispId(1)]
void OnChannelEvents(string a);
}
and I checked "Register for COM interop" checkbox of project property.
when I want to Create this from JavaScript like this.
var a = ActiveXObject("Scripting.Commands");
I am getting "Automation Server Can't create object" exception. What is my wrong.
Thank you
There are a large number of reasons for this kind of error.
Ensure you have an assembly level GuidAttribute for the type library
First check the registry that interface, type library and coclass registration are correct.
Use Process Monitor to check the registration is being read correctly.
Attach a debugger to the process, so you can add breakpoints to your code.
Does a C# client (using COM, so you'll need to import tge typelib to create a PIA) work?
But I notice your class does not have a GuidAttribute, so coclass registration will have failed.
Make sure that your site is in 'Trusted Sites' on the client's machine.

Categories