I'm trying to find the first visible instance of Word. I have found some helpful code here and modified it sligthly.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Office.Interop.Word;
namespace TestConsole
{
internal class Program
{
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable pprot);
private static void Main(string[] args)
{
Application word1 = new Application();
word1.Visible = false;
Application word2 = new Application();
word2.Visible = true;
Application word3 = new Application();
word3.Visible = false;
int index = 0;
while (true)
{
Application application = Program.GetRunningCOMObjectOfType<Application>(++index);
if (application != null)
{
Console.WriteLine($"{index}) IsVisible: {application.Visible}");
Debug.WriteLine($"{index}) IsVisible: {application.Visible}");
}
else
{
break;
}
}
Console.WriteLine("############# End of program #############");
Console.ReadLine();
}
public static T GetRunningCOMObjectOfType<T>(int index)
{
IRunningObjectTable runningObjectTable = null;
IEnumMoniker monikerList = null;
try
{
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null)
{
return default(T);
}
runningObjectTable.EnumRunning(out monikerList);
monikerList.Reset();
IMoniker[] monikerContainer = new IMoniker[1];
IntPtr pointerFetchedMonikers = IntPtr.Zero;
int counter = 0;
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
runningObjectTable.GetObject(monikerContainer[0], out object comInstance);
if (comInstance is T castedInstance)
{
if (index == ++counter)
{
return castedInstance;
}
}
}
}
finally
{
if (runningObjectTable != null)
{
Marshal.ReleaseComObject(runningObjectTable);
}
if (monikerList != null)
{
Marshal.ReleaseComObject(monikerList);
}
}
return default(T);
}
}
}
The result of this code looks like this:
1) IsVisible: False
2) IsVisible: False
3) IsVisible: False
I expect that for one instance Visible should return true. It seems that always the first instance is returned. If word1 is made visible true is returned for all instances.
After trying hard to get this working it turned out that this is not possible with that generic approach because some components register different instances with identical keys on the Running Object Table (ROT). IRunningObjectTable.GetObject
returns the first registered instance in this case. E.g. Word do it that way with the instances of its ApplicationClass
.
Solution:
There seams to be no clean and generic solution but something that worked for me. Word also registers the instances of the Document
in the ROT. So we can easily get this documents and from there we get the application.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Office.Interop.Word;
namespace ConsoleApp
{
internal class Program
{
#region public methods
private static void Main(string[] args)
{
Application word1 = new Application();
word1.Visible = false;
word1.Documents.Add();
Application word2 = new Application();
word2.Visible = true;
word2.Documents.Add();
word2.Documents.Add();
Application word3 = new Application();
word3.Visible = false;
word3.Documents.Add();
word3.Documents.Add();
word3.Documents.Add();
List<(IMoniker moniker, IBindCtx bindingContext, object instance)> x = Program.GetRunningComObjects();
foreach ( (IMoniker moniker, IBindCtx bindingContext, object instance) in x)
{
// get only the instances that
if (instance is Document doc && doc.Application.ActiveDocument == doc)
{
moniker.GetDisplayName(bindingContext, moniker, out string displayName);
Console.WriteLine($"{displayName}");
Application wordInstance = doc.Application;
Console.WriteLine($"\tVisible:\t{wordInstance.Visible}");
Console.WriteLine($"\tDocumentCount:\t{wordInstance.Documents.Count}");
}
}
word1.Quit(false);
word2.Quit(false);
word3.Quit(false);
Console.WriteLine();
Console.WriteLine("############## End of program ##############");
Console.WriteLine("############## press enter to continue ##############");
Console.ReadLine();
}
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable pprot);
private static List<(IMoniker moniker, IBindCtx bindingContext, object instance)> GetRunningComObjects()
{
List<(IMoniker, IBindCtx, object)> result = new List<(IMoniker, IBindCtx, object)>();
IRunningObjectTable runningObjectTable = null;
IEnumMoniker monikerList = null;
try
{
if (Program.GetRunningObjectTable(0, out runningObjectTable) != 0
|| runningObjectTable == null)
{
return result;
}
runningObjectTable.EnumRunning(out monikerList);
monikerList.Reset();
IMoniker[] monikerContainer = new IMoniker[1];
IntPtr pointerFetchedMonikers = IntPtr.Zero;
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
Program.CreateBindCtx(0, out IBindCtx bindingContext);
runningObjectTable.GetObject(monikerContainer[0], out object comInstance);
result.Add((monikerContainer[0], bindingContext, comInstance));
}
}
finally
{
if (runningObjectTable != null)
{
Marshal.ReleaseComObject(runningObjectTable);
}
if (monikerList != null)
{
Marshal.ReleaseComObject(monikerList);
}
}
return result;
}
#endregion
}
}
This code produces the following result:
Dokument1
Visible: False
DocumentCount: 1
Dokument3
Visible: True
DocumentCount: 2
Dokument6
Visible: False
DocumentCount: 3
OK, this is a little fishy. A nasty problem with this approach is that instances that do not have a document can not be found.