May, 2010

  • The Old New Thing

    If you can detect the difference between an emulator and the real thing, then the emulator has failed

    • 2 Comments

    Recall that a corrupted program sometimes results in a "Program too big to fit in memory" error. In response, Dog complained that while that may have been a reasonable response back in the 1980's, in today's world, there's plenty of memory around for the MS-DOS emulator to add that extra check and return a better error code.

    Well yeah, but if you change the externally visible behavior, then you've failed as an emulator. The whole point of an emulator is to mimic another world, and any deviations from that other world can come back to bite you.

    MS-DOS is perhaps one of the strongest examples of requiring absolute unyielding backward compatibility. Hundreds if not thousands of programs scanned memory looking for specific byte sequences inside MS-DOS so it could patch them or hunted around inside MS-DOS's internal state variables so it could modify them. If you move one thing out of place, those programs stop working.

    MS-DOS contains chunks of "junk DNA", code fragments which do nothing but waste space, but which exist so that programs which go scanning through memory looking for specific byte sequences will find them. (This principle is not dead; there's even some junk DNA in Explorer.)

    Given the extreme compatibility required for MS-DOS emulation, I'm not surprised that the original error behavior was preserved. There is certainly some program out there that stops working if attempting to execute a COM-style image larger than 64KB returns any error other than 8. (Besides, if you wanted it to return some other error code, you had precious few to choose from.)

  • The Old New Thing

    Why doesn't the Windows Vista copy progress dialog show the names of the files being copied?

    • 56 Comments

    When you copy multiple files in Windows Vista, the progress dialog gives you an estimate of the time remaining as well as an indication of what fraction of the operation has been completed. But one thing it doesn't tell you is the name of the file currently being copied. Why not?

    The programmer responsible for the file copy progress dialog in Windows Vista explained to me that there were a few reasons. First, there's the problem of presenting information to the user faster than the user could read it. All those filenames flashing by made users feel like they had lost control of the computer, as if it had decided to go off and do something at lightning computer speeds. Everything was happening so fast that they couldn't keep up with it much less understand what was going on and be ready to stop it if something didn't look right. Users may have been conditioned by the Hollywood Operating System, in which a rapidly changing stream of file names usually means something Really Bad is going on.

    Users also reported that the constantly-changing dialog felt unstable, like it never could figure out what it wanted to say. And of course it all happened faster than any human being could read it. There's not much value in presenting to the user information they can't read anyway.

    On the technical side, there were slight performance benefits to suppressing the display of the file names. As we saw, updating the screen can be a significant cost when you are updating continuously. Furthermore, even determining what name to display for the file is nontrivial. It's more than just extracting the string after the last backslash, because you can configure a file or directory so that the name displayed to the user differs from the physical file name. This is important when dealing with languages other than English. On Chinese systems, for example, the file whose name is Calculator.lnk is displayed to the user as 計算器.

    In Windows 7, the file copy dialog returned to showing the names of the files being copied, but only if you ask for it by clicking the More Information button. That way, the standard dialog looks normal, and only if you say, "firehose, please" do you get inundated with information faster than you can read it.

  • The Old New Thing

    If Windows 3.11 required a 32-bit processor, why was it called a 16-bit operating system?

    • 0 Comments

    Commenter Really16 asks via the Suggestion Box how 32-bit Win32s was, and why Windows 3.11 was called 16-bit Windows when it required a 32-bit CPU and ran in 32-bit protected mode.

    First, let's look at how Windows worked in so-called Standard mode. Actually, it was quite simple: In Standard mode, Windows consisted of a 16-bit protected-mode kernel which ran applications in 16-bit protected mode. I suspect there would be no controversy over calling this a 16-bit operating system.

    With the introduction of Enhanced mode, things got more complicated. With Enhanced mode, there were actually three operating systems running at the same time. The operating system in charge of the show was the 32-bit virtual machine manager which ran in 32-bit protected mode. As you might suspect from its name, the virtual machine manager created virtual machines. Inside the first virtual machine ran... a copy of Standard mode Windows. (This is not actually true, but the differences are not important here. Don't make me bring back the Nitpicker's Corner.)

    The other virtual machines each ran a copy of MS-DOS and were responsible for your MS-DOS sessions. Recall that Enhanced mode Windows allowed you to run multiple MS-DOS prompts that were pre-emptively multi-tasked. These other virtual machines ran in a variety of modes, but spent most of their time in virtual-86 mode. MS-DOS applications could use the DPMI interface to switch into 16-bit protected mode, or even 32-bit protected mode if they wanted to. (And that's how Standard mode Windows ran inside the first virtual machine: It used the DPMI interface to switch to 16-bit protected mode.)

    It's kind of stunning to realize that Enhanced mode Windows was really a completely new operating system with multiple virtual machines, pre-emptively multi-tasked with virtual memory. In principle, it could have created a virtual machine and hosted yet another random operating system inside it, but in practice the only two operating systems it bothered to host were Standard mode Windows and MS-DOS.

    Enhanced mode Windows was called a 16-bit operating system because it ran 16-bit Windows applications (inside a "Windows box", you might say). The supervisor operating system was a 32-bit operating system, but since applications didn't run in supervisor mode, that really didn't mean much. For all anybody cared, the supervisor operating system could have been written in 6502 assembly language. As long as it does its supervisory job, it doesn't matter what it's written in. What people care about is the applications that you could run, and since Enhanced mode Windows ran 16-bit Windows applications, and since it ran a copy of 16-bit Standard mode Windows to do all the things that people considered Windows-y, it was the number 16 that was important.

    Besides, you can imagine the uproar from the Slashdot crowd (if Slashdot had existed back then) that an operating system whose purpose is to run 16-bit applications in a 16-bit GUI environment would dare call itself a 32-bit operating system.

    How 32-bit was Win32s? Pretty darned 32-bit. A Win32s application ran in the same virtual machine as the rest of Standard mode Windows, but when it ran, the CPU really was in 32-bit protected mode. Naturally, it did all its work under the supervision of the virtual machine manager, and it had to coordinate its work with the Standard mode Windows kernel that it was sharing the virtual machine with. But when your 32-bit application was running, you were bought in: Your registers were 32-bit, your pointers were 32-bit, you accessed data in a 32-bit data segment, and you executed 32-bit instructions out of a 32-bit code segment.

    [The blog server is undergoing a long-overdue upgrade which will take a week to complete. During that time, comments will be disabled.]

  • The Old New Thing

    That's a great idea, it's on the list

    • 38 Comments

    The great thing about the taskbar is that everybody and their pet dog have an idea for how it can be improved.

    The bad thing about the taskbar is that everybody and their pet dog have an idea for how it can be improved. (And everybody and their pet dog also think that their feature suggestion is the important one that should be worked on next. Especially if it's "just a tiny little feature.")

    For a few years, my office sat across the hall from the person responsible for the taskbar design and features. To help manage all the wonderful ideas that came in, my colleague maintained a spreadsheet of all suggestions that were submitted and deemed worthy of further investigation. And for each suggestion, there was a rough estimate of how much developer time it would take to implement that feature.

    The total came out to over 200 days of idealized developer time.

    In other words, under perfect conditions (for example, on the rosy assumptions that no earlier feature made a subsequent feature more complicated and that all the code will work the first time), it would take one person over 40 weeks of vacation-free full-time coding to implement all the little features people requested. Death by a thousand cuts. Or in this case, 200 cuts.

    That calculation of course ignores the time spent designing the feature, developing a test plan, running it through usability tests, writing the automated testing, debugging all the issues that arise, doing the threat modeling, fixing the compatibility issues from applications that relied on the old behavior, and all the other things that you have to do to ship a feature beyond simply writing the code for it. (Indeed, the "write the code" step is one of the smallest steps in the product development cycle!)

    Obviously, you can't implement everything on the list: You don't know what you do until you know what you don't do. (And imagine the size of the resulting taskbar configuration dialog!) But it also means that there are a lot of people with pet dogs who think you're an idiot.

    Maintaining the list was a useful exercise for another reason: Whenever anybody stopped by and said, "Hey, why doesn't the taskbar...", my colleague could say, "That's a great idea. It's on the list."

    When my colleague left the group, ownership of the list was transferred to the next person responsible for the taskbar design. For all I know, there's still a list sitting in a spreadsheet on a designer's computer somewhere.

    Part of the hard job of product design is deciding which 20 features you have the resources to pursue and which 180 to leave for next time. If you choose the right 20, people will say you're the best new UI feature in Windows, or even the best Windows 7 feature, and they won't mind too much that you didn't get to the other 180.

  • The Old New Thing

    How do I accept files to be opened via IDropTarget instead of on the command line?

    • 24 Comments

    Commenter Yaron wants to know how to use the new IDropTarget mechanism for receiving a list of files to open. (Also asked by Anthony Wieser as a comment to an article.) The MSDN documentation on Verbs and File Assocations mentions that DDE has been deprecated as a way of launching documents and that you should use the DropTarget method instead. But what is the DropTarget method? (Note that the word method here is in the sense of technique and not in the C++ sense of function that belongs to a class.)

    The documentation in MSDN tells you what to do, but it does so very tersely. It says to create a DropTarget key under the verb key and create a Clsid string value whose data is the string version of the CLSID for your drop target. The documentation tells you to be careful in your IDropTarget::Drop, so it stands to reason that IDropTarget is the interface that will be used. From context, therefore, you should expect that the shell is going to simulate a drop on your drop target.

    You can implement your drop target either as an in-process server or a local server. The in-process case is well-known; nearly all shell extensions are in-process. But using an in-process server for the DropTarget technique only solves half the problem: Sure, the IDropTarget::Drop will take place and you will get your IDataObject, but you still have to transfer the file list from your shell extension running inside the context menu host to your application. May as well let COM do the heavy lifting of marshalling the data. (Well, okay, maybe using COM is overkill. You might have a lighter weight way of getting the data across, but since that's out of scope for today's exercise, I'll leave it for you to figure out.)

    Okay, let's roll up our sleeves and get to it! It turns out that nearly all the work is just creating a COM local server. If you know how to do that already, then I apologize in advance for the oppressive boredom about to fall upon you. I'll try to remember to wake you up when something interesting is about to happen. Note also that I am not an expert on COM local servers, so if you find a discrepancy between what I write and information from people who actually know what they're doing, go with the people who know what they're doing. (Actually, that sentence pretty much applies in general to everything I write.) Indeed, I had never written a COM local server before now, so all of what you see here is the result of a crash course in COM local servers from reading the documentation. (Translation: You could've done this too.)

    Start by adding some header files and a forward reference.

    #include <shlobj.h>
    #include <shellapi.h>
    #include <new> // for new(nothrow)
    
    void OpenFilesFromDataObject(IDataObject *pdto);
    

    Next, I'm going to steal the Process­Reference class which I had created some time ago. It's not the most efficient solution to the problem, but it works well enough, and it's a nice preparatory step in case a shell extension loaded into our process needs to take a process reference. We use the process reference object to keep track of our outstanding objects and locks.

    ProcessReference *g_ppr;
    

    Of course our custom drop target needs a class ID:

    const CLSID CLSID_Scratch = { ... };
    

    I leave it to you to fill in the CLSID structure from the output of uuidgen -s.

    Next, our simple drop target. COM servers need to keep track of the number of objects that have been created, so we'll piggyback off our existing process reference.

    class SimpleDropTarget : public IDropTarget
    {
    public:
     SimpleDropTarget() : m_cRef(1) { g_ppr->AddRef(); }
     ~SimpleDropTarget() { g_ppr->Release(); }
    
     // *** IUnknown ***
     STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
     {
      if (riid == IID_IUnknown || riid == IID_IDropTarget) {
        *ppv = static_cast<IUnknown*>(this);
        AddRef();
        return S_OK;
      }
      *ppv = NULL;
      return E_NOINTERFACE;
     }
    
     STDMETHODIMP_(ULONG) AddRef()
     {
      return InterlockedIncrement(&m_cRef);
     }
    
     STDMETHODIMP_(ULONG) Release()
     {
      LONG cRef = InterlockedDecrement(&m_cRef);
      if (cRef == 0) delete this;
      return cRef;
     }
    

    Next come the methods of IDropTarget, none of which are particularly interesting. We just say that we are going to copy the data.

     // *** IDropTarget ***
     STDMETHODIMP DragEnter(IDataObject *pdto,
        DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
     {
      *pdwEffect &= DROPEFFECT_COPY;
      return S_OK;
     }
    
     STDMETHODIMP DragOver(DWORD grfKeyState,
       POINTL ptl, DWORD *pdwEffect)
     {
      *pdwEffect &= DROPEFFECT_COPY;
      return S_OK;
     }
    
     STDMETHODIMP DragLeave()
     {
      return S_OK;
     }
    
     STDMETHODIMP Drop(IDataObject *pdto, DWORD grfKeyState,
        POINTL ptl, DWORD *pdwEffect)
     {
      OpenFilesFromDataObject(pdto);
      *pdwEffect &= DROPEFFECT_COPY;
      return S_OK;
     }
    
    private:
     LONG m_cRef;
    };
    

    People who know how COM servers work wake up: When something is dropped on our drop target, we call Open­Files­From­Data­Object. That's actually not all that interesting, but at least it's nontrivial. People who know how COM servers work can go back to sleep now.

    The next part of the code is just setting up our class factory.

    class SimpleClassFactory : public IClassFactory
    {
    public:
     // *** IUnknown ***
     STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
     {
      if (riid == IID_IUnknown || riid == IID_IClassFactory) {
        *ppv = static_cast<IUnknown*>(this);
        AddRef();
        return S_OK;
      }
      *ppv = NULL;
      return E_NOINTERFACE;
     }
    
     STDMETHODIMP_(ULONG) AddRef()
     {
      return 2;
     }
    
     STDMETHODIMP_(ULONG) Release()
     {
      return 1;
     }
    
     // *** IClassFactory ***
     STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
     {
        *ppv = NULL;
        if (punkOuter) return CLASS_E_NOAGGREGATION;
        SimpleDropTarget *pdt = new(nothrow) SimpleDropTarget();
        if (!pdt) return E_OUTOFMEMORY;
        HRESULT hr = pdt->QueryInterface(riid, ppv);
        pdt->Release();
        return hr;
     }
    
     STDMETHODIMP LockServer(BOOL fLock)
     {
      if (!g_ppr) return E_FAIL; // server shutting down
      if (fLock) g_ppr->AddRef(); else g_ppr->Release();
      return S_OK;
     }
    };
    
    SimpleClassFactory s_scf;
    

    The Lock­Server call takes advantage of our process reference object by forwarding lock and unlock calls into the reference count of the process reference object. This keeps our process running until the server is unlocked.

    Remember that COM rules specify that the class factory itself does not count as an outstanding COM object, so we don't use the same m_punkProcess trick that we did with our drop target. Instead, we just use a static object.

    People who know how COM servers work wake up: The COM server code is pretty much done. Now we're back to user interface programming.

    The next part of the code is just copied from our scratch program, with the following changes:

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
        g_hwndChild = CreateWindow(
            TEXT("listbox"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP,
            0, 0, 0,0, hwnd, (HMENU)1, g_hinst, 0);
        return TRUE;
    }
    

    The list box is not an important part of the program. We'll just fill it with data to prove that we actually did something.

    void OpenFilesFromDataObject(IDataObject *pdto)
    {
     if (!g_hwndChild) { /* need to create a new main window */ }
     FORMATETC fmte = { CF_HDROP, NULL, DVASPECT_CONTENT,
                        -1, TYMED_HGLOBAL };
     STGMEDIUM stgm;
     if (SUCCEEDED(pdto->GetData(&fmte, &stgm))) {
      HDROP hdrop = reinterpret_cast<HDROP>(stgm.hGlobal);
      UINT cFiles = DragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);
      for (UINT i = 0; i < cFiles; i++) {
       TCHAR szFile[MAX_PATH];
       UINT cch = DragQueryFile(hdrop, i, szFile, MAX_PATH);
       if (cch > 0 && cch < MAX_PATH) {
        ListBox_AddString(g_hwndChild, szFile);
       }
      }
      ReleaseStgMedium(&stgm);
     }
    }
    

    The Open­Files­From­Data­Object function does only enough work to prove that it actually got the list of file names. When we receive a data object from the simulated drop, we retrieve the HDROP and enumerate the files in it. For each file, we add it to the list box.

    There's some code I've not bothered to write: Namely, if a request to open some files comes in after the user closed our main window, we need to open a new main window. (Exercise: How can this happen?)

    Another difference between this program and real life is that in real life, your Open­Files­From­Data­Object would do some real work. But wait, if your function does any actual work, you should just AddRef the data object and return, so that the shell can return to interacting with the user. If you stop to do a lot of work before returning, the shell will lock up because it's waiting for your drop to complete.

    // Version of OpenFilesFromDataObject that is more
    // appropriate for real life.
    
    void OpenFilesFromDataObject(IDataObject *pdto)
    {
     if (!g_hwndChild) { /* need to create a new main window */ }
     pdto->AddRef();
     PostMessage(GetParent(g_hwndChild), WM_OPENFILES, 0,
                 reinterpret_cast<LPARAM>(pdto));
    }
    
    case WM_OPENFILES:
     IDataObject *pdto = reinterpret_cast<IDataObject*>(lParam);
     ... rest of code from the original OpenFilesFromDataObject ...
     pdto->Release();
     break;
    

    In real life, you just AddRef the data object and then post a message to finish processing it later. The aim here is to release the shell thread as quickly as possible. When the posted message is received, we can extract the information from the data object at our leisure. People who know how COM servers work can go back to sleep now.

    Finally, we hook up our class factories to the main program:

    int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                       LPSTR lpCmdLine, int nShowCmd)
    {
        MSG msg;
        HWND hwnd;
    
        g_hinst = hinst;
    
        if (!InitApp()) return 0;
    
        if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */
            HRESULT hrRegister;
            DWORD dwRegisterCookie;
    
            {
                ProcessReference ref;
                g_ppr = &ref;
    
                hrRegister = CoRegisterClassObject(CLSID_Scratch, &s_scf,
                      CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE,
                      &dwRegisterCookie);
    
                hwnd = CreateWindow(
                    TEXT("Scratch"),                /* Class Name */
                    TEXT("Scratch"),                /* Title */
                    WS_OVERLAPPEDWINDOW,            /* Style */
                    CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
                    CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
                    NULL,                           /* Parent */
                    NULL,                           /* No menu */
                    hinst,                          /* Instance */
                    0);                             /* No special parameters */
    
                if (CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE,
                     lpCmdLine, -1, "-Embedding", -1) != CSTR_EQUAL &&
                    CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE,
                     lpCmdLine, -1, "/Embedding", -1) != CSTR_EQUAL) {
                    /* OpenFilesFromCommandLine(); */
                }
    
                ShowWindow(hwnd, nShowCmd);
    
                while (GetMessage(&msg, NULL, 0, 0)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
                g_hwndChild = NULL;
    
            } // wait for process references to die
            g_ppr = NULL;
    
            if (SUCCEEDED(hrRegister)) {
                CoRevokeClassObject(dwRegisterCookie);
            }
            CoUninitialize();
        }
    
        return 0;
    }
    

    After creating our process reference, we register our class factory by calling Co­Register­Class­Object. We do this even if not invoked by COM, because we want COM to be able to find us once we're up and running: If the user runs the application manually and then double-clicks an associated document, we want that document to be handed to us rather than having COM launch another copy of the program.

    After creating the window, we check if the command line is -Embedding or /Embedding. This is the magic command line switch which COM gives us when we are being launched as a local server. If we don't have that switch, then we're being launched with a file name on our command line, so proceed with "old school" command line parsing. (I didn't bother writing the Open­Files­From­Command­Line function since it is irrelevant to the topic.)

    After our message loop exits, we clear the g_hwndChild so Open­Files­From­Data­Object knows that there is no main window any more. In real life, we'd have to create a new main window and restart the message loop.

    Once all outstanding COM objects and server locks and process references are gone, we can tear down the process. We unregister the COM server (if we registered it) so that COM won't try to ask us to open any more documents. (COM will instead launch a new copy of the program.)

    And that's it.

    Oh wait, we also have to register this program so COM and the shell can find us.

    Registering the COM server is just a matter of setting the registry key

    [HKCR\CLSID\{...}\LocalServer32]
    @="C:\\Path\\To\\scratch.exe"
    

    You probably should also set a friendly name into HKCR\CLSID\{...} so people will have a clue what your server is for.

    People who know how COM servers work wake up: After we register our class with COM, we can register it with the shell. For demonstration purposes, we'll make our command a secondary verb on text files.

    [HKCR\txtfile\shell\scratch\DropTarget]
    "Clsid"="{...}"
    

    Wow, all we had to do was set two registry values and boom, we can now accept files via drop target. Multiselect a whole bunch of text files, right-click them, and then select "scratch". The shell sees that the verb is registered as a drop target, so it calls Co­Create­Instance on the CLSID you provided. COM looks up the CLSID in the registry and finds the path to your program. Your program runs with the -Embedding flag, registers its class factory, and awaits further instructions. COM asks your class factory to create a drop target, which it returns to the shell. The shell then performs the simulated drop, and when you get the IDropTarget::Drop, your code springs into action and extracts all the files in the data object.

    Now that we have all this working, it's just one more tiny step to register your application's drop target so that it is invoked when the user drops a group of files on the EXE itself (or on a shortcut to the EXE):

    [HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\scratch.exe]
    "DropTarget"="{...}"
    

    With this additional registration, grab that bunch of files and drop it on the scratch.exe icon. Instead of passing all those files on the command line (and possibly overflowing the command line limit), the shell goes through the same procedure as it did with the context menu to hand the list of files to your program via the data object.

    Nearly all of the work here was just managing the COM local server. The parts that had to do with the shell were actually quite small.

  • The Old New Thing

    On the Portuguese custom of the couvert, and other restaurant customs

    • 36 Comments

    In restaurants in the United States, the custom is that anything brought to the table that you didn't order is complimentary. For example, after you place your order, the waiter returns to your table with a basket of bread. The bread is provided at no extra charge. These complimentary items are usually small, like some bread or a one-bite appetizer. (If anything bigger is brought to the table that you didn't request, it is customary to ask the waiter, "Is this ours?" just to make sure it wasn't delivered to the wrong table by mistake.)

    In Portugal, the custom is that these items (known as couvert) are brought to your table as a convenience so you don't have to order them, but you still have to pay for them if you eat them. Again, they are typically small items like bread or a small plate of olives. It took me a while to adjust to the Portuguese custom, and I would absently start eating them before realizing that they weren't complimentary. Fortunately, the mistake is not costly; these couvert items usually cost only about one Euro, two tops, and they are things you usually wouldn't have minded ordering anyway.

    (History of the couvert practice.)

    There was one restaurant in the touristy restaurant part of Lisbon which was clearly trying to exploit the inattentive foreigners. Before my main course was ready, the waiter brought a not insubstantial cheese plate to the table. The cheese appeared quite old. (Yes, I know cheese is supposed to be old, but this one had dried out at the edges.) I may even have detected a layer of dust on it. This was the last meal of my visit, so by then I had figured out this couvert thing and knew to stay away. Which was a good move, because I checked the menu on the way out, and the cheese plate costs a whopping €7.50.

    Another restaurant custom different between the United States and Portugal is the doggie bag, In the United States, it is customary for restaurants to offer to pack your uneaten food for you to take home. This is common because portion sizes in the United States have grown to ridiculous levels. The term doggie bag appears to have fallen out of favor, though. Now it's just called a box, as in "Would you like a box for that?" or "Would you like me to box this up?" The doggie bag is not part of Portuguese custom; when asked for a box to take home uneaten food, the waiter reacted as if this were a completely unheared-of situation. To his credit, he did find a solution: He went to the Chinese restaurant next door and took one of their take-out containers.

    Nice to know that Chinese restaurants are the same all over the world.

    Bonus restaurant tip: In my experience, in Portugal, you have to ask for the check. In the United States, whether you have to ask for the check or whether it will be brought to you varies regionally. In the Northeast, you have to ask for it; in the Seattle area, it will typically be brought to you, usually with a remark from the waiter like "Take your time," which means "I'm not trying to rush you out of here; I'm just saving you the trouble of asking for the check. Pay when you're ready." (Unless you're in a Vietnamese restaurant, in which case you have to ask for the check, per custom.)

  • The Old New Thing

    How do I prevent users from dragging and dropping files in Explorer?

    • 20 Comments

    More than once, I've had a customer ask, "How do I prevent users from dragging and dropping files in Explorer?" Actually, three of them in the past year phrased it in an even more provocative way: "I want to write a program that hooks Explorer and displays a prompt before every drag/drop operation."

    This is one of those cases where you have to figure out what the customer really wants. They've solved half of their problem and are asking you for help with the other half.

    In my experience so far, when customers ask this question, their real problem is always one of the following:

    First, they just want to prevent the user from moving files into or out of directories that the users shouldn't be modifying. In other words, the question isn't "How do I prevent users from dragging and dropping files in Explorer?" but rather "How do I prevent users from moving files I don't want them to move, and to prevent them from moving files into directories I don't want them to modify?"

    The answer to the customer's real question is not to try to block the user interface operation (because, even if you block it there, they can still move the file around by other means, like say, the command prompt) but rather to apply ACLs to the underlying files and directories so that the users don't have permission to perform the operation you want to block.

    Another common response to "Please explain why you think you need to do this" is "Well, I find that my users will accidentally move files around when they didn't mean to. They might be clicking on a file, but accidentally drag the mouse while clicking, resulting in the file being dropped into a folder by mistake." In other words, the real question is "How do I prevent users from performing accidental drag and drop operations?"

    The answer to this customer's real question is to increase the mouse drag sensitivity (via System­Parameters­Info(SPI_SETDRAGWIDTH) and SPI_SETDRAGHEIGHT) to require uses to drag the mouse a "definitely not accidental" distance before the system considers a drag operation to be in progress. Depending on your users, you might crank this up to 10, 30, or even 60 pixels.

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

  • The Old New Thing

    Why can programs empty the clipboard when they start up?

    • 43 Comments

    Via the Suggestion Box, Johan Almén asks, "What was the rationale behind the decision to let Excel empty the clipboard when launched?"

    Why can an application empty the clipboard? Because it's there.

    After all, the point of the clipboard is to hold information temporarily. Programs are permitted to empty the clipboard, add data to the clipboard, or retrieve data from the clipboard. That's why it's there.

    (I'm assuming that the naming of the program Excel was just an example of a program, and that the question wasn't "Why doesn't Windows have a specific check for the program EXCEL.EXE and block its clipboard access while still allowing clipboard access to everybody else.")

    Okay, maybe the question wasn't so much "Why are programs allowed to empty the clipboard" as it was "Why are programs allowed to empty the clipboard when they launch?" Well, because that might have been the whole point of the program! Somebody might write a program called empty­clip whose sole purpose in life is to empty the clipboard. You run the program, it empties the clipboard, and then it exits. Short and sweet. If Windows didn't allow programs to empty the clipboard when they started up, then this program would not be able to get its work done.

    You might not consider that particularly useful, but there are actually quite a few programs which empty the clipboard when they start up. For example, the clip program that comes with Windows takes its standard input and places it on the clipboard. Implied in that functional description is that it erases what used to be on the clipboard. Everything the program does is in its startup.

    echo I'm on the clipboard! And I erased what use to be there.| clip
    

    Many scripting languages provide access to the clipboard, if not natively, then through an extension. Since these are typically not GUI programs, as far as the window manager can tell, these programs as perpetually stuck in their startup code: They never go input idle because they never pump messages. Prohibiting programs from accessing the clipboard during startup means that console programs are effectively banned from modifying the clipboard at all.

    Okay, maybe the question wasn't "Why are programs allowed to empty the clipboard when they launch?" so much as it was "Why are programs allowed to empty the clipboard outside of an explicit user action (like a click or a hotkey)?" Well, we still have the problem of programs whose design is to empty the clipboard without any user interaction, like all those console scripts. But you also remove many GUI usage patterns, such as pushing work to a background thread so that the program can remain responsive. And it would also prevent you from writing a program that modified the clipboard in response to a drop operation. I can imagine a program called file­contents­to­clip which just sits there and waits for you to drag/drop a file onto its window. When you do that, it opens the file and places the file's contents onto the clipboard. Since the drag/drop operation is handled by the drag source, the drop target receives no input and (according to the rule of "no clipboard access without user input") is denied permission to erase the old clipboard contents.

    In order for these sorts of interaction models to work, there would have to be some sort of Allow­Clipboard­Access function (akin to Allow­Set­Foreground­Window) so that one process can temporarily transfer clipboard access permission to another process. It could be done, but it would make writing applications more complicated, because you would have to anticipate what operations might result in another application wanting to access the clipboard and scattering calls to Allow­Clipboard­Access in various places in your program. If you miss a spot, you'll get some bug filed against your program that says, "When I click the Preview button, and I've set my custom previewer to program X and configure program X to say 'always copy image to clipboard when previewing', the feature doesn't work."

    The clipboard was part of Windows 1.0, and back in those days, you didn't have a lot of memory available. You had to get a lot done with very little. Programmers were trusted to use their great power with great responsibility. And besides, as we saw with programs like clip (and hypothetical programs like empty­clip and file­contents­to­clip), allowing programs to empty the clipboard at startup made it possible to write some interesting and useful tools. Windows historically didn't stop programmers from doing stupid things because that would also prevent them from doing clever things.

  • The Old New Thing

    What are these strange =C: environment variables?

    • 30 Comments

    You won't see them when you execute a SET command, but if you write a program that manually enumerates all the environment variables and prints them out, and if you launch it from a command prompt, then you'll see weird variables with names like =C: and whose values correspond to directories on that drive. What are these things?

    These variables are part of the private bookkeeping of the command processor cmd.exe. That's why I added if you launch it from a command prompt to the steps above, because if you run the program from Explorer's Run dialog, you won't see them. If a cmd.exe is not in the chain of custody of your environment block, then you won't see the weird cmd.exe bookkeeping variables.

    Okay, so the command processor sets these things, but what are they? They are a leftover from the command processor's attempt to mimic the old MS-DOS way that drives and directories were handled. Whereas in Win32, there is only one current directory, in MS-DOS, there was one current directory for each drive. Consider the following sequence of commands:

    A> CD \SUBDIR
    // current directory for drive A is A:\SUBDIR
    A> B:
    B> CD \TWO
    // current directory for drive B is B:\TWO
    B> A:
    A> DIR
    // shows a directory listing for A:\SUBDIR
    

    During this sequence of commands, we start with A: as the current drive and set its current directory to A:\SUBDIR. Next, we set the current drive to B: and set B:\TWO as its current directory. Finally, we set the current drive back to A:, and when we ask for a listing, we get the contents of A:\SUBDIR because that is the current directory on the current drive.

    Win32 does not have the concept of a separate current directory for each drive, but the command processor wanted to preserve the old MS-DOS behavior because people were accustomed to it (and batch files relied upon it). The solution was to store this "per-drive current directory" in the environment, using a weird-o environment variable name so it wouldn't conflict with normal environment variables.

    If you repeated the above commands in cmd.exe, the output is the same, but it is accomplished in a very different way.

    A> CD \SUBDIR
    // Environment variable =A: set to A:\SUBDIR
    // Current Win32 directory set to A:\SUBDIR
    A> B:
    B> CD \TWO
    // Environment variable =B: set to B:\TWO
    // current Win32 directory set to B:\TWO
    B> A:
    // Current Win32 directory set to A:\SUBDIR
    A> DIR
    // shows a directory listing for A:\SUBDIR
    

    When we switch back to drive A:, the command processor says, "Hey, what was the current directory on drive A: the last time I was there?" It looks into its environment and finds the =A: variable, which tells it, "Oh, it was A:\SUBDIR". And that's the Win32 directory that it sets as current.

    But why put these internal variables in the environment? Can't they just be regular variables inside the cmd.exe process?

    The variables are exported into the environment because you want these "fake per-drive current directories" to be inherited by child processes. For example, consider that you are sitting at your command prompt, you run emacs, then from emacs, you shell out to another command prompt. You would expect that the nested command prompt will have the same "per-drive current directories" that you set back in the outer command prompt.

    C:\SUBDIR> D:
    D:\> emacs
    M-x shell
    D:\> C:
    C:\SUBDIR>
    // the "current directory on drive C" was inherited as expected
    

    What should you do about these variables, then?

    Nothing. Just let them be and do their jobs. I'm just mentioning them here so you won't freak out when you see them.

  • The Old New Thing

    Welcome to Belém, the scam artist capital of Lisbon

    • 16 Comments

    It has been quite a while since I was in Lisbon for a conference, but I still have a bunch of tiny travel stories. They may not be timely, but they're just stories.

    Lisbon is a wonderful city, and unlike Madrid, it doesn't have the feeling that it's overrun with tourists. It may very well be overrun with tourists, but at least it doesn't scream it at you.

    Well, until you get to the Belém neighborhood, which is where all the big monuments and famous historical buildings are. The sense begins to grow at the Padrão dos Descobrimentos (Monument to the Discoveries), and by the time you reach the Torre de Belém (Belém Tower), the touristiness is in full swing. I didn't notice any aggressive scam artists anywhere else in the city, but around the Tower of Belém you couldn't throw a rock without hitting one.

    First, there were the usual vendors selling overpriced trinkets or "artists" whose paintings of quaint Alfama alleyways are, in fact, color-by-number. These are not actually scam artists, but they are still out to separate tourists from their money in exchange for something of dubious value.

    As I approached Torre de Belém, arguably the signature landmark of the Belém district if not all of Lisbon, a gentleman approached me selling discounted tickets to enter the tower. "Half price, compared to buying them at the Tower." I politely declined, and when I reached the tower, I realized that the scam went beyond merely just selling fake tickets. You see, the tower was closed indefinitely due to a labor dispute. Even if the tickets were real, they were still no good. You could wander the grounds around the Tower but you couldn't go in, no matter how many tickets you bought or how cheap they were.

    A short time thereafter, I encountered my second Belém scam artist. There is one public rest room in the area. It doesn't get a lot of traffic, but that makes the job easier for the elderly gentleman who sat on a chair outside the entrance to the women's toilet. When a woman approached, he would get up and assist her with the door, guide her to a stall, and afterwards run the water in the sink, pump the soap for her, hand her a paper towel to dry her hands, and when his "services" were complete, he would block the exit until the woman paid him a small gratuity.

    (Just so you know, the sink area of the washroom is plainly visible from outdoors; I didn't go snooping into the women's bathroom for the purpose of investigative reporting. But I was intrigued when I saw the gentleman get up from his chair and go into the women's bathroom.)

    Related scam (not true, but an amusing story nonetheless).

    Random tourist tips:

    • If you choose to walk up to Castelo de São Jorge, you may see conflicting signs for directions, some of them official city signs and some of them merely graffiti. We decided to follow the graffiti and it worked okay, though the government path may have been more scenic (passing by more shops rather than wandering through a residential alley).
    • At least the day I was there (March 11, 2008), the Sintra train during off-peak runs every 20 minutes, not 15 as more than one guide book claims.
    • If you get a Lisbon Card, make sure to bring the booklet with you as well as the card, because many of the discounts are in the form of coupons from the book.
Page 1 of 4 (32 items) 1234