1

我正在开发一个使用第三方 COM 服务器 API 的项目。COM 服务器是我无法控制的本地服务器(进程外 exe)。

我正在尝试从 runnin 对象表访问 COM 对象,以在从应用程序的每个实例开始的几个 COM 对象实例之间进行选择:

private static List<object> GetRunningInstances(string progId) {
    // get Running Object Table ...
    IRunningObjectTable Rot = null;
    GetRunningObjectTable(0, out Rot);
    if (Rot == null)
        return null;

    // get enumerator for ROT entries
    IEnumMoniker monikerEnumerator = null;
    Rot.EnumRunning(out monikerEnumerator);

    if (monikerEnumerator == null) return null;

    monikerEnumerator.Reset();

    List<object> instances = new List<object>();

    IntPtr pNumFetched = new IntPtr();
    IMoniker[] monikers = new IMoniker[1];

    while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0) {
        IBindCtx bindCtx;
        CreateBindCtx(0, out bindCtx);

        if (bindCtx == null) continue;

        Guid clsid = Type.GetTypeFromProgID(progId).GUID;

        string displayName;
        Guid monikerClsid;
        Guid riid = Marshal.GenerateGuidForType(typeof(IApplication));
        object obj;

        monikers[0].GetDisplayName(bindCtx, null, out displayName);
        monikers[0].GetClassID(out monikerClsid);

        if (displayName.IndexOf(clsid.ToString(), StringComparison.OrdinalIgnoreCase) > 0) {
            //monikers[0].BindToObject(bindCtx, null, ref unkid, out obj);
            Rot.GetObject(monikers[0], out obj);
            instances.Add((IApplication)obj);
        }
    }
}

如果我启动目标应用程序的两个实例,ROT 转储会显示相应 COM 对象(此处命名为 IApplication)的两个实例,因为 GetDisplayName 显示在注册表中注册的接口 IApplication 的正确 clsid。

问题是我从 Rot.GetObject 获得的对象被描述为 System.__ComObject 并且不能强制转换为 IApplication(InvalidCastException,因为 QueryInterface 因 E_NOINTERFACE 失败),即使它们的绰号描述了正确的 clsid ...

我尝试将它以编程方式投射到我的项目中的每个可用类型,只是为了看看,但唯一的成功是将它投射到 System.__ComObject...

我也尝试使用 IMoniker.BindToObject 而不是 Rot.GetObject 但这次,当我提供相应的接口 GUID 时,我得到了 FileNotFound 异常。当我为 IUnknown 提供 riid 时,BindToObject 有效,但它给了我一个 System.__ComObject 我无法转换(回到第一方!)。

有关信息,在 ROT 转储中,我还可以显示与目标应用程序中打开的项目相对应的文件名字对象,但我也无法从中创建 COM 对象。

任何人都知道如何从 ROT 中正确检索对象?

感谢和问候。

编辑 :

几天后,对这个问题有了新的认识,我发现名字对象显示名称中的 CLSID 与我想要的不完全相同,但有两个数字(indexof 测试结果是错误的)。

仔细阅读 clsid 后发现,名字对象的显示名称中的 clsid 是 IApplication 的 coclass 的 clsid,而不是 IApplication 的 clsid。

我尝试将对象转换为“ApplicationClass”(前面提到的 coclass),但这给了我一个例外。我得到的附加信息(法语)可以翻译如下:不可能将 __ComObject 包装器实例转换为另一个类,但只要底层 com 组件支持接口 IID 的 QueryInterface 调用,就可以将这些实例转换为接口。

关于如何从这里开始的任何想法?

4

1 回答 1

0

如果您仍然有问题,我现在不知道,但由于还没有答案,我想分享我在几周研究后所做的关于在 ROT 上处理多个 COM 接口的研究。

我已经从 ROT 获取所有对象并将它们存储在哈希表中,以通过键/值确保唯一性,但您可以根据自己的喜好将其调整为列表。您只需要获取运行对象值即可添加您的列表。

public static Hashtable GetRunningObjectTable()
{
    Hashtable result = new Hashtable();

    IntPtr numFetched = new IntPtr();
    IRunningObjectTable runningObjectTable;
    IEnumMoniker monikerEnumerator;
    IMoniker[] monikers = new IMoniker[1];

    GetRunningObjectTable(0, out runningObjectTable);
    runningObjectTable.EnumRunning(out monikerEnumerator);
    monikerEnumerator.Reset();

    while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
    {
        IBindCtx ctx;
        CreateBindCtx(0, out ctx);

        string runningObjectName;
        monikers[0].GetDisplayName(ctx, null, out runningObjectName);

        object runningObjectVal;
        runningObjectTable.GetObject(monikers[0], out runningObjectVal);

        result[runningObjectName] = runningObjectVal;
    }

    return result;
}

之后,您可以将存储在您的列表中的对象投射到您的 COM 接口。例如,我还在下面分享了适用于 Rhapsody COM 接口的方法;

Hashtable runningObjects = GetRunningObjectTable();
IDictionaryEnumerator rotEnumerator = runningObjects.GetEnumerator();
while (rotEnumerator.MoveNext())
{
    string candidateName = (string)rotEnumerator.Key;
    if (!candidateName.StartsWith("Rhapsody"))
        continue;

    rhapsody.RPApplication app = rotEnumerator.Value as rhapsody.RPApplication;

    if (app == null)
        continue;

    // Do your stuff app (com object) in here..
}
于 2020-03-24T11:42:14.057 回答