The Old New Thing's Journal
 
[Most Recent Entries] [Calendar View] [Friends]

Below are the 14 most recent journal entries recorded in The Old New Thing's LiveJournal:

    Friday, January 3rd, 2014
    3:00 pm
    What happened in real-mode Windows when somebody did a longjmp into a discardable segment?

    During the discussion of how real-mode Windows handled return addresses into discarded segments, Gabe wondered, "What happens when somebody does a long­jmp into a discardable segment?"

    I'm going to assume that everybody knows how long­jmp traditionally works so I can go straight to the analysis.

    The reason long­jmp is tricky is that it has to jump to a return address that isn't on the stack. (The return address was captured in the jmp_buf.) If that segment got relocated or discarded, then the jump target is no longer valid. It would have gotten patched to a return thunk if it were on the stack, but since it's in a jmp_buf, the stack walker didn't see it, and the result is a return address that is no longer valid. (There is a similar problem if the data segment or stack segment got relocated. Exercise: Why don't you have to worry about the data segment or stack segment being discarded?)

    Recall that when a segment got discarded, all return addresses which pointed into that segment were replaced with return thunks. I didn't mention it explicitly in the original discussion, but there are three properties of return thunks which will help us here:

    • It is safe to invoke a return thunk even if the associated code segment is in memory. All that happens is that the "ensure the segment is present" step is a nop, and the return thunk simply continues with its work of recovering the original state.
    • It is safe to abandon a return thunk without needing to do any special cleanup. All the state used by the return thunk is stored in the patched stack itself, so if you want to abandon a return thunk, all you need to do is free the stack space.
    • It is safe to reuse a return thunk. Since they are statically allocated, you can use them over and over as long as the associated code segment has not been freed.

    The first property (idempotence of the return thunk) is no accident. It's required behavior in order for return thunks to work at all! After all, if the segment was loaded (say by a direct call or some other return thunk), then the return thunk needs to say, "Well, I guess that was easy," and simply skip the "load the target segment" step. (It still needs to do the rest of the work, of course.)

    The second property (abandonment) is also no accident. An application might decide to exit without returning all the way to Win­Main (the equivalent of calling Exit­Process instead of returning from Win­Main). This would abandon all the stack frames between the exit point and the Win­Main.

    The third property (reuse) is a happy accident. (Well, it was probably designed in for the purpose we're about to put it to right here.)

    Okay, now let's look at the jump buffer again. If you've been following along so far, you may have guessed the solution: Pre-patch the return address as if it had already been discarded. If it turns out that the segment was discarded, then the return thunk will restore it. If the segment is present (either because it was never discarded, or because it was discarded and reloaded, possibly at a new address), the return thunk will figure out where the code is and jump to it.

    Actually, since the state is being recorded in a jmp_buf, the tight space constraints of stack patching do not apply here. If it turns out you need 20 bytes of memory to record this information, then go ahead and make your jmp_buf 20 bytes. You don't have to try to make it all fit inside an existing stack frame.

    The jmp_buf therefore doesn't have to try to play the crazy air-squeezing games that stack patching did. It can record the return thunk, the handles to the data and stack segments, and the return IP without any encoding at all. And in fact, the long­jmp function doesn't need to invoke the return thunk directly. It can just extract the segment number after the initial INT 3Fh and pass that directly to the segment loader.

    (There is a little hitch if the address being returned to is fixed; in that case, there is no return thunk. But that just makes things easier: The lack of a return thunk means that the return address cannot be relocated, so there is no patching needed at all!)

    This magic with return thunks and segment reloading is internal to the operating system, so the core set­jmp and long­jmp functionality was provided by the kernel rather than the C runtime library in a pair of functions called Catch and Throw. The C runtime's set­jmp and long­jmp functions merely forwarded to the kernel versions.

    Thursday, January 2nd, 2014
    3:00 pm
    If the cursor clip rectangle is a global resource, how come I can't change it with ClipCursor?

    A customer reported that the Clip­Cursor function was not working. "The cursor clip rectangle is a shared global resource, so I should be able to change it any time I like. My app installs a global mouse hook and sets the clip cursor inside the hook function, but the change doesn't have any effect. Why can't I change the clip cursor inside a mouse hook?"

    Sure, you can change the clip cursor inside a mouse hook. But remember, a shared global resource cuts both ways. Since anybody can change it, your app can change it any time it likes. But since anybody can change it, another app can also change it any time they like.

    In this case, what's happening is that your hook comes in and sets the clip rectangle. And the application gets the mouse message and passes it to Def­Window­Proc, and the default behavior for focus changes is to clear the clip rectangle so that any clip rectangle set by the previous window doesn't spill over into the new focus window.

    The convention for the clip rectangle is that the focus window (perhaps after some negotiation with one of its ancestors) controls the clip rectangle.

    This convention is not enforced for a few reasons. First of all, you can't programmatically determine whether code is executing on behalf of any particular window. Even if you say "can be called only during the handling of a message", that doesn't prove that the code is associated with the window. The message handler might call into some other component, and that other component might decide to clip the cursor just because.

    Another reason the rule isn't enforced is that the clip cursor was invented back in the day when programmers were trusted to do the right thing. The theory was that preventing people from doing sneaky things would also prevent them from doing clever things.

    (Nowadays, the API design philosophy prefers to prevent people from doing sneaky things, even though it also prevents them from doing clever things, because the bad guys are sneakier than the good guys are clever.)

    Wednesday, January 1st, 2014
    3:00 pm
    We know your job is hard, you don't have to show us

    Some years ago, I attended a internal presentation where one group was teaching another group how to use their new feature. This particular feature was a "Just plug in the things you want, click the Apply button, and sit back and relax while we figure out how to do what you asked" type of feature.

    The presentation leader showed some examples of the feature in action, and gave some clear, concise guidance on how the feature should be used, guidance like "Use Pattern A when the user is faced with a choice between two clear options, but use Pattern B when the situation is more open-ended." So far so good.

    The next part of the presentation was given to the feature's lead designer. The lead designer called out some design principles that the feature adhered to. For example, "Notice that we always position the Widget above the Frob."

    But then the lead designer started getting into details that were basically a fifteen-minute way of saying, "Look how hard our job is." The designer called up the graphic design software, showing off the bazillion buttons and sliders and switches that the designers used to fine-tune the colors, gradients, and shading. The designer then went through the animation storyboard templates and showed how each of the carefully-plotted curves achieves the desired visual effect.

    Once we reached the "Look how hard our job is" portion, the presentation ground to a halt.

    The lead designer lost sight of the fact that all this information about how hard the feature was to design was not actionable. The attendees did not need this information in order to use the feature effectively. It was just showing off for the sake of showing off, and it basically wasted everybody's time.

    Tuesday, December 31st, 2013
    3:00 pm
    2013 year-end link clearance

    Another round of the semi-annual link clearance.

    And, as always, the obligatory plug for my column in TechNet Magazine:

    The retirement of TechNet Magazine also spells the end of the Windows Confidential column, so this is the last of the obligatory plugs, at least until I have some other obligatory thing to plug.

    3:00 pm
    New Year's Eve is sometimes a stressful occasion

    Today is New Year's Eve, another opportunity for to mark that an approximately-integral number of revolutions of the earth have occurred since some point in time that wasn't even calculated correctly in the first place. (We retain it for backward compatibility.)

    December 31, 1999 was a particularly anxious day in the technology sector. Microsoft's Director of Y2K Readiness and vice president of Product Support Services described some of the steps that were being taken to prepare for any timekeeping-related issues that would arise as the calendar ticked over to 2000.

    We've analyzed phone capacity, IT systems backup for both data and power, and additional lab environments for enhanced product support capabilities. We have redundant power systems, including back-up generators, at key locations in the unlikely event we lose power.

    I got to see one of those key locations, or at least I saw it from a distance.

    Parked outside Building 26, the home of the Core Operating Systems Division, were semi-trailers filled with computers and back-up generators, ready to spring into action in case of a disaster of biblical proportions and somebody needed to develop a hotfix to Windows on the spot as civilized society collapsed into anarchy all around. (Personally, I would think that if civilization were collapsing, you would have more important things to worry about than patching an operating system.)

    At least to make things more pleasant for the people who had to remain at work that night, Microsoft threw "a New Year's Eve party with a disc jockey, dinner, and a champagne toast at midnight. Our employees can bring their spouses and children, and we'll even host a separate children's party."

    Actually, it sounds like they had more fun than I did that night. I think I stayed at home and watched television.

    It turns out that civilization did not collapse into anarchy. There were minor glitches, but nothing serious. "No problems were seen in Angola, Uganda and Kenya, where the telephone system was said to be functioning as erratically as usual. Italy, one of the worst-prepared countries in the West, also appeared to cross into the new century without any major trouble." Nice to know you can be snarky in a newspaper article.

    (Nitpicker's corner: January 1, 2000 was not the first day of a new century.)

    Depending on how you look at it, Y2K was either an overhyped event created to generate revenue for the technology industry, or was a demonstration of how people can solve a major problem if they just worked hard and coöperated.

    Reality is probably a mix of the two.

    Happy new year, everybody. See you on the other side.

    Monday, December 30th, 2013
    3:00 pm
    There's no seating up there, so you just have to hang on for dear life

    I dreamed that through a friend, I got to join a handful of other people atop Prince Charles's carriage as it wound its way through London. There was no seating up there, so you just have to hang on for dear life. When we reached Buckingham Palace, the assembled crowd and reporters swarmed the carriage for an opportunity to meet the Prince. This provided a sufficient diversion to allow us to climb down from the roof and sneak into the palace undetected.

    We've come to the end of the year, so that's all for Monday dream blogging. For those of you who hated it: You can uncover your eyes now.

    3:00 pm
    How can I get the list of programs the same way that Programs and Features gets it?

    A customer wanted to get the list of programs the same way that the Programs and Features folder gets it.

    Here, here's an idea: Instead of trying to mimic the Programs and Features folder, just ask the Programs and Features folder for its contents! That way, no matter what changes are made to how the Programs and Features folder obtains its contents (and those changes occur pretty often), your program will always match it, because you're just showing the same thing.

    Here's the basic idea, in scripting language since it's quicker:

    var shell = new ActiveXObject("Shell.Application");
    var programsFolder = shell.Namespace(
        "::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\" +
        "::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}");
    var items = programsFolder.Items();
    for (var i = 0; i < items.Count; i++) {
        var item = items.Item(i);
        WScript.StdOut.WriteLine(item);
        WScript.StdOut.WriteLine("Size = " + item.ExtendedProperty("System.Size"));
        WScript.StdOut.WriteLine("------------");
    }
    

    Okay, first of all, how did I get that magic string for the Programs and Features folder? I opened the Control Panel and dragged the Uninstall a program link onto the program from a few weeks ago.

    The program itself is pretty straightforward. It's the standard enumerate everything in a folder and print it program we've seen before. The only trick was finding the folder.

    As for the C++ version, it should also look familiar, because we've done it before more than once. The only difference is the way we create the folder and the details we choose to display. (For extra credit: Change this program to bind to the persisted pidl instead of the parsing name.)

    int __cdecl wmain(int argc, wchar_t **argv)
    {
     CCoInitialize init;
     CComPtr<IShellItem> spPrinters;
     SHCreateItemFromParsingName(
       L"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\"
       L"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}", nullptr,
       IID_PPV_ARGS(&spPrograms));
     CComPtr<IEnumShellItems> spEnum;
     spPrograms->BindToHandler(nullptr, BHID_EnumItems,
                                  IID_PPV_ARGS(&spEnum));
     for (CComPtr<IShellItem> spProgram;
          spEnum->Next(1, &spProgram, nullptr) == S_OK;
          spProgram.Release()) {
      CComHeapPtr<wchar_t> spszName;
      spProgram->GetDisplayName(SIGDN_NORMALDISPLAY, &spszName);
      wprintf(L"%ls\n", spszName);
      PrintDetail(CComQIPtr<IShellItem2>(spProgram), &PKEY_Size, L"Size");</font>
     }
     return 0;
    }
    

    Bonus script: You can even see what verbs are available.

    var shell = new ActiveXObject("Shell.Application");
    var programsFolder = shell.Namespace(
        "::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\" +
        "::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}");
    var items = programsFolder.Items();
    for (var i = 0; i < items.Count; i++) {
        var item = items.Item(i);
        WScript.StdOut.WriteLine(item);
        WScript.StdOut.WriteLine("Size = " + item.ExtendedProperty("System.Size"));
        var verbs = item.Verbs();
        for (var j = 0; j < verbs.Count; j++) {
           var verb = verbs.Item(j);
           WScript.StdOut.WriteLine("Action: " + verb.Name);
        }
        WScript.StdOut.WriteLine("------------");
    }
    

    And if you're really ambitious, you can even call verb.DoIt to carry out the action. Don't use this power for evil.

    Note: Since we are working with the Programs and Features folder, we are necessarily targeting Windows Vista and later, since that was the version of Windows in which the Programs and Features folder was introduced. Therefore, I am free to use functionality introduced in Windows Vista.

    I've been doing Little Programs for a year now. I kind of like it, so I'm going to continue for another year, but I'm going to relax the rules a bit: The Little Programs are now just programs that I think are interesting for whatever reason. They don't need to actually solve a problem.

    Friday, December 27th, 2013
    3:00 pm
    Brief Q&A on the HeapEnableTerminationOnCorruption heap information flag

    Question: What type of heaps are controlled by the Heap­Enable­Termination­On­Corruption flag?

    Answer: Any user-mode heap created by the Heap­Create function. This includes the process heap (Get­Process­Heap) but not the managed heap. Some components use Heap­Create under the hood. If so, then those heaps would also be affected.

    Question: What versions of Windows support Heap­Enable­Termination­On­Corruption?

    Answer: The flag was introduced in Windows Vista and Windows Server 2008. It is also available on Windows XP Service Pack 3. In table form:

    Support Client Server

    Not Available
    ...
    Windows 2000
    Windows XP RTM, SP1, SP2
    ...
    Windows Server 2000
    Windows Server 2003
    Available
    Windows XP SP3
    Windows Vista
    Windows 7
    ...

    Windows Server 2008
    Windows Server 2008 R2
    ...

    Question: For operating systems that support it, under what conditions will termination on corruption be enabled?

    Answer:

    1. For all 64-bit processes.
    2. For all 32-bit processes whose executable sets the subsystem major version to 6 or higher in the image header.
    3. If you call Heap­Set­Information with the Heap­Enable­Termination­On­Corruption parameter.

    Question: What is the effect of setting the subsystem major version in a DLL? Will that control whether termination on corruption is enabled for any heaps created by my DLL?

    Answer: No. For the purpose of rule 2 above, it is the major subsystem of the executable that decides whether termination on corruption. The major subsystem of any DLLs loaded by the process have no effect. This is consistent with other process decisions.

    Question: Can I enable termination on corruption for some heaps but not others?

    Answer: No. Turning on termination on corruption turns it on for all heaps in the process.

    Question: Can I disable termination on corruption after it has been enabled?

    Answer: No. It is a one-way door.

    Thursday, December 26th, 2013
    3:00 pm
    I think we're going to be getting frozen leftovers for lunch today

    There are a few times a year when a large fraction of employees are out on vacation at the same time, such as a single work day wedged between a holiday and a weekend (as happened this year on July 5). The most extreme case of this is the week between the Christmas holiday and New Year's Day, where the offices are practically empty. On these days of low demand, many services are scaled back and some choose to close entirely so that they can do inventory, perform routine maintenance, or upgrade equipment.

    One of the most visible service reductions is in food service. Smaller locations (such as snack bars) are closed, and the kitchens which remain open offer a reduced menu. But just because most people are on vacation doesn't mean that nobody is watching. Here's a menu from one kitchen that was posted almost exactly one year ago:

    Breakfast Roberts Waffles
    Breakfast Burrito
    Warm Tortilla filled with Scrambled Egg, Golden Hash Browns, Onion Green Chiles, Monterey Jack Cheese and Salsa.
    Today's Soups Clam Chowder
    Tomato Basil Bisque
    Exhibition Station Closed for Holiday
    Shanghai Shanghai
    Greek Potato Salad
    A delicious blend of potatoes, tomatoes, red onions, flavored with mustard, parsley, dill seed, mint and lemon juice.
    Wild Greens Grill and Greens
    Chef's Table Station Closed for Holiday
    Pizza Specialty Pizza by the Slice
    Deli Mozzarella, Tomato and Basil Panini
    Mozzarella, Tomato and Basil Panini
    Spcied [sic] Cranberry Turkey Salad Served on a Flaky Crossaint [sic]
    Entrée Check Freezer
    Grill Tuna Melt
    Steak Frites
    3:00 pm
    Why is GetWindowLongPtr returning a garbage value on 64-bit Windows?

    A customer was running into problems with their application on 64-bit Windows 8. They claimed that on Windows 8, the Get­Window­Long­Ptr is returning a garbage pointer, which causes their program to crash. The same program works fine on 64-bit Windows 7. They asked the Windows team why they broke Get­Window­Long­Ptr.

    An investigation of the customer's code quickly turned up the issue:

    INT_PTR CALLBACK AwesomeDialogProc(
        HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
      Awesome *pThis = (Awesome*)GetWindowLongPtr(hdlg, DWLP_USER);
    
      switch (uMsg) {
      case WM_INITDIALOG:
        pThis = (Awesome*)lParam;
        SetWindowLongPtr(hdlg, DWLP_USER, (LONG)pThis);
        ...
        return TRUE;
    
       case WM_COMMAND:
         if (pThis != nullptr) {
           // This line crashes with pThis = garbage nonzero value
           return pThis->OnCommand(wParam, lParam);
         }
         return FALSE;
    
       ...
      }
      return FALSE;
    }
    

    See if you can spot the problem.

    The error is in the line that calls Set­Window­Long­Ptr. It takes the 64-bit pointer value pThis and casts it to a LONG, which is a 32-bit integer type. This truncates the pointer and throws away the upper 32 bits of data. Therefore, when read back, the pointer looks like garbage because the top 32 bits were set to zero (or to 0xFFFFFFFF, depending on the value of bit 31).

    Windows 8 made some improvements to the memory manager, and a side effect was a seemingly harmless change to the way memory is allocated in 64-bit processes. As a result of the change, pointer values greater than 4GB are much more common, which means that the pointer truncation will actually destroy data. (In Windows 7, the default heap tended to hang out below the 2GB boundary, so the code merely truncated zeros, which is mostly harmless.)

    What I found particularly interesting about this error is that the DWL_USER window long was specifically renamed to DWLP_USER in 64-bit Windows in order to force a build break. Therefore, developers had to go in and convert each separate use of [GS]et­Window­Long with DWL_USER to a version that used [GS]et­Window­Long­Ptr with DWLP_USER, being careful not to truncate the pointer.

    This customer missed that last little bit about not truncating the pointer, and all they did was a global search/replace:

    s/\bGetWindowLong\b/GetWindowLongPtr/g;
    s/\bSetWindowLong\b/SetWindowLongPtr/g;
    s/\bDWL_USER\b/DWLP_USER/g;
    

    "There, I fixed it."

    Wednesday, December 25th, 2013
    3:00 pm
    The chain of stories triggered by seeing a package of Ahoj-Brause

    While surfing the Web aimlessly doing valuable background research, I happened across a page that had a picture of a package of Ahoj-Brause (pronounced ahoy browse-uh). Seeing that package triggered a bunch of memories.

    My emergency vacation from several years ago included a visit to a friend spending the year at Uppsala University in Sweden. The following year, he invited one of his classmates (a student from Germany) to the United States to join his family for the Christmas holiday season.

    She brought with her some small gifts, among them a package of Ahoj-Brause. On its own, Ahoj-Brause is just a drink mix powder, but those in the know consume it by dumping the contents of a packet into your mouth, then adding a shot of vodka.

    (When written by hand, it looks like Ahoj-Braŭse with a breve over the u. That's a trait of German handwriting: A breve is written over the u so that it isn't confused with a handwritten n. Compare putting a slash through a 0 or a crossbar through a 7 to avoid confusion with O and 1, respectively.)

    During her visit, I got to practice some German, telling her the story of Bill Gates and the hotel next door. Her conclusion was that my German was fairly good, and with one month's immersion, I could become fluent. Unfortunately, it's not practical for me to spend a month in Germany just to bring my German skills from "fairly good" to "fluent". For one thing, my wife would be pretty annoyed. (And this is completely setting aside the question of "Why would you devote an entire month of your life to becoming fluent in German? If you're going to devote an entire month of your life to becoming fluent in another language, shouldn't it be Chinese?")

    Man, that's a lot of digressions before getting the story I actually wanted to tell.

    My friend's classmate wanted to head into downtown Seattle to do touristy things, so she was taken to the neighborhood bus stop and given instructions on which bus number to take and where to get off in downtown. That part of the plan worked great. The part that didn't work so great was returning home.

    When you're unfamiliar with an area, traveling a road in the opposite direction doesn't quite trigger the memory cells. My friend's classmate got on the return bus, but couldn't quite remember where to get off to get back home. She got off somewhere close, but the houses didn't look familiar. "Okay, now I'm lost. What do I do?" She had my friend's address and phone number, but she didn't have a mobile phone or a map of the residential neighborhood.

    She walked down the street and saw a house with the sign Mi casa es tu casa hanging by the front door. She considered this an indication that the people in the house were friendly and welcoming, so she knocked on the door. She was okay with the possibility that the people in the house spoke only Spanish, because she had been learning Spanish in anticipation of studying there the following semester. (For those who are keeping score, this means that my friend's classmate speaks at a minimum German, Swedish, English, and Spanish.)

    The assumption that they spoke Spanish was correct. (They also spoke English.) The assumption that the family was friendly and helpful also held up. What she didn't expect was that they spoke German, too! Apparently, the family spent a few years in Germany because the father was assigned there by his work.

    It so happens that she was only two blocks or so from my friend's home; the hard part of course is knowing which two blocks to go.

    The family was so enamored of their unexpected German-and-English-and-Spanish-speaking visitor that they invited her to stay for dinner, but she had to decline due to other plans for the evening.

    Bonus chatter: When my friend sent back some photos from Uppsala, he didn't include any description with the photos, so I made up my own narrative. I had to make up names for all the people in the photos, and Astrid was the name I chose for the subject of today's story. She liked the name so much that she adopted it as a secret nickname.

    Tuesday, December 24th, 2013
    3:00 pm
    Essays from the funniest man in Microsoft Research

    James Mickens has written a number of essays for ;login: magazine. The wall-of-text presentation is kind of scary, and the first time I encountered them, I skimmed the essays rather than reading them through. As a result, my reaction was, "I got tired." But if you follow the path and read the essays through, you realize that they are all brilliant.

    You can't just place a LISP book on top of an x86 chip and hope the hardware learns about lambda calculus by osmosis.

    and in the "so funny because it's true that it wraps around and isn't funny any more, but then wraps around a second time and is funny again, but with a tinge of sadness" category:

    I HAVE NO TOOLS BECAUSE I'VE DESTROYED MY TOOLS WITH MY TOOLS.
    ... because in those days, you could XOR anything with anything and get something useful.
    When researchers talk about mobile computers, they use visionary, exciting terms like "fast", "scalable", and "this solution will definitely work in practice."
    With careful optimization, only 14 gajillion messages are necessary.

    One of my colleagues found the stash of columns in the "Miscellaneous Excellence" section on Mickens' Web site and reacted with "This is better than getting free cookies."

    Here's an interview with "the funniest man in Microsoft Research".

    I would have done this for TechNet Magazine if I had known this was possible.

    Also if I had the talent.

    Mostly the talent part.

    Bonus Mickensian reading: What is art?

    Monday, December 23rd, 2013
    3:00 pm
    That doesn't sound like South Frisian to me

    I dreamed that I was back in college taking a course in South Frisian, but I suspected something was up because the words didn't sound Germanic at all, and we were taught the words to a Christmas carol as Nom Yom Hear What I Hear?

    Also, because the course was taught by known prevaricator/exaggerator Robert Irvine.

    3:00 pm
    Creating custom tasks on a jump list

    Today's Little Program adds a custom task to the application's jump list. Take the scratch program and make the following changes. (Remember, Little Programs do very little error checking because that's how they roll.)

    #include <shlobj.h>
    #include <propkey.h>
    #include <wrl/client.h>
    
    using namespace Microsoft::WRL;
    
    ComPtr<IShellLink>
    CreateShellLinkForTask(
        PCWSTR pszTitle,
        PCTSTR pszArgs,
        int idIcon)
    {
        ComPtr<IShellLink> spsl;
    
        CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spsl));
    
        wchar_t szBuf[MAX_PATH];
        GetModuleFileName(g_hinst, szBuf, ARRAYSIZE(szBuf));
        spsl->SetPath(szBuf);
    
        spsl->SetArguments(pszArgs);
        spsl->SetIconLocation(szBuf, idIcon);
    
        PROPVARIANT pvar;
        pvar.vt = VT_LPWSTR;
        pvar.pwszVal = const_cast<PWSTR>(pszTitle);
        ComPtr<IPropertyStore> spps;
        spsl.As(&spps);
        spps->SetValue(PKEY_Title, pvar);
        spps->Commit();
    
        return spsl;
    }
    

    This helper function creates an in-memory shell link object with the specified title, command line arguments, and icon. The underlying executable is assumed to be the running executable.

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      ComPtr<ICustomDestinationList> spcdl;
      CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_ALL,
                       IID_PPV_ARGS(&spcdl));
    
      ComPtr<IObjectCollection> spoc;
      UINT cMinSlots;
      spcdl->BeginList(&cMinSlots, IID_PPV_ARGS(&spoc));
      spoc->Clear();
    
      spoc->AddObject(CreateShellLinkForTask(L"New frob",
                       TEXT("/frob"), -2).Get());
    
      spcdl->AddUserTasks(spoc.Get());
      spcdl->CommitList();
    
      return TRUE;
    }
    

    When our window is created, we get the destination list for our application and ask it for an object collection so we can fill it with tasks. We empty the existing collection and add a single shortcut called "New frob" and which passes the /frob command line argument. The icon here is given as a negative number to indicate that it is an icon ID rather than an icon index. We then tell the destination list that this is our new task collection.

    Before we forget, let's add the icon to our resource file.

    // scratch.rc
    1 ICON icon1.ico
    2 ICON icon2.ico
    

    I'll leave you to find some icons to use. Icon number 2 is the one that will be used for the jump list. (Icon number 1 I left to represent the application itself.)

    Finally, we respond to the command line switch.

    int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPSTR lpCmdLine, int nShowCmd)
    {
      if (strcmp(lpCmdLine, "/frob") == 0) {
        MessageBox(nullptr, L"Frob!", L"Title", MB_OK);
        return 0;
      }
      ...
    }
    

    If the command line switch /frob is passed, then we say something silly. In real life, we would create a new frob, possibly by looking for an existing running copy of the program and asking it to do the creation.

    Okay, run this program and then right-click on the taskbar icon. Observe that there is now a New frob task, and if you select it, you get the silly message.

The Old New Thing   About Sviesta Ciba