views:

549

answers:

1

Folks,

My question is: Can this be done better? and if so, How? Any ideas?

We need to start a captive IE session from within an "invisible" C# .NET 3.5 application, and quit both the IE session and the "parent" application after processing a certain request.

I've been mucking around with this problem for the last week or so... and this morning I've finally reached what I think is a robust solution; but I'm a bit of a C# noob (though I've been a professional programmer for 10 years), so I'm seeking a second or third opinion; and any other options, critiques, suggestions, or comments... Especially: is SHDocVw still the preferred method of creating a "captive but not imbedded" Internet Explorer session?

As I see things, the tricky bit is Disposing of the unmanaged InternetExplorerApplication COM object, so I've wrapped it in an IDisposable class called InternetExplorer

My basic approach is:

  1. Application.Run MyApp, which is-a ApplicationContext, and is IQuitable.
    • I think an app is needed to keep the program open whilste we wait for the IE request?
    • I guess maybe a (non-daemon) listener-loop thread might also work?
  2. MyApp's constructor creates a new InternetExporer object passing (IQuitable)this
  3. InternetExporer's constructor starts a new IE session, and navigates it to a URL.
  4. When a certain URL is requested InternetExporer calls-back the "parents" Quit method.

Background: The real story is: I'm writing a plugin for MapInfo (A GIS Client). The plugin hijacks the "Start Extraction" HTTP request from IE to the server, modifies the URL slightly and sends a HTTPRequest in it's place... we parse the respose XML into MIF files [PDF 196K], which we then import and open in MapInfo. Then we quit the IE session, and close the "plugin" application.

SSCCE

using System;
using System.Windows.Forms;

// IE COM interface
// reference ~ C:\Windows\System32\SHDocVw.dll 
using SHDocVw; 

namespace QuitAppFromCaptiveIE
{
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyApp());
        }
    }

    interface IQuitable {
        void Quit();
    }

    public class MyApp : ApplicationContext, IQuitable {
        private InternetExplorer ie = null; 

        public MyApp() {
            // create a new Internet Explorer COM component - starts IE application.
            this.ie = new InternetExplorer(this);
            this.ie.Open("www.microsoft.com");
        }

        #region IQuitable Members

        public void Quit() {
            if (ie != null) {
                ie.Dispose();
                ie = null;
            }
            Application.Exit();
        }

        #endregion
    }

    class InternetExplorer : IDisposable, IQuitable
    {
        // allows us to end the parent application when IE is closed.
        private IQuitable parent;
        private bool _parentIsQuited = false;
        private bool _ieIsQuited = false;

        private SHDocVw.InternetExplorer ie; // Old (VB4 era) IE COM component

        public InternetExplorer(IQuitable parent) {
            // lock-onto the parent app to quit it when IE is closed.
            this.parent = parent;
            // create a new Internet Explorer COM component - starts IE application.
            this.ie = new SHDocVw.InternetExplorerClass();
            // hook-up our navigate-event interceptor
            ie.BeforeNavigate2 += new DWebBrowserEvents2_BeforeNavigate2EventHandler(ie_BeforeNavigate2);
        }

        public void Open(string url) {
            object o = null;
            // make the captive IE session navigate to the given URL.
            ie.Navigate(url, ref o, ref o, ref o, ref o);
            // now make the ie window visible
            ie.Visible = true;
        }

        // this callback event handler is invoked prior to the captive IE 
        // session navigating (opening) a URL. Navigate-TWO handles both
        // external (normal) and internal (AJAX) requests. 
        // For example: You could create a history-log file of every page
        // visited by each captive session.
        // Being fired BEFORE the actual navigation allows you to hijack
        // (ie intercept) requests to certain URLs; in this case a request
        // to http://support.microsoft.com/ terminates the Browser session
        // and this program!
        void ie_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel) {
            if (URL.Equals("http://support.microsoft.com/")) {
                this.Quit();
            }
        }

        #region IDisposable Members

        public void Dispose() {
            quitIE();
        }

        #endregion

        private void quitIE() {
            // close my unmanaged COM object
            if (ie != null && !_ieIsQuited) {
                _ieIsQuited = true;
                ie.Quit();
                ie = null;
            }
        }

        #region IQuitable Members

        public void Quit() {
            // close my unmanaged COM object
            quitIE();
            // quit the parent app as well.
            if (parent != null && !_parentIsQuited) {
                _parentIsQuited = true;
                parent.Quit();
                parent = null;
            }
        }

        #endregion
    }

}

Thanks y'all. Cheers. Keith.

EDIT: Forgive me this hit and run... It's two o'clock in the morning and I desperately need some sleep... I'll be back in the morning, in about 8 hours time ;-)


EDIT: The long and the short of it appears to be (I'm still NOT an expert, by any stretch of the imagination) that SHDocVw.dll is still the preferred method for launching a "captive" Internet Explorer session (as apposed to embedding a browser in your application).

The posted code isn't the best solution IMHO. The "tested, released" version just implements IDisposable (i.e. IQuitable is gone)... That is: both the MyApp class and the InternetExplorer (IE for short) class implement IDisposable

Both Dispose methods just return if the local isDisposed attribute is already set to true. Ergo (in pseudocode for brevity):

  private bool isDisposed = false;
  public void Dispose() {
    if (!isDisposed) {
      isDisposed = true;
      try {
        try { release my unmanaged resources } catch { log }
        // the isDisposed attribute prevents infinite recursion between the two calls
        try {
          IE calls MyApp.Dispose() here
          MyApp calls IE.Dispose(); here
        } catch {
          log
        }
      } finally {
        base.Dispose();
      }
    }
  }

To quit the application from the IE class you just call it's local Dispose method, which calls MyApps Dispose, which calls IE's Dispose again but isDisposed is true so it just returns, so we call Application.ExitThread() then fall out of MyApp's Dispose, and then we fall out of IE's Dispose method, and the event-system stops; and the application terminates nicely.

So... We can call this question answered, I think.

Thanx to everyone for your interest,

Cheers. Keith.

A: 

I'm reasonably sure that System.Windows.Forms.WebBrowser actually uses the IE Trident browser control internally. It shouldn't be necessary to do COM interop unless you are using C# 1.x.

Brian Reiter
Thanx Brian. I'll go play with the WebBrowser class. I thought there had to be a better way; the SHDocVw approach was "inherited" from a VB4 app... Suddenly I feel OLD ;-) Cheers. Keith.
corlettk
Brian, unfortunately the WebBrowser class is (I think) unsuitable. It allows an IE control to be embedded in a C# form; and has some support for .NET controls to be placed within the webpage displayed therein... I doesn't (from what I've read) cater for the simple "fire-up a "captive" instance of the IE application". I thank thee anyway. I googled "C# WebBrowser example" and found a couple of really nice little (powerful) examples, esp: http://www.devhood.com/Tutorials/tutorial_details.aspx?tutorial_id=312. Cheers. Keith.
corlettk