4

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.

3
  • 1
    Excel is a single-instance app. If you start it again, it will simply ask the first instance to open the document. And quits. Use Task Manager, Processes tab to look at the number of Excel.exe processes you have running. Just one is normal. Commented Apr 3, 2014 at 17:50
  • Thank you, that makes it more clear. But I sometime open two instances of excel in order to compare between the files visually. It's hard to some of that when opening both the files under one instance. That's why I have been trying to figure out the way to access objects from all the instances. Commented Apr 4, 2014 at 7:43
  • @HansPassant It is incorrect that you can only create one instance of Excel. Depending on how you start Excel, you can create several instances. Commented Dec 11, 2024 at 11:30

3 Answers 3

3

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?

Sign up to request clarification or add additional context in comments.

1 Comment

Well, there's another way mentioned to get Workbook, Workbook wb = Marshal.BindToMoniker(fullName) instead of DllImport Win API
2

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);   
            }
        }
    }
}

2 Comments

it's an amazing answer, thank you! the typo "worbook" instead of "workbook" in the test, did took some time to find =)
@AlexS. I have corrected the typo, thank you.
1

ShamilS's code worked perfectly for me. But I don't really like using dynamic. So I modified it a little to work with Microsoft.Office.Interop.Excel. Also I found out that it is better to use typeof(Excel.Workbook).GUID instead of hardcoded new Guid("{00000000-0000-0000-C000-000000000046}"), because it only gives you excel workbooks without redundant com objects.

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

using Excel = Microsoft.Office.Interop.Excel;
private IEnumerable<Excel.Workbook> GetExcelWorkbooks()
{
    GetRunningObjectTable(0, out var rot);

    rot.EnumRunning(out var monikerEnumerator);

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

    CreateBindCtx(0, out var bindCtx);

    while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
    {
        Excel.Workbook? workBook = null;
        try
        {
            var workBookGuid = typeof(Excel.Workbook).GUID;
            monikers[0].BindToObject(bindCtx, null, ref workBookGuid, out var tmpWbObject);
            workBook = (Excel.Workbook)tmpWbObject;
        }
        catch (Exception ex)
        {
            continue;
        }

        if (workBook == null)
            continue;

        yield return workBook;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.