views:

2566

answers:

5

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)

+3  A: 

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.

ee
Scenario 1: Not sure what you're asking. Scenario 2: Goal is to launch the web-browser with source. Scenario 3: Web-browser is conceptual example, that i happen to be trying to solve. Others who are trying to port COM code to .NET might benefit from the answer that this case provides.
Ian Boyd
Scenario 1: was leaving room for my misinterpretation. Basically, I had knowledge of one of the ".NET ways" of doing it. I couldn't tell from the original question whether you A. just wanted to simply solve html display, or if B. that was just a sample of the larger problem. It seems it was B.
ee
i like to use SO to get answers to conceptual questions, rather than specific cases.
Ian Boyd
Understood. It actually seems like something that happens quite a lot at SO: Ambiguity between a higher-level conceptual/approach question that use a specific scenario as an example, and questions about very specific questions. I think its a good distinction to know. Thanks for your feedback.
ee
A: 

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.

Stephen Martin
Not every COM object supports interfaces besides IDispatch. Besides, the code you posted won't work (you'll find the imported declaration of the write() method is incorrect). i actually have 7 questions on stack overflow trying to solve the problem by attacking it from different avenues.
Ian Boyd
i discovered the relativly easy way to call late-bound IDispatch based interfaces in C#.
Ian Boyd
+1  A: 

Late bound IDispatch called is relativly easy in .NET.

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.

http://stackoverflow.com/questions/403218/does-c-net-support-idispatch-late-binding

Ian Boyd
A: 

This tool makes the job easier : PInvoke Interop Assistant
More technical info here : Best Practices For Managed And Native Code Interoperability

lsalamon
A: 

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