sonderak
sonderak

Reputation: 51

Accessing Excel.Application objects of other excel instances in Running Object Table in C#

I have been trying to access the COM objects of all the instances of Excel 2010 registered in ROT in Visual C# Express 2010. I found a code on http://adndevblog.typepad.com/autocad/2013/12/accessing-com-applications-from-the-running-object-table.html, which I modified a little in order to return all the possible Excel.Application objects registered in Running Object Table. The code :-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace APPROT
{
    [ComVisible(true)]
    class COMApp
    {
        [DllImport("Ole32.dll")]
        public static extern int CreateBindCtx(
            uint reserved,
            out IBindCtx ppbc);

        [DllImport("Ole32.dll")]
        public static extern int GetRunningObjectTable(
            int reserved,
            out IRunningObjectTable prot);

        [STAThread]
        public static List<Excel.Application> GetRunningInstances()
        {
            string[] progIds = new string[] { "Excel.Application"};
            List<string> clsIds = new List<string>();

            // get the app clsid
            foreach (string progId in progIds)
            {
                Type type = Type.GetTypeFromProgID(progId);

                if (type != null)
                    clsIds.Add(type.GUID.ToString().ToUpper());
            }

            // get Running Object Table ...
            IRunningObjectTable Rot = null;
            GetRunningObjectTable(0, out Rot);
            if (Rot == null)
                return null;

            // get enumerator for ROT entries
            IEnumMoniker monikerEnumerator = null;
            Rot.EnumRunning(out monikerEnumerator);

            if (monikerEnumerator == null)
                return null;

            monikerEnumerator.Reset();

            List<Excel.Application> instances = new List<Excel.Application>();
            IntPtr pNumFetched = new IntPtr();
            IMoniker[] monikers = new IMoniker[1];

            // go through all entries and identifies app instances
            while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0)
            {
                    object ComObject;
                    Rot.GetObject(monikers[0], out ComObject);

                    if (ComObject == null)
                        continue;
                    try
                    {
                        instances.Add((Excel.Application)ComObject);
                    }
                    catch {}
            }

            return instances;
        }
    }
}

But this only returns the Excel.Application object of the first instance of excel. I also tried to access the object using the filename with the code mentioned on http://dotnet-snippets.de/snippet/laufende-com-objekte-abfragen/526 (site in german) i.e. with GetRunningCOMObjectByName(string objectDisplayname), but after getting the COM object with that, when I try to cast it as Excel.Application, I get the following error :-

Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.Office.Interop.Excel.Application'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{000208D5-0000-0000-C000-000000000046}' failed due to the following error: Cette interface n’est pas prise en charge (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

I have tried checking for faulty dlls, checking the registry if the CLSID has a conflict due to existance of different "Versions" in TypeLib under HKEY_CLASSES_ROOT, tried repairing Ms Office 2010, uninstalled old versions of Office (2003) etc. to get around this error. But nothing works. I have also tried using the Microsoft.VisualBasic reference and then using Interaction.GetObject, but this gives the same error as well.

Any ideas?

I have also tried the late-binding method mentioned on https://stackoverflow.com/a/779710/2960814. But this also gives access to the first instance of Excel in ROT.

Upvotes: 4

Views: 3699

Answers (2)

ShamilS
ShamilS

Reputation: 1594

Try the following code snippet for C# 4.0+ :

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace ROT.TestConsole
{

    /// <summary>
    /// Gets MS Excel running workbook instances via ROT
    /// </summary>
    public class MSExcelWorkbookRunningInstances
    {
        [DllImport("ole32.dll")]
        static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);

        [DllImport("ole32.dll")]
        public static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot);

        public static IEnumerable<dynamic> Enum()
        {
            // get Running Object Table ...
            IRunningObjectTable Rot;
            GetRunningObjectTable(0, out Rot);

            // get enumerator for ROT entries
            IEnumMoniker monikerEnumerator = null;
            Rot.EnumRunning(out monikerEnumerator);

            IntPtr pNumFetched = new IntPtr();
            IMoniker[] monikers = new IMoniker[1];

            IBindCtx bindCtx;
            CreateBindCtx(0, out bindCtx);

            while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0)
            {
                string applicationName = "";
                dynamic workBook = null;
                try
                {
                    Guid IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}");                    
                    monikers[0].BindToObject(bindCtx, null, ref IUnknown, out workBook);
                    applicationName = workBook.Application.Name;
                }
                catch { }

                if (applicationName == "Microsoft Excel") yield return workBook;
            }
        }

    }
}

Test it with:

using System;

namespace ROT.TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                int index = 1;
                foreach (dynamic workbook in MSExcelWorkbookRunningInstances.Enum())
                    System.Console.WriteLine("{0}.  '{1}' '{2}'", index++, workbook.Application.Name, workbook.FullName);
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("Error = '{0}'", ex.Message);   
            }
        }
    }
}

Upvotes: 1

XBK
XBK

Reputation: 45

I think we are facing the same problem. I'm using windows7 and office2010, using the same way as you mentioned to get ROT table to deal with all opened excel.

you have GetRunningCOMObjectByName(string objectDisplayname) to get com object, and try to convert to Excel.Application, but it's not of that type. I've tried, if you get the object by the fullName, the object you get is the type of Excel.Workbook, so it can be converted to Workbook.It works for me.

So you can see from the ROT, every doc you opened in Excel, there is a fullName related to it.And the comObj you get by name is type of Workbook .

But I still have some questions about ROT. if we look into the ROT table, it has two items with names:!{00024505-0014-0000-C000-000000000046} and !{00024500-0000-0000-C000-000000000046}, the similar classId with Excel.Application . And if you get comObj of these 2 item, you can convert to Excel.Application. Just like the way Marshal.GetActiveObject("Excel.Application") does. And the 2 item are reference equaled.

But when there are more than one EXCEL.EXE process running, I guess that I can get more items of Application in ROT, but the truth is

  1. there is still 2 items I mentioned above in ROT, which means only one Application in ROT(you can open 2 excel workbook to check it, workbook1 and workbook2)
  2. But the Workbook works well, you can get alll Workbook opened(workbook1 and workbook2).
  3. workbook1.Application reference equals to Application in ROT, but workbook2.Application not.

So, if there is only one Application in ROT and equals to workbook1.Applicaton, Where is the workbook2.Application? Why workbook2.Application is not registered in ROT Table?

Upvotes: 3

Related Questions