Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can my VSTO Excel addin tell if a workbook is embedded in a word document?

I am working on an existing Excel VSTO addin that is causing problems when the user edits a workbook that is embedded in a MS Word document. It is not a requirement that the addin function in that environment, but it is causing the embedding to malfunction, even when the customer is manipulating files unrelated to what the addin manipulates. At minimum, I need to get it to not initialize itself for that workbook.

Some avenues I have investigated:

  1. The documentation for Microsoft.Office.Interop.Excel.Workbook.Application reads: "When used without an object qualifier, this property returns an Application object that represents the Microsoft Excel application. When used with an object qualifier, this property returns an Application object that represents the creator of the specified object (you can use this property with an OLE Automation object to return the application of that object)." This sounds promising, however, I don't understand what "with an object qualifier" means in the context of C#.
  2. This link suggested examining the command line arguments. However, if I open Excel standalone, then open my Word document with embedded Excel objects, Word uses the same instance for the embedding, and the command line arguments will not contain the "-embedded" flag.
  3. I would love to force OLE to use a new instance of Excel (instead of reusing an existing standalone instance), but I cannot figure out how to do that either.

Since the single instance of Excel can be hosting both embedded and standalone workbooks at the same time, this information needs to be at the workbook level.

like image 720
Dave Tillman Avatar asked Sep 08 '25 08:09

Dave Tillman


2 Answers

This works for telling you if the workbook is an embedded OLE object (the other answer for checking the workbook.Container did not work for me on Office 2016): https://theofficecontext.com/2013/04/10/how-to-determine-if-an-excel-workbook-is-embedded-and-more/

public static class ExcelExtensionMethods
{

[DllImport("ole32.dll")]
static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);

/// <summary>
/// WORKBOOK EXTENSION METHOD
/// Checks to see if the Workbook is embeeded inside of 
/// another ActiveX Document type, sy=uch as Word or Excel.
/// </summary>
/// <param name="PobjWb"></param>
/// <returns></returns>
public static bool IsEmbedded(this Excel.Workbook PobjWb)
{
    if (PobjWb.Path == null || PobjWb.Path.Length == 0)
    {
        try
        {
            // requires using Microsoft.VisualStudio.OLE.Interop;
            // and you have to manually add this to reference from here:
            // C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.OLE.Interop.dll
            IOleObject LobjOleObject = ((object)PobjWb) as IOleObject;
            IOleClientSite LobjPpClientSite;
            // get the client site
            LobjOleObject.GetClientSite(out LobjPpClientSite);
            // if there is one - we are embedded
            if (LobjPpClientSite != null)
            {
                return true;
            }
            else
            {
                // not embedded
                return false;
            }
        }
        catch (Exception ex)
        {
            // exception
            Debug.Print(ex.ToString());
            return false;
        }
        finally { }
    }
    else
    {
        // not embedded
        return false;
    }
}

/// <summary>
/// WORKBOOK EXTENSION METHOD
/// This method return the name of the class that we
/// are embedded inside of.
/// If we are not embedded it return null.
/// If there is any exception it return null.
/// If the container cannot be accessed it returns UNKNOWN.
/// </summary>
/// <param name="PobjWb"></param>
/// <returns></returns>
public static string EmbedClassName(this Excel.Workbook PobjWb)
{
    try
    {
        IOleObject LobjOleObject = ((object)PobjWb) as IOleObject;
        IOleClientSite LobjPpClientSite;
        // get the client site
        LobjOleObject.GetClientSite(out LobjPpClientSite);
        if (LobjPpClientSite != null)
        {
            IOleContainer LobjPpContainer;
            LobjPpClientSite.GetContainer(out LobjPpContainer);
            if (LobjPpContainer != null)
            {
                return LobjPpContainer.GetType().Name;
            }
            else
            {
                // something wrong - container is not valid
                return "UNKNOWN";
            }
        }
        else
        {
            // not embedded
            return null;
        }
    }
    catch (Exception ex)
    {
        Debug.Print(ex.ToString());
        return null; // failed
    }
}

/// <summary>
/// WORKBOOK EXTENSION METHOD
/// Get the full path to the file that the workbook is embedded 
/// inside of. 
/// If we are not embeeded then this will return null.
/// If we are embedded but there are issues with the container
/// or an exception occurs, it will return null.
/// Otherwise we get the full path and filename.
/// </summary>
/// <param name="PobjWb"></param>
/// <returns></returns>
public static string EmbedMoniker(this Excel.Workbook PobjWb)
{
    try
    {
        IOleObject LobjOleObject = ((object)PobjWb) as IOleObject;
        IOleClientSite LobjPpClientSite;
        // get the client site
        LobjOleObject.GetClientSite(out LobjPpClientSite);
        if (LobjPpClientSite != null)
        {
            IOleContainer LobjPpContainer;
            LobjPpClientSite.GetContainer(out LobjPpContainer);
            if (LobjPpContainer != null)
            {
                // get the moniker
                IMoniker LobjMoniker;
                LobjPpClientSite.GetMoniker((uint)OLEGETMONIKER.OLEGETMONIKER_FORCEASSIGN,
                                            (uint)OLEWHICHMK.OLEWHICHMK_OBJFULL,
                                            out LobjMoniker);
                if (LobjMoniker != null)
                {
                    // now pull the moniker display name
                    // this will be in the form of PATH!Context
                    string LstrDisplayName;
                    IBindCtx LobjCtx = null;
                    CreateBindCtx(0, out LobjCtx); // required (imported function)
                    LobjMoniker.GetDisplayName(LobjCtx, null, out LstrDisplayName);
                    // remove context is exists
                    if (LstrDisplayName.Contains("!"))
                    {
                        string[] LobjMonikerArray = LstrDisplayName.Split('!');
                        // return the first part - which should be the path
                        return LobjMonikerArray[0];
                    }
                    else
                    {
                        // return full display name
                        return LstrDisplayName;
                    }
                }
                else
                {
                    // no moniker value
                    return null;
                }
            }
            else
            {
                // something wrong - container is not valid
                return null;
            }
        }
        else
        {
            // not embedded
            return null;
        }
    }
    catch (Exception ex)
    {
        Debug.Print(ex.ToString());
        return null; // failed
    }
}
}
like image 188
tjsmith Avatar answered Sep 09 '25 23:09

tjsmith


I had the same problem with embedded Excel in Word. And the solutions have been Marshal.ReleaseComObject all objects in the end of each event function.

Example:

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
   // code
   Marshal.ReleaseComObject(sender);
   Marshal.ReleaseComObject(e);
}
like image 38
Rui Oliveira Avatar answered Sep 09 '25 23:09

Rui Oliveira