C# 运行 活动自动化对象 - 如何获取事件源?

C# Running Active Automation Object - How to Source events?

我有一个(长 运行)控制台应用程序,用 C# 编写,我希望能够通过 COM 对其进行操作(因此没有 InProc DLL 和 regasm.exe)。 IDispatch 是我所需要的 - 所以一个 classic OLE Automation 对象。

在这里,我将展示我尝试做的事情的最小版本。我定义了一个 COM class 这样的:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("9009311a-c0b2-42a4-8e7c-f42091d71594")]
public interface ITestEvents {
    void OnEvent();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ComSourceInterfaces(typeof(ITestEvents))]
public class ComClass {
    public event Action OnEvent;

    public int Test() {
        return 100;
    }
}

Main() 中,我只是在 运行 对象 Table (ROT) 中使用 "comTestApp" Moniker 注册了对象,然后让应用程序休眠。 你可以看到完整的来源 here.

当我尝试调用该对象的方法时,它工作得很好。例如,此 VBScript 工作正常:

Set obj = GetObject("comTestApp")
WScript.Echo obj.Test() Rem prints 100

但是当我尝试连接事件时:

Set obj = GetObject("comTestApp")
WScript.Echo obj.Test() Rem Works

WScript.ConnectObject obj, "obj_" Rem Fails

Sub obj_OnEvent
    WScript.Echo "Wish it worked"
End Sub

调用 ConnectObject 时出错(错误 0x80020009 "Could not connect object")。

如果我使用 regasm.exe 将程序集注册为 InProc 对象,则相同的代码(仅需添加 class GUID/ProgID)有效,但我不需要它。我需要访问 运行 应用程序,这就是我使用 ROT 的原因。

我创建了一个简单的 C++ 测试,看看是否可以找到有关该问题的更多信息。来源是here。我已经编写了一个实现 IDispatch 接口的最小 COM 对象,它应该充当事件接收器。首先,我从 ROT 中获取对象,查询 IConnectionPointContainer,然后获取 IConnectionPoint 以获得 ITestEvents 的 IID,最后调用其 Advise() 方法。与 VBScript 一样,它失败了(尽管我收到另一个错误 - 0x80040202)。我在事件接收器的 QueryInterface 方法上放置了一个断点,以查看调用 Advise() 时会发生什么。我可以看到 QueryInterface 被各种接口调用,最后它请求我的 ITestEvents,我 return 并设置状态 S_OK。但是,Advise() 方法 return 仍然会出现上述错误。

我还尝试了另一件事:我将 ITestEvents 的 GUID 设置为 {00020400-0000-0000-C000-000000000046},这是 IDispatch 的 IID。而现在 Advise() returns S_OK!我什至模拟了一个事件,事件接收器的 Invoke() 方法被调用了!唉,这并不能解决一般的问题。如果您通过 IID 直接从 IConnectionPointContainer 请求它 - 您会得到它,但它似乎没有被 ITypeInfo 正确枚举并且 VBScript 仍然不起作用。

我几乎没有使用 COM 的经验,所以我不确定从这里该何去何从。事实上,如果我使用 IDispatch IID 使其工作,让我想知道 ITestEvents 接口是否需要一些自定义封送处理,尽管它是纯 IDispatch 所以我认为它应该是运行时处理得很好。

谢谢!

我的结论是,无法提供接口 IID 未在注册表中注册的连接接收器(到进程外对象)。

似乎编组信息是强制性的,以便将接收器接口发送到服务器。我希望由于 C# 类 提供完整 ITypeInfo,COM 运行时将确定接口是 IDispatch 类型并使用默认代理。 las,情况似乎并非如此,因此唯一的选择仍然存在 - 注册表。

作为最低限度,我发现这是必需的:

[HKEY_CLASSES_ROOT\Interface\{9009311a-c0b2-42a4-8e7c-f42091d71594}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"

这定义了我们的事件接口,并表示我们使用标准 IDispatch 代理作为代理。

我已经在 Windows 7 和 10 上测试过它并且有效! - VBScript 可以 ConnectObject 并且一切正常。

无意中我发现了一个 Win7 安装,但它不起作用 (win ver 6.1 build 7601 sp1)。它仅在注册类型库 (regasm.exe /tlb) 后起作用。有问题的安装已禁用 Windows 更新(我的另一个 Win7 已完全更新)所以我猜在某些时候已经更改了一些内容,这使得即使没有类型库也可以编组接口,基于我们指定的事实默认 IDispatch 代理。

最后,由于我想让我的应用程序保持可移植性并独立于注册表,我求助于手动事件实现。可以看到类似的东西 here.