Skip to content

Instantly share code, notes, and snippets.

@klinkby
Last active March 14, 2021 13:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save klinkby/4d95bc02d12c7d68f14d85030704e887 to your computer and use it in GitHub Desktop.
Save klinkby/4d95bc02d12c7d68f14d85030704e887 to your computer and use it in GitHub Desktop.
Wraps IE to support lifetime
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using SHDocVw;
namespace Capto
{
/// <summary>
/// Wraps IE to support lifetime. *** REQUIRES COM REFERENCE TO %windir%\system32\shdocvw.dll ***
/// </summary>
/// <example><![CDATA[
/// using (var wb = new ManagedWebBrowser())
/// {
/// wb.NavigateComplete += (sender, e) => Console.WriteLine("Nav " + e.Address);
/// wb.NavigateError += (sender, e) => Console.WriteLine("Err" + e.Address);
/// wb.Closed += (sender, e) => Console.WriteLine("Closed");
/// wb.Navigate(new Uri("https://www.bing.com/"));
/// Thread.Sleep(1000);
/// wb.Navigate(new Uri("https://www.google.dk/"));
/// Thread.Sleep(1000);
/// wb.Close();
/// Thread.Sleep(1000);
/// wb.ClosedWaitHandle.WaitOne(10000);
/// }
/// ]]></example>
public class ManagedWebBrowser : IDisposable
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633522(v=vs.85).aspx
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(100);
private readonly ManualResetEvent _exited = new ManualResetEvent(false);
private bool _initialized = false;
private bool _done = false;
private bool _disposedValue = false; // To detect redundant calls
private Thread _monitorThread;
private InternetExplorer _webBrowser = new InternetExplorer
{
Silent = true,
StatusBar = false,
AddressBar = false,
FullScreen = false,
Width = 600,
Height = 400,
Resizable = false,
Left = 400,
Top = 400,
MenuBar = false,
ToolBar = 0,
};
/// <summary>
/// Constructor
/// </summary>
public ManagedWebBrowser()
{
_webBrowser.NavigateComplete2 += WebBrowser_NavigateComplete2;
_webBrowser.NavigateError += WebBrowser_NavigateError;
}
/// <summary>
/// Tell web browser to navigate to new address.
/// </summary>
/// <param name="address">href</param>
public void Navigate(Uri address)
{
if (_done) return;
_webBrowser.Navigate2(address.AbsoluteUri);
if (!_initialized)
{
Initialize();
}
}
// Called on first navigate
private void Initialize()
{
_webBrowser.Visible = true; // here we go! this launches IE
IntPtr hWndBrowser = new IntPtr(_webBrowser.HWND);
int processId;
GetWindowThreadProcessId(hWndBrowser, out processId);
var ieProcess = Process.GetProcessById(processId);
ieProcess.EnableRaisingEvents = true;
ieProcess.Exited += (sender, e) => OnClosed();
_monitorThread = new Thread(WatchdogThread)
{
IsBackground = true
};
_monitorThread.SetApartmentState(ApartmentState.MTA);
_monitorThread.Start(this);
}
// Background thread for monitoring lifetime of another thread
[MethodImplAttribute(MethodImplOptions.NoInlining)]
static void WatchdogThread(object state)
{
var self = (ManagedWebBrowser)state;
while (!self._exited.WaitOne(PollInterval))
{
var wb = self._webBrowser;
if (null == wb) return;
try
{
GC.KeepAlive(wb.HWND);
}
catch (COMException)
{
self.OnClosed();
return;
}
}
}
/// <summary>
/// For thread waiting
/// </summary>
public WaitHandle ClosedWaitHandle
{
get { return _exited; }
}
#region WebBrowser COM event callbacks
private void WebBrowser_NavigateComplete2(object pDisp, ref object URL)
{
OnNavigateComplete(new Uri((string)URL));
}
private void WebBrowser_NavigateError(object pDisp, ref object URL, ref object Frame, ref object StatusCode, ref bool Cancel)
{
OnNavigateError(new Uri((string)URL));
}
#endregion
#region Managed events
protected virtual void OnNavigateComplete(Uri address)
{
var eh = NavigateComplete;
if (null != eh) eh(this, new AddressEventArgs(address));
}
public event EventHandler<AddressEventArgs> NavigateComplete;
protected virtual void OnNavigateError(Uri address)
{
var eh = NavigateError;
if (null != eh) eh(this, new AddressEventArgs(address));
}
public event EventHandler<AddressEventArgs> NavigateError;
protected virtual void OnClosed()
{
if (_disposedValue || _done) return;
_done = true;
_exited.Set();
var eh = Closed;
if (null != eh) eh(this, EventArgs.Empty);
}
public event EventHandler Closed;
#endregion
/// <summary>
/// Quit the browser
/// </summary>
public void Close()
{
if (!_done)
{
try
{
_webBrowser.Quit();
}
catch (COMException)
{
// already closed
}
}
}
#region IDisposable Support
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
Close();
var mt = _monitorThread;
if (null != mt && mt.IsAlive)
{
mt.Join(PollInterval);
}
((IDisposable)_exited).Dispose();
}
_webBrowser = null;
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
#region class AddressEventArgs
public class AddressEventArgs : EventArgs
{
public Uri Address { get; private set; }
public AddressEventArgs(Uri address)
{
this.Address = address;
}
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment