Welcome to MSDN Blogs Sign in | Join | Help

Readers-- I'm leaving MSFT for other opportunities.  As such, this will be my last post.  Thanks for reading! 

I hope you will all continue to read the IE Team Blog: http://blogs.msdn.com/ie

 

You're writing some code that uses COM.  Do you use SmartPointers or not use SmartPointers?  Debate on this topic flares up now and then. 

The trick to answering it is to seperate the technical facts from stubbornly held beliefs.  Like any technology, there is a right time and a wrong time to use it.  But that's not what I want to talk about today.  Let's assume you've decided to use SmartPointers.  I posit that CComPtr<> is good and CComQIPtr<> is bad.

The reason for this is simple: CComQIPtr<> masks the return value of the QueryInterface() call. 

Lets take a look at two snippets of code to frame the discussion:

void ExFunc(IFoo *pFoo)
{
    HRESULT hr = pFoo->Init();
    if (SUCCEEDED(hr))
    {
        CComPtr<IBar> spBar;
        hr = pfoo->QueryInterface(IID_PPV_ARGS(spBar));
        if (SUCCEEDED(hr))
        {
            hr = spBar->DoSomething();
        }
    }
}
void ExFunc(IFoo *pFoo)
{
    HRESULT hr = pFoo->Init();
    if (SUCCEEDED(hr))
    {
        hr = E_NOINTERFACE;
        CComQIPtr<IBar> spBar = pFoo;
        if (spBar)
        {
            hr = spBar->DoSomething();
        }
    }
}

Some points:

  1. When you have a large number of people working on the same code base, you want to create patterns in the code that people recognize.  CComQIPtr<> breaks the pattern of COM-call/check-HRESULT.  Instead you have to check for the non-NULLness of the out parameter. 
  2. You can assume the QueryInterface() call failed with E_NOINTERFACE, but you don't know for sure.  What if it failed with some other HRESULT?  Nobody says QueryInterface() can't do things besides just casting this around.  E.g. a function that implements IPersistFile::Load() could delay loading the file until someone does a QueryInterface() for an interface that actually needs the contents of the file it was asked to load.
  3. CComQIPtr<> masks errors in QueryInterface().  The contract for QueryInterface() is the out parameter must be valid if it returns success, otherwise it must be set to NULL.  So, in theory, if (SUCCEEDED(hr)) { ... } and if (spBar) { ... } should be equivalent.  However, people often get QueryInterface() wrong.  It seems to me that it is much more likely an implementer will fail to set the out parameter to NULL on failure than they will fail to return S_OK on success.
  4. Lastly, I just don't like the fact that magic is happening in the assignment.  It is easy to screw things up with COM, and it seems CComQIPtr<> just makes it easier.  CComPtr<> is a lot less magic and more predictable.  It makes it harder to screw things up.

In summary: use CComPtr<> and don't use CComQIPtr<>.  I would especially encourage those who would write Internet Explorer extensions to use CComPtr<>. 

Thoughts? 

Today I have a mini-FAQ on Favicons.

Q: How do I make a favicon appear for my site in IE7?
A: There are two ways.  The first is to put a file in the root of your domain called favicon.ico.  The second is to use a <link> tag with the rel="shortcut icon" value and the href value set to the URL for the Icon you wish to display.

Q: How often does IE download the favicon?
A: IE will download the icon when a user first visits the site.  The icon is stored in the Temporary Internet Files folder on the client machine.  Additional metadata about the favicon is stored in the user's Url History database. If either store is cleared, or items relating to the favicon have naturally expired, then the icon will be downloaded again on the next visit.  If more than one page (or site) shares the same favicon, it is only downloaded once.  IE takes great pains to download the icon as few times as possible to reduce load on the server.

Q: I see the wrong favicon for some sites I visit.  How do I fix this?
A: If the history database has become corrupted in some way, this can happen.  The simplest solution is just to use Delete Browsing History (on the Tools menu) to clear the cache and the history store. 

Q: I put a favicon.ico on my site as you described, but it still doesn't appear.
A: It must actually be a .ico (an Icon) file.  Bitmaps, pngs, gifs, etc, will not work.  IE7 will download your favicon to the Temporary Internet Files folder and call ExtractIcon() on the file.  If this fails, we will show the default icon instead of your favicon.

Q: I verified that my favicon really is an icon, but it still doesn't appear.
A: Since IE loads your icon out of the Temporary Internet Files folder, it must be able to actually store it there.  If you are setting the no-cache directive for the icon file, then IE will not be able to display your icon and will display the default icon instead.  You can use Fiddler to verify.

Q: How do I create a different favicon for every page on my site?
A: Put a different <link> tag on each page, pointing to a different icon.

Q: I changed my site's favicon to a different icon, but the old one still shows in IE.  How do I force IE to update?
A: If you just put the favicon.ico file in the root of your domain, IE doesn't have any way of knowing if it changed.  To force an update, you need to use a <link> tag and point to a different filename than you previously used.  The current filename is compared against the known filename stored in the Url History database.  When IE sees the filename has changed, it will download your new icon.  Alternatively, you can ask your users to clear their history and cache (Tools->Internet Options->Delete Browsing History), which will also force IE to download the new file.

That should cover most of the questions I've received about favicons in IE7.  If you have more questions, feel free to ask.

Updated on Monday, 5th March to fix a spelling error and add some additional questions.

My posting rate has declined somewhat since we shipped IE7.  It seems posts come in bursts.  I have a theory for this. 

At the beginning of a product cycle we are focused on planning, architecting, and implementing.  There is not much to talk about, since we can not talk about anything that has not been publicly announced yet.  A few posts on context-free problems may trickle out.

Then we ship a Beta and the bugs start rolling in.  This is where things get interesting.  Lots of problems to solve translates into having lots of interesting topics to post about.

Then we lock down for the final release.  We focus on fixing the last few compat issues, localizing, branding, and marketing.  The devs start dreaming about the next release.  Then Thanksgiving and Christmas come a long and we're all taking vacations... Now the new year has started and we are back in the planning phase, which I can't talk about.

Imagine IE is a ship.  We just got back into harbor; we delivered our goods.  Some of the crew has moved on; we have taken on some new members.  Some are still trickling back in from shore leave.  Some of us are focused on fixing damage from the last voyage.  Some of us are scrubbing the decks and taking care of day-to-day business.  Some are dreaming about our next destination and what the voyage there will look like. 

And from there the metaphore starts to brake down, but I though it was pretty good.  I just like big ships.  Anyway, we are hard at work.  I have a lot of hard thinking and planning to do.  I will not be posting until I have something interesting to say; but I have faith that I will, indeed, post again.  At the very least I will have to explain the decisions I am about to make when we ship our first Beta :) -- in the mean time, there is always the IE Blog.  (If you're just interested in what I'm doing, feel free to drop in on this blog, which has an entirely different focus.)

I also wanted to do a small summary of IE7, just for some personal closure. 

Overall I think IE7 is great.  I personally use it at home, which is my bar for everything we build.  I have worked on a product (which shall remain nameless) that I did not use at home.  Those who know me know my philosophy on installing software: just don't do it.  But I have great faith in IE7 and I find myself cursing machines I have to use that have not installed it (I'm looking at you CS Department...).  Sure, there are some things that still need work, and I am happy to see us addressing them in our planning for the next version. 

In the things-I-worked-on department, I am thrilled that the favicon code I wrote is performing well and web server administrators are not yet calling for my head on a pike. 

I think it is great that the Quick Tabs feature has been so well recieved.  For those who don't know, QT was originally implemented by two of the most industrious Interns I have worked with, Kevin and Eric.  They wrote a lot of code.  I mean a lot.  They prototyped it (by which I mean they had full working versions, just minus fancy window dressing) in three different technologies.  They blew way past the spec and saved a great feature from the cut list.  When they left all I had to do was put some drop shadows in, tweak some aspect ratios and fix some performance-related issues (and some other bugs, as which the tester would be sure to mention :P). 

