{"id":4133,"date":"2013-06-10T07:00:00","date_gmt":"2013-06-10T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/06\/10\/a-big-little-program-monitoring-internet-explorer-and-explorer-windows-part-1-enumeration\/"},"modified":"2013-06-10T07:00:00","modified_gmt":"2013-06-10T07:00:00","slug":"a-big-little-program-monitoring-internet-explorer-and-explorer-windows-part-1-enumeration","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130610-00\/?p=4133","title":{"rendered":"A big little program: Monitoring Internet Explorer and Explorer windows, part 1: Enumeration"},"content":{"rendered":"<p><P>\nNormally, Monday is the day for Little Programs,\nbut this time I&#8217;m going to spend a few days\non a single Little Program.\nNow, this might very well disqualify it from the name <I>Little Program<\/I>,\nbut the concepts are still little;\nall I&#8217;m doing is\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2009\/08\/04\/9856634.aspx\">\nsnapping blocks together<\/A>.\n(Plus, it&#8217;s my Web site, so you can just suck it.)\n<\/P>\n<P>\nThe goal of our Little Program is to monitor Internet Explorer\nand Explorer windows as they are created, navigate to new locations,\nand are destroyed.\n(In principle, other Web browsers can participate in this protocol,\nbut I don&#8217;t know of any that do, so I&#8217;ll assume that only Explorer\nand Internet Explorer\nregister with the <CODE>Shell&shy;Windows<\/CODE> object.)\n<\/P>\n<P>\nThe key to all this is the\n<CODE>Shell&shy;Windows<\/CODE> object,\nwhich we&#8217;ve\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/04\/22\/10412906.aspx\">\nspent time<\/A>\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/03\/18\/10403054.aspx\">\nplaying with<\/A>\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2005\/07\/05\/435657.aspx\">\nin the past<\/A>.\n<\/P>\n<P>\nToday we&#8217;re going to write a helper function that takes an object\nreturned by the \n<CODE>Shell&shy;Windows<\/CODE> object\nand extract the window handle and current location.\nThis is the guts of our Little Program,\nso I&#8217;m basically doing the cool stuff up front,\nand then leaving the annoying bits for later.\n<\/P>\n<PRE>\n#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/07\/15\/184076.aspx\">UNICODE<\/A>\n#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/02\/12\/71851.aspx\">_UNICODE<\/A>\n#define STRICT\n#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/01\/24\/10387757.aspx\">STRICT_TYPED_ITEMIDS<\/A>\n#include &lt;windows.h&gt;\n#include &lt;ole2.h&gt;\n#include &lt;iostream&gt;<\/p>\n<p>#include &lt;shlobj.h&gt;\n#include &lt;atlbase.h&gt;\n#include &lt;atlalloc.h&gt;\n<\/PRE>\n<P>\nNow that we got the preliminary header file goop out of the way,\nwe can write the exciting function.\n<\/P>\n<PRE>\nHRESULT GetBrowserInfo(IUnknown *punk, HWND *phwnd,\n                       PWSTR *ppszLocation)\n{\n HRESULT hr;<\/p>\n<p> CComPtr&lt;IShellBrowser&gt; spsb;\n hr = IUnknown_QueryService(punk, SID_STopLevelBrowser,\n                            IID_PPV_ARGS(&amp;spsb));\n if (FAILED(hr)) return hr;<\/p>\n<p> hr = spsb-&gt;GetWindow(phwnd);\n if (FAILED(hr)) return hr;<\/p>\n<p> hr = GetLocationFromView(spsb, ppszLocation);\n if (SUCCEEDED(hr)) return hr;<\/p>\n<p> return GetLocationFromBrowser(punk, ppszLocation);\n}\n<\/PRE>\n<P>\nAwfully short for what purported to be an exciting function,\nbut that&#8217;s because I hid the exciting parts in helper functions.\n<\/P>\n<P>\nFirst, we take the object and ask to locate the top-level browser,\nsince that&#8217;s where some of the interesting information hangs out.\nWe ask for the <CODE>IShell&shy;Browser<\/CODE> so we can get the\nwindow handle via the base class method\n<CODE>IOle&shy;Window::Get&shy;Window<\/CODE>.\nThat&#8217;s the easy part.\n<\/P>\n<P>\nGetting the current location is tricky,\nbecause Explorer windows do it one way,\nand Internet Explorer does it another way.\nThat&#8217;s because Explorer windows browse the shell namespace,\nwhereas Internet Explorer windows browse the Internet.\nShell namespace locations are represented by pidls,\nwhereas Internet locations are represented by URLs.\n<\/P>\n<P>\nFirst, the Explorer way:\n<\/P>\n<PRE>\nHRESULT GetLocationFromView(IShellBrowser *psb,\n                            PWSTR *ppszLocation)\n{\n HRESULT hr;<\/p>\n<p> *ppszLocation = nullptr;<\/p>\n<p> CComPtr&lt;IShellView&gt; spsv;\n hr = psb-&gt;QueryActiveShellView(&amp;spsv);\n if (FAILED(hr)) return hr;<\/p>\n<p> CComQIPtr&lt;IPersistIDList&gt; sppidl(spsv);\n if (!sppidl) return E_FAIL;<\/p>\n<p> CComHeapPtr&lt;ITEMIDLIST_ABSOLUTE&gt; spidl;\n hr = sppidl-&gt;GetIDList(&amp;spidl);\n if (FAILED(hr)) return hr;<\/p>\n<p> CComPtr&lt;IShellItem&gt; spsi;\n hr = SHCreateItemFromIDList(spidl, IID_PPV_ARGS(&amp;spsi));\n if (FAILED(hr)) return hr;<\/p>\n<p> hr = spsi-&gt;GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING,\n                           ppszLocation);\n return hr;\n}\n<\/PRE>\n<P>\nThe maze we navigate here is to start from the\n<CODE>IShell&shy;Browser<\/CODE> and get to the\n<CODE>IShell&shy;View<\/CODE> by calling\n<CODE>IShell&shy;Browser::Query&shy;Active&shy;Shell&shy;View<\/CODE>.\nIt&#8217;s rather annoying that the\n<CODE>IShell&shy;Browser::Query&shy;Active&shy;Shell&shy;View<\/CODE>\nmethod always returns you an\n<CODE>IShell&shy;View<\/CODE> rather than being forward-looking and\nletting you pass a <CODE>riid<\/CODE>\/<CODE>ppv<\/CODE> pair.\n(The shell has for the most part learned this lesson, and new\nobject creation or retrieval functions tend to take the\n<CODE>riid<\/CODE>\/<CODE>ppv<\/CODE> pair so you can specify\nyour ring size when you place the order instead of always getting\na size&nbsp;6 ring and then having to resize it.)\nSince\n<CODE>IShell&shy;Browser::Query&shy;Active&shy;Shell&shy;View<\/CODE>\ndoesn&#8217;t let us specify the desired interface, we have to\ndo the <CODE>Query&shy;Interface<\/CODE> ourselves\nto convert the <CODE>IShell&shy;View<\/CODE> into what we really\nwant: The <CODE>IPersist&shy;ID&shy;List<\/CODE>.\n<\/P>\n<P>\nFrom the <CODE>IPersist&shy;ID&shy;List<\/CODE> we ask for the pidl,\nwhich now tells us what the Explorer window is looking at.\nFor display purposes, we convert it into a string by\nconverting the pidl into an <CODE>IShell&shy;Item<\/CODE>\n(notice the handy\n<CODE>riid<\/CODE>\/<CODE>ppv<\/CODE> pair produced by the\ntype-checking\n<CODE>IID_PPV_ARGS<\/CODE> macro)\nand then asking the shell item for its parsing name.\n<\/P>\n<P>\n(We saw techniques similar to this\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/07\/20\/188696.aspx\">\na few years ago<\/A>.)\n<\/P>\n<P>\nIf it turns out we don&#8217;t have an Explorer window, then we try\nagain using the Web browser interfaces:\n<\/P>\n<PRE>\nHRESULT GetLocationFromBrowser(IUnknown *punk,\n                               PWSTR *ppszLocation)\n{\n HRESULT hr;<\/p>\n<p> CComQIPtr&lt;IWebBrowser2&gt; spwb2(punk);\n if (!spwb2) return E_FAIL;<\/p>\n<p> CComBSTR sbsLocation;\n hr = spwb2-&gt;get_LocationURL(&amp;sbsLocation);\n if (FAILED(hr)) return hr;<\/p>\n<p> return SHStrDupW(sbsLocation, ppszLocation);\n}\n<\/PRE>\n<P>\nWe turn the object into an <CODE>IWeb&shy;Browser2<\/CODE> and ask\nfor the <CODE>Location&shy;URL<\/CODE> property.\nThe annoyance here is that\n<CODE>IWeb&shy;Browser2<\/CODE> is an automation interface,\nso it uses <CODE>BSTR<\/CODE> for passing strings around,\nwhich is different from\n<CODE>IShell&shy;Item::Get&shy;Display&shy;Name<\/CODE>\nwhich uses <CODE>Co&shy;Task&shy;Mem&shy;Alloc<\/CODE>,\nsince that is the convention for non-dispatch COM interfaces.\nWe therefore have to convert the <CODE>BSTR<\/CODE>\nto a task-allocated\n<CODE>PWSTR<\/CODE> before returning,\nso that the return value is consistent with\n<CODE>Get&shy;Location&shy;From&shy;View<\/CODE>.\n<\/P>\n<P>\nFinally, we call the function in a loop to test that it actually works:\n<\/P>\n<PRE>\nCComPtr&lt;IShellWindows&gt; g_spWindows;<\/p>\n<p>HRESULT DumpWindows()\n{\n CComPtr&lt;IUnknown&gt; spunkEnum;\n HRESULT hr = g_spWindows-&gt;_NewEnum(&amp;spunkEnum);\n if (FAILED(hr)) return hr;<\/p>\n<p> CComQIPtr&lt;IEnumVARIANT&gt; spev(spunkEnum);\n for (CComVariant svar;\n      spev-&gt;Next(1, &amp;svar, nullptr) == S_OK;\n      svar.Clear()) {\n  if (svar.vt != VT_DISPATCH) continue;<\/p>\n<p>  HWND hwnd;\n  CComHeapPtr&lt;WCHAR&gt; spszLocation;\n  if (FAILED(GetBrowserInfo(svar.pdispVal, &amp;hwnd,\n                            &amp;spszLocation))) continue;<\/p>\n<p>   std::wcout &lt;&lt; hwnd\n              &lt;&lt; L&#8221; &#8221;\n              &lt;&lt; static_cast&lt;PCWSTR&gt;(spszLocation)\n              &lt;&lt; std::endl;\n }\n return S_OK;\n}<\/p>\n<p>int __cdecl wmain(int, PWSTR argv[])\n{\n <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/05\/20\/135841.aspx\">CCoInitialize<\/A> init;\n g_spWindows.CoCreateInstance(CLSID_ShellWindows);\n DumpWindows();\n g_spWindows.<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/05\/20\/135841.aspx\">Release()<\/A>;<\/p>\n<p> return 0;\n}\n<\/PRE>\n<P>\nYes, I stupidly made <CODE>g_spWindows<\/CODE>\na global variable, but it&#8217;ll come in handy later.\n(It&#8217;s still stupid, but at least there&#8217;s a reason for\nthe stupidity.)\n<\/P>\n<P>\nOkay, we can take this program for a spin.\nWhen you run it, it should print the window handles and\nlocations of all your Explorer and Internet Explorer windows.\n<\/P>\n<P>\nBefore we can start hooking up events to keep this list up to date,\nwe need to learn a bit about connection points and using\ndispatch interfaces as connection point interfaces.\nWe&#8217;ll spend a few days on those topics,\nthen return to our program.\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Normally, Monday is the day for Little Programs, but this time I&#8217;m going to spend a few days on a single Little Program. Now, this might very well disqualify it from the name Little Program, but the concepts are still little; all I&#8217;m doing is snapping blocks together. (Plus, it&#8217;s my Web site, so you [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-4133","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Normally, Monday is the day for Little Programs, but this time I&#8217;m going to spend a few days on a single Little Program. Now, this might very well disqualify it from the name Little Program, but the concepts are still little; all I&#8217;m doing is snapping blocks together. (Plus, it&#8217;s my Web site, so you [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=4133"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4133\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=4133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=4133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=4133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}