Last active
March 14, 2021 13:45
-
-
Save klinkby/4d95bc02d12c7d68f14d85030704e887 to your computer and use it in GitHub Desktop.
Wraps IE to support lifetime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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