November, 2013

  • The Old New Thing

    Notes on gift card and gift certificates in the state of Washington

    • 25 Comments

    Today is the unofficial start of the holiday shopping season. One of my colleagues read the fine print of a gift card he received:

    A monthly maintenance fee of $3 applies but is waived for the first twelve months after the card is issued. Thereafter, monthly maintenance fees are waived for an additional three months if the card is used in any given month.

    This seems kind of backwards. They charge a maintenance fee if you don't create any work for them. Shouldn't the maintenance fee be charged when you use the card, rather than when you don't?

    This "maintenance fee" is really just an "account inactivity fee". The issuer doesn't want to have to deal with the liability and associated risk of having lots of unused gift cards floating around, so they create a mechanism by which unused gift cards slowly lose value until they are worthless.

    In the state of Washington, RCW 19.240 has rules governing how certain types of gift cards behave.

    Gift cards issued by a bank (typically those branded with a credit card) are the biggest exception not covered by RCW 19.240, however. (Another exception is prepaid phone cards.)

    You can read the entire chapter RCW 19.240 for the details on expiration dates (split over 19.240.030, 19.240.050, and 19.240.060), and inactivity fees.

    Interesting notes:

    • In order for an inactivity fee to be allowed, a gift card must remain inactive for two years. Furthermore, the remaining balance after the fee must be $5 or less, and the inactivity fee cannot exceed $1 per month.
    • Once an inactivity fee is assessed, you can redeem the gift card for cash.
    • If you make a purchase that consumes only part of the value of a gift card or gift certificate, and the remaining balance is less than $5, you can redeem the balance for cash.
    • Gift cards that were not explicitly purchased (such as those received as part of a loyalty program or other) can have an expiration date.

    I learned one way a local company addressed this problem: When my gift card went dormant, they sent me a check for the balance, hoping that I would cash it (hey, who wouldn't?) and thereby retire the liability from their books.

  • The Old New Thing

    Why can't I create my dialog with DialogBox, DialogBoxParam, CreateDialog, CreateDialogParam, or the indirect versions of same?

    • 2 Comments

    One of the purposes of my dialog manager series was to help people diagnose problems with their dialog boxes. But since I embedded the tips inside the series body, it's hard for people to find them, and I still end up answering the same questions over and over.

    So here it is in a separate article that hopefully people can find.

    Why your call to Dialog­Box or Create­Dialog is failing. This also goes for CDialog::Do­Modal, but perhaps extra so because the MFC source code says

    * 3) CreateDialogIndirect() did NOT create the window (ie. due to error in template)
    *    and returns NULL. 
    

    even though the problem is rarely due to an error in the template. A wrong comment is worse than no comment at all.¹

    I've decided to put the reasons in most-likely-error-first order rather than chronological.

    • Your dialog template uses a custom control which has not been registered.

    This is by far the number one reason why dialog boxes fail to be created. As we saw in Part 3, if a child control cannot be created from the template, then the dialog creation fails. (There is a special dialog box style DS_NO­FAIL­CREATE that tells the dialog manager to ignore the error and just continue without the child control.)

    For example, maybe your dialog box template uses a List View control, but you forgot to call Init­Common­Controls or Init­Common­Controls­Ex. Or it uses a Rich Edit control and you forgot to load the appropriate library. Or it contains an ActiveX control but you forgot to call Afx­Enable­Control­Container to enable ActiveX control hosting.

    A special case of this error is where your dialog template uses a custom control which was registered in a different activation context. If you are trying to use a control from version 6 of the common controls, then you must use the appropriate manifest and make sure that the manifest's activation context is active at the time you call Dialog­Box or whatever. If you forget to do this, then you will be using whatever activation context happens to be lying around at the time you call Dialog­Box, and it may not be the one you want. (It's like walking up to a computer and looking on the Desktop and not seeing the file you created yesterday. "Hey, who deleted my file!" But you forgot to do a Switch User to your own account. You're looking at the Desktop of whoever used the computer last. You're in the wrong context.)

    • Your resource name is incorrectly declared in the resource template.

    One of the annoying features of the Resource Compiler is that it happily accepts typos! For example, suppose you have the following resource header file:

    // ids.h
    
    #define IDD_BRILLIANT 100
    

    and the following resource file:

    IDD_BRILLANT DIALOG ...
    

    Since you misspelled IDD_BRILLIANT, you are not in fact creating a dialog box whose ID is 100 (which is what would have happened if you had spelled it correctly). Instead, you're creating a dialog whose name is the string "IDD_BRILLANT".

    Later, your code passes MAKE­INT­RESOURCE(IDD_BRILLIANT) to ask for dialog 100, and the dialog manager says, "Sorry, I don't see one of those." (To get the typo'd dialog template, you would have to pass TEXT("IDD_BRILLANT") as the template name.)

    Even if you spell everything correctly, you will also have this problem if you forget to #include "ids.h" in your resource template file in the first place!

    To diagnose this error, you can add a diagnostic call to Find­Resource (or simply trace through the dialog manager's call to the same function) to see if it can find the resource. Many IDEs will let you load a DLL and inspect its resources interactively. You can check the dialog template to see whether it is listed as resource 100 or as resource "IDD_BRILLANT".

    • You passed the wrong instance handle or dialog ID.

    This is a generalization of the previous error. The dialog template needs to exist in the module you passed, with the ID or name you passed. If you pass the wrong module or the wrong name, then you're not going to find it.

    An even more generalized version of this error is the case where you forgot to add the dialog to the module's resources in the first place. (Maybe you forgot to add the resource file to your project.)

    The diagnosis for this case is the same as the case of the misspelled dialog identifier.

    • The control refused its creation.

    In rare cases, a control may fail its creation by returning FALSE in response to the WM_NC­CREATE message or -1 in response to the WM_CREATE message.

    • You passed a bad window handle as the hwnd­Parent.

    An invalid parameter will naturally result in the function failing. This rarely occurs in practice because you should be using the handle of a window under your control, so it shouldn't be destroyed out from under you.

    • There is an error in the dialog template.

    I have never seen this occur. The Resource Compiler is pretty good about not generating erroneous templates.

    ¹ Perhaps the author meant to use e.g. (exempli gratia, which means "for example") instead of i.e. (id est, which means "that is"). As written, the comment is saying that an error in the template is the only reason that Create­Dialog­Indirect could have failed, when in fact it is only one example of a failure. Getting the two Latin abbreviations confused is not just a pedantic error; here, it created genuine confusion and probably wasted a lot of developers' time.

  • The Old New Thing

    Things that can happen when your kitchen gets taken over by others

    • 18 Comments

    Today is the Thanksgiving holiday in the United States, one of the the major holidays for family get-togethers. (Another big one is Christmas.) One year, it was our family's turn to host Thanksgiving, and when that happens, it means that the kitchen is overrun by relatives cooking all the dishes and hunting through the cabinets and drawers of an unfamiliar kitchen.

    One year, I learned that a pastry blender can be used as a potato masher and that a cheese planer can be used as a pie server.

    This is what happens when other people use your kitchen.

    It could also mean that my kitchen has too many froofy gadgets.

    Nadine Kano, best known to geeks as the author of Developing International Software for Windows 95 and Windows NT, also has a pastry degree from Le Cordon Bleu, graduating at the top of her class. She corrected me: "One can never have too many froofy kitchen gadgets." Nadine has since retired from Microsoft to start her own consulting firm and be a pastry chef on the side.

    (Actually, the fact that my relatives were using a pastry blender as a potato masher and a cheese planer as a pie server suggests that I don't have enough froofy gadgets. Specifically, I'm missing a potato masher and a pie server.)

  • The Old New Thing

    If you try to declare a variadic function with an incompatible calling convention, the compiler secretly converts it to cdecl

    • 16 Comments

    Consider the following function on an x86 system:

    void __stdcall something(char *, ...);
    

    The function declares itself as __stdcall, which is a callee-clean convention. But a variadic function cannot be callee-clean since the callee does not know how many parameters were passed, so it doesn't know how many it should clean.

    The Microsoft Visual Studio C/C++ compiler resolves this conflict by silently converting the calling convention to __cdecl, which is the only supported variadic calling convention for functions that do not take a hidden this parameter.

    Why does this conversion take place silently rather than generating a warning or error?

    My guess is that it's to make the compiler options /Gr (set default calling convention to __fastcall) and /Gz (set default calling convention to __stdcall) less annoying.

    Automatic conversion of variadic functions to __cdecl means that you can just add the /Gr or /Gz command line switch to your compiler options, and everything will still compile and run (just with the new calling convention).

    Another way of looking at this is not by thinking of the compiler as converting variadic __stdcall to __cdecl but rather by simply saying "for variadic functions, __stdcall is caller-clean."

    Exercise: How can you determine which interpretation is what the compiler actually does? In other words, is it the case that the compiler converts __stdcall to __cdecl for variadic functions, or is it the case that the calling convention for variadic __stdcall functions is caller-clean?

  • The Old New Thing

    The case of the DLL that refuses to load

    • 17 Comments

    A customer reported that they had a problem that occurred only on some machines but not others. Their application called

    HINSTANCE hinst = LoadLibraryEx(strModule, LOAD_WITH_ALTERED_SEARCH_PATH);
    

    and the call succeeded on some machines, but failed on others with error ERROR_MOD_NOT_FOUND ("The specified module could not be found"). The path was a fully-qualified path to a file that was confirmed to exist and be readable.

    strModule = 0x09e875b4 "C:\Users\Bob\Desktop\CopyAndRun\Contoso.dll"
    

    If the sxe ld Contoso.dll command was used in the debugger to break when the DLL loads, the breakpoint does fire, but a breakpoint on Contoso's Dll­Main is never hit. "I think this means that the problem is not that Contoso failed to initialize, but what does it mean?"

    If you get a break from sxeld but no breakpoint on Dll­Main, then it means that the DLL was found but couldn't be loaded. You can use loader snaps will tell you what went wrong. "My psychic powers tell me that a dependent DLL could not be found or initialized."

    The customer replied, "Ah, of course. We'll look into that."

    A short while later, they confirmed the diagnosis. "The Contoso DLL was dependent on a version of the C runtime library that was not installed on the machines where it failed to load. But as a follow-up question: I would have expected that the standard The program can't start because XYZ is missing from your computer. dialog to appear in this case. Why isn't it?"

    The reason is there in the error message: The "missing file" error message is shown only when a program cannot start due to a missing file. Specifically, it is raised by the loader only during the initial DLL resolution phase that occurs as part of process initialization. These are the DLLs linked implicitly via the module header because you linked against kernel32.lib, for example. DLLs loaded explicitly via Load­Library do not display this error message; instead, the error is returned back to the program, where it is expected to take appropriate recovery steps. By comparison, if DLL resolution fails during process initialization, there is nowhere to return the failure code. You can't return it to the program since the program isn't running yet. The only place to put the error is on the screen.

  • The Old New Thing

    Why did Raymond bring a knitting bag to every meeting?

    • 31 Comments

    I stopped knitting a few years ago, but back when I knitted regularly, I tended to bring my knitting bag with me everywhere I went.

    There are a lot of idle minutes in your typical day. Waiting for the bus, waiting in line for the ATM, waiting for a meeting to start. There's not enough time in those idle minutes to do anything substantial, but it's enough time to sneak in a little bit of knitting.

    But there's another reason I brought a knitting bag to every meeting.

    Nothing says "I don't care about this meeting" like knitting.

  • The Old New Thing

    My friend lived in an apartment inside a museum

    • 10 Comments

    One evening, I had a series of three dreams. In each one, I visited an unusual home.

    In the second dream, I visited the home of a friend of mine. She lived in a modern luxury apartment inside an art museum. It was a little tricky, because you could visit her only during museum hours. If you stayed past closing time, then you were locked inside the museum, and you were spending the night whether you liked it or not.

    On the other hand, it meant that you could go out an view the art to your heart's content without any crowds.

    And no, the museum did not come to life. You just got free run of a museum for the night.

  • The Old New Thing

    Extracting GPS coordinates from a photo and plotting it on a map

    • 13 Comments

    Today's Little Program extracts GPS coordinates from a photo and plots it on a map. Remember, Little Programs do little to no error checking, because that's how they roll.

    #define STRICT
    #define UNICODE
    #define _UNICODE
    #include <windows.h>
    #include <shlobj.h>
    #include <shellapi.h>
    #include <propidl.h>
    #include <propkey.h>
    #include <propvarutil.h>
    #include <atlbase.h>
    #include <atlalloc.h>
    #include <strsafe.h>
    
    void OpenMap(double dblLatitude, double dblLongitude)
    {
     wchar_t szUrl[1024];
     StringCchPrintf(szUrl, ARRAYSIZE(szUrl),
      L"http://www.bing.com/maps/default.aspx?v=2&q=%f,%f",
      dblLatitude, dblLongitude);
     ShellExecute(nullptr, nullptr, szUrl, nullptr, nullptr, SW_NORMAL);
    }
    

    We start with a simple function that takes a latitude and longitude and opens a Web page that highlights that coordinate. In a real program, you probably would do something more interesting with the coordinates, but I'm opening a Web page just to do something.

    class CPropVariant : public PROPVARIANT {
    public:
     CPropVariant() { PropVariantInit(this); }
     ~CPropVariant() { PropVariantClear(this); }
    };
    

    The CProp­Variant class is an incredibly lame wrapper around PROP­VARIANT for RAII purposes.

    HRESULT GetGPSCoordinateAsDecimal(
        IShellItem2 *psi2,
        REFPROPERTYKEY pkey,
        REFPROPERTYKEY pkeyRef,
        double *pdbl)
    {
     CPropVariant spvar;
     HRESULT hr = psi2->GetProperty(pkey, &spvar);
     if (FAILED(hr)) return hr;
    
     double rgdbl[3];
     ULONG cElt;
     hr = PropVariantToDoubleVector(spvar, rgdbl, 3, &cElt);
     if (FAILED(hr)) return hr;
     if (cElt != 3) return E_INVALIDARG;
    
     double coord = rgdbl[0] + rgdbl[1] / 60.0 + rgdbl[2] / 60.0 / 60.0;
    
     CComHeapPtr<wchar_t> spszDir;
     hr = psi2->GetString(pkeyRef, &spszDir);
     if (FAILED(hr)) return hr;
    
     if (spszDir[0] == L'W' || spszDir[0] == L'S') coord = -coord;
    
     *pdbl = coord;
     return S_OK;
    }
    

    The Get­GPS­Coordinate­As­Decimal function is where the real work happens. GPS latitude and longitude are encoded in the shell property system as a bunch of related properties.

    Property Type Meaning
    System.GPS.DestLatitudeNumerator UINT[3] numerators for degrees, minutes, and seconds of latitude
    System.GPS.DestLatitudeDenominator UINT[3] denominators for degrees, minutes, and seconds of latitude
    System.GPS.DestLatitude double[3] degrees, minutes, and seconds of latitude (numerator ÷ denominator)
    System.GPS.DestLatitudeRef string "N" or "S"
    System.GPS.DestLongitudeNumerator UINT[3] numerators for degrees, minutes, and seconds of longitude
    System.GPS.DestLongitudeDenominator UINT[3] denominators for degrees, minutes, and seconds of longitude
    System.GPS.DestLongitude double[3] degrees, minutes, and seconds of Longitude (numerator ÷ denominator)
    System.GPS.DestLongitudeRef string "E" or "W"

    Each of the coordinates is recorded in DMS form as pairs of unsigned integers (numerator and denominator). The direction is recorded as a string as a separate property. Why this wacky format? Probably because that's the way EXIF records it.

    For convenience, there is a combo property which does the division for you (but frustratingly, does not flip the sign for direction). And if you want the coordinates in decimal form, then you'll have to do the DMS-to-decimal conversion yourself.

    We start by getting the DMS value as a PROP­VARIANT then converting it to an array of doubles. (There had better be three of them.) We then use the power of mathematics to convert from DMS to decimal degrees.

    Finally, we flip the sign if the direction from center is West or South.

    Now it's time to put these functions together.

    int __cdecl wmain(int argc, wchar_t **argv)
    {
     if (argc < 2) return 0;
     CCoInitialize init;
    
     CComPtr<IShellItem2> spsi2;
     if (FAILED(SHCreateItemFromParsingName(argv[1],
                  nullptr, IID_PPV_ARGS(&spsi2)))) return 0;
    
     double dblLong, dblLat;
     if (FAILED(GetGPSCoordinateAsDecimal(spsi2, PKEY_GPS_Longitude,
                        PKEY_GPS_LongitudeRef, &dblLong))) return 0;
     if (FAILED(GetGPSCoordinateAsDecimal(spsi2, PKEY_GPS_Latitude,
                        PKEY_GPS_LatitudeRef, &dblLat))) return 0;
    
     OpenMap(dblLong, dblLat);
    
     return 0;
    }
    

    Find a photo with GPS information encoded inside it and pass it on the command line as a fully-qualified path. (Because I'm too lazy to call Get­Full­Path­Name.) The program should open a Web page that shows where the picture was taken.

  • The Old New Thing

    How do I get the effect of CW_USEDEFAULT positioning on a window I've already created?

    • 37 Comments

    A customer wanted to know how to get the effect of CW_USE­DEFAULT positioning on a window that already exists. In particular, they wanted to be able to reposition a dialog box to get the CW_USE­DEFAULT cascade effect, but since you can't actually pass CW_USE­DEFAULT in a dialog template, the repositioning has to be done after the fact. (Presumably in the WM_INIT­DIALOG handler, which runs before the dialog is visible, so that there is no visible flicker.)

    The solution here is simple: Create a temporary invisible window with CW_USE­DEFAULT as its position and the same height and width as your dialog box. See where the window manager puts that temporary window and move your dialog box to match that position. Then destroy the temporary window.

  • The Old New Thing

    How do I get the path to the default user's profile?

    • 22 Comments

    A customer wanted to know how to get the path to the default user's profile. On older versions of Windows, the default location of the default user's profile was C:\WINNT\Profiles\Default. Then it moved to C:\Documents and Settings\Default User. Now it's in C:\Users\Default. And the location may have been customized, so in principle it could be anywhere.

    The function to get the default user profile's directory is is the deviously-named Get­Default­User­Profile­Directory.

    But the reason I'm writing this article is not to call your attention to the Get­Default­User­Profile­Directory function, but rather to something in the function documentation.

    The documentation for the Get­Default­User­Profile­Directory function includes the strings C:\Documents and Settings\Default User and C:\Users\Default, so all somebody had to do was type either of those paths into a search engine scoped to MSDN, and it would have found the function to use.

    This sort of counts as a counterexample to the suggestion that in order to help people find the correct function to use (instead of whacking an undocumented registry key), MSDN should include the path to the registry key so that a search for the undocumented registry key will kick up a page that says, "Do not use this registry key. Call this other function instead." That experiment was attempted (inadvertently) with the Get­Default­User­Profile­Directory function, and it didn't work.

Page 1 of 3 (26 items) 123