• The Old New Thing

    Wrapper templates to make writing callback functions slightly easier

    • 18 Comments

    I previously discussed why callback functions must be static if they are member functions.

    Writing the correct prototype for the callback function is usually somewhat clumsy. It's not hard. Just clumsy.

    class Sample {
     static DWORD CALLBACK StaticThreadProc(LPVOID *lpParameter)
     {
      Sample *self = reinterpret_cast<Sample*>(lpParameter);
      return self->RealThreadProc();
     }
     DWORD __stdcall RealThreadProc()
     {
       ... do stuff ...
     }
     void DoSomething()
     {
       ... CreateThread(NULL, 0, StaticThreadProc, this, 0, &dwTid); ...
     }
    };
    

    (If you read my previous article, you'd recognizing sticking a __stdcall in the declaration for RealThreadProc as a micro-optimization.)

    Every class that has a thread procedure needs this "trampoline" function StaticThreadProc that has the correct function signature, then massages it into something that is easier to work with (in this case, an object pointer and method call). Well, okay, you could do the work directly in the trampoline if you wanted to, but it's usually much more convenient to put the bulk of the work in a proper member function so you have access to all the "this" shorthand.

    If you do this a lot, you can write a template function to do the boring grunt work, freeing your brain to do "real thinking".

    template<class T, DWORD (__stdcall T::*F)()>
    DWORD CALLBACK ThreadProc(void *p)
    {
        return ((reinterpret_cast<T*>(p))->*F)();
    }
    

    This template function declares a templatized thread procedure. Notice that the calling convention for the ThreadProc template function is correct for a thread function, so this guy can be passed straight to CreateThread. Your class then would look like this:

    class Sample {
     DWORD __stdcall Background()
     {
       ... do stuff ...
     }
     void DoSomething()
     {
       ... CreateThread(NULL, 0, ThreadProc<Sample, &Sample::Background>, this, 0, &dwTid); ...
     }
    };
    

    This takes the trampoline function out of the class declaration. Instead, it is auto-generated by the compiler via the template.

    Okay, so maybe this isn't much of a typing-savings after all, considering the rather clumsy expression for the template invocation. I wonder if it can be made simpler.

    [Raymond is currently on vacation; this message was pre-recorded.]

    [2004 July 31 - fix obvious typos.]

  • The Old New Thing

    How to display a string without those ugly boxes

    • 17 Comments

    You've all seen those ugly boxes. When you try to display a string and the font you have doesn't support all of the characters in it, you get an ugly box for the characters that aren't available in the font.

    Start with our scratch program and add this to the PaintContent function:

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
        TextOutW(pps->hdc, 0, 0,
                L"ABC\x0410\x0411\x0412\x0E01\x0E02\x0E03", 9);
    }
    

    That string contains the first three letters from three different alphabets: "ABC" from the Roman alphabet; "АБВ" from the Cyrillic alphabet; and "กขฃ" from the Thai alphabet.

    If you run this program, you get a bunch of ugly boxes for the non-Roman characters because the SYSTEM font is very limited in its character set support.

    But how to pick the right font? What if the string contained Korean or Japanese characters? There is no single font that contains every character defined by Unicode. (Or at least, none that is commonly available.) What do you do?

    This is where font linking comes in.

    Font linking allows you to take a string and break it into pieces, where each piece can be displayed in an appropriate font.

    The IMLangFontLink2 interface provides the methods necessary to do this breaking. GetStrCodePages takes the string apart into chunks, such that all the characters in a chunk can be displayed by the same font, and MapFont creates the font.

    Okay, so let's write our font-link-enabled version of the TextOut function. We'll do this in stages, starting with the idea kernel.

    #include <mlang.h>
    
    HRESULT TextOutFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)
    {
      ...
      while (cch > 0) {
        DWORD dwActualCodePages;
        long cchActual;
        pfl->GetStrCodePages(psz, cch, 0, &dwActualCodePages, &cchActual);
        HFONT hfLinked;
        pfl->MapFont(hdc, dwActualCodePages, 0, &hfLinked);
        HFONT hfOrig = SelectFont(hdc, hfLinked);
        TextOut(hdc, ?, ?, psz, cchActual);
        SelectFont(hdc, hfOrig);
        pfl->ReleaseFont(hfLinked);
        psz += cchActual;
        cch -= cchActual;
      }
      ...
    }
    

    After figuring out which code pages the default font supports, we walk through the string asking GetStrCodePages to give us the next chunk of characters. From that chunk, we create a matching font and draw the characters in that font at "the right place". Repeat until all the characters are done.

    The rest is refinement and paperwork.

    First of all, what is "the right place"? We want the next chunk to resume where the previous chunk left off. For that, we take advantage of the TA_UPDATECP text alignment style, which says that GDI should draw the text at the current position, and update the current position to the end of the drawn text (therefore, in position for the next chunk).

    Therefore, part of the paperwork is to set the DC's current position and set the text mode to TA_UPDATECP:

      SetTextAlign(hdc, GetTextAlign(hdc) | TA_UPDATECP);
      MoveToEx(hdc, x, y, NULL);
    

    Then we can just pass "0,0" as the coordinates to TextOut, because the coordinates passed to TextOut are ignored if the text alignment mode is TA_UPDATECP; it always draws at the current position.

    Of course, we can't just mess with the DC's settings like this. If the caller did not set TA_UPDATECP, then the caller is not expecting us to be meddling with the current position. Therefore, we have to save the original position and restore it (and the original text alignment mode) afterwards.

      POINT ptOrig;
      DWORD dwAlignOrig = GetTextAlign(hdc);
      SetTextAlign(hdc, dwAlignOrig | TA_UPDATECP);
      MoveToEx(hdc, x, y, &ptOrig);
      while (cch > 0) {
        ...
        TextOut(hdc, 0, 0, psz, cchActual);
        ...
      }
      // if caller did not want CP updated, then restore it
      // and restore the text alignment mode too
      if (!(dwAlignOrig & TA_UPDATECP)) {
        SetTextAlign(hdc, dwAlignOrig);
        MoveToEx(hdc, ptOrig.x, ptOrig.y, NULL);
      }
    

    Next is a refinement: We should take advantage of the second parameter to GetStrCodePages, which specifies the code pages we would prefer to use if a choice is avialable. Clearly we should prefer to use the code pages supported by the font we want to use, so that if the character can be displayed in that font directly, then we shouldn't map an alternate font.

      HFONT hfOrig = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
      DWORD dwFontCodePages = 0;
      pfl->GetFontCodePages(hdc, hfOrig, &dwFontCodePages);
      ...
      while (cch > 0) {
        pfl->GetStrCodePages(psz, cch, dwFontCodePages, &dwActualCodePages, &cchActual);
        if (dwActualCodePages & dwFontCodePages) {
          // our font can handle it - draw directly using our font
          TextOut(hdc, 0, 0, psz, cchActual);
        } else {
          ... MapFont etc ...
        }
      }
      ...
    

    Of course, you probably wonder this magical pfl comes from. It comes from the Multilanguage Object in mlang.

      IMLangFontLink2 *pfl;
      CoCreateInstance(CLSID_CMultiLanguage, NULL,
                       CLSCTX_ALL, IID_IMLangFontLink2, (void**)&pfl);
      ...
      pfl->Release();
    

    And of course, all the errors we've been ignoring need to be taken care of. This does create a big of a problem if we run into an error after we have already made it through a few chunks. What should we do?

    I'm going to handle the error by drawing the string in the original font, ugly boxes and all. We can't erase the characters we already drew, and we can't just draw half of the string (for our caller won't know where to resume). So we just draw with the original font and hope for the best. At least it's no worse than it was before font linking.

    Put all of these refinements together and you get this final function:

    HRESULT TextOutFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)
    {
      HRESULT hr;
      IMLangFontLink2 *pfl;
      if (SUCCEEDED(hr = CoCreateInstance(CLSID_CMultiLanguage, NULL,
                          CLSCTX_ALL, IID_IMLangFontLink2, (void**)&pfl))) {
        HFONT hfOrig = (HFONT)GetCurrentObject(hdc, OBJ_FONT);
        POINT ptOrig;
        DWORD dwAlignOrig = GetTextAlign(hdc);
        if (!(dwAlignOrig & TA_UPDATECP)) {
          SetTextAlign(hdc, dwAlignOrig | TA_UPDATECP);
        }
        MoveToEx(hdc, x, y, &ptOrig);
        DWORD dwFontCodePages = 0;
        hr = pfl->GetFontCodePages(hdc, hfOrig, &dwFontCodePages);
        if (SUCCEEDED(hr)) {
          while (cch > 0) {
            DWORD dwActualCodePages;
            long cchActual;
            hr = pfl->GetStrCodePages(psz, cch, dwFontCodePages, &dwActualCodePages, &cchActual);
            if (FAILED(hr)) {
              break;
            }
    
            if (dwActualCodePages & dwFontCodePages) {
              TextOut(hdc, 0, 0, psz, cchActual);
            } else {
              HFONT hfLinked;
              if (FAILED(hr = pfl->MapFont(hdc, dwActualCodePages, 0, &hfLinked))) {
                break;
              }
              SelectFont(hdc, hfLinked);
              TextOut(hdc, 0, 0, psz, cchActual);
              SelectFont(hdc, hfOrig);
              pfl->ReleaseFont(hfLinked);
            }
            psz += cchActual;
            cch -= cchActual;
          }
          if (FAILED(hr)) {
            //  We started outputting characters so we have to finish.
            //  Do the rest without font linking since we have no choice.
            TextOut(hdc, 0, 0, psz, cch);
            hr = S_FALSE;
          }
        }
    
        pfl->Release();
    
        if (!(dwAlignOrig & TA_UPDATECP)) {
          SetTextAlign(hdc, dwAlignOrig);
          MoveToEx(hdc, ptOrig.x, ptOrig.y, NULL);
        }
      }
    
      return hr;
    }
    

    Finally, we can wrap the entire operation inside a helper function that first tries with font linking and if that fails, then just draws the text the old-fashioned way.

    void TextOutTryFL(HDC hdc, int x, int y, LPCWSTR psz, int cch)
    {
      if (FAILED(TextOutFL(hdc, x, y, psz, cch)) {
        TextOut(hdc, x, y, psz, cch);
      }
    }
    

    Okay, now that we have our font-linked TextOut with fallback, we can go ahead and adjust our PaintContent function to use it.

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
      TextOutTryFL(pps->hdc, 0, 0,
                   TEXT("ABC\x0410\x0411\x0412\x0E01\x0E02\x0E03"), 9);
    }
    

    Observe that the string is now displayed with no black boxes.

    One refinement I did not do was to avoid creating the IMlangFontLink2 pointer each time we want to draw text. In a "real program" you would probably create the multilanguage object once per drawing context (per window, perhaps) and re-use it to avoid going through the whole object creation codepath each time you want to draw a string.

    [Raymond is currently on vacation; this message was pre-recorded.]

  • The Old New Thing

    Don't forget to #define UNICODE if you want Unicode

    • 9 Comments

    I answered this comment directly, but it deserves reiteration with wider visibility.

    If you don't #define UNICODE, you get ANSI by default.

    If you want to see characters beyond the boring 7-bit ASCII, make sure you are using a font that can display those characters.

    I am assuming a level of competence where issues like this go without saying, so that I can dig into the more advanced topics without having to explain all the basics, but I have to accept that people of all levels of programming experience read my stuff.

    But the second part raises an advanced question: How do you find a font that can display the characters you want? What if the characters can come from a source outside your control? We'll look at this tomorrow.

  • The Old New Thing

    Why "Under Construction" should be made illegal on the web

    • 29 Comments

    The site www.emr.fr has been "under construction" for over eight years. I noticed it in June 1996 and check back on it periodically. Still Under Construction.

    Another site that I noticed as "Under Construction" in June 1996 is www.leary.com. And as of this writing, it is still under construction.

  • The Old New Thing

    How do you detect "Large Fonts"?

    • 58 Comments

    When people ask, "How do I detect Large Fonts", they aren't really asking how to detect Large Fonts specifically. Rather, Large Fonts is just the most common manifestation of "unusual DPI".

    Windows uses a nominal DPI of 96 pixels per inch. In other words, if Windows wants to draw a line that is one inch long, it draws 96 pixels. Of course, the physical length of this line depends on your screen resolution and the size of your monitor. The value of 96 is merely nominal.

    You can change this DPI setting from the Display control panel, either by choosing Large Fonts, or by choosing a custom font size. Standard size is 96DPI. Large is 120DPI. Custom is, well, custom.

    DPI higher than 96 will become more and more prevalent as LCD technology improves.

    Programs can query the DPI setting by asking GetDeviceCaps for the LOGPIXELSX of the screen DC.

    int GetScreenDPI()
    {
      HDC hdcScreen = GetDC(NULL);
      int iDPI = -1; // assume failure
      if (hdcScreen) {
        iDPI = GetDeviceCaps(hdcScreen, LOGPIXELSX);
        ReleaseDC(NULL, hdcScreen);
      }
      return iDPI;
    }
    

    The code above assumes that pixels are square, which is true of most modern devices. (You can choose an odd screen resolution and get non-square pixels, but most people avoid such resolutions.) Back in the old days, there were many devices with non-square pixels. For example, the EGA video adapter had pixels which were 1.33 times as tall as they were wide.

    For nonsquare-pixel devices, the values of the LOGPIXELSX and LOGPIXELSY metrics will be different. On an EGA, if the value of the LOGPIXELSX metric were 96, then the LOGPIXELSY metric would be 72, since there are only 72 vertical pixels per inch. Similarly, the ASPECTX, ASPECTY and ASPECTXY values for nonsquare-pixel devices will be somewhat interesting as well, as this diagram demonstrates:

    36 27 45

    The ASPECTX is 27 and the ASPECTY is 36, representing the 4:3 ratio of vertical to horizontal, and the ASPECTXY is 45, representing the hypotenuse.

  • The Old New Thing

    There are two things you can do if you have a mad crush on a boy

    • 3 Comments

    And Eric Lippert finds out what they are.

  • The Old New Thing

    Watch out for those sample URLs

    • 29 Comments

    When writing documentation, one often has need to come up with a sample URL to illustrate some point or other. When you do, make sure the sample URL is under your control.

    I remember a Windows beta that used the sample URL http://www.xxxxx.com/ in a dialog box. You can imagine where that actually goes.

    This web site uses www.wallyworld.com as a sample URL. Perhaps they didn't realize that it's a gay porn site.

    (Raymond's strange dream story: One night I dreamt that I found a web site that had a complete Dilbert archive, and for some reason the name of the site was "Wally World". In the morning, I checked out the site and was in for a big surprise...)

    So play it safe. When you need a sample URL, don't just make something up. If you do, odds are good that somebody is going to rush in and register it. Make your sample URLs point back to your company's home page, or use http://www.example.com, which the IANA has reserved for use in sample URLs. If that's too dorky, you can always go out and register the domain you want to use as your sample, so that nobody else can sneak in and steal it. (This does have the problem of incurring renewal fees.)

  • The Old New Thing

    Positioned vs. non-positioned listview views

    • 7 Comments

    Occasionally, I'll see a question that betrays a lack of understanding of difference between the positioned and non-positioned listview views. The question usually goes along the lines of "I inserted an item with LVM_INSERTITEM but it went to the end of the list instead of in the location I inserted it."

    To understand what is going on, you need to know that some listview views are "positioned" and others are "non-positioned".

    "(Large) icon view", "small icon view", and "tile view" are positioned views. Each item carries its own coordinates, which you can customize via LVM_SETITEMPOSITION. When a new item is inserted, it gets an item index based on the insertion point, but its physical location on the screen is the first available space not already occupied by another item. Existing items are not moved around to make room for the inserted item.

    The other views, "list view" and "report (aka details) view", are non-positioned views. In these views, items do not get to choose their positions. Instead, the position of an item is determined by its item index. In non-positioned views, inserting or deleting an item will indeed cause all subsequent items to shift.

    Now you can answer the question. Why did the item go to the end of the list instead of at the position the caller inserted it?

  • The Old New Thing

    Welcoming a new era in Swiss army knives

    • 18 Comments

    Victorinox, the company authorized to make so-called Swiss Army merchandise, has run into a bit of a problem: Is there anything else you could possibly think of putting on a pocketknife for an outdoorsman? And even if you did, there's no room for it. The pocketknife has already reached the point where it's too unwieldy to carry around in your pocket any more.

    So they decided to think outside the box.

    First, they let go of the "must fit in your pocket" concept. Welcome the Victorinox Swiss Champ XXLT. Check out the picture of the knife in its display box. The knife is as thick as it is long!

    Second, they let go of the whole "things for the outdoorsman" concept. Now you can get the Swiss Army Knife USB Drive and the Swiss Army Knife CyberTool.

    Swiss Army Knife. For the geek in your life. Embrace it.

  • The Old New Thing

    How does Add/Remove Programs get the size and other information?

    • 120 Comments

    If the program doesn't provide this information itself, Add/Remove Programs is forced to guess.

    The problem is that there is no "obvious" way to map an entry in the Add/Remove Programs list to an actual program. Each entry in the list, for those who care about such things, comes from the HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall registry key. The only mandatory properties for an uninstallable program are the DisplayName and the UninstallPath. Everything else is optional.

    Let's suppose Add/Remove Programs is given a program registration like this:

    HKEY_LOCAL_MACHINE\
     Software\
      Microsoft\
       Windows\
        CurrentVersion\
         Uninstall\
          SomeProgram
           DisplayName=REG_SZ:"Awesome Program for Windows"
           UninstallPath=REG_SZ:"C:\WINDOWS\uninstall.exe -SomeParameters"
    

    In order to get the "Last Used" and "Frequency" values, Add/Remove Programs needs to know the name of the EXE so it can ask the Start menu "Hey, how often did the user run this program, and when was the last time it happened?"

    Notice that there are no clues in the registration above as to the identity of this EXE file.

    So Add/Remove Programs starts guessing. It goes through all the programs on your Start menu and compares their names with the display name of the uninstallable item. It looks for Start menu items which share at least two words with the words in the DisplayName.

    For example, if there were a Start menu item called "Pretty Decent Windows Program", this would count as a two-word match ("Windows" and "Program").

    It then takes the one with the most matches and decides, "Okay, I guess this is it." Suppose for the sake of illustration that the best match is indeed "Pretty Decent Windows Program.lnk", which is a shortcut to "C:\Program Files\LitWare\Decent Program\Decent.exe". Add/Remove Programs would decide that "Awesome Program for Windows" should get the icon for "Pretty Decent Windows Program.lnk", that the frequency of use and most-recently-used information for "C:\Program Files\LitWare\Decent Program\Decent.exe" will be displayed for "Awesome Program for Windows".

    But wait, there's more. There's also the program size. Add/Remove Programs looks in your "Program Files" directory for directories whose names share at least two words in common with the DisplayName. The best match is assumed to be the directory that the program files are installed into. The sizes are added together and reported as the size of "Awesome Program for Windows".

    A program can add some properties to its registration to avoid a lot of this guessing. It can set an EstimatedSize property to avoid making Add/Remove Programs guess how big the program is. It can also set a DisplayIcon property to specify which icon to show for the program in the list.

    But if a program omits all of these hints, the guess that Add/Remove Programs ends up making can often be ridiculously wide of the mark due to coincidental word matches. In my experience, Spanish suffers particularly badly from this algorithm, due to that language's heavy use of prepositions and articles (which result in a lot of false matches).

    Yes, this is all lame, but when you are forced to operate with inadequate information, lame is the best you can do.

    [July 15 2004: Emphasizing that this is done only if the program fails to provide the information itself. For some reason, a lot of people who link to this page fail to notice that little detail.]

Page 388 of 434 (4,334 items) «386387388389390»