I have this code in a C# add-in for the VBE (emphasis "VBE": it's not a MS-Office add-in):
public abstract class HostApplicationBase<TApplication> : IHostApplication
where TApplication : class
{
protected readonly TApplication Application;
protected HostApplicationBase(string applicationName)
{
Application = (TApplication)Marshal.GetActiveObject(applicationName + ".Application");
}
Where TApplication
is a MS-Office interop Application
class, for example, a Microsoft.Office.Interop.Excel.Application
type; here the applicationName
parameter would be "Excel" for, well, Excel.
The problem is that Marshal.GetActiveObject
seems to only ever return the first instance created, and that's not necessarily the instance that's hosting the current VBE environment, and this causes issues.
How can I get ahold of the actual host application instance?
For the sake of slaying a zombie, you can get the name of the VBE's host by:
Inspecting the caption of the CommandBarButton that takes you to the host. The button is on the Standard Toolbar, but also in the View Menu.
Inspecting the names of the references. Typically the first reference that isn't VBA but is BuiltIn
.
Inspecting the Application.Name property of document component's Properties collection.
And to reliably get a reference to the VBE's host, you have to use the Properties collection of the document-type components. For example, once you know that the name of the host is "Microsoft Excel", you just need to find the Workbook document-component (typically named ThisWorkbook), then you can get the host from the component's Properties collection.
var appProperty = vbe.VBProjects
.Cast<VBProject>()
.Where(project => project.Protection == vbext_ProjectProtection.vbext_pp_none)
.SelectMany(project => project.VBComponents.Cast<VBComponent>())
.Where(component => component.Type == vbext_ComponentType.vbext_ct_Document
&& component.Properties.Count > 1)
.SelectMany(component => component.Properties.OfType<Property>())
.FirstOrDefault(property => property.Name == "Application");
if (appProperty != null)
{
Application = (TApplication)appProperty.Object;
}
else
{
Application = (TApplication)Marshal.GetActiveObject(applicationName + ".Application");
}
But not all vbProjects have document-type components, so for such projects you'll have to resort to the GetActiveObject
approach.