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

发布于 2024-07-12 11:06:27 字数 2538 浏览 5 评论 0原文

在我的上一个开发环境中,我能够轻松地与 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 翻译,其相关部分是:

[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)方法的方法是什么?

快速搜索 Stack Overflow 以从 C# 使用 IDispatch 会发现这篇文章 在 .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 时)

In my last development environment, I was able to easily interact with COM, calling methods on COM objects. Here is the original code, translated into C# style code (to mask the original language):

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;
}

Now begins the tedious, painful, process of trying to interop with COM from managed code.

PInvoke.net already contains the IWebBrower2 translation, the relavent porition of which is:

[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; }
}

I've created the COM class:

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

So now it's time for my actual C# transaction:

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;
}

The careful readers notice that IWebBrowser2.Document is a late-bound IDispatch.
We're using Visual Studio 2005, with .NET 2.0 on our, and our customer's, machines.

So what's the .NET 2.0 method to invoke methods on an object that, on some level, only supports late-bound IDispatch?

A quick search of Stack Overflow for using IDispatch from C# turns up this post saying what I want is not possible in .NET.

So is it possible to use COM from C# .NET 2.0?


The question is that there is an accepted design pattern that I want to use in C#/.NET. It involves launching Internet Explorer out of process, and giving it HTML content, all the while not using temporary files.

A rejected design idea is hosting Internet Explorer on a WinForm.

An acceptable alternative is launching the system registered web browser, giving it HTML to display, without using a temporary file.

The stumbling block is continuing to use COM objects in the .NET world. The specific problem involves performing late-binding calls to IDispatch without needing C# 4.0. (i.e. while using .NET 2.0)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

嘦怹 2024-07-19 11:06:27

更新:根据问题更新,我删除了答案中与问题不再相关的部分。 但是,如果其他读者正在寻找一种在 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>");

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

Update: Based on question updates, I have removed the portions of my answer that are no longer relevant to the question. However, in case other readers are looking for a quick and dirty way to generate HTML in a winforms app and do not require an in-process IE, I will leave the following:

Possible Scenario 1: The ultimate goal is to simply display HTML to your end user and are using Windows Forms

System.Windows.Forms.WebBrowser is the painstakingly easy .NET wrapper for the interface you are trying to manually implement. To get it, Drag and drop an instance of that object from your toolbar (listed as "Web Browser" under the "All Windows Forms" section) onto your form. Then, on some suitable event handler:

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

On my test app, this correctly displayed the haunting message we all have learned to fear and loath.

违心° 2024-07-19 11:06:27

在 .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 后期绑定吗?

Late bound IDispatch called is relativly easy in .NET, although piss-poor:

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);
}

The referenced SO question that originally said "not possible until C# 4.0" was amended to show how it is possible in .NET 2.0.

Does C# .NET support IDispatch late binding?

泡沫很甜 2024-07-19 11:06:27

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

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

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

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

在极少数情况下,自动化对象不支持替代接口。 那么您应该使用 VB.Net 来包装该接口。 将 Option Strict 设置为关闭(仅适用于包装类)时,您可以使用 VB 对后期绑定调用的内置支持来简单地在幕后调用适当的 IDispatch 方法。 在极少数情况下,如果参数类型不寻常,您可能需要对调用进行一些修改,但一般来说,在 VB 中您可以这样做! 即使对 C# v4 进行了动态添加,VB 仍可能对后期绑定 COM 调用提供更好的支持。

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

The answers in the post that you link to are actually incorrect. It is generally very easy to deal with IDispatch based objects in .Net. Basically you go through three steps:

Most automation objects (probably well over 90%) that are exposed as IDispatch interfaces have other interfaces that can be used by non-scripting type COM clients (either the IDispatch interface is actually a full COM interface derived from IDispatch or the object supports one or more other IUnknown derived interfaces). In this case, you would simply import the appropriate COM interface definition and then cast the object to the appropriate interface. The cast calls QueryInterface under the covers and returns a wrapped reference to the interface you want.

This is the technique you would use in the scenario that you presented above. The Document object that is returned from the IE automation object supports the IHTMLDocument, IHTMLDocument2, IHTMLDocument3, IHTMLDocument4 and IHTMLDocument5 interfaces (depending on the version of IE you are using). You should cast to the appropriate interface and then call the appropriate method. For example:

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

In the rare case where the automation object does not support an alternative interface. Then you should use VB.Net to wrap that interface. With Option Strict set to off (for the wrapper class only) you can use VB's built in support for late bound calls to simply call the appropriate IDispatch methods under the covers. In rare cases with unusual argument types you may need to fiddle a bit with the call but, in general, in VB you can just do it! Even with the dynamic additions to C# v4 VB will still probably have significantly better support for late-bound COM calls.

If for some reason you can't use VB to wrap the automation interface then you can still make any necessary calls from C# using reflection. I won't go into any details since this option should basically never be used but here is a small example involving Office automation.

你如我软肋 2024-07-19 11:06:27

请参阅这篇文章:
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);

See this article :
http://www.codeproject.com/KB/cs/IELateBindingAutowod.aspx

Internet Explorer Late Binding Automation
By yincekara

Internet Explorer automation sample code using late binding, without Microsoft.mshtml and shdocvw dependency.

for htmlDoc.write(htmlString);
modify

   [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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文