I think that tabbed browsing beat everyone's expectations and is, in my opinion, the new gold standard in tabbed browsing UI.  It is very usable UI.  There were some very tough trade-offs, places where the behavior people expected split fifty-fifty in complete opposites.  There were some incredibly challenging app-compat issues to sort out.  I use it everyday, and after I changed some options (Tools->Internet Options->Tabs->Settings) it works pretty much the way I want it to. 

I am still not 100% happy with the Delete Browsing History dialog layout, but we made vast improvements, the most important of which was changing it to use the copy engine infrastructure so that the deletion operations no longer happen on the UI thread.  About 0.5% (which looks small, but is huge considering the size of the data set) of all IE crashes that were reported to Microsoft were people who had killed the IE process because clearing the cache on the UI thread made them think the process was hung. 

And the features that got cut, well, I think we made the right decisions.  May they rise again some day.  And I am sure there are some other things I forgot.

In the mean time, weigh anchor -- full speed ahead!

I know, I'm a bit behind the times, but I finally installed it to check it out.  If you haven't yet, and you're the sort of person who thinks they may someday make a webpage, you should get it. 

The features are nice. The spy++-esque features, such as highlighting objects in the DOM, etc, are especially useful.  And it provides a quick way to clear your history.

http://www.microsoft.com/downloads/details.aspx?familyid=e59c3964-672d-4511-bb3e-2d5e1db91038&displaylang=en

Raymond mentioned this particular API once.  I mention it, because I had to call it today.  A certain third party app has existed for many years, happily calling CreateProcess() and not putting a space in the lpCommandLine string between the executable name and the first argument.  (I assume this was a bug, not intentional.) This worked for them because the program files directory has a space in it; thus the full path to the executable has quotes around it. 

Example malformed command line:
lpCommandLine = ""c:\program files\application\app.exe"param1 param2 ..."

CreateProcess(), aparently, matches the quotes and ignores everything else past the closing quote--at least in as much as it needs to figure out what executable to run.  This actually worked just fine for previous versions of IE, because IE parsed the command line in a similar way, just looking for the close quote and assuming everything after it was an argument, even if there was no whitespace.  However, for IE7 we changed it to not call GetCommandLine() for the arguments, but just to use the parameters passed to WinMain(), which--surprise, surprise--meant we did not get param1.  (This was done for various reasons that are not relevant to this discussion.)  This, of course, means apps that used to work no longer work, and we need to fix that.

So I am looking at changing IE's argument parsing back to the old code.  What is my point?  I have two reasons for bringing it up.  First, I just like to tell app-compat stories.  Here is more fuel for the "break them or accomodate them" debate.  (What would you do?) Second, GetCommandLine() returns a non-const string, which I found odd.  What happens if I change it?  What if someone calls GetCommandLine() after I change it?  (Note to self: sometime, when it is not so late, try it and see what happens.)

CoUnmarshalInterface() and CoGetInterfaceAndReleaseStream() are not re-entrancy safe.  This has certain implications for objects that attempt to unmarshal interfaces into member variables, as a member of my team recently discovered.

Suppose you have something that looks like this:

class MyObject
{
public:

   MyObject() { _pUnk = NULL; }

   HRESULT DoStuff(IStream *pStream)
   {
      ...

      hr = CoUnmarshalInterface(pStream, IID_IUnknown, (void **)&_pUnk);

      ...
   }

   HRESULT DoOtherStuff()
   {
      ...

      IDispatch pDispatch = NULL;
      if (_pUnk)
      {
           _pUnk->QueryInterface(IID_IDispatch, (void **)&pDispatch);
      }

      ...
   }

private:
   IUnknown _pUnk;
};

