• 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

    Why did Win32 define BOOL as a signed int instead of an unsigned int?

    • 48 Comments

    Igor Levicki wants somebody from Microsoft to explain why BOOL was defined as a signed int instead of an unsigned int.

    You don't need to work for Microsoft to figure this out. All the information you need is publically available.

    Quoting from K&R Classic, which was the operative C standards document at the time Windows was being developed:

    7.6 Relational Operators

    The [relational operators] all yield 0 if the specified relation is false and 1 if it is true. The type of the result is int.

    Win32 defined BOOL as synonymous with int because Brian and Dennis said so. If you want to know why Brian and Dennis decided to have the result of relational operators be signed instead of unsigned, you'll have to ask them.

  • The Old New Thing

    Paul Cézanne and Camille Saint-Saëns may have similar-sounding last names, but they are not the same person

    • 12 Comments

    Next week, the Seattle Symphony Orchestra performs the Saint-Saëns Organ Symphony, but the people responsible for the symphony's radio advertisements don't realize that.

    As the strains of the symphony resound in the background, the announcer proudly announces that tickets are still available for "Cézanne's Organ Symphony."

    The names Cézanne and Saint-Saëns may sound similar, but I can assure you that they are not the same person. Here's a handy chart:

    Cézanne Saint-Saëns
    French X X
    Born in 1830's X X
    Master painter X  
    Master composer   X
    Carnival X  
    The Carnival of the Animals   X
    Died of pneumonia X X

    This is not the first time the Seattle Symphony's radio announcer has made this substitution. I don't know whether somebody is giving him incorrect copy to read, or whether he is performing on-the-fly auto-correction. Or maybe the announcer guy asked, "How do you pronounce Saint-Saëns?" and somebody answered, "It sounds like Cézanne."

    And now that I cleared everything up, let me confuse things some more:

  • 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

    Function requirements are cumulative: If you fail to meet any of them, then all bets are off

    • 33 Comments

    A customer was having problems with the Wait­For­Multiple­Objects function:

    We are looking for a clarification of the behavior of Wait­For­Multiple­Objects. We have a thread that waits on two handles (call them Handle1 and Handle2) with Wait­For­Multiple­Objects, bWaitAll = FALSE. Under certain conditions, we signal Handle1 and close Handle2 from another thread while the wait is in progress. This results in WAIT_FAILED being returned from the wait call. MSDN is not clear on what the expected behavior is here. On the one hand, it says

    1. When bWait is FALSE, this function checks the handles in the array in order starting with index 0, until one of the objects is signaled. If multiple objects become signaled, the function returns the index of the first handle in the array whose object was signaled.

    This description implies that the wait should never fail, because the function should have noticed that Handle1 is signaled before it got around to noticing that Handle2 was invalid.

    On the other hand, the documentation also says

    1. If one of these handle is closed while the wait is still pending, the function's behavior is undefined.

    What behavior is guaranteed here?

    Once you violate a constraint (in this case, the constraint that the handles remain valid for the duration of the wait operation), you have failed to meet the prerequisites and the behavior is undefined. You can't say, "Well, sure you say I can't do X, but if you follow exactly the steps given in this description of how signaled objects are selected, then the function wouldn't even have noticed X before coming to a decision, so the fact that I broke one of the rules is irrelevant!"

    The description of the behavior of the Wait­For­Multiple­Objects function when bWait is FALSE is not an implementation specification. It's a description of how to interpret the behavior of the function. The algorithmic way the function behavior is described is to assist in understanding the behavior; it doesn't mean that the function actually follows the algorithm step-by-step. (In reality, there is no polling loop, as the algorithmic description implies. All the handles are waited on simultaneously. It's like Lebesgue integration: You integrate over the entire domain at once.)

    An algorithm-free description of the behavior when bWait is false might go like this:

    1. When bWait is FALSE, the function does not return until one of the handles in the array becomes signaled. The return value is the smallest index corresponding to a signaled handle.

    This description is purely declarative but gives you no mental model.

    It's like saying that "Water seeks its own level." Water doesn't have a will that compels it to behave in a certain way, but describing the behavior in that way makes reasoning about water easier.

  • The Old New Thing

    Why is there the message '!Do not use this registry key' in the registry?

    • 40 Comments

    Under Software\Microsoft\Windows\Current­Version\Explorer\Shell Folders, there is a message to registry snoopers: The first value is called "!Do not use this registry key" and the associated data is the message "Use the SH­Get­Folder­Path or SH­Get­Known­Folder­Path function instead."

    I added that message.

    The long and sad story of the Shell Folders key explains that the registry key exists only to retain backward compatibility with four programs written in 1994. There's also a TechNet version of the article which is just as sad but not as long.

    One customer saw this message and complained, "That registry key and that TechNet article explain how to obtain the current locations of those special folders, but they don't explain how to change them. This type of woefully inadequate documentation only makes the problem worse."

    Hey, wow, a little message in a registry key and a magazine article are now "documentation"! The TechNet article is historical background. And the registry key is just a gentle nudge. Neither is documentation. It's not like I'm going to put a complete copy of the documentation into a registry key. Documentation lives in places like MSDN.

    But it seems that some people need more than a nudge; they need a shove. Let's see, we're told that the functions for obtaining the locations of known folders are SH­Get­Folder­Path and its more modern counterpart SH­Get­Known­Folder­Path. I wonder what the names of the functions for modifying those locations might be?

    Man that's a tough one. I'll let you puzzle that out for a while.

    Okay, here, I'll tell you: The corresponding functions go by the completely unobvious names SH­Set­Folder­Path and SH­Set­Known­Folder­Path.

    Sorry you had to use your brain. I'll let you get back to programming now.

  • The Old New Thing

    How does the C runtime know whether to use the static-linking or dynamic-linking version of the header file?

    • 14 Comments

    In response to a description of what happens when you get dll­import wrong, nksingh asks, "This seems like a problem for the CRT. As far as I know, VC gives you the option of statically or dynamically linking the CRT. But it seems like the headers will have to make a choice to support one thing better than the other. Conditional compilation would work, but then people would have to remember to include a #define somewhere. Is this dllimport vs. static linking thing something the compiler could figure out on its own if you're doing Link-time codegen?"

    Let's start from the beginning.

    Yes, this would be a problem for the CRT since it wouldn't know whether to declare the functions as normal static functions or as dllimport-style functions, and the headers have to make a choice which way to go.

    And if you look at the headers, you can see that it is indeed done via conditional compilation.

    ...
    _CRTIMP int __cdecl fflush(FILE * _File);
    ...
    

    This magic _CRTIMP symbol is defined in crtdefs.h like so:

    /* Define _CRTIMP */
    #ifndef _CRTIMP
    #ifdef _DLL
    #define _CRTIMP __declspec(dllimport)
    #else  /* _DLL */
    #define _CRTIMP
    #endif  /* _DLL */
    #endif  /* _CRTIMP */
    

    Conditional compilation decides whether _CRTIMP expands to __declspec(dllimport) or to nothing at all, depending on whether the _DLL symbol is defined.

    And yet nobody bothers writing #define _DLL before they #include <stdio.h>. There must be something else going on.

    In fact, we can run some experiments to see what's going on.

    #ifdef _DLL
    #error "_DLL is defined"
    #else
    #error "_DLL is not defined"
    #endif
    

    Save this as dummy.c and run a few tests.

    C:\tests> cl /MT dummy.c
    dummy.c
    dummy.c(4) : fatal error C1189: #error :  "_DLL is not defined"
    
    C:\tests> cl /MD dummy.c
    dummy.c
    dummy.c(2) : fatal error C1189: #error :  "_DLL is defined"
    

    Well how's about that. The compiler uses the /MT and /MD flag to decide whether or not to define the preprocessor symbol _DLL, which is the secret signal it passes to the crtdef.h header file to control the conditional compilation.

    The compiler has to use this technique instead of deferring the decision to link-time code generation because it cannot assume that everybody has enabled link-time code generation. (Indeed, we explicitly did not in our sample command lines.)

    If link-time code generation were enabled, then is this something that could be deferred until that point?

    In principle yes, because link-time code generation in theory could just make the .obj file a copy of the source file (and all the header files) and do all the actual compiling at link time. This is a sort of extreme way of doing it, but I guess it could've been done that way.

    On the other hand, it also means that the compiler folks would have to come up with a new nonstandard extension that means "This function might be a normal static function or it might be a dll­import function. I haven't decided yet; I'll tell you later."

    Seeing as how the CRT already has to solve the problem in the case where there is no link-time code generation, it doesn't seem worth the effort to add a feature to link-time-code generation that you don't actually need. It would be a feature for which the only client is the C runtime library itself, for which the C runtime library already requires a separate solution when link-time code generation is disabled, and for which that separate solution still works when link-time code generation is enabled.

    No engineering purpose is served by writing code just for the sake of writing code.

  • 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?

  • 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?"

Page 120 of 439 (4,383 items) «118119120121122»