Other

  • The Old New Thing

    Microspeak: Party, in various forms

    • 13 Comments

    Remember, Microspeak includes words and phrases in general use, as long as they are employed at Microsoft at a higher rate than in the general population, or in specific situations that may not be obvious to the uninitiated. They are the words and phrases you need to use in order to fit in.

    Today's word is party, in various forms, and usually paired with the preposition on. In general, it means to use, change, or modify with few or no constraints. These aren't genteel tea parties; they're more like wild college parties, the kind that end with the police being called.

    LockBits returns a pointer to the pixel buffer, and the caller can party on the memory inside the rectangle until it calls UnlockBits.

    When used with permission verbs like can and may, the usage indicates that the component has permission to read from and write to the memory, subject to the given constraints.

    The code partied on our data structures because it used a pointer after freeing it.

    It is often used in a negative sense to indicate that the component wrote to memory that it should not have. Sort of an unauthorized party. (Compare fandango on core.)

    The Contoso notifier injects a DLL into Explorer so it can party on the internal data that keeps track of icons in the notification area and thereby disable the icons of its competitors.

    These sorts of unauthorized parties can be malicious and willful as well as merely accidental.

    The exp branch is a party branch. You can commit your changes there so we can test it before pulling it into the release branch.

    The word party can be used to describe an environment in which the normal rules and constraints are reduced or removed entirely. Here, the party branch is presumably a branch of the project in which the usual procedures for code changes don't apply, or at least apply less strictly than normal. You can put any experimental changes in the exp branch, and then when a new build comes out the next day, you can run your tests against it to see if they solve the problem. If so, then you can start filling out the necessary paperwork to pull the changes into the release branch.

    Many release branches have an experimental offshoot.¹ The idea is that people developing fixes to the product can commit their changes to the experimental branch to see how they work out. If the changes look good, they are pulled into the release branch. If the changes doesn't pass muster, they are rolled back. The developers who use the experimental branch are on their honor to keep the branch in good condition.

    Note that this sense of party is relative. The experimental branch is a big party compared to the staid and formal release branch, but it's still not a crazy free-for-all. You still need to be judicious about what you put into the party branch so you don't ruin the party for everybody else.

    The Q1 branch is locked down for the beta, but you can party your post-beta fixes into the Q2 branch.

    The above example further highlights the relative nature of the term party. Even though the Q2 branch is open to post-beta fixes, you still have to go through the usual test and review processes for fixing bugs. It's just that Q2 will accept any approved bug fix, whereas Q1 will accept only fixes for bugs marked beta-blocking.

    (That's a little extra Microspeak for you: blocking. In Microspeak, a beta blocker is not a pharmacological agent. Rather, it's something that prevents the beta from being released.)

    ¹ In Windows, the experimental branch associated with a release branch is typically called cbt. This officially stands for Central Build Team, but some people who live in my house like to joke that it stands for Can't Be Trusted.

  • The Old New Thing

    Racing email against a snail

    • 13 Comments

    The Windows team double-dogfoods Windows Server and Exchange Server, and while this is good for both products, it can be quite frustrating when something goes wrong.

    I remember back in the early days of the Windows 95 project, the mail servers once got so messed up that some email messages were not delivered for several days. After a colleague told me that he had just received an email message that I had sent several days earlier, I went to the library to look up the typical speed of a garden snail. (This was back in the days when you had to use an actual library to look up facts, and cat videos were available only once a week. The Internet looked like this and a few years later, this.)

    Conclusion: A garden snail would have delivered the message faster than our email system.

    More recently, a wrinkle in the space-time continuum resulted in one of our automated systems sending out warning messages four months after the anomalous situation was detected. (The anomalous situation was repaired almost immediately, so the warning was not only late, it was spurious.)

    One of my colleagues remarked,

    I have a story I read to my grandkids where Frog writes Toad a letter and gives it to a passing snail, who delivers it to Toad's house four days later.

    Can we hire that snail?

    Today is Thank a Mailman Day.

  • The Old New Thing

    When the Web page says that a tool is not supported, it means that if you find a problem and contact technical support, they're just going to point you back to the Web page

    • 35 Comments

    I file this under the category of People refuse to read what is right in front of them.

    There used to be a number of utilities available for download which all go by the name PowerToys. And they all contain text like this:

    Note: We take great care to ensure that PowerToys work as they should, but they are not part of XYZ and are not supported by Microsoft. For this reason, Microsoft Technical Support is unable to answer questions about PowerToys.

    This sentence isn't exactly in the fine print either. It's right there at the top of the page.

    This nevertheless does not stop a customer from contacting their support representative, who then turns around and sends email to the product team that goes something like this:

    I am working on a customer case, 602214129.

    The customer reports that the XYZ PowerToy does not work under conditions A, B, and C. My questions are

    1. Is a fix available for this?
    2. What are the support limitations around this tool?

    The tool was downloaded from ⟨http://www.microsoft.com/…⟩

    Somebody then has to point the customer liaison to the text right at the top of the Web page they linked to that quite clearly spells out the support limitations of the tool: It comes with no support. Apparently both the customer and the customer liaison failed to notice it.

  • The Old New Thing

    There are so many things that call themselves message queues

    • 10 Comments

    There are a whole bunch of things in Windows that call themselves message queues, and none of them have anything to do with each other.

    There is the window manager message queue, which holds window messages.

    And there is the Microsoft Message Queue (MSMQ) which is a networking technology for allowing multiple computers to communicate with each other by sending and reading messages.

    The Windows Mobile folks didn't want to feel left out, so they created their own Message Queue Point-to-Point message queue system.

    These are all unrelated technologies. Trying to, say, read window messages from a MSMQ message queue will get you nowhere.

  • The Old New Thing

    We know your job is hard, you don't have to show us

    • 10 Comments

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

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

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

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

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

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

  • The Old New Thing

    2013 year-end link clearance

    • 11 Comments

    Another round of the semi-annual link clearance.

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

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

  • The Old New Thing

    Essays from the funniest man in Microsoft Research

    • 16 Comments

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

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

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

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

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

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

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

    Also if I had the talent.

    Mostly the talent part.

    Bonus Mickensian reading: What is art?

  • The Old New Thing

    Wouldn't the Recycle Bin sample program have been simpler without COM?

    • 17 Comments

    Steve Wolf suggests that the sample program would have been much simpler had the shell extension model been a flat Win32 interface.

    Okay, let's try it.

    Since this is an extension model, each extension needs to specify the callbacks for each namespace operation. Perhaps it could have been done like this:

    HRESULT (CALLBACK *SHELLFOLDER_EXTENDHANDLER)(
        void *lpContext,
        OBJECTTYPE type, void **phObject);
    HRESULT (CALLBACK *SHELLFOLDER_PARSEDISPLAYNAMEHANDLER)(
        void *lpContext,
        HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,
        ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes);
    HRESULT (CALLBACK *SHELLFOLDER_ENUMOBJECTSHANDLER)(
        void *lpContext,
        HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl);
    HRESULT (CALLBACK *SHELLFOLDER_BINDTOOBJECTHANDLER)(
        void *lpContext,
        PCUIDLIST_RELATIVE pidl, LPBINDCTX pbc,
        OBJECTTYPE type, void **phObject);
    HRESULT (CALLBACK *SHELLFOLDER_BINDTOSTRAGEHANDLER)(
        void *lpContext,
        PCUIDLIST_RELATIVE pidl, LPBINDCTX pbc,
        OBJECTTYPE type, void **phObject);
    HRESULT (CALLBACK *SHELLFOLDER_COMPAREIDSHANDLER)(
        void *lpContext,
        LPARAM lParam, PCUIDLIST_RELATIVE pidl1,
        PCUIDLIST_RELATIVE pidl2);
    ... (etc) ...
    
    HFOLDER CreateShellFolderImplementation(
        SHELLFOLDER_EXTENDHANDLER pfnExtend,
        SHELLFOLDER_PARSEDISPLAYNAMEHANDLER pfnParseDisplayName,
        SHELLFOLDER_ENUMOBJECTSHANDLER pfnEnumObjects,
        SHELLFOLDER_BINDTOOBJECTHANDLER pfnBindToObject,
        SHELLFOLDER_BINDTOSTRAGEHANDLER pfnBindToStorage,
        SHELLFOLDER_COMPAREIDSHANDLER pfnCompareIDs,
        ... (etc) ...
        void *lpContext);
    

    This would be the function that allows a third party to create a shell folder implementation. You pass it a bunch of flat callback functions, one for each operation that a shell folder supports, so that when the application tries to perform that operation on your custom folder, the operating system can ask your custom implementation to do that thing.

    If additional shell folder operations are added in the future, the operating system needs to know how to ask your shell extension whether it knows how to do those extended things. That's what the Extend method is for. The operating system could ask to extend your object to one that supports HFOLDER2 operations.

    Actually, if you look at it, these are exactly the same as COM methods. The first parameter says what object you are operating on ("this"), and the rest are the parameters.

    Okay, so I'm setting up a straw man that looks just like COM. So let's do something that looks very different from COM. We could use the window procedure paradigm:

    HRESULT (CALLBACK *SHELLFOLDER_INVOKE)(
        void *lpContext,
        FOLDERCOMMAND cmd, void *parameters);
    
    HFOLDER CreateShellFolderImplementation(
        SHELLFOLDER_INVOKE pfnInvoke,
        void *lpContext);
    

    Your invoke function receives a FOLDER­COMMAND enumeration which specifies what command the client is trying to perform, and then switches on the command to perform the command, or returns E_NOT­IMPL if you don't handle the command. Since each of the methods takes different parameters, we have to do some work to pack them up into a generic parameter block, and then unpack it on the receiving end. Let's assume some helper functions that do this packing and unpacking.

    HRESULT UnpackParseDisplayName(
        void *parameters,
        HWND *phwnd,
        LPBINDCTX *ppbc,
        LPWSTR *ppszDisplayName,
        ULONG **ppchEaten,
        PIDLIST_RELATIVE **ppidl,
        ULONG **ppdwAttributes);
    );
    
    HRESULT UnpackEnumObjects(
        void *parameters,
        HWND *phwnd,
        SHCONTF *pgrfFlags,
        HENUMIDLIST **ppheidl);
    
    HRESULT AwesomeShellFolderInvoke(
        void *lpContext,
        FOLDERCOMMAND cmd,
        void *parameters)
    {
      HRESULT hr = E_NOTIMPL;
      CAwesome *self = reinterpret_cast<CAwesome*>(lpContext);
    
      switch (cmd) {
      case FOLDERCOMMAND_PARSEDISPLAYNAME:
        {
          HWND hwnd;
          LPBINDCTX pbc;
          LPWSTR pszDisplayName;
          ULONG *ppchEaten;
          PIDLIST_RELATIVE *pidl;
          ULONG *pdwAttributes;
          hr = UnpackParseDisplayName(parameters, &hwnd, &pbc,
                  &pszDisplayName, &ppchEaten, &pidl,
                  &pdwAttributes);
          if (SUCCEEDED(hr)) {
            hr = ... do the actual work ...
          }
        }
        break;
    
      case FOLDERCOMMAND_ENUMOBJECTS:
        {
          HWND hwnd;
          SHCONTF grfFlags;
          HENUMIDLIST *pheidl;
          hr = UnpackEnumObjects(parameters, &hwnd, &grfFlags,
                  &pheidl);
          if (SUCCEEDED(hr)) {
            hr = ... do the actual work ...
          }
        }
        break;
        ... (etc) ...
      }
      return hr;
    }
    

    This could be made a lot simpler with the addition of some helper functions.

    HRESULT DispatchParseDisplayName(
      HRESULT (CALLBACK *)(
        void *lpContext,
        HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,
        ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes),
      void *lpContext,
      void *parameters);
    
    HRSEULT DispatchEnumObjects(
      HRESULT (CALLBACK *)(
        void *lpContext,
        HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl),
      void *lpContext,
      void *parameters);
    

    The implementation would then go like this:

    HRESULT AwesomeParseDisplayName(
        void *lpContext,
        HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,
        ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
    {
      CAwesome *self = reinterpret_cast<CAwesome*>(lpContext);
      HRESULT hr;
      ... do the actual work ...
      return hr;
    }
    
    HRESULT AwesomeEnumObjects(
        void *lpContext,
        HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl),
    {
      CAwesome *self = reinterpret_cast<CAwesome*>(lpContext);
      HRESULT hr;
      ... do the actual work ...
      return hr;
    }
    
    HRESULT AwesomeShellFolderInvoke(
        void *lpContext,
        FOLDERCOMMAND cmd,
        void *parameters)
    {
      switch (cmd) {
      case FOLDERCOMMAND_PARSEDISPLAYNAME:
        return DispatchParseDisplayName(AwesomeParseDisplayName,
                  lpContext, parameters);
    
      case FOLDERCOMMAND_ENUMOBJECTS:
        return DispatchEnumObjects(AwesomeEnumObjects,
                  lpContext, parameters);
        ... (etc) ...
      }
      return E_NOTIMPL;
    }
    

    You might decide to make the parameter packing transparent instead of opaque, so that they are passed as, say, an array of generic types like VARIANTs. (Note that I'm abusing VARIANTs here. These are not valid VARIANTs, but it saves me from having to declare my own generic type. This is just a design discussion, not an actual implementation.)

    HRESULT (CALLBACK *SHELLFOLDER_INVOKE)(
        void *lpContext,
        FOLDERCOMMAND cmd,
        VARIANT *rgvarArgs,
        UINT cArgs);
    
    // error checking elided for expository purposes
    // In real life, you would have to validate cArgs
    // and the variant types.
    
    HRESULT AwesomeShellFolderInvoke(
        void *lpContext,
        FOLDERCOMMAND cmd,
        VARIANT *rgvarArgs,
        UINT cArgs)
    {
      CAwesome *self = reinterpret_cast<CAwesome*>(lpContext);
    
      switch (cmd) {
      case FOLDERCOMMAND_PARSEDISPLAYNAME:
        return self->ParseDisplayName(
          reinterpret_cast<HWND>(rgvarArgs[0]->byref),
          reinterpret_cast<LPBINDCTX>(rgvarArgs[1]->byref),
          reinterpret_cast<LPWSTR>(rgvarArgs[2]->byref),
          reinterpret_cast<ULONG*>(rgvarArgs[3]->byref),
          reinterpret_cast<PIDLIST_RELATIVE*>(rgvarArgs[4]->byref),
          reinterpret_cast<ULONG**>(rgvarArgs[5]->byref));
    
      case FOLDERCOMMAND_ENUMOBJECTS:
        return self->EnumObjects(
          reinterpret_cast<HWND>(rgvarArgs[0]->byref),
          reinterpret_cast<SHCONTF>(rgvarArgs[1]->lVal),
          reinterpret_cast<HENUMIDLIST *>(rgvarArgs[2]->byref));
    
        ... (etc) ...
      }
      return E_NOTIMPL;
    }
    

    (This is basically the plug-in model that some people have chosen to pursue. It is also basically the same as IDispatch::Invoke.)

    Okay, that's how you implement the plug-in. Now how do you call it?

    You would have to pack the parameters, then call through the Invoke method with your command ID. For example, a call to FOLDER­COMMAND_ENUM­OBJECTS would go like this:

    // was: hr = psf->EnumObjects(hwnd, shcontf, &peidl);
    // now:
    HENUMIDLIST heidl;
    VARIANT args[3];
    
    args[0].vt = VT_BYREF;
    args[0].byref = hwnd;
    
    args[1].vt = VT_I4;
    args[1].lVal = shcontf;
    
    args[2].vt = VT_BYREF;
    args[2].byref = &heidl;
    
    hr = InvokeShellFolder(hsf, FOLDERCOMMAND_ENUMOBJECTS, args, 3);
    

    Yuck.

    Let's assume that the shell provides helper functions that do all this parameter packing for you. (This is more than certain plug-in models give you.)

    HRESULT ShellFolder_ParseDisplayName(
        HSHELLFOLDER hsf,
        HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,
        ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
    {
      VARIANT args[6];
    
      args[0].vt = VT_BYREF;
      args[0].byref = hwnd;
    
      args[1].vt = VT_BYREF;
      args[1].byref = pbc;
    
      args[2].vt = VT_BYREF;
      args[2].byref = pszDisplayName;
    
      args[3].vt = VT_BYREF;
      args[3].byref = pchEaten;
    
      args[4].vt = VT_BYREF;
      args[4].byref = ppidl;
    
      args[5].vt = VT_BYREF;
      args[5].byref = pdwAttributes;
    
      return InvokeShellFolder(hsf, FOLDERCOMMAND_PARSEDISPLAYNAME,
                               args, 6);
    }
    
    HRESULT ShellFolder_EnumObjects(
        HSHELLFOLDER hsf,
        HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl)
    {
      VARIANT args[3];
    
      args[0].vt = VT_BYREF;
      args[0].byref = hwnd;
    
      args[1].vt = VT_I4;
      args[1].lVal = shcontf;
    
      args[2].vt = VT_BYREF;
      args[2].byref = &heidl;
    
      return InvokeShellFolder(hsf, FOLDERCOMMAND_ENUMOBJECTS, args, 3);
    }
    ... (etc) ...
    

    The naming convention above is kind of awkward, so let's give them a bit less clumsy names.

    HRESULT ParseShellFolderDisplayName(
        HSHELLFOLDER hsf,
        HWND hwnd, LPBINDCTX pbc, LPWSTR pszDisplayName,
        ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes);
    HRESULT EnumShellFolderObjects(
        HSHELLFOLDER hsf,
        HWND hwnd, SHCONTF grfFlags, HENUMIDLIST *pheidl);
    ... (etc) ...
    

    Okay, now that we have a flat API, let's convert the original code. The first function now goes like this:

    HRESULT BindToCsidl(int csidl,
        // REFIID riid, void **ppv
        HSHELLFOLDER *phsf)
    {
     HRESULT hr;
     PIDLIST_ABSOLUTE pidl;
     hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
     if (SUCCEEDED(hr)) {
      // IShellFolder *psfDesktop;
      HSHELLFOLDER hsfDesktop;
      hr = SHGetDesktopFolder(&hsfDesktop);
      if (SUCCEEDED(hr)) {
       if (pidl->mkid.cb) {
        // hr = psfDesktop->BindToObject(pidl, NULL, riid, ppv);
        hr = BindToShellFolderObject(hsfDesktop, pidl, NULL, phsf);
       } else {
        // hr = psfDesktop->QueryInterface(riid, ppv);
        *phsf = hsfDesktop;
        hsfDesktop = nullptr; // transfer to owner
        hr = S_OK;
       }
       // psfDesktop->Release();
       if (hsfDesktop) ShellFolder_Destroy(hsfDesktop);
      }
      CoTaskMemFree(pidl);
     }
     return hr;
    }
    

    What happened here? The IShell­Folder interface was replaced by a HSHELL­FOLDER flat handle. Flat APIs use handles to refer to objects instead of interface pointers.

    A method call on an interface pointer becomes a flat API call. In general, pInterface->VerbNoun(args) gets flattened to VerbInterfaceNoun(h, args). But that's just renaming and doesn't change the underlying complexity of the issue.

    I could've added reference counting to these flat objects, but then I would be accused of intentionally making it look like COM, so let's say that these flat objects are not reference-counted. Therefore, we have to be more careful about not destroying the object we plan on returning.

    On to the next two functions:

    void PrintDisplayName(
        // IShellFolder *psf,
        HSHELLFOLDER hsf,
        PCUITEMID_CHILD pidl, SHGDNF uFlags, PCTSTR pszLabel)
    {
     STRRET sr;
     // HRESULT hr = psf->GetDisplayNameOf(pidl, uFlags, &sr);
     HRESULT hr = GetShellFolderDisplayNameOf(hsf, pidl, uFlags, &sr);
     if (SUCCEEDED(hr)) {
      PTSTR pszName;
      hr = StrRetToStr(&sr, pidl, &pszName);
      if (SUCCEEDED(hr)) {
       _tprintf(TEXT("%s = %s\n"), pszLabel, pszName);
       CoTaskMemFree(pszName);
      }
     }
    }
    
    void PrintDetail(
        // IShellFolder2 *psf,
        HSHELLFOLDER hsf,
        PCUITEMID_CHILD pidl,
        const SHCOLUMNID *pscid, PCTSTR pszLabel)
    {
     VARIANT vt;
     // HRESULT hr = psf->GetDetailsEx(pidl, pscid, &vt);
     HRESULT hr = GetShellFolderDetailsEx(hsf, pidl, pscid, &vt);
     if (SUCCEEDED(hr)) {
      hr = VariantChangeType(&vt, &vt, 0, VT_BSTR);
      if (SUCCEEDED(hr)) {
       _tprintf(TEXT("%s: %ws\n"), pszLabel, V_BSTR(&vt));
      }
      VariantClear(&vt);
     }
    }
    

    Not really all that different. Last function:

    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     HRESULT hr = CoInitialize(NULL);
     if (SUCCEEDED(hr)) {
      // IShellFolder2 *psfRecycleBin;
      HSHELLFOLDER hsfRecycleBin;
      hr = BindToCsidl(CSIDL_BITBUCKET, &hsfRecycleBin);
      if (SUCCEEDED(hr)) {
       // IEnumIDList *peidl;
       HENUMIDLIST heidl;
       // hr = psfRecycleBin->EnumObjects(NULL,
       hr = EnumShellFolderObjects(hsfRecycleBin, NULL,
         SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &heidl);
       if (hr == S_OK) {
        PITEMID_CHILD pidlItem;
        // while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
        while (EnumerateNextShellFolderObject(heidl, 1, &pidlItem, NULL) == S_OK) {
         _tprintf(TEXT("------------------\n"));
    
         PrintDisplayName(hsfRecycleBin, pidlItem,
                          SHGDN_INFOLDER, TEXT("InFolder"));
         PrintDisplayName(hsfRecycleBin, pidlItem,
                          SHGDN_NORMAL, TEXT("Normal"));
         PrintDisplayName(hsfRecycleBin, pidlItem,
                          SHGDN_FORPARSING, TEXT("ForParsing"));
    
         PrintDetail(hsfRecycleBin, pidlItem,
                     &SCID_OriginalLocation, TEXT("Original Location"));
         PrintDetail(hsfRecycleBin, pidlItem,
                     &SCID_DateDeleted, TEXT("Date deleted"));
         PrintDetail(hsfRecycleBin, pidlItem,
                     &PKEY_Size, TEXT("Size"));
    
         CoTaskMemFree(pidlItem);
        }
       }
       // psfRecycleBin->Release();
       DestroyShellFolder(hsfRecycleBin);
      }
      CoUninitialize();
     }
     return 0;
    }
    

    So we see that flattening the API didn't really change the code at all. You're still invoking methods on objects. Whether you use a flat API to do it or an object-based API is just changing the decorations. The underlying logic doesn't change.

    One disadvantage of the flat version is that it requires everything to be mediated by the shell. Instead of invoking a method directly on the object, you have to call the flat function in the shell, which then packages up the call and dispatches it, and the recipient then needs to unpack the parameters (possibly with help from the shell) before finally getting around to doing the actual work.

    It also means that any interface change requires an operating system upgrade, since the mediator (the shell) needs to understand the new interface.

    But if this whole object-oriented syntax really annoys you and you want a flat API, then feel free to add the line

    #define CINTERFACE
    

    before including COM header files. If you do that, then you get the old flat C-style version of COM. Instead of the p->Method(args) new hotness, you can stick to the old trustworthy p->lpVtbl->Method(p, args) version, or use the InterfaceName_MethodName(p, args) helper macro.

  • The Old New Thing

    Whether your application should display its content in RTL should be based on the content

    • 15 Comments

    A customer had the following puzzle:

    We have a small bootstrapper application that consists of a dialog box and a few message boxes. The problem is that we want our application to work properly on Arabic and Hebrew systems, and we can't come up with a good way to determine text direction of the underlying system. We found this article by Michael Kaplan that tells us how not to do it, which is great, but what's the recommended way of actually doing it?

    You already know whether you should be displaying your application's UI in LTR or RTL: If this is the Arabic-localized or Hebrew-localized version of your application, then display it as RTL. If this is the English-localized or French-localized version, then display it as LTR.

    There's no point in trying to display your English-language strings in RTL just because the underlying operating system is Arabic. If your strings are in English, then display them in the way they should look to an English speaker. A dialog box like this helps nobody:

    ...Please wait ×
    ,(Preparing setup (50% complete
    .your patience is appreciated

    When your localization team translates the application into Arabic, they can insert two copies of U+200E (LEFT-TO-RIGHT MARK) at the start of the File­Description in the version resource. That is the signal to Windows that the application should have RTL as its default layout direction.

    If you want your application to choose a language dynamically (say, to use English strings if running on an English system but Arabic strings if running on an Arabic system), then you can add a flag in your resources so that the localizers can indicate whether a particular language pack expects text to run left-to-right or right-to-left.

    IDS_LANGUAGE_DIRECTION "LTR" // change to RTL if localized in Arabic, etc.
    

    Your application could then check the direction and call Set­Process­Default­Layout based on the result.

  • The Old New Thing

    Some vice presidents forget that not everybody attends the same meetings that they do

    • 16 Comments

    There are some vice presidents who forget that not everybody attends the same meetings that they do. When they send email to the entire division, they use buzzwords and acronyms that are not widely-understood. For example, they may mention the great progress that the Nosebleed team is making with DOXLA,¹ but that doesn't mean much to people who aren't on the Nosebleed team. Meanwhile, the people on the Nosebleed team probably don't know what the vice president is talking about when they compliment the Bunion team's recent breakthough in MT1 alignment.¹

    When that happens, I like to send email back to the vice president admitting my lack of knowledge and asking what DOXLA and MT1 alignment are. "I'm sorry, but I must not have attended the right meetings. Can you explain what DOXLA and MT1 stand for?"

    Often, I find that the vice president doesn't exactly know either.

    I don't know what DOXLA stands for, but it's the feature that keeps oxygen at the right level.

    I don't know if the vice presidents ever get the message that they are writing email messages that nobody understands. I suspect they don't really care. They're just fulfilling what they believe to be their job duties: making everybody feel good by mentioning something positive about every team in their division.

    ¹ Dynamic OXygen Level Adjustment, and metatarsal bone number 1 alignment, obviously. (By the way, I completely made those up.)

Page 2 of 91 (909 items) 12345»