.NET2.0 C#互操作:如何从C#调用COM代码?

3
在我的上一个开发环境中,我能够轻松地与COM进行交互,调用COM对象的方法。这是原始代码,已经翻译成C#风格的代码(为了掩盖原始语言):
public static void SpawnIEWithSource(String szSourceHTML)
{
    OleVariant ie; //IWebBrowser2
    OleVariant ie = new InternetExplorer();
    ie.Navigate2("about:blank");

    OleVariant webDocument = ie.Document;
    webDocument.Write(szSourceHTML);
    webDocument.close;

    ie.Visible = True;
}

现在开始尝试从托管代码与COM进行交互的漫长而痛苦的过程。

PInvoke.net已经包含了IWebBrower2 translation,其中相关部分如下:

[ComImport, 
   DefaultMember("Name"), 
   Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E"), 
   InterfaceType(ComInterfaceType.InterfaceIsIDispatch), 
   SuppressUnmanagedCodeSecurity]
public interface IWebBrowser2
{
    [DispId(500)]
    void Navigate2([In] ref object URL, [In] ref object Flags, [In] ref object TargetFrameName, [In] ref object PostData, [In] ref object Headers);

    object Document { [return: MarshalAs(UnmanagedType.IDispatch)] [DispId(0xcb)] get; }
}

我已经创建了COM类:

[ComImport]
[Guid("0002DF01-0000-0000-C000-000000000046")]
public class InternetExplorer
{
}

现在是我实际进行 C# 事务的时候了:

public static void SpawnIEWithSource(String szHtml)
{
    PInvoke.ShellDocView.IWebBrowser2 ie;
    ie = (PInvoke.ShellDocView.IWebBrowser2)new PInvoke.ShellDocView.InternetExplorer();

    //Navigate to about:blank to initialize the browser
    object o = System.Reflection.Missing.Value;
    String url = @"about:blank";
    ie.Navigate2(ref url, ref o, ref o, ref o, ref o);

    //stuff contents into the document
    object webDocument = ie.Document;
    //webDocument.Write(szHtml);
    //webDocument.Close();

    ie.Visible = true;
}

细心的读者会注意到,IWebBrowser2.Document是一个后期绑定的IDispatch。我们使用的是Visual Studio 2005,在我们和客户的机器上都安装了.NET 2.0。
那么,在仅支持后期绑定IDispatch的对象上调用方法的.NET 2.0方法是什么呢?
在Stack Overflow上快速搜索使用C#从IDispatch中调用方法,this post说我想要的在.NET中不可能实现。
那么,从C# .NET 2.0中是否可以使用COM呢?
问题在于有一个被接受的设计模式,我想在C#/.NET中使用它。它涉及到启动Internet Explorer进程,并给它HTML内容,同时不使用临时文件。
一个被拒绝的设计想法是在WinForm上托管Internet Explorer。
一个可接受的替代方案是启动系统注册的Web浏览器,给它要显示的HTML,而不使用临时文件。
绊脚石是在.NET世界中继续使用COM对象。具体问题涉及在不需要C# 4.0的情况下执行对IDispatch的后期绑定调用。(即在使用.NET 2.0时)

出于好奇,总的目标是什么?看起来你想启动IE,然后显示传递字符串的HTML。是否必须具体启动IE,而不是System.Windows.Forms.WebBrowser控件? - el2iot2
你能否将 System.Windows.Forms.WebBrowser 以外的进程启动吗? - Ian Boyd
我想这是可行的,但我自己还没有完成过。如果我成功了,我会再发帖通知的。 - el2iot2
1
如果你成功完成了它(没有参考PIA),你将得到一个湿漉漉的吻。 - Ian Boyd
4个回答

4

更新:根据问题的更新,我已经删除了我的答案中与问题不相关的部分。然而,如果其他读者正在寻找一种快速简便的方法在Windows Forms应用程序中生成HTML,并且不需要使用进程内IE,则我将保留以下内容:

可能的方案1:最终目标是向最终用户显示HTML,并且正在使用Windows Forms

System.Windows.Forms.WebBrowser 是.NET包装接口的极其简单易用的封装。要获取它,请从工具栏中拖放该对象的一个实例(在“所有Windows窗体”部分下列为“Web浏览器”)。然后,在一些适当的事件处理程序中:

webBrowser1.Navigate("about:blank");
webBrowser1.Document.Write("<html><body>Hello World</body></html>");

在我的测试应用程序中,这正确地显示了我们所有人都学会了害怕和厌恶的令人不安的消息。

