Retrieving IHTMLDocument & IWebBrowser2 objects from hWnd

Hi Community,

For the past year, I’ve been pretty busy assisting one of my customers to deliver an integrated desktop solution built with Microsoft USD as part of a major transformation program that encompasses Dynamics CRM 365, Microsoft Azure, Apttus (CPQ), ERPly, Guidewire products and banking software. During this time we’ve faced some technical challenges, specially when having to integrate and automate disparate systems.

Today’s post is about one of those technical challenges and how we manage to overcome it. As mentioned earlier, one of systems that was integrated into this solution runs only in Internet Explorer because it heavily uses and relies in COM (WebBrowser control to be specific). The approach we took to being able to solve this issue also applies to HTA (HTML applications that are hosted by mshta.exe process).

When a web application uses the browser control embedded in HTML to open dialogs the situation becomes very tricky, because it’s a browser using another browser to display an IFRAME as a dialog, but since we needed to automate/handle those dialogs the challenge was to being able to access the underlying browser control to do so and this post talks about it.

First thing we had to do was to determine what was opening those “Web Dialogs”, validate whether it was done via an ActiveX (COM) and then determine the window hierarchy to get to WebBrowser control.

HTML that shows what ActiveX control is being used.

One 

Check in the Registry what COM library is referencing to

Two

Then determine what’s the window hierarchy and at what level the WebBrowser control is.

Three

Once we know what’s the window hierarchy and we’ve confirmed MSHTML is being used we needed to implement a helper class to being able to obtain the underlying COM object that represents the WebBrowser control from a window handle (hWnd).

using mshtml;

using SHDocVw;

using System;

using System.Runtime.InteropServices;

using System.Windows;

using System.Windows.Interop;

using static Microsoft.Uii.Csr.Win32API;


namespace WebIntegration {
	public class WebDriverHelper {

		#region "Interfaces' GUIDs"

		public static readonly Guid IID_IWebBrowserApp = new Guid("{0002DF05-0000-0000-C000-000000000046}");
		public static readonly Guid IID_IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}");
		public static readonly Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E");

		#endregion


		#region "IServiceProvide interface"

		[ComImport,
		ComVisible(true),
		Guid("6D5140C1-7436-11CE-8034-00AA006009FA"),
		InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
		public interface IServiceProvider {
			[return: MarshalAs(UnmanagedType.I4)]
			[PreserveSig]
			int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
		}

		#endregion


		private const string WindowMessage = "WM_HTML_GETOBJECT";

		public static IHTMLDocument2 GetIEObjFromHwnd(IntPtr targetHwnd, bool changeOwnershipToUSD = false, bool keepAlwaysOnTop = false) {
			IHTMLDocument2 retval = null;

			Application.Current.Dispatcher.Invoke(() => {
				var sendMessageResult = UIntPtr.Zero;
				var IID_IHTMLDocument = typeof(IHTMLDocument2).GUID;
				var aMssg = (uint)RegisterWindowMessage(WindowMessage);
				var childHwnd = Win32Helper.GetWindow(targetHwnd, GW_CHILD);
				var result = SendMessageTimeout(childHwnd, aMssg, UIntPtr.Zero, IntPtr.Zero,
												SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
												1000, out sendMessageResult);

				// Let's get underlying IHTMLDocument

				if (sendMessageResult != UIntPtr.Zero)
					retval = ObjectFromLresult(sendMessageResult, IID_IHTMLDocument, IntPtr.Zero) as IHTMLDocument2;

				// Do we need to change ownership of browser window/dialog

				if (changeOwnershipToUSD) {
					var usdHwnd = new WindowInteropHelper(Application.Current.MainWindow).Handle;
					Win32Helper.SetWindowLongPtr(targetHwnd, Win32Helper.GWL_HWNDPARENT, usdHwnd);
				}

				// Do we need to keep windows on top?

				if (keepAlwaysOnTop) {
					var onTopFlags = Win32Helper.SetWindowPosFlags.IgnoreMove | Win32Helper.SetWindowPosFlags.SwpNoSize;
					Win32Helper.SetWindowPos(targetHwnd, Win32Helper.HWND_TOPMOST, 0, 0, 0, 0, onTopFlags);
				}
			});

			return retval;
		}

		public static IWebBrowser2 GetBrowserFromHtmlWindow(IHTMLWindow2 window) {
			int hResult = 0;
			object refObject = null;
			IWebBrowser2 retval = null;
			var riid = IID_IWebBrowser2;
			var guid = IID_IWebBrowserApp;
			IServiceProvider svcProvider = null;

			if (window != null && (svcProvider = window as IServiceProvider) != null) {
				try {
					hResult = svcProvider.QueryService(ref guid, ref riid, out refObject);

					if (hResult == 0)
						retval = refObject as IWebBrowser2;
				} catch (Exception e) {
					// Safe to ignore. Just to protect ourselves from COM randomness

					var whatisit = e.Message;
				}
			}
			return retval;
		}
	}
}

Leave a Reply

Your email address will not be published. Required fields are marked *