CoUnmarshalInterface() can make a cross-thread QueryInteface() call, which might cause your object to be re-entered.  Before it performs the call, however, it stores an internal object in the out parameter.  This value is non-null but still invalid.  Thus if DoOtherStuff() is called as a result of re-entrancy, you will crash when you deref _pUnk.

To prevent this, you should unmarshal into a temporary value and then check that the value of the member variable is still NULL before you copy the temporary to the member.  If it is no longer NULL, someone else wrote to it and you should Release() the temporary.

0 Comments
Filed under:

Note: This is part three in a series of posts explaining how ActiveX controls and the IE Pop-up Blocker should interact.  The first post is here.  If you are a user (and not an ActiveX control developer), and have found this post in an attempt to track down why you are still seeing unwanted pop-up windows, please refer to this post.

Problem: You have written an ActiveX control.  You have done your due diligence in making sure it does the right thing as described in parts one and two of this series.  Now you find a pop-up that should not be blocked is being blocked.
Example: A real life example happened to me a few days ago;  a Microsoft control, in certain circumstances, launches a dialog and then tries to open a new window in response to certain options on that dialog.  The user initiated action is long past at that point.
Solution: When we shipped WindowsXP SP2 we add two new commands to CGID_MSHTML (see mshtmcid.h in the latest SDK).  The first is IDM_BEGINUSERACTION and the second is IDM_ENDUSERACTION.  They are pretty self-explanitory.  Send the first command to tell mshtml a user initiated action is beginning.  You will be able to open a new window.  Once you have opened your new window, send the second command to tell mshtml that the user action has finished.

For more info on sending commands to mshtml, see this post.

Note: This is part two in a series of posts explaining how ActiveX controls and the IE Pop-up Blocker should interact.  The first post is here.  If you are a user (and not an ActiveX control developer), and have found this post in an attempt to track down why you are still seeing unwanted pop-up windows, please refer to this post.

Method: HlinkSimpleNavigateToMoniker(), HlinkSimpleNavigateToString().
Mitigation: I would like to discourage you from calling these functions.  They are not, in my opinion, very good functions.  If your control must navigate, you should use the IWebBrowser2 function.  If you are already calling these functions, and cannot change things, then what you must do is pass the correct pointer in the pUnk parameter.  The documentation implies that if pUnk is NULL and you are an ActiveX control then your navigation will not succeed.  This is not the case--the function has no good way to even tell if you are an ActiveX control or not.  You identify yourself as one or the other by passing or not passing a non-NULL value.  If you pass NULL, then the pop-up blocker will not get involved.  If, however, you pass a non-NULL value, you have identified yourself as an ActiveX control and pop-up blocking will be in effect.  You should not lie to this function.

The documentation is also not very clear on what, exactally, you should pass in the pUnk parameter or what it is used for.  Your control is being hosted by mshtml.  You should pass a pointer to your client site, which is the object in mshtml hosting you.  The HlinkSimpleNavigate functions will attempt to query this object for various interfaces related to pop-up blocking if your call is going to result in a new window.  If they fail, your new window will be blocked and the HlinkSimpleNavigate function will return E_ACCESSDENIED.  For more information about client site pointers, see this post. 

Additionally the function will attempt to get pointers to various webbrowser interfaces to perform the navigation (whether it is a new window or not).  If this fails (because you passed NULL) a new browser process will be created and navigated.

As I mentioned previously, one reason users may continue to experience unwanted pop-up windows while browsing is creative use of ActiveX controls that provide methods that allow web sites to open new browser windows.  This series of posts will provide best-practices for ActiveX control implementors.

There are two things to keep in mind while considering this topic.  The first is, as always, we are committed to application and site compatibility and thus a lot of the pop-up blocker is opt-in and in the abscence of concrete knowledge that a pop-up is unwanted, our default is often to allow the pop-up.  The second thing is there are an incredible number of ways to navigate IE, so this will, necessarily, be part one in a survey of this multitude.

Note: If you are a user (and not an ActiveX control developer), and have found this post in an attempt to track down why you are still seeing unwanted pop-up windows, please refer to this post. 

