After spending much time evaluating different approaches, I’ve found a way to easily and reliably grab the URL of my hosting page from inside of a C# based ActiveX Control.  This process involves using dynamic COM invocation to obtain the IWebBrower interface via my ActiveX control’s implicit IOleObject interface.

 

Introduction

IWebBrowser is useful for many things beyond just getting the currently displayed URL.  In fact, it can control almost every aspect of Internet Explorer.  It can be used to completely control browser navigation, redirecting the browser to different pages, refreshing or even going forward and back.

Also, it would be possible to go about this via a statically generated interface to shdocvw.dll.  However, using a static interface has the distinct disadvantage of failing quite explosively if the dll is not available.  Dynamic invocation is much safer.  If something goes wrong we will simply get back null values instead of what we expected.

If you don’t have much ActiveX experience, there are three important pieces of information to know about before starting:

  1. Everything in COM is referenced by GUID.
  2. All COM access is done through querying existing COM objects.
  3. With a little syntactic sugar, C# takes care of much of the pain involved.

 

Defining our COM Interfaces

First we need to define the COM reference Guids for our top level browser service and the Internet Explorer application.

  1: private static readonly Guid _topLevelBrowserGuid = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");
  2: private static readonly Guid _webBrowserAppGuid = new Guid("0002DF05-0000-0000-C000-000000000046"); 

Secondly, we need to define the IServiceProvider COM interface.

  1: [ComImport,
  2:  Guid("6d5140c1-7436-11ce-8034-00aa006009fa"),
  3:  InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  4: public interface IServiceProvider
  5: {
  6:   void QueryService(
  7:     ref Guid guidService, 
  8:     ref Guid riid,
  9:     [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
 10: }

Third, we need to define the IWebBrowser COM interface. 

  1: [ComImport, 
  2:  TypeLibType((short)0x1050), 
  3:  Guid("EAB22AC1-30C1-11CF-A7EB-0000C05BAE0B")]
  4: public interface IWebBrowser
  5: {
  6:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(100)]
  7:   void GoBack();
  8:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x65)]
  9:   void GoForward();
 10:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x66)]
 11:   void GoHome();
 12:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x67)]
 13:   void GoSearch();
 14:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x68)]
 15:   void Navigate([In, MarshalAs(UnmanagedType.BStr)] string URL, [In, Optional, MarshalAs(UnmanagedType.Struct)] ref object Flags, [In, Optional, MarshalAs(UnmanagedType.Struct)] ref object TargetFrameName, [In, Optional, MarshalAs(UnmanagedType.Struct)] ref object PostData, [In, Optional, MarshalAs(UnmanagedType.Struct)] ref object Headers);
 16:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(-550)]
 17:   void Refresh();
 18:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x69)]
 19:   void Refresh2([In, Optional, MarshalAs(UnmanagedType.Struct)] ref object Level);
 20:   [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x6a)]
 21:   void Stop();
 22:   [DispId(200)]
 23:   object Application { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(200)] get; }
 24:   [DispId(0xc9)]
 25:   object Parent { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xc9)] get; }
 26:   [DispId(0xca)]
 27:   object Container { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xca)] get; }
 28:   [DispId(0xcb)]
 29:   object Document { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xcb)] get; }
 30:   [DispId(0xcc)]
 31:   bool TopLevelContainer { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xcc)] get; }
 32:   [DispId(0xcd)]
 33:   string Type { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xcd)] get; }
 34:   [DispId(0xce)]
 35:   int Left { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xce)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xce)] set; }
 36:   [DispId(0xcf)]
 37:   int Top { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xcf)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xcf)] set; }
 38:   [DispId(0xd0)]
 39:   int Width { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xd0)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xd0)] set; }
 40:   [DispId(0xd1)]
 41:   int Height { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xd1)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xd1)] set; }
 42:   [DispId(210)]
 43:   string LocationName { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(210)] get; }
 44:   [DispId(0xd3)]
 45:   string LocationURL { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xd3)] get; }
 46:   [DispId(0xd4)]
 47:   bool Busy { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0xd4)] get; }
 48: }

It’s quite a large and so I won’t go into the details of each member.  You can find out more by reading the IWebBrowser2 MSDN article.  It’s an extension of the IWebBrowser interface which means the overlapping members will have the same effect.

 

Using our COM Interfaces

Now that we have all of the necessary infrastructure in place, we must build a set of COM queries in order to obtain our IWebBrowser instance.  The first step is getting our ActiveX control’s IOleObject interface.

  1: Type type = control.GetType();
  2: Type iOleObjectType = type.GetInterface("IOleObject", true);

We then use the iOleObjectType interface to retrieve the client site service provider from our ActiveX control.

  1: if (iOleObjectType != null)
  2: {
  3:   oleClientSiteObj = iOleObjectType.InvokeMember(
  4:     "GetClientSite",
  5:     BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
  6:     null, control, null);
  7:   serviceProvider = oleClientSiteObj as IServiceProvider;
  8: }

By querying our COM Object’s client side service provider, we can obtain shdocvw.dll’s COM interface via it’s GUID and the GUID of the interface.

  1: if (serviceProvider != null)
  2: {
  3:   Guid topLevelBrowserGuid = _topLevelBrowserGuid;
  4:   Guid iServiceProviderGuid = typeof(IServiceProvider).GUID;
  5:   serviceProvider.QueryService(
  6:     ref topLevelBrowserGuid,
  7:     ref iServiceProviderGuid,
  8:     out topServiceProviderObj);
  9:   topServiceProvider = topServiceProviderObj as IServiceProvider;
 10: }

Finally, in a similar way, we can now obtain the IWebBrowser interface from Internet Explorer’s COM interface via their GUIDs.

  1: if (topServiceProvider != null)
  2: {
  3:   Guid webBrowserAppGuid = _webBrowserAppGuid;
  4:   Guid iWebBrowserGuid = typeof(IWebBrowser).GUID;
  5:   topServiceProvider.QueryService(
  6:     ref webBrowserAppGuid,
  7:     ref iWebBrowserGuid,
  8:     out webServiceProviderObj);
  9:   webBrowser = webServiceProviderObj as IWebBrowser;
 10: }

Now, we have an instance the IWebBrowser interface.   For my purposes, I need only the URL currently displayed in the browser window.

  1: if (webBrowser != null)
  2: {
  3:   url = webBrowser.LocationURL;
  4: }

 

Conclusion

Using the IWebBrowser allows you to access and control Internet Explorer in a way which is otherwise unavailable for .NET ActiveX Controls.  Thanks to .NET’s excellent COM interop capabilities it’s possible to do it easily in a safe and reliable way.  Also, this technique is known to work for IE 6 all the way through the newest versions of Internet Explorer.  For future reference, at the time of this article the newest is IE8.0.6001.18702.

 

References and Additional Information