为什么我可以将 COM 对象转换为错误的接口?

Why can I cast a COM object to a wrong interface?

我有一个 .NET 程序,它与来自另一个进程的 mshtml 对象进行交互。我从头写了一个小 sample project 来说明这个问题。在此示例中,我直接使用 COM 引用来实现 mshtml 互操作。

HTMLDocument document = Document;
IHTMLElement activeElement = document.activeElement;
Log.Verbose(activeElement.tagName);
bool isHtmlFrameElement = activeElement is HTMLFrameElement;
Log.Verbose("active Element is " + (isHtmlFrameElement ? "" : "NOT ") + "a frame element");

我引用了一个自定义的 mshtml,它是通过以下调用生成的:

tlbimp c:\Windows\System32\mshtml.tlb /out:c:\tmp\Interop.mshtml.dll

在我的开发机器(安装了 Office)上,我得到了这个日志,这是预期的行为:

INPUT 
active Element is NOT a frame element

但是在 naked 机器上,没有安装 office(也没有 mshtml interop),我得到以下日志:

INPUT 
active Element is a frame element

当然它不是 HTMLFrameElement 并且任何对其成员之一的访问都会导致 找不到成员 异常。

为什么 COM 在第二种情况下允许这种无效的转换?我可以使用我的互操作(在构建目录中)还是必须将它安装到 GAC(就像 MS Office 一样)?

转换没有失败,因为返回的对象是 NULL。详细说明here

简而言之,它说在转换不当时没有异常。 老实说,使用 COM 对象并不容易。 首先要正确注册dll,其次要正确描述对象。

mshtml 的 HTMLFrameElement 是由 .NET 生成的 COM coclass。实际上,简而言之,除了工具(如 tlbimp 等)之外,这并不存在。通常我们只使用它的 GUID(CLSID)来“共同创建”它。

这个 coclass 声明它实现了 DispHTMLFrameElement,这是一个 dispinterface。同样,这是更多用于工具的元数据,它对您的情况没有实际用处。您可以在 Windows SDK 的 mshtml.h:

中看到它的声明

EXTERN_C const IID DIID_DispHTMLFrameElement;

#if defined(__cplusplus) && !defined(CINTERFACE)

MIDL_INTERFACE("3050f513-98b5-11cf-bb82-00aa00bdce0b")
DispHTMLFrameElement : public IDispatch
{
};

#else /* C style interface */

因此 .NET 在这些基础上构建了一些奇特的 类 但它们不应该用于检测您的情况下的 COM 接口支持。为什么行为因上下文而异只是意味着这些 类 的实现会有所不同。

在你的情况下,你应该检查 IHTMLFrameElement,它也在 MsHTML.h:

中定义

EXTERN_C const IID IID_IHTMLFrameElement;

#if defined(__cplusplus) && !defined(CINTERFACE)

MIDL_INTERFACE("3050f313-98b5-11cf-bb82-00aa00bdce0b")
IHTMLFrameElement : public IDispatch
{
public:
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_borderColor( 
        /* [in] */ VARIANT v) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_borderColor( 
        /* [out][retval] */ __RPC__out VARIANT *p) = 0;
    
};

#else /* C style interface */

请注意 IID 相似但实际上不同,用这个进行测试确实是您想要做的:

bool isHtmlFrameElement = activeElement is IHTMLFrameElement;