.NET2.0 C#Interop:如何从C#调用COM代码?

时间:2009-01-22 16:17:25

标签: c# com interop .net-2.0 idispatch

在我的上一个开发环境中,我能够轻松地与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。

那么调用对象上的方法的.NET 2.0方法是什么,在某种程度上,它只支持后期绑定的IDispatch?

使用C#中的IDispatch快速搜索Stack Overflow会显示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时)

4 个答案:

答案 0 :(得分:4)

  

更新:根据问题更新,我删除了与问题不再相关的部分答案。但是,如果其他读者正在寻找一种快速而肮脏的方式来在winforms应用程序中生成HTML并且不需要进程内IE,我将留下以下内容:

可能的方案1:最终目标是简单地向最终用户显示HTML并使用Windows窗体

System.Windows.Forms.WebBrowser是您尝试手动实现的界面的轻松.NET包装器。要获取它,将该对象的实例从工具栏(在“所有Windows窗体”部分下列为“Web浏览器”)拖放到窗体上。然后,在一些合适的事件处理程序上:

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

在我的测试应用程序中,这正确地显示了我们都学会了恐惧和厌恶的令人难忘的消息。

答案 1 :(得分: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中它是如何实现的。

Does C# .NET support IDispatch late binding?

答案 2 :(得分:0)

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

作为IDispatch接口公开的大多数自动化对象(可能超过90%)具有非脚本类型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来包装该接口。将Option Strict设置为off(仅适用于包装类),您可以使用VB内置的对后期绑定调用的支持,只需调用相应的IDispatch方法。在极少数情况下使用不常见的参数类型时,您可能需要对调用进行一些调整,但通常情况下,在VB中您可以执行此操作!即使动态添加到C#v4,VB仍然可能对后期绑定的COM调用有更好的支持。

如果由于某种原因您不能使用VB来包装自动化界面,那么您仍然可以使用反射从C#进行任何必要的调用。我不会详细说明,因为这个选项基本上不会被使用,但这里是small example involving Office automation

答案 3 :(得分:0)

看到这篇文章: http://www.codeproject.com/KB/cs/IELateBindingAutowod.aspx

Internet Explorer后期绑定自动化 由yincekara

使用后期绑定的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);