Получить несколько запущенных COM-объектов одного типа
Я пытаюсь найти первый видимый экземпляр Word. Я нашел здесь полезный код и немного его изменил.
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);
}
}
}
Результат этого кода выглядит следующим образом:
1) IsVisible: False
2) IsVisible: False
3) IsVisible: False
Я ожидаю, что для одного экземпляра Visible должен вернуть true. Кажется, всегда возвращается первый экземпляр. Если word1 сделан видимым, true возвращается для всех экземпляров.
1 ответ
После долгих попыток заставить это работать, оказалось, что это невозможно при таком универсальном подходе, потому что некоторые компоненты регистрируют разные экземпляры с одинаковыми ключами в таблице запущенных объектов (ROT). IRunningObjectTable.GetObject
возвращает первый зарегистрированный экземпляр в этом случае. Например, Word делает это так с экземплярами его ApplicationClass
,
Решение:
Там швы, чтобы быть не чистым и общим решением, но что-то, что работало для меня. Слово также регистрирует случаи Document
в ROT. Таким образом, мы можем легко получить эти документы, и оттуда мы получаем заявление.
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
}
}
Этот код дает следующий результат:
Dokument1
Visible: False
DocumentCount: 1
Dokument3
Visible: True
DocumentCount: 2
Dokument6
Visible: False
DocumentCount: 3
ОК, это немного подозрительно. Противной проблемой этого подхода является то, что экземпляры, у которых нет документа, не могут быть найдены.