Beschreibung:
Viele bekannte Anwendungen (z.B. Excel, Word, Access, Outlook oder Visual Studio) bieten Automatisierungsmöglichkeiten über COM-Schnittstellen. Manchmal möchte man eine bereits geöffnete Instanz einer solchen Anwendung fernsteuern. Wenn nun z.B. aber gleich mehrere Excel-Sheets gleichzeitig geöffnet sind? Wie findet man die richtige Instanz?
Das geht über die "Running Object Table". Dort werden laufende Objekte registiert, die als COM-Server fungieren. Jedes laufende Objekt bekommt einen Namen. Oft ist es der Dateiname des Dokuments oder eine GUID (Das hängt von der Anwendung ab).
Das folgende Snippet gibt alle laufenden COM-Objekte aus der Running Object Table zurück und kann direkt auf die einzelnen Objekte über ihren Namen zugreifen.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace Rainbird.Tools.COMInterop
{
/// <summary>
/// Ermöglicht .NET-Anwendungen direkten Zugriff auf die Running Object Table (Tabelle mit allen momentan laufenden COM-Objekte)
/// </summary>
public class RunningObjectTable
{
/// <summary>
/// Privater Standardkonstruktor.
/// </summary>
private RunningObjectTable() { }
// Win32-API-Aufruf zum lesen der ROT
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable pprot);
// Win32-API-Aufruf zum erstellen von Bindungen
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx pctx);
/// <summary>
/// Gibt einen Verweis auf eine laufendes COM-Objekt anhand ihres Anzeigenamens zurück.
/// </summary>
/// <param name="objectDisplayName">Anzeigename einer COM-Instanz</param>
/// <returns>Verweis auf COM-Objekt, oder null, wenn kein COM-Objekt mit dem angegbenen Namen läuft</returns>
public static object GetRunningCOMObjectByName(string objectDisplayName)
{
// ROT-Schnittstelle
IRunningObjectTable runningObjectTable = null;
// Moniker-Auflistung
IEnumMoniker monikerList = null;
try
{
// Running Object Table abfragen und nichts zurückgeben, wenn keine COM-Objekte laufen
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null) return null;
// Moniker abfragen
runningObjectTable.EnumRunning(out monikerList);
// An den Anfang der Auflistung springen
monikerList.Reset();
// Array für Moniker-Abfrage erzeugen
IMoniker[] monikerContainer = new IMoniker[1];
// Zeiger auf die Anzahl der tatsächlich abgefragten Moniker erzeugen
IntPtr pointerFetchedMonikers = IntPtr.Zero;
// Alle Moniker durchlaufen
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
// Objekt für Bindungsinformationen
IBindCtx bindInfo;
// Variable für den Anzeigenamen des aktuellen COM-Objekts
string displayName;
// Bindungsobjekt erzeugen
CreateBindCtx(0, out bindInfo);
// Anzeigename des COM-Objekts über den Moniker abfragen
monikerContainer[0].GetDisplayName(bindInfo, null, out displayName);
// Bindungsobjekt entsorgen
Marshal.ReleaseComObject(bindInfo);
// Wenn der Anzeigename mit dem gesuchten übereinstimmt ...
if (displayName.IndexOf(objectDisplayName) != -1)
{
// Variable für COM-Objekt
object comInstance;
// COM-Objekt über den Anzeigenamen abrfragen
runningObjectTable.GetObject(monikerContainer[0], out comInstance);
// COM-Objekt zurückgeben
return comInstance;
}
}
}
catch
{
// Nichts zurückgeben
return null;
}
finally
{
// Ggf. COM-Verweise entsorgen
if (runningObjectTable != null) Marshal.ReleaseComObject(runningObjectTable);
if (monikerList != null) Marshal.ReleaseComObject(monikerList);
}
// Nichts zurückgeben
return null;
}
/// <summary>
/// Gibt eine Liste mit Anzeigenamen aller momentan laufenden COM-Objekte zurück.
/// </summary>
/// <returns>Liste mit Anzeigenamen</returns>
public static IList<string> GetRunningCOMObjectNames()
{
// Auflistung der Anzeigenamen erzeugen
IList<string> result = new List<string>();
// Informationsobjekt der laufenden COM-Instanzen
IRunningObjectTable runningObjectTable = null;
// Moniker-Auflistung
IEnumMoniker monikerList = null;
try
{
// Running Object Table abfragen und nichts zurückgeben, wenn keine COM-Objekte laufen
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null) return null;
// Moniker abfragen
runningObjectTable.EnumRunning(out monikerList);
// An den Anfang der Auflistung springen
monikerList.Reset();
// Array für Moniker-Abfrage erzeugen
IMoniker[] monikerContainer = new IMoniker[1];
// Zeiger auf die Anzahl der tatsächlich abgefragten Moniker erzeugen
IntPtr pointerFetchedMonikers = IntPtr.Zero;
// Alle Moniker durchlaufen
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
// Objekt für Bindungsinformationen
IBindCtx bindInfo;
// Variable für den Anzeigenamen des aktuellen COM-Objekts
string displayName;
// Bindungsobjekt erzeugen
CreateBindCtx(0, out bindInfo);
// Anzeigename des COM-Objekts über den Moniker abfragen
monikerContainer[0].GetDisplayName(bindInfo, null, out displayName);
// Bindungsobjekt entsorgen
Marshal.ReleaseComObject(bindInfo);
// Anzeigenamen der Auflistung zufügen
result.Add(displayName);
}
// Auflistung zurückgeben
return result;
}
catch
{
// Nichts zurückgeben
return null;
}
finally
{
// Ggf. COM-Verweise entsorgen
if (runningObjectTable != null) Marshal.ReleaseComObject(runningObjectTable);
if (monikerList != null) Marshal.ReleaseComObject(monikerList);
}
}
}
}
Schlagwörter: ROT,Running Object Table,Office,Excel,COM,COM-Interop,Access,Word,Visio,PowerPoint,Outlook,GetObject,COM-Server,OLE
Quelle: .NET-Snippets
Hallo Rainbird,
Vielen Dank für deine Klasse RunningObjectTable!
habe soeben eine Excel-Datei mit deiner Funktion GetRunningCOMObjectByName als object zurückerhalten. Nun schaff ichs einfach nicht dieses Object in ein Excel.Application-Objekt zu verwandeln, damit ich ich dann die Datei auch schliessen oder anderes damit anstellen kann.
Herzliche Grüsse
Tecla
Aufgrund einer Frage im Office Forum habe ich eine modifizierte Variante erstellt.
Diese arbeitet nicht nach Display-Name in der ROT sondern vielmehr nach ClassID bzw. Komponentenname und Klassenname(die ClassID setzt sich üblicherweise daraus zusammen) Damit ist eine ähnliche Verwendung wie Marshal.GetActiveObject und Co. möglich.
public class RunningObjectTable
{
// Win32-API-call for reading ROT
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable pprot);
// Win32-API-call to create binding
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx pctx);
/// <summary>
/// Gibt eine laufene Instanz aus der Running Object Table zurück
/// </summary>
/// <param name="componentName">Einfache Komponentenangabe z.B. "Excel" oder "Outlook"</param>
/// <param name="className">Name der Klasse z.B. "Application" oder "Workbook"</param>
/// <returns>Den ersten COM Proxy in der RunngingObject Table der den Eingangsparametern entspricht oder null</returns>
private static object GetApplicationInstanceFromROT(string componentName, string className)
{
IEnumMoniker monikerList = null;
IRunningObjectTable runningObjectTable = null;
try
{
// query table and returns null if no objects runnings
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null)
return null;
// query moniker & reset
runningObjectTable.EnumRunning(out monikerList);
monikerList.Reset();
IMoniker[] monikerContainer = new IMoniker[1];
IntPtr pointerFetchedMonikers = IntPtr.Zero;
// fetch all moniker
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
// create binding object
IBindCtx bindInfo;
CreateBindCtx(0, out bindInfo);
// query com proxy info
object comInstance = null;
runningObjectTable.GetObject(monikerContainer[0], out comInstance);
string name = TypeDescriptor.GetClassName(comInstance);
string component = TypeDescriptor.GetComponentName(comInstance, false);
if ((component == componentName) && (name == className))
{
Marshal.ReleaseComObject(bindInfo);
return comInstance;
}
else if ((component == "Micrcosoft " + componentName) && (name == className))
{
// nur manche office anwendungen führen je nach version "Microsoft" im Komponentennamen
Marshal.ReleaseComObject(bindInfo);
return comInstance;
}
else
Marshal.ReleaseComObject(comInstance);
Marshal.ReleaseComObject(bindInfo);
}
// not running
return null;
}
finally
{
// release proxies
if (runningObjectTable != null)
Marshal.ReleaseComObject(runningObjectTable);
if (monikerList != null)
Marshal.ReleaseComObject(monikerList);
}
}
/// <summary>
/// Gibt eine oder mehrere laufende Instanzen aus der Running Object Table zurück
/// </summary>
/// <param name="componentName">infache Komponentenangabe z.B. "Excel" oder "Outlook"</param>
/// <param name="className">Name der Klasse z.B. "Application" oder "Workbook"</param>
/// <returns>Alle COM Proxies in der RunngingObject Table die den Eingangsparametern entsprechen</returns>
private static List<object> GetApplicationInstancesFromROT(string componentName, string className)
{
IEnumMoniker monikerList = null;
IRunningObjectTable runningObjectTable = null;
List<object> resultList = new List<object>();
try
{
// query table and returns null if no objects runnings
if (GetRunningObjectTable(0, out runningObjectTable) != 0 || runningObjectTable == null)
return null;
// query moniker & reset
runningObjectTable.EnumRunning(out monikerList);
monikerList.Reset();
IMoniker[] monikerContainer = new IMoniker[1];
IntPtr pointerFetchedMonikers = IntPtr.Zero;
// fetch all moniker
while (monikerList.Next(1, monikerContainer, pointerFetchedMonikers) == 0)
{
// create binding object
IBindCtx bindInfo;
CreateBindCtx(0, out bindInfo);
// query com proxy info
object comInstance = null;
runningObjectTable.GetObject(monikerContainer[0], out comInstance);
string name = TypeDescriptor.GetClassName(comInstance);
string component = TypeDescriptor.GetComponentName(comInstance, false);
if ((component == componentName) && (name == className))
{
Marshal.ReleaseComObject(bindInfo);
resultList.Add(comInstance);
}
else if ((component == "Micrcosoft " + componentName) && (name == className))
{
// nur manche office anwendungen führen je nach version "Microsoft" im Komponentennamen
Marshal.ReleaseComObject(bindInfo);
resultList.Add(comInstance);
}
else
Marshal.ReleaseComObject(comInstance);
Marshal.ReleaseComObject(bindInfo);
}
// not running
return resultList;
}
finally
{
// release proxies
if (runningObjectTable != null)
Marshal.ReleaseComObject(runningObjectTable);
if (monikerList != null)
Marshal.ReleaseComObject(monikerList);
}
}
}
Der Aufruf ist vergleichsweise simpel:
foreach(object proxy in RunningObjectTable.GetApplicationInstancesFromROT("Excel", "Application"))
{
Excel.Application app = proxy as Excel.Application; // Interop Beispiel
Marshal.ReleaseComObject(proxy);
}
Hallo,
Vielen Dank an Rainbird und Sebastian, habt mir sehr geholfen.
gruß
mfg