Method: IWebBrowser::Navigate(), IWebBrowser2::Navigate2()
Mitigation: Each of these methods takes a set of BrowserNavConstants in the Flags parameter.  Controls (and applications) should pass navNewWindowsManaged if they desire pop-up blocker to be applied.  Clues that you want this flag would be such things as you are also passing navOpenInNewWindow or are targeting a frame name via the TargetFrameName parameter and that frame may not exist--in which case a new browser window may be created with that name.

I was looking at the documentation for TrackPopupMenuEx() today and saw this in the remarks section.  This, I suspect, explains a lot of frustration I have experienced in the past.

To display a context menu for a notification icon, the current window must be the foreground window before the application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user clicks outside of the menu or the window that created the menu (if it is visible).

So, if you own an app that does this, please fix it.  The World and I thank you for it.

If you write an application that hosts the WebBrowser Control, and you want the control to do something, you can send commands to mshtml via the IOleCommandTarget interface.

However, if you are an ActiveX control and you want to send CGID_MSHTML commands, you may try something like this:

    ...
    IOleCommandTarget *pCommandTarget = NULL;
    hr = _punkSite->QueryInterface(IID_IOleCommandTarget, (void **)&pCommandTarget);
    if (SUCCEEDED(hr))
    {
        hr = pCommandTarget->Exec(&CGID_MSHTML, IDM_FOO, 0, NULL, NULL);
        pCommandTarget->Release();
    }
    ...

If you have tried this, you will see that it fails for CGID_MSHTML commands.  It fails because your control is hosted by an object inside of mshtml.dll that delegates all incoming commands to the nearest DocHostUIHandler.  For IE, that is implemented by an object in shdocvw.dll (or ieframe.dll for IE7+).  That object does not recognize CGID_MSHTML commands.

In order for your call to be routed correctly, you need to get an object "above" the object that is hosting your control.  To do this, you can first ask the client site for an IServiceProvider:

    ...
    IServiceProvider *pServiceProvider = NULL;
    hr = _punkSite->QueryInterface(IID_IServiceProvider, (void **)&pServiceProvider);
    if (SUCCEEDED(hr))
    {
        IOleCommandTarget *pCommandTarget = NULL;
        hr = pServiceProvider->QueryService(SID_SContainerDispatch, IID_IOleCommandTarget, &pCommandTarget);
        if (SUCCEEDED(hr))
        {
            hr = pCommandTarget->Exec(&CGID_MSHTML, IDM_FOO, 0, NULL, NULL);
            pCommandTarget->Release();
        }
        pServiceProvider->Release();
    }

By asking for the ContainerDispatch's command target, you get the correct target for MSHTML commands.

IOleCommandTarget is very useful.  It provides a generic way of sending commands between objects.  IE makes extensive use of IOleCommandTarget, both publically and internally.  And, like IUnknown, people frequently get it wrong.

Each command is composed of a GUID (Command Group Identifier) and a DWORD (Command Identifier). 


First, what is wrong with this code:

