May, 2004

  • The Old New Thing

    How do the FILE_SHARE_* bits interact with the desired access bits?

    • 19 Comments

    It's really not that complicated. If you permit, say, FILE_SHARE_READ, then you're saying, "I'm okay with other people reading this file while I have it open." And if you leave off the flag, then you're saying, "I do not want other people reading this file while I have it open."

    Now all that's left to do is work out what that means.

    So suppose you omit the flag, indicating that you don't want to let others read the file. Then when you attempt to open it yourself, the open will fail if anybody else has the file open for reading. And if the open succeeds, then the system will prevent anybody else from opening the file for reading until you close your handle.

    That's all.

    Of course, if the file is already open, then a corresponding check is made between your desired access and the file sharing mode of the people who already opened it. For example, if somebody already has the file open and denies read sharing, then if you try to open for read, you will get a sharing violation.

    These restrictions are cumulative, of course. If one person opens a file without FILE_SHARE_READ and another person opens a file without FILE_SHARE_WRITE, then attempts to open the file for read or for write will fail. (The read fails because the first person didn't permit read, and the write fails because the second person didn't permit write.)

    Repeat the above logic for "delete" and "write" permission, and that's it in a nutshell.

    There is a big nasty table in MSDN that walks through all the combinations, but I personally think it confuses the matter rather than clarifying.

    Even more confusingly, the table uses "X" to mean that the combination is permitted. They should've used a bullet ("•") or a check mark ("ü"), since an "X" has the connotation of "not allowed".

    Let's look at one row of the table and see how the information in it is "obvious": Say, the row that reads "GENERIC_READ / GENERIC_WRITE / FILE_SHARE_READ". You are asking for read and write, and you permit read (and implicitly deny write).

    The requested access (read/write) requires that all previous openers have granted both read and write. There are three columns that correspond to this, namely the ones that say "FS_R FS_W".

    The requested sharing mode (read only) requires that all previous openers have requested read-only access. In other words, there can't be any G_W entries. That rules out two of the columns, leaving just "G_R FS_R FS_W", and indeed only one column is checked in the table.

    Notice that the file share bits you pass don't have to match up with your file access bits. The file share bits indicate what you want to allow other people to do. The access bits indicate what you want to do yourself.

  • The Old New Thing

    Links about COM threading models

    • 2 Comments

    There have been a few good articles about threading models, one from Eric Lippert and another from Larry Osterman. Go off and read them if you haven't already.

  • The Old New Thing

    There are two types of scrollbars

    • 26 Comments

    Remember that there are two types of scrollbars.

    One is the standalone scrollbar control. This one has its own window handle, and consequently can be given focus and all those other fun things you can do with window handles. To manipulate them, pass the handle to the scrollbar control to the appropriate scrollbar function (SetScrollInfo, for example) and pass SB_CTL as the nBar parameter to indicate that you have a scrollbar control.

    The other type is the horizontal or vertical scrollbar (or both) attached to a window by virtue of having the WS_HSCROLL and/or WS_VSCROLL style. These are nonclient scrollbars and are not controls. They are just decorations added to some other window. You can't give them focus since they aren't windows in their own right. To manipulate them, pass the handle to the containing window to the appropriate scrollbar function and pass SB_HORZ or SB_VERT as the nBar parameter to indicate that you want to manipulate the nonclient horizontal or vertical scrollbar.

    I'm writing this down since some people seem to miss the distinction between these two cases.

  • The Old New Thing

    A guide to British pub etiquette

    • 25 Comments

    Social researchers dissect the interactions and unveil the unwritten rules that govern British pub etiquette.

  • The Old New Thing

    Image File Execution Options

    • 5 Comments

    Hereby incorporating by reference Junfeng Zhang's discussion of the Image File Execution Options registry key.

  • The Old New Thing

    When should your destructor be virtual?

    • 20 Comments

    When should your C++ object's destructor be virtual?

    First of all, what does it mean to have a virtual destructor?

    Well, what does it mean to have a virtual method?

    If a method is virtual, then calling the method on an object always invokes the method as implemented by the most heavily derived class. If the method is not virtual, then the implementation corresponding to the compile-time type of the object pointer.

    For example, consider this:

    class Sample {
    public:
     void f();
     virtual void vf();
    };
    
    class Derived : public Sample {
    public:
     void f();
     void vf();
    }
    
    void function()
    {
     Derived d;
     Sample* p = &d;
     p->f();
     p->vf();
    }
    

    The call to p->f() will result in a call to Sample::f because p is a pointer to a Sample. The actual object is of type Derived, but the pointer is merely a pointer to a Sample. The pointer type is used because f is not virtual.

    On the other hand, the call to The call to p->vf() will result in a call to Derived::vf, the most heavily derived type, because vf is virtual.

    Okay, you knew that.

    Virtual destructors work exactly the same way. It's just that you rarely invoke the destructor explicitly. Rather, it's invoked when an automatic object goes out of scope or when you delete the object.

    void function()
    {
     Sample* p = new Derived;
     delete p;
    }
    

    Since Sample does not have a virtual destructor, the delete p invokes the destructor of the class of the pointer (Sample::~Sample()), rather than the destructor of the most derived type (Derived::~Derived()). And as you can see, this is the wrong thing to do in the above scenario.

    Armed with this information, you can now answer the question.

    A class must have a virtual destructor if it meets both of the following criteria:

    • You do a delete p.
    • It is possible that p actually points to a derived class.

    Some people say that you need a virtual destructor if and only if you have a virtual method. This is wrong in both directions.

    Example of a case where a class has no virtual methods but still needs a virtual destructor:

    class Sample { };
    class Derived : public Sample
    {
     CComPtr<IStream> m_p;
    public:
     Derived() { CreateStreamOnHGlobal(NULL, TRUE, &m_p); }
    };
    
    Sample *p = new Derived;
    delete p;
    

    The delete p will invoke Sample::~Sample instead of Derived::~Derived, resulting in a leak of the stream m_p.

    And here's an example of a case where a class has virtual methods but does not require a virtual destructor.

    class Sample { public: virtual void vf(); }
    class Derived : public Sample { public: virtual void vf(); }
    
    Derived *p = new Derived;
    delete p;
    

    Since the object deletion occurs from the pointer type that matches the type of the actual object, the correct destructor will be invoked. This pattern happens often in COM objects, which expose several virtual methods corresponding to its interfaces, but where the object itself is destroyed by its own implementation and not from a base calss pointer. (Notice that no COM interfaces contain virtual destructors.)

    The problem with knowing when to make your destructor virtual or not is that you have to know how people will be using your class. If C++ had a "sealed" keyword, then the rule would be simpler: If you do a "delete p" where p is a pointer to an unsealed class, then that class needs have a virtual destructor. (The imaginary "sealed" keyword makes it explicit when a class can act as the base class for another class.)

  • The Old New Thing

    Batman and Robin patrolling Whitley

    • 2 Comments

    Batman and Robin have been spotted in Whitley, providing valuable services like rescuing damsels in distress (pushing her car to the nearest petrol station), scaring away potential muggers, and even chasing streakers off a football pitch.

  • The Old New Thing

    Why did InterlockedIncrement/Decrement only return the sign of the result?

    • 16 Comments

    If you read the fine print of the InterlockedIncrement and InterlockedDecrement functions, you'll see that on Windows NT 3.51 and earlier and on Windows 95, the return value only matches the sign of the result of the increment or decrement. Why is that?

    The 80386 instruction set supports interlocked increment and decrement, but the result of the increment/decrement operation is not returned. Only the flags are updated by the operation. As a result, the only information you get back from the CPU about the result of the operation is whether it was zero, positive, or negative. (Okay, you also get some obscure information like whether there were an even or odd number of 1 bits in the result, but that's hardly useful nowadays.)

    Since those operating systems supported the 80386 processor, their implementations of the InterlockedIncrement and InterlockedDecrement functions were limited by the capabilities of the processor.

    The 80486 introduced the XADD instruction which returns the original value of the operand. With this additional information, it now becomes possible to return the result of the operation exactly.

    Windows NT 4 dropped support for the 80386 processor, requiring a minimum of an 80486, so it could take advantage of this instruction. Windows 98 still had to support the 80386, so it couldn't.

    So how did Windows 98 manage to implement an operation that was not supported by the CPU?

    Windows 98 detected whether you had a CPU that supported the new XADD instruction. If not, then it used an alternate mechanism which was mind-bogglingly slow: It called a driver whenever you wanted to increment or decrement a variable. The driver would then emulate the XADD instruction by disabling interrupts and performing the operation in locked memory. Since Windows 98 was a uniprocessor operating system, it didn't have to worry about a second processor changing the memory at the same time; all it needed to ensure was that the single processor didn't get interrupted while it was performing the "atomic" operation.

  • The Old New Thing

    Chris Pratley's history lesson

    • 9 Comments

    If you haven't read it yet, check out Chris Pratley's voluminous discourse on various aspects of the history of Word. It packs more history into one entry than I do all year.

    And that was a sequel! You can read the first half, too.

    In fact, the good stuff keeps on coming. just read it all.

  • The Old New Thing

    Scripting is a two-edged sword

    • 15 Comments

    A three line VB script will disable your firewall.

    The advantage of scripting is that you can control so many things with just a few lines of code.

    The disadvantage of scripting is that bad people can control so many things with just a few lines of code.

    I wonder how long it will be before there's a virus that disables the firewall.

Page 4 of 5 (47 items) 12345