How do I get the *actual* host application instanc

2019-06-28 05:10发布

问题:

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?

回答1:

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.