HRESULT CFoo::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
{
   if (IsEqualGUID(CGID_FooCommands, *pguidCmdGroup))
   {
      ...

The answer is obvious: pguidCmdGroup might be NULL and you crash.  Furthermore, pguidCmdGroup is frequently NULL, since NULL is how you specify the standard group

You might say to yourself, "Self, I don't need to worry about NULL, since I am the only consumer of this particular implementation,"  and that may be the case.  However, if you can be CoCreate()'ed, than it is definitively not the case.  Remember, any control on your machine can be instantiated as an ActiveX control.  During the ActiveX control instantiation process, mshtml will query your object for certain well-known interfaces, like IObjectSafety, IOleObject, and, you guessed it, IOleCommandTarget.  If your control responds to IOleCommandTarget, mshtml will attempt to Exec() standard commands.  If you do not guard for NULL, you will cause Internet Explorer (and other hosts) to crash.


The second set of common errors come in the form of returning incorrect error codes.  The documentation outlines the rules and you must be careful to implement them.  If you recognize the group but not the command, OLECMDERR_E_NOTSUPPORTED is the correct return value.  If you do not recognize the group, OLECMDERR_E_UNKNOWNGROUP is correct.  There is a special case though, which is when the group is NULL and the command is not supported; in this case the correct return value is OLECMDERR_E_NOTSUPPORTED.

Question: What is wrong with this code?

case WM_DRAWITEM:
{
   LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT) lParam;
   COMBOBOXEXITEM cbexItem = {0};
   cbexItem.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE;
   
cbexItem.iItem = pdis->itemID;

   CallWindowProc(pfnOldWndProc, hwnd, CBEM_GETITEM, 0, (LPARAM)&cbexItem);
   ...
}

Answer: It will break on 64 bit machines.

This is just one example of a well-known issue.  ComboBoxEx makes frequent use of -1 when asking for the item in the box.  Notice the item we ask the ComboBoxEx for comes from the itemID member of the DRAWITEMSTRUCT.  This seems like a perfectly normal thing to do.  However, when using data structures such as the ones commonly defined throughout Win32 and ComCtl, data structures that you the programmer did not define, it is important to look closely at the data types in the documentation.

DRAWITEMSTRUCT's itemID member is a UINT, while COMBOBOXEXITEM's iItem member is an INT_PTR.  So what happens when -1 is assigned to itemID then you assign that value to iItem?

Well, the UINT value is a 32 bit number on 64 bit machines and will have a value of 0xFFFFFFFF when comctl32 assigns -1.  Later, when this is assigned to the INT_PTR value, which is a 64 bit value on 64 bit machines, it will have a value of 0x00000000'FFFFFFFF.  This value is handed back to comctl32 in the form of the CBEM_GETITEM message.  When comctl32 does a comparison of the INT_PTR value against -1, it will be comparing against 0xFFFFFFFF'FFFFFFFF, and the comparison will fail.  Since your combo box probably doesn't have 4294967295 items in it, the CBEM_GETITEM call will fail to return useful information. The corrected code is:

cbexItem.iItem = (INT)pdis->itemID;

The moral of this story, in case you missed it, was know your data types and think carefully about what will happen in the 64 bit case when performing assignments that imply casts. 

0 Comments
Filed under:

ActiveX® controls frequently need to communicate with their containing object.  For example, a control may want to QueryService for the cached InternetSecurityManager object to decide whether or not to take a particular action.  Controls can obtain a pointer to their containing object (also called the object's site) in one of at least two ways. 

The typical scenario is Internet Explorer hosts the ActiveX® control inside of an object implemented in mshtml.dll.  Controls that need to communicate with their container implement IOleObject and mshtml will call the SetClientSite method with a pointer to itself.  Communication can now proceed by querying the given IOleClientSite pointer.

However, there is another scenario that authors of ActiveX® controls must consider.  Controls can be dynamically created through script, e.g.:

<script>
oAX = new ActiveXObject('MyControl');
</script>

In this case, the control is now being instantiated not by mshtml.dll, but by jscript.dll.  Since jscript.dll does not implement any OLE containers, it does not set a client site pointer via IOleObject.  It does, however, maintain an IServiceProvider chain to mshtml that can be accessed by an ActiveX® control.  The control must implement IObjectWithSite.  Now jscript will be able to call SetSite and the control can query the IUnknown pointer for services it requires from its container. 

Astute readers may, at this point, notice something funny in the documentation.  IObjectWithSite's documentation says, without elaboration:  "This interface should only be used when IOleObject is not already in use."  This has to be read carefully.  Notice the documentation uses the words "...in use," not "...implemented."  You should keep track of the two different site pointers and prefer the one obtained from IOleObject. If, however, you find that site is NULL then IOleObject is not being used and it is acceptable to fallback to using the site obtained from IObjectWithSite.

More Posts Next page »
 
Page view tracker