3
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Running Object Tableからオブジェクトを取得する(例:開かれているExcelのブックの一覧を取得する)

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.BindToMonikerMarshal.GetActiveObject などはここにあるオブジェクトを取得している。

モニカ(Moniker)

ROT内のオブジェクトなどを特定するのに使われるもの。
文字列で表現でき、GetObjectの第1引数やBindToMonikerに渡すものに相当する。

いくつか種類があり、下の記事で使用しているのはそのうちのファイルモニカとなる。

GetObject(, "Excel~")だけで無く、GetObject(ファイル名)も活用しよう - Qiita

コード

「開かれているExcelのブックの一覧を取得する」を目的にコードを作成した。

Excelの場合、ブックを開いている→ファイルモニカがROTに登録されているとなっていることが多い1
そのため、ROTからファイルモニカで登録されているオブジェクトを探し、さらにそれらの中からExcel.Workbookにキャストできるものを探している。

複数の EXCEL.EXE が動いている時に、ひとつの Excel.Application を GetObject で特定して C# で操作するには? - QA@IT

にあった「BindToObject の実行時に例外が起こることがあります」の問題はつぶしたつもり。

C#はあまり詳しくないので若干レガシーな書き方だと思われる。

RunningObjectTableManager.cs
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


  1. ローカルドライブやUNCパスの範囲内ならROTに登録されている。OneDrive上などネットワーク上だと見つけられなかった。 

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
3
Help us understand the problem. What are the problem?