Running Object Table(ROT)に関して調べてみたことのメモ。
ざっくりとした理解なので、間違いなどあれば指摘していただけるとうれしいです。
参考サイト
モニカ EternalWindows
複数の EXCEL.EXE が動いている時に、ひとつの Excel.Application を GetObject で特定して C# で操作するには? - QA@IT
概念
Running Object Table
その端末上で実行中(Running)のCOMオブジェクト(Object)の一覧(Table)。
VBAなどのGetObject 関数、.NET Frameworkの Marshal.BindToMoniker・Marshal.GetActiveObject などはここにあるオブジェクトを取得している。
モニカ(Moniker)
ROT内のオブジェクトなどを特定するのに使われるもの。
文字列で表現でき、GetObject
の第1引数やBindToMoniker
に渡すものに相当する。
いくつか種類があり、下の記事で使用しているのはそのうちのファイルモニカとなる。
コード
「開かれているExcelのブックの一覧を取得する」を目的にコードを作成した。
Excelの場合、ブックを開いている→ファイルモニカがROTに登録されているとなっていることが多い1。
そのため、ROTからファイルモニカで登録されているオブジェクトを探し、さらにそれらの中からExcel.Workbook
にキャストできるものを探している。
複数の EXCEL.EXE が動いている時に、ひとつの Excel.Application を GetObject で特定して C# で操作するには? - QA@IT
にあった「BindToObject の実行時に例外が起こることがあります」の問題はつぶしたつもり。
C#はあまり詳しくないので若干レガシーな書き方だと思われる。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Excel = Microsoft.Office.Interop.Excel;
/// <summary>
/// Running Object Table関連操作のラッパークラス
/// </summary>
public class ROTManager
{
/*
* 参考サイト
* モニカの概念について
* [モニカ](http://eternalwindows.jp/com/moniker/moniker00.html)
* 実際のC#の実装例
* [複数の EXCEL.EXE が動いている時に、ひとつの Excel.Application を GetObject で特定して C# で操作するには? - QA@IT]
* (https://qa.atmarkit.co.jp/q/4634)
*/
public static Excel.Workbook[] GetOpendLocalWorkbooks()
{
return GetOpendComObjects<Excel.Workbook>();
}
/// <summary>
/// ファイルモニカを元に取得できるオブジェクトから、指定した型のオブジェクトを探す
/// </summary>
/// <typeparam name="T">取得したいCOMの型</typeparam>
/// <returns>型変換に成功したオブジェクト</returns>
public static T[] GetOpendComObjects<T>() where T : class
{
List<T> ts = new List<T>();
// OfType<T>は解放のタイミングが難しそう
foreach (var o in GetRunningObjects(MKSYS.MKSYS_FILEMONIKER))
{
T t = o as T;
if (t != null)
{
ts.Add(t);
}
else
{
Marshal.FinalReleaseComObject(o);
}
}
return ts.ToArray();
}
/// <summary>
/// モニカーの種類
/// </summary>
/// <see cref="https://docs.microsoft.com/ja-jp/windows/desktop/api/objidl/ne-objidl-tagmksys"/>
enum MKSYS
{
MKSYS_NONE,
MKSYS_GENERICCOMPOSITE,
MKSYS_FILEMONIKER,
MKSYS_ANTIMONIKER,
MKSYS_ITEMMONIKER,
MKSYS_POINTERMONIKER,
MKSYS_CLASSMONIKER,
MKSYS_OBJREFMONIKER,
MKSYS_SESSIONMONIKER,
MKSYS_LUAMONIKER
}
/// <summary>
/// Returns a pointer to an implementation of IBindCtx (a bind context object). This object stores information about a particular moniker-binding operation.
/// </summary>
/// <param name="reserved">This parameter is reserved and must be 0.</param>
/// <param name="ppbc">Address of an IBindCtx* pointer variable that receives the interface pointer to the new bind context object. When the function is successful, the caller is responsible for calling Release on the bind context. A NULL value for the bind context indicates that an error occurred.</param>
/// <returns>This function can return the standard return values E_OUTOFMEMORY and S_OK</returns>
/// <see cref="https://docs.microsoft.com/en-us/windows/desktop/api/objbase/nf-objbase-createbindctx"/>
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
/// <summary>
/// Running Object Table から指定されたモニカのオブジェクトを取得する
/// </summary>
/// <param name="monikerType">モニカの種類</param>
/// <returns>見つかったオブジェクト</returns>
private static object[] GetRunningObjects(MKSYS monikerType)
{
// Running Object Table を取得する
const uint reserved = 0;
IBindCtx ctx;
CreateBindCtx(reserved, out ctx);
IRunningObjectTable runningObjectTable;
ctx.GetRunningObjectTable(out runningObjectTable);
IEnumMoniker enumMoniker;
runningObjectTable.EnumRunning(out enumMoniker);
// ここまではほぼ定型
enumMoniker.Reset();
List<object> returnObjects = new List<object>();
while (true)
{
const int S_OK = 0;
IMoniker[] tmpMks = new IMoniker[1];
IntPtr fetched = IntPtr.Zero;
// bufMonikers の数ずつモニカーを取得
bool successNext = enumMoniker.Next(tmpMks.Length, tmpMks, fetched) == S_OK;
if (!successNext)
{
break;
}
try
{
//for Debug
string dispName;
tmpMks[0].GetDisplayName(ctx, null,out dispName);
Debug.WriteLine("DisplayName\t" + dispName);
Guid clsId;
tmpMks[0].GetClassID(out clsId);
Debug.WriteLine(clsId);
// Debug.WriteLine(Marshal.GetTypeFromCLSID(clsId)); //-> System.__ComObject
int pdwMksys;
if (tmpMks[0].IsSystemMoniker(out pdwMksys) != S_OK)
{
Debug.WriteLine("not SystemMoniker");
continue;
}
MKSYS mkType = (MKSYS)Enum.ToObject(typeof(MKSYS), pdwMksys);
Debug.WriteLine(mkType);
if (mkType != monikerType)
{
continue;
}
object obj;
if (runningObjectTable.GetObject(tmpMks[0], out obj) != S_OK)
{
continue;
}
returnObjects.Add(obj);
}
finally
{
Marshal.FinalReleaseComObject(tmpMks[0]);
}
}
Marshal.FinalReleaseComObject(enumMoniker);
Marshal.FinalReleaseComObject(runningObjectTable);
Marshal.FinalReleaseComObject(ctx);
return returnObjects.ToArray();
}
}
使用例
C#
using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
public class Program
{
static void Main(string[] args)
{
foreach (var wb in ROTManager.GetOpendLocalWorkbooks())
{
Console.WriteLine(wb.Name);
Marshal.FinalReleaseComObject(wb);
}
Console.WriteLine("Finish");
Console.ReadKey();
}
}
PowerShell
上記のRunningObjectTableManagerをどこかに○○.csで保存して以下を実行。
ファイルとして保存する代わりにTypeDefinition
にコード文字列を指定しても動くはず。
[hashtable]$addTypeParams = @{
Path = 上記のサンプルをどこかに○○.csで保存したパス
ReferencedAssemblies = @('Office', 'Microsoft.Office.Interop.Excel')
}
Add-Type @addTypeParams
foreach ($wb in [ROTManager]::GetOpendLocalWorkbooks()) {
$wb.Name
[System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($wb) > $null
}
参考
モニカ EternalWindows
複数の EXCEL.EXE が動いている時に、ひとつの Excel.Application を GetObject で特定して C# で操作するには? - QA@IT
関連COMインターフェイス
IBindCtx | Microsoft Docs
IRunningObjectTable | Microsoft Docs
IEnumMoniker | Microsoft Docs
IMoniker | Microsoft Docs
tagMKSYS | Microsoft Docs
-
ローカルドライブやUNCパスの範囲内ならROTに登録されている。OneDrive上などネットワーク上だと見つけられなかった。 ↩