场景1:不确定你在问什么。场景2:目标是使用源代码启动Web浏览器。场景3:Web浏览器是一个概念性的例子,我恰好正在尝试解决它。其他试图将COM代码移植到.NET的人可能会从这个案例提供的答案中受益。 - Ian Boyd
情景1:留下了我误解的空间。基本上,我知道一种“.NET方式”来做这件事。我无法从原始问题中确定您是A.只想简单地解决HTML显示,还是B.那只是更大问题的一个示例。看起来是B。 - el2iot2
我喜欢使用Stack Overflow寻找概念问题的答案,而不是具体情况。 - Ian Boyd
明白了。实际上在 Stack Overflow 上这种情况经常发生:高层次的概念/方法问题与使用特定场景作为例子的问题之间存在歧义,以及关于非常具体问题的问题。我认为这是一个很好的区分。感谢您的反馈。 - el2iot2

3

在.NET中,调用后期绑定的IDispatch相对容易,但质量很差:

public static void SpawnIEWithSource(String szHtml)
{
    // Get the class type and instantiate Internet Explorer.
    Type ieType = Type.GetTypeFromProgID("InternetExplorer.Application");
    object ie = Activator.CreateInstance(ieType);

    //Navigate to the blank page in order to make sure the Document exists
    //ie.Navigate2("about:blank");
    Object[] parameters = new Object[1];
    parameters[0] = @"about:blank";
    ie.GetType().InvokeMember("Navigate2", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, ie, parameters);

    //Get the Document object now that it exists
    //Object document = ie.Document;
    object document = ie.GetType().InvokeMember("Document", BindingFlags.GetProperty | BindingFlags.IgnoreCase, null, ie, null);

    //document.Write(szSourceHTML);
    parameters = new Object[1];
    parameters[0] = szHtml;
    document.GetType().InvokeMember("Write", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, document, parameters);

    //document.Close()
    document.GetType().InvokeMember("Close", BindingFlags.InvokeMethod | BindingFlags.IgnoreCase, null, document, null);

    //ie.Visible = true;
    parameters = new Object[1];
    parameters[0] = true;
    ie.GetType().InvokeMember("Visible", BindingFlags.SetProperty | BindingFlags.IgnoreCase, null, ie, parameters);
}

原先说“在C# 4.0之前不可能”的引用的SO问题已经被修改,以显示它如何在.NET 2.0中实现。

C# .NET支持IDispatch后期绑定吗?


0
请查看这篇文章: http://www.codeproject.com/KB/cs/IELateBindingAutowod.aspx 作者:yincekara 标题:Internet Explorer Late Binding Automation
本文介绍了使用后期绑定的 Internet Explorer 自动化示例代码,无需依赖 Microsoft.mshtml 和 shdocvw。
对于 htmlDoc.write(htmlString); 进行修改。
   [Guid("332C4425-26CB-11D0-B483-00C04FD90119")]
    [ComImport]
    [TypeLibType((short)4160)]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
    internal interface IHTMLDocument2
    {
        [DispId(1054)]
        void write([MarshalAs(UnmanagedType.BStr)] string psArray);
        //void write([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] object[] psarray);

0

你链接的帖子中的答案实际上是不正确的。在.NET中处理基于IDispatch的对象通常非常容易。基本上,您需要经过三个步骤:

大多数自动化对象(可能超过90%)作为IDispatch接口公开,具有其他接口可供非脚本类型的COM客户端使用(IDispatch接口实际上是从IDispatch派生的完整COM接口或对象支持一个或多个其他IUnknown派生的接口)。在这种情况下,您只需导入适当的COM接口定义,然后将对象转换为适当的接口。转换调用QueryInterface并在幕后返回对所需接口的包装引用。

这是您在上述场景中使用的技术。从IE自动化对象返回的Document对象支持IHTMLDocument、IHTMLDocument2、IHTMLDocument3、IHTMLDocument4和IHTMLDocument5接口(取决于您使用的IE版本)。您应该转换为适当的接口,然后调用适当的方法。例如:

IHTMLDocument2 htmlDoc = (IHTMLDocument2)webDocument;
htmlDoc.Write(htmlString);
htmlDoc.Close();

在极少数情况下,自动化对象不支持替代接口。那么你应该使用 VB.Net 来包装该接口。对于包装类而言,将选项 Strict 设置为 off(仅限包装类),你可以使用 VB 内置的延迟绑定调用来简单地调用适当的 IDispatch 方法。在罕见的情况下,如果有不寻常的参数类型,你可能需要稍微调整一下调用,但总体而言,在 VB 中,你只需这样做!即使是在 C# v4 的动态添加中,VB 仍然可能会对延迟绑定的 COM 调用提供显着更好的支持。

如果由于某种原因你不能使用 VB 来包装自动化接口,那么你仍然可以使用反射从 C# 中进行任何必要的调用。我不会详细介绍,因为基本上不应该使用这个选项,但这里有一个涉及 Office 自动化的小例子


并非每个COM对象都支持除IDispatch之外的接口。此外,您发布的代码无法正常工作(您会发现导入的write()方法声明不正确)。我在stackoverflow上实际上有7个问题,尝试从不同的角度解决这个问题。 - Ian Boyd
我发现了在C#中调用基于IDispatch的迟绑定接口相对容易的方法。 - Ian Boyd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接