March, 2011

  • The Old New Thing

    Charlie Sheen v Muammar Gaddafi: Whose line is it anyway?

    • 23 Comments

    I got seven out of ten right.

  • The Old New Thing

    Microspeak: Cadence

    • 22 Comments

    Originally, the term cadence meant the rate at which a regular event recurs, possibly with variations, but with an overall cycle that repeats. For example, the cadence for team meetings might be "Every Monday, with a longer meeting on the last meeting of each month."

    Project X is on a six-month release cadence, whereas Project Y takes two to three years between releases.

    Q: What was the cadence of email requests you sent out to drive contributions?

    A: We started with an announcement in September, with two follow-up messages in the next month.

    In what I suspect is a case of I want to use this cool word other people are using, even though I don't know exactly what it means, the term has been applied more broadly to mean schedule or timeline, even for nonrecurring events. Sample usage: "What is our cadence for making this available outside the United States?"

  • The Old New Thing

    The old DEBUG program can load COM files bigger than 64KB, but that doesn't mean they actually load as a program

    • 22 Comments

    Some times ago, I described why a corrupted binary sometimes results in the error "Program too big to fit in memory". Commenter Neil was under the impression that nonrelocatable programs files could be larger than 64KB and used the DEBUG command to verify this assertion.

    While it's true that DEBUG can load files bigger than 64KB, that doesn't mean that they will load as a program. If DEBUG decide that you didn't give it a program (the file extension is not EXE or COM),¹ then it treats the file on the command line as a data file and loads it into memory in its entirety, provided it fits in memory in its entirety. When it does this, the BX register contains the upper 16 bits of the file size, and CX contains the lower 16 bits. This is also the format that is used when writing files back out: Use the n command to set the name of the output file and set BX:CX to the file size.

    Even though DEBUG has been obsolete for over a decade, it is still useful for exactly this purpose: You can use it as a hex editor for files less than around 512KB.

    But don't deceive yourself into thinking that you created a COM file that is bigger than 64KB.

    ¹There is another extension which has special meaning to DEBUG, but it's not relevant to the discussion.

  • The Old New Thing

    How do I monitor, or even control, the lifetime of an Explorer window?

    • 19 Comments

    A customer wanted help with monitoring the lifetime of an Explorer window.

    We want to launch a copy of Explorer to open a specific folder, then wait until the user closes the folder before continuing. We tried launching a copy of Explorer with the folder on the command line, then doing a Wait­For­Single­Object on the process handle, but the wait sometimes completes immediately without waiting. How do we wait until the user closes the Explorer window?

    This is another case of solving a problem halfway and then having trouble with the other half.

    The reason that Wait­For­Single­Object returns immediately is that Explorer is a single-instance program (well, limited-instance). When you open an Explorer window, the request is handed off to a running copy of Explorer, and the copy of Explorer you launched exits. That's why your Wait­For­Single­Object returns immediately.

    Fortunately, the customer was willing to explain their underlying problem.

    We have a wizard that creates some files in a directory based on information provided by the user, and we want to launch Explorer to view that directory so users can verify that things are set up the way they want them. When users close the Explorer window, we ask them if everything was good; if not, then we back up and let the user try again.

    Aha, the program is using Explorer as a "view this folder for a little while" subroutine. Unfortunately, Explorer doesn't work that way. For example, the user might decide to use the Address Bar and go visit some other folders completely unrelated to your program, and your program would just be sitting there waiting for the user to close that window; meanwhile, the user doesn't realize that your program is waiting for it.

    What you can do is host the Explorer Browser control inside a page of your wizard and control it with interfaces like IExplorer­Browser. You can disable navigation in the Explorer Browser (so the user can look only at the folder you want to preview), and the user can click Back if they want to try again or Next if they are happy and want to continue. This has the additional advantage of keeping all the parts of your wizard inside the wizard framework itself, allowing users to continue using the wizard navigation model that they are already familiar with.

    A sample program which uses the Explorer Browser control can be found in the Platform SDK.

    For the impatient, here's the scratch program version. Note that this is the minimal version; in real life, you would probably want to set some options and stuff like that.

    #include <shlobj.h>
    
    IExplorerBrowser *g_peb;
    
    void
    OnSize(HWND hwnd, UINT state, int cx, int cy)
    {
        if (g_peb) {
            RECT rc = { 0, 0, cx, cy };
            g_peb->SetRect(NULL, rc);
        }
    }
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
        BOOL fSuccess = FALSE;
        RECT rc;
        PIDLIST_ABSOLUTE pidl = NULL;
        if (SUCCEEDED(CoCreateInstance(CLSID_ExplorerBrowser, NULL,
                             CLSCTX_INPROC, IID_PPV_ARGS(&g_peb))) &&
            GetClientRect(hwnd, &rc) &&
            SUCCEEDED(g_peb->Initialize(hwnd, &rc, NULL)) &&
            SUCCEEDED(SHParseDisplayName(
                             L"C:\\Program Files\\Internet Explorer",
                                            NULL, &pidl, 0, NULL)) &&
            SUCCEEDED(g_peb->SetOptions(EBO_NAVIGATEONCE)) &&
            SUCCEEDED(g_peb->BrowseToIDList(pidl, SBSP_ABSOLUTE))) {
            fSuccess = TRUE;
        }
        ILFree(pidl);
        return fSuccess;
    }
    
    void
    OnDestroy(HWND hwnd)
    {
        if (g_peb) {
            g_peb->Destroy();
            g_peb->Release();
        }
        PostQuitMessage(0);
    }
    

    This same technique of hosting the Explorer Browser control can be used for other types of "build your own burrito" scenarios: For example, you might host the Explorer Browser control in a window and tell users to copy files into that window. When they click OK or Next or whatever, you can enumerate the contents of the folder and do your business.

    Armed with this knowledge, you can answer these customers' questions:

    We have found that the process state of Explorer.exe changes to signaled before the process terminates. Here's a sample program:

    int _tmain(int argc, TCHAR **argv)
    {
     STARTUPINFO si = { sizeof(si) };
     PROCESS_INFORMATION pi;
     if (CreateProcess(TEXT("C:\\Windows\\Explorer.exe"), TEXT(" /e,C:\\usr"),
                       NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
      WaitForSingleObject(pi.hProcess);
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
     }
     return 0;
    }
    

    If we change "Explorer.exe" to "Notepad.exe" then the process handle is signaled after Notepad terminates, as expected.

    We have a 32-bit shell extension for which a 64-bit version is not available. Since our clients are running 64-bit Windows, the 32-bit shell extension is not available in Explorer. How can we obtain access to this context menu?
    We have a shell extension that is not UAC-compliant. It requires that the user have administrative privileges in order to function properly. We would rather not disable UAC across the board just for this one shell extension. Is there a workaround that lets us run Explorer elevated temporarily?

    Bonus sample program: The Explorer Browser Search Sample shows how to filter the view.

    Bonus alternative: If you really just want to watch Explorer windows rather than host one, you can use the ShellWindows object, something I covered many years ago (and followed up with a much shorter scripting version).

  • The Old New Thing

    How can I generate a consistent but unique value that can coexist with GUIDs?

    • 18 Comments

    A customer needed to generate a GUID for each instance of a hardware device they encounter:

    The serial number for each device is 20 bits long (four and a half bytes). We need to generate a GUID based on each device, subject to the constraints that when a device is reinserted, we generate the same GUID for it, that no two devices generate the same GUID, and that the GUIDs we generate not collide with GUIDs generated by other means. One of our engineers suggested just running uuidgen and substituting the serial number for the final nine hex digits. Is this a viable technique?

    This is similar to the trap of thinking that half of a GUID is just as unique as the whole thing. Remember that all the pieces of a GUID work together to establish uniqueness. If you just take parts of it, then the algorithm breaks down.

    For this particular case, you're in luck. The classic Type 1 GUID uses 60 bits to encode the timestamp and 48 bits to identify the location (computer). You can take a network card, extract the MAC address, then smash the card with a hammer. Now you have a unique location. Put your twenty bits of unique data as the timestamp, and you have a Type 1 GUID that is guaranteed never to collide with another GUID.

    If you have more than 60 bits of unique data, then this trick won't work. Fortunately, RFC4122 explains how to create a so-called name-based UUID, which is a UUID that can be reliably regenerated from the same source data. Section 4.3 explains how it's done. The result is either a type 3 or type 5 UUID, depending on which variant of the algorithm you chose.

  • The Old New Thing

    Although the x64 calling convention reserves spill space for parameters, you don't have to use them as such

    • 18 Comments

    Although the x64 calling convention reserves space on the stack as spill locations for the first four parameters (passed in registers), there is no requirement that the spill locations actually be used for spilling. They're just 32 bytes of memory available for scratch use by the function being called.

    We have a test program that works okay when optimizations are disabled, but when compiled with full optimizations, everything appears to be wrong right off the bat. It doesn't get the correct values for argc and argv:

    int __cdecl
    wmain( int argc, WCHAR** argv ) { ... }
    

    With optimizations disabled, the code is generated correctly:

            mov         [rsp+10h],rdx  // argv
            mov         [rsp+8],ecx    // argc
            sub         rsp,158h       // local variables
            mov         [rsp+130h],0FFFFFFFFFFFFFFFEh
            ...
    

    But when we compile with optimizations, everything is completely messed up:

            mov         rax,rsp 
            push        rsi  
            push        rdi  
            push        r13  
            sub         rsp,0E0h 
            mov         qword ptr [rsp+78h],0FFFFFFFFFFFFFFFEh 
            mov         [rax+8],rbx    // ??? should be ecx (argc)
            mov         [rax+10h],rbp  // ??? should be edx (argv)
    

    When compiler optimizations are disabled, the Visual C++ x64 compiler will spill all register parameters into their corresponding slots. This has as a nice side effect that debugging is a little easier, but really it's just because you disabled optimizations, so the compiler generates simple, straightforward code, making no attempts to be clever.

    When optimizations are enabled, then the compiler becomes more aggressive about removing redundant operations and using memory for multiple purposes when variable lifetimes don't overlap. If it finds that it doesn't need to save argc into memory (maybe it puts it into a register), then the spill slot for argc can be used for something else; in this case, it's being used to preserve the value of rbx.

    You see the same thing even in x86 code, where the memory used to pass parameters can be re-used for other purposes once the value of the parameter is no longer needed in memory. (The compiler might load the value into a register and use the value from the register for the remainder of the function, at which point the memory used to hold the parameter becomes unused and can be redeployed for some other purpose.)

    Whatever problem you're having with your test program, there is nothing obviously wrong with the code generation provided in the purported defect report. The problem lies elsewhere. (And it's probably somewhere in your program. Don't immediately assume that the reason for your problem is a compiler bug.)

    Bonus chatter: In a (sadly rare) follow-up, the customer confessed that the problem was indeed in their program. They put a function call inside an assert, and in the nondebug build, they disabled assertions (by passing /DNDEBUG to the compiler), which means that in the nondebug build, the function was never called.

    Extra reading: Challenges of debugging optimized x64 code. That .frame /r command is real time-saver.

  • The Old New Thing

    Although the default icon for a shortcut is the icon of the target, you can override that

    • 17 Comments

    A customer reported that a shortcut they deployed to their employees' desktops was triggering unwanted server traffic.

    My customer deploys a shortcut on %ALLUSERSPROFILE%\Desktop, and this shortcut points to an EXE file on a remote server. Once a local user logs on, the computer will try logging onto the remote computer to query information and generate a login failure alert on the server.

    Is there any way to stop Explorer from querying the shortcut information?

    Fortunately, the customer provided context for the question, because the question the customer is asking doesn't actually match the scenario. The customer doesn't want to stop Explorer from querying the shortcut information; the customer just wants to stop Explorer from contacting the server to get the icon.

    The default icon for a shortcut is the icon of the target, and in order to get that icon, Explorer needs to contact the target. But you can override that default. Programmatically, you call IShellLink::SetIconLocation; interactively, you view the shortcut's properties and click Change Icon.... In either case, set it to an icon that doesn't reside on the server. Save the changes and deploy the modified shortcut.

  • The Old New Thing

    How to rescue a broken stack trace: Recovering the EBP chain

    • 15 Comments

    When debugging, you may find that the stack trace falls apart:

    ChildEBP RetAddr
    001af118 773806a0 ntdll!KiFastSystemCallRet
    001af11c 7735b18c ntdll!ZwWaitForSingleObject+0xc
    001af180 7735b071 ntdll!RtlpWaitOnCriticalSection+0x154
    001af1a8 2f6db1a9 ntdll!RtlEnterCriticalSection+0x152
    001af1b4 2fe8d533 ABC!CCriticalSection::Lock+0x12
    001af1d0 2fe8d56a ABC!CMessageList::Lock+0x24
    001af234 2f6e47ac ABC!CMessageWindow::UpdateMessageList+0x231
    001af274 2f6f040e ABC!CMessageWindow::UpdateContents+0x84
    001af28c 2f6e4474 ABC!CMessageWindow::Refresh+0x1a8
    001af360 2f6e4359 ABC!CMessageWindow::OnChar+0x4c
    001af384 761a1a10 ABC!CMessageWindow::WndProc+0xb31
    00000000 00000000 USER32!GetMessageW+0x6e
    

    This can't possible be the complete stack. I mean, where's the thread procedure? That should be at the start of the stack for any thread.

    What happened is that the EBP chain got broken, and the debugger can't walk the stack any further. If the code was compiled with frame pointer optimization (FPO), then the compiler will not create EBP frames, permitting it to use EBP as a general purpose register instead. This is great for optimization, but it causes trouble for the debugger when it tries to take a stack trace through code compiled with FPO for which it does not have the necessary information to decode these types of stacks.

    Begin digression: Traditionally, every function began with the sequence

            push ebp      ;; save caller's EBP
            mov ebp, esp  ;; set our EBP to point to this "frame"
            sub esp, n    ;; reserve space for local variables
    

    and ended with

            mov esp, ebp  ;; discard local variables
            pop ebp       ;; recover caller's EBP
            ret n
    

    This pattern is so common that the x86 has dedicated instructions for it. The ENTER n,0 instruction does the push / mov / sub, and the LEAVE instruction does the mov / pop. (In C/C++, the value after the comma is always zero.)

    if you look at what this does to the stack, you see that this establishes a linked list of what are called EBP frames. Suppose you have the following code fragment:

    void Top(int a, int b)
    {
     int toplocal = b + 5;
     Middle(a, local);
    }
    
    void Middle(int c, int d)
    {
     Bottom(c+d);
    }
    
    void Bottom(int e)
    {
     int bottomlocal1, bottomlocal2;
     ...
    }
    

    When execution reaches the ... inside function Bottom the stack looks like the following. (I put higher addresses at the top; the stack grows downward. I also assume that the calling convention is __stdcall and that the code is compiled with absolutely no optimization.)

    Top's stack frame  
    0040F8F8 parameter b passed to Top During execution of Top,
    EBP = 0040F8EC
    0040F8F4 parameter a passed to Top
    0040F8F0 return address of Top's caller
    0040F8EC EBP of Top's caller
    0040F8E8 toplocal
    Middle's stack frame  
    0040F8E4 parameter d passed to Middle During execution of Middle,
    EBP = 0040F8D8
    0040F8E0 parameter c passed to Middle
    0040F8DC return address of Middle's caller
    0040F8D8 0040F8EC = EBP of Middle's caller
    Bottom's stack frame  
    0040F8D4 parameter e passed to Bottom During execution of Bottom,
    EBP = 0040F8CC
    0040F8D0 return address of Bottom's caller
    0040F8CC 0040F8D8 = EBP of Bottom's caller
    0040F8C8 bottomlocal1
    0040F8C4 bottomlocal2

    Each stack frame is identified by the EBP value which the function uses during its execution.

    The structure of each stack frame is therefore

    [ebp+n]Offsets greater than 4 access parameters
    [ebp+4]Offset 4 is the return address
    [ebp+0]Zero offset accesses caller's EBP
    [ebp-n]Negative offsets access locals

    And the stack frames are all connected to each other in the form of a linked list threaded through the EBP values. This linked list is known as the EBP chain. End digression.

    To recover from the broken EBP chain, start dumping the stack a little before things go bad (in this case, I would start at 001af384-80) and then look for something that looks like a valid stack frame. Since the parameters and locals to a function can be pretty much anything, all you have left to work with is the EBP and the return address. In other words, you are looking for pairs of values of the form

    «pointer a little higher up the stack».
    «code address»
    

    In this case, I got lucky and didn't have to go very far:

      001af474  00000000
     -001af478  001af494
    / 001af47c  14f4fba8 DEF!SubclassBase::CallOriginalWndProc+0x1a
    | 001af480  2f6e4317 ABC!CMessageWindow::WndProc
    | 001af484  00970338
    | 001af488  0000000f
    | 001af48c  00000000
    \ 001af490  00000000
     >001af494  001af4f0
      001af498  14f4fcd6 DEF!SubclassBase::ForwardMessage+0x23
      001af49c  00970338
      001af4a0  0000000f
      001af4a4  00000000
      001af4a8  00000000
      001af4ac  00000000
      001af4b0  2f6e4317 ABC!CMessageWindow::WndProc
      001af4b4  ed758311
      001af4b8  00000000
      001af4bc  15143f70
      001af4c0  00000000
      001af4c4  14f4fb8e DEF!CView::SortItems+0x96
      001af4c8  00000000
      001af4cc  2f6e4317 ABC!CMessageWindow::WndProc
      001af4d0  00000000
    

    At stack address 001af478, we have a pointer to memory higher up the stack followed by a code address. if you follow that pointer, it points to another instance of the same pattern: A pointer higher up the stack followed by the code address.

    Once you find where the EBP chain resumes, you can ask the debugger to resume its stack trace from that point with the =n option to the k command.

    0:000> k=001af478
    ChildEBP RetAddr
    001af478 14f4fba8 ntdll!KiFastSystemCallRet
    001af494 14f4fcd6 DEF!SubclassBase::CallOriginalWndProc+0x1a
    001af4f0 14f4fc8b DEF!SubclassBase::ForwardMessage+0x23
    001af514 14f32dd1 DEF!SubclassBase::ForwardChar+0x59
    001af530 14f4fcd6 DEF!SubclassBase::OnChar+0x3c
    001af58c 14f4fd76 DEF!HelpSubclass::WndProc+0x51
    001af5e4 761a1a10 DEF!SubclassBase::s_WndProc+0x1b
    001af610 761a1ae8 USER32!GetMessageW+0x6e
    001af688 761a1c03 USER32!GetMessageW+0x146
    001af6e4 761a3656 USER32!GetMessageW+0x261
    001af70c 77380e6e USER32!OffsetRect+0x4d
    001af784 761a2a98 ntdll!KiUserCallbackDispatcher+0x2e
    001af794 698fd0aa USER32!DispatchMessageW+0xf
    001af7a4 2f7bf15c ABC!CThread::DispatchMessageW+0x23
    001af7e0 2f7befc9 ABC!CMessageWindow::MessageLoop+0x3a2
    001af808 2ff56d20 ABC!CMessageWindow::ThreadProc+0x9f
    001af898 75c2384b ABC!CMessageWindow::s_ThreadProc+0x10
    001af8a4 7735a9bd kernel32!BaseThreadInitThunk+0x12
    001af8e4 00000000 ntdll!LdrInitializeThunk+0x4d
    

    When you do this, make sure to ignore the first line of the resumed stack trace, since that is based on your current EIP, not the return address stored in the stack frame.

    Today was really just a warm-up for another debugging technique that I haven't finished writing up yet, so you're just going to be in suspense for another two years or so, though if you attended my TechEd China talk, you already know where I'm going.

    Bonus reading: In Ryan Mangipano's two-part series on kernel mode stack overflows, the second part does a bit of EBP chain chasing. (Feel free to read the first part, as well as earlier discussion on the subject of stack overflows.)

  • The Old New Thing

    What does the "l" in lstrcmp stand for?

    • 15 Comments

    If you ask Michael Kaplan, he'd probably say that it stands for lame.

    In his article, Michael presents a nice chart of the various L-functions and their sort-of counterparts. There are other L-functions not on his list, not because he missed them, but because they don't have anything to do with characters or encodings. On the other hand, those other functions help shed light on the history of the L-functions. Those other functions are lopen, lcreat, lread, lwrite, lclose, and llseek. There are all L-version sort-of counterparts to open, creat, and read, write, close, and lseek. Note that we've already uncovered the answer to the unasked question "Why does llseek have two L's?" The first L is a prefix (whose meaning we will soon discover) and the second L comes from the function it's sort-of acting as the counterpart to.

    But what does the L stand for? Once you find those other L-functions, you'll see next door the H-functions hread and hwrite. As we learned a while back, being lucky is simply observing things you weren't planning to observe. We weren't expecting to find the H-functions, but there they were, and they blow the lid off the story.

    The H prefix in hread and hwrite stands for huge. Those two functions operated on so-called huge pointers, which is 16-bit jargon for pointers to memory blocks larger than 64KB. To increment your average 16:16 pointer by one byte, you increment the bottom 16 bits. But when the bottom 16 bits contain the value 0xFFFF, the increment rolls over, and where do you put the carry? If the pointer is a huge pointer, the convention is that the byte that comes after S:0xFFFF is (S+__AHINCR):0x0000, where __AHINCR is a special value exported by the Windows kernel. If you allocate memory larger than 64KB, the GlobalAlloc function breaks your allocation into 64KB chunks and arranges them so that incrementing the selector by __AHINCR takes you from one chunk to the next.

    Working backwards, then, the L prefix therefore stands for long. These functions explicitly accept far pointers, which makes them useful for 16-bit Windows programs since they are independent of the program's memory model. Unlike the L-functions, the standard library functions like strcpy and read operate on pointers whose size match the data model. If you write your program in the so-called medium memory model, then all data pointers default to near (i.e., they are 16-bit offsets into the default data segment), and all the C runtime functions operate on near pointers. This is a problem if you need to, say, read some data off the disk into a block of memory you allocated with GlobalAlloc: That memory is expressible only as a far pointer, but the read function accepts a near pointer.

    To the rescue comes the lread function, which you can use to read from the disk into your far pointer.

    How did Windows decide which C runtime functions should have corresponding L-functions? They were the functions that Windows itself used internally, and which were exported as a courtesy.

    Okay, now let's go back to the Lame part. Michael Kaplan notes that the lstrcmp and lstrcmpi functions actually are sort-of counterparts to strcoll and strcolli. So why weren't these functions called lstrcoll and lstrcolli instead?

    Because back when lstrcmp and lstrcmpi were being named, the strcoll and strcolli functions hadn't been invented yet! It's like asking, "Why did the parents of General Sir Michael Jackson give him the same name as the pop singer?" or "Why didn't they use the Space Shuttle to rescue the Apollo 13 astronauts?"

  • The Old New Thing

    You can extend the PROPSHEETPAGE structure with your own bonus data

    • 15 Comments

    ... for when regular strength lParam just isn't enough.

    A little-known and even less-used feature of the shell property sheet is that you can hang custom data off the end of the PROPSHEETPAGE structure, and the shell will carry it around for you. Mind you, the shell carries it around by means of memcpy and destroys it by just freeing the underlying memory, so whatever you stick on the end needs to be plain old data. (Though you do get an opportunity to "construct" and "destruct" if you register a PropSheetPageProc callback, during which you are permitted to modify your bonus data and the lParam field of the PROPSHEETPAGE.)

    Here's a program that illustrates this technique. It doesn't do much interesting, mind you, but maybe that's a good thing: Makes for fewer distractions.

    #include <windows.h>
    #include <prsht.h>
    
    HINSTANCE g_hinst;
    
    struct ITEMPROPSHEETPAGE : public PROPSHEETPAGE
    {
     int cWidgets;
     TCHAR szItemName[100];
    };
    

    ITEMPROPSHEETPAGE is a custom structure that appends our bonus data (an integer and a string) to the standard PROPSHEETPAGE. This is the structure that our property sheet page will use.

    INT_PTR CALLBACK DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
    {
     switch (uiMsg) {
     case WM_INITDIALOG:
      {
       ITEMPROPSHEETPAGE *ppsp =
          reinterpret_cast<ITEMPROPSHEETPAGE*>(lParam));
       SetDlgItemText(hwnd, 100, ppsp->szItemName);
       SetDlgItemInt(hwnd, 101, ppsp->cWidgets, FALSE);
      }
      return TRUE;
     }
     return FALSE;
    }
    

    The lParam passed to WM_INITDIALOG is a pointer to the shell-managed copy of the PROPSHEETPAGE structure. Since we associated this dialog procedure with a ITEMPROPSHEETPAGE structure, we can cast it to the larger structure to get at our bonus data (which the shell happily memcpy'd from our copy into the shell-managed copy).

    HPROPSHEETPAGE CreateItemPropertySheetPage(
        int cWidgets, PCTSTR pszItemName)
    {
     ITEMPROPSHEETPAGE psp;
     ZeroMemory(&psp, sizeof(psp));
     psp.dwSize = sizeof(psp);
     psp.hInstance = g_hinst;
     psp.pszTemplate = MAKEINTRESOURCE(1);
     psp.pfnDlgProc = DlgProc;
     psp.cWidgets = cWidgets;
     lstrcpyn(psp.szItemName, pszItemName, 100);
     return CreatePropertySheetPage(&psp);
    }
    

    It is here that we associate the DlgProc with the ITEMPROPSHEETPAGE. Just to highlight that the pointer passed to the DlgProc is a copy of the ITEMPROPSHEETPAGE used to create the property sheet page, I created a separate function to create the property sheet page, so that the ITEMPROPSHEETPAGE on the stack goes out of scope, making it clear that the copy passed to the DlgProc is not the one we passed to CreatePropertySheetPage.

    Note that you must set the dwSize of the base PROPSHEETPAGE to the size of the PROPSHEETPAGE plus the size of your bonus data. In other words, set it to the size of your ITEMPROPSHEETPAGE.

    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
                       LPSTR lpCmdLine, int nCmdShow)
    {
     g_hinst = hinst;
     HPROPSHEETPAGE hpage =
       CreateItemPropertySheetPage(42, TEXT("Elmo"));
     if (hpage) {
      PROPSHEETHEADER psh = { 0 };
      psh.dwSize = sizeof(psh);
      psh.dwFlags = PSH_DEFAULT;
      psh.hInstance = hinst;
      psh.pszCaption = TEXT("Item Properties");
      psh.nPages = 1;
      psh.phpage = &hpage;
      PropertySheet(&psh);
     }
     return 0;
    }
    

    Here is where we display the property sheet. It looks just like any other code that displays a property sheet. All the magic happens in the way we created the HPROPSHEETPAGE.

    If you prefer to use the PSH_PROPSHEETPAGE flag, then the above code could have been written like this:

    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
                       LPSTR lpCmdLine, int nCmdShow)
    {
     ITEMPROPSHEETPAGE psp;
     ZeroMemory(&psp, sizeof(psp));
     psp.dwSize = sizeof(psp);
     psp.hInstance = g_hinst;
     psp.pszTemplate = MAKEINTRESOURCE(1);
     psp.pfnDlgProc = DlgProc;
     psp.cWidgets = cWidgets;
     lstrcpyn(psp.szItemName, pszItemName, 100);
    
     PROPSHEETHEADER psh = { 0 };
     psh.dwSize = sizeof(psh);
     psh.dwFlags = PSH_PROPSHEETPAGE;
     psh.hInstance = hinst;
     psh.pszCaption = TEXT("Item Properties");
     psh.nPages = 1;
     psh.ppsp = &psp;
     PropertySheet(&psh);
     return 0;
    }
    

    If you want to create a property sheet with more than one page, then you would pass an array of ITEMPROPSHEETPAGEs. Note that passing an array requires all the pages in the array to use the same custom structure (because that's how arrays work; all the elements of an array are the same type).

    Finally, here's the dialog template. Pretty anticlimactic.

    1 DIALOG 0, 0, PROP_SM_CXDLG, PROP_SM_CYDLG
    STYLE WS_CAPTION | WS_SYSMENU
    CAPTION "General"
    FONT 8, "MS Shell Dlg"
    BEGIN
        LTEXT "Name:",-1,7,11,42,14
        LTEXT "",100,56,11,164,14
        LTEXT "Widgets:",-1,7,38,42,14
        LTEXT "",101,56,38,164,14
    END
    

    And there you have it. Tacking custom data onto the end of a PROPSHEETPAGE, an alternative to trying to cram everything into a single lParam.

    Exercise: Observe that the size of the PROPSHEETPAGE structure has changed over time. For example, the original PROPSHEETPAGE ends at the pcRefParent. In Windows 2000, there are two more fields, the pszHeaderTitle and pszHeaderSubTitle. Windows XP added yet another field, the hActCtx. Consider a program written for Windows 95 that uses this technique. How does the shell know that the cWidgets is really bonus data and not a pszHeaderTitle?

Page 2 of 3 (28 items) 123