Welcome to MSDN Blogs Sign in | Join | Help

How many days long is a one-day sale? The answer might surprise you

A friend of mine received a flyer for a major department store proudly proclaiming that they were having a One-Day-Only sale.

Sale prices were in effect on Saturday and Sunday.

Previously on the subject For large values of 1. If this keeps up, I may have to create a subcategory for it.

Posted by oldnewthing | 16 Comments
Filed under:

How does delay-loading use binding information?

In the documentation for delay-loading, there's a remark that says that the call to GetProcAddress can be avoided if there is binding information. A customer who received the explanation of why you can't delay-load kernel32 pointed out that paragraph and asked whether this means that you can delay-load kernel32 if you bind to it. (Getting around to answering this question was the point of the past few days.)

Let's take another look at what that GetProcAddress-avoidance optimization does. Actually, it's just another look at what the module loader does when it's time to resolve imports to a bound DLL: At build time, the actual function pointers are precomputed and cached, along with the timestamp of the DLL those precomputed values came from. At run time, the delay-load stubs check the timestamp of the target DLL and compare it against the timestamp that it had cached. If they are the same, then they skip the call to GetProcAddress and use the cached value.

In other words, the delay-load stubs use binding information in exactly the same way the module loader does.

Does this mean that you can now delay-load kernel32?

No. First of all, if the timestamps don't match or if the target DLL was not loaded at its preferred address, then the binding information is of no use—you have a cache miss. In that case, the module loader (and the delay-load stubs) must obtain the function pointers the old-fashioned way. You can't assume that your binding information will always be accurate. (For example, after your module was bound to kernel32, there may have been a security update which modified kernel32, which invalidates your binding information.)

And besides, even if the binding information were used, you still have to call LoadLibrary to get the target DLL loaded in the first place. Even though binding may have optimized away one call to kernel32, you still have that LoadLibrary to deal with.

Posted by oldnewthing | 10 Comments
Filed under:

What is DLL import binding?

Last time, we saw how hinting is used to speed up the resolving of imported functions. Today, we'll look at binding.

Recall that the module loader resolves imports by locating the function in the export table of the linked-to DLL and recording the results in the loaded module's table of imported function addresses so that code from the module can jump indirectly through the table and reach the target function.

One of the consequences of this basic idea is that the table of imported function addresses is written to at module load time. Writeable data in a module is stored in the form of copy-on-write pages. Copy-on-write pages are a form of computer optimism: "I'm going to assume that nobody writes to this page, so that I can share it among all copies of the DLL loaded into different processes" (assuming other conditions are met, not important to this discussion; don't make me bring back the nitpicker's corner). "In this way, I can conserve memory, leaving more memory available for other things." But once you write to the page, that assumption is proven false, and the memory manager needs to make a private copy of the page for your process. If two processes load your DLL, they each get their own copy of the memory once they write to it, and the opportunity to share the memory between the two DLLs is lost.

What is particularly sad is when the copy-on-write page is forced to be copied because two processes wrote to the pages, even if the processes wrote the same value. Since the two pages are now once again identical, they could in principle be shared again. (The memory manager doesn't do memcmps of every potentially-shared page each time you write to it, on the off chance that you happened to make two pages coincidentally identical. Once a copy-on-write page is written to, the memory manager makes the copy and says, "Oh well, it was good while it lasted.")

One of the cases where two processes both write to the page and write the same value is when they are resolving imports to the same DLL. In that case, the call to GetProcAddress will return the same value in both processes (assuming the target DLL is loaded at the same base address in both processes), and you are in the sad case where two processes dirty the page by writing the same value.

To make this sad case happy again, the module loader has an optimization to avoid writing to pages it doesn't have to: We pre-initialize the values in the table of imported function addresses to a prediction as to what the actual address of the function will be. Then we can have the module loader compare the return value of GetProcAddress against the predicted value, and if they are the same, it skips the write. In context diff format:

// error checking deleted since it's not relevant to the discussion

for (Index = 0; Index < NumberOfImportedFunctions; Index++) {
  FunctionPointer = GetProcAddress(hinst, ImportEntry[Index]);

- TableEntry[Index] = FunctionPointer;

+ if (TableEntry[Index] != FunctionPointer)
+   TableEntry[Index] = FunctionPointer;
}

But wait, we can optimize this even more. How about avoiding the entire loop? This saves us the trouble of having to call GetProcAddress in the first place.

There is an extra field in the import descriptor table entry called TimeDateStamp which records the timestamp of the DLL from which the precomputed function pointer values were obtained. Every DLL has a timestamp, recorded in the module header information. (The format of this timestamp is in seconds since January 1, 1970, commonly known as unix time format.) Before the module loader resolves imported functions, it compares the timestamp in the import descriptor table entry against the timestamp in the actual DLL that got loaded. If they match (and if the actual DLL was loaded at its preferred base address), then the module loader skips the loop entirely: All the precomputed values are correct.

That's the classical model for binding. There have been some changes since the original implementation, but they don't change the underlying principle: Precompute the answers and associate them with a key which lets you determine whether the information against which the values were precomputed matches the information that you actually have.

Binding therefore is a performance optimization to address both wall-clock running time (by reducing the amount of computation performed at module load time) and memory consumption (by reducing the number of copy-on-write pages actually written to).

Exercise: Why is the timestamp stored in the module header? Why not just use the actual file last-modified time?

Exercise: When you rebase a DLL, does it update the timestamp?

Posted by oldnewthing | 25 Comments
Filed under:

What is DLL import hinting?

Binding and hinting are two types of optimizations to improve the load-time performance of a module (executable or DLL). We'll start with hinting, then look at binding, and then look at how it affects delay-loading.

The import table for a module contains a list of DLLs and a list of functions from that DLL which the module wishes to link to. The basic idea is that for each target DLL, the linker loads the DLL and then obtains the address of each imported function and from that DLL, records the results in the loaded module's table of imported function addresses.

Hinting is a technique for speeding up this lookup.

In addition to recording the name of the function the module wishes to link to, the linker also records the index of the function in the target DLL's export table. For example, suppose we had a DLL named FLINT whose export table looks like this:

1Barney
2Fred
3Wilma

I've numbered the entries for reasons you'll see soon.

You wrote a DLL which imports all three of these functions. The import table for your DLL goes something like this:

Import from FLINT
Fred (hint = 2)
Wilma (hint = 3)
Barney (hint = 1)

When your DLL gets loaded, the module loader loads the target DLL FLINT.DLL, and then it goes about resolving the imports. First up is Fred. But instead of just searching the export table of FLINT.DLL for the function Fred, it sees the hint and says, "Hey, let me look in slot 2 first." And lo and behold, there the function is in slot 2. Yay, a full search of the export table was not necessary; the hint sent us directly to the correct slot.

The hint is just a hint, though. If FLINT.DLL doesn't have the function Fred in slot 2, then the loader just does things the old-fashioned way, by searching the export table for the Fred function.

In general, hints are fairly resilient as long as the DLL doesn't change too much. If FLINT.DLL is updated, say by a security patch, the list of functions typically does not change, so the position in the exported names table remains unchanged. It's only if a function is added to or removed from FLINT.DLL do the hints begin to lose their effectiveness.

Next time, we'll look at binding, which is an optimization even more powerful than hinting, but which is also more fragile.

Posted by oldnewthing | 10 Comments
Filed under:

Raymond's highly scientific predictions for the 2010 NCAA men's basketball tournament

Once again, it's time for Raymond to come up with an absurd, arbitrary criterion for filling out his NCAA bracket.

This year, we go to the well-known dispute arbiter Google Fight. The criterion is the number of Google hits for the quoted phrase "%s basketball", divided by the school's seed. (I would have used Bing hits, except Bing's numbers are highly erratic. Only 291 hits for "Arkansas-Pine Bluff basketball"? The results may be more meaningful, but I'm not looking for meaningful results; I'm looking for numbers I can plug into my bracket-o-matic.)

Once the field has been narrowed to eight teams, the results are determined by a coin flip.


Opening Round Game

Arkansas-Pine Bluff(80,900) Arkansas-Pine Bluff
(80,900)
Winthrop(29,200)

Midwest bracket

1Kansas(94,200) Kansas
(94,200)
Kansas
(94,200)
Kansas
(heads)
Kansas
16Lehigh(1,400)
8UNLV(43,875) Northern Iowa
(57,444)
9Northern Iowa(57,444)
5Michigan State(15,120) Michigan State
(15,120)
Maryland
(27,750)
12New Mexico State(11,083)
4Maryland(27,750) Maryland
(27,750)
13Houston(6,784)
6Tennessee(11,550) San Diego State
(17,272)
Georgetown
(24,766)
Ohio State
(tails)
11San Diego State(17,272)
3Georgetown(24,766) Georgetown
(24,766)
14Ohio(2,057)
7Oklahoma State(2,857) Oklahoma State
(2,857)
Ohio State
(34,700)
10Georgia Tech(2,750)
2Ohio State(34,700) Ohio State
(34,700)
15UC Santa Barbara(4,746)

West bracket

1Syracuse(131,000) Syracuse
(131,000)
Syracuse
(131,000)
Syracuse
(tails)
Pittsburgh
16Vermont(5,312)
8Gonzaga(5,325) Gonzaga
(5,325)
9Florida State(1,644)
5Butler(26,000) Butler
(26,000)
Vanderbilt
(61,750)
12UTEP(7,600)
4Vanderbilt(61,750) Vanderbilt
(61,750)
13Murray State(6,761)
6Xavier(26,500) Xavier
(26,500)
Pittsburgh
(54,666)
Pittsburgh
(heads)
11Minnesota(3,427)
3Pittsburgh(54,666) Pittsburgh
(54,666)
14Oakland(1,728)
7BYU(6,785) BYU
(6,785)
Kansas State
(25,000)
10Florida(5,850)
2Kansas State(25,000) Kansas State
(25,000)
15North Texas(7,066)

East bracket

1Kentucky(285,000) Kentucky
(285,000)
Kentucky
(285,000)
Kentucky
(tails)
New Mexico
16East Tennessee State(3,687)
8Texas(24,250) Texas
(24,250)
9Wake Forest(3,022)
5Temple(32,600) Cornell
(60,000)
Cornell
(60,000)
12Cornell(60,000)
4Wisconsin(8,325) Wisconsin
(8,325)
13Wofford(2,192)
6Marquette(15,283) Marquette
(15,283)
New Mexico
(17,633)
New Mexico
(heads)
11Washington(4,009)
3New Mexico(17,633) New Mexico
(17,633)
14Montana(1,778)
7Clemson(4,885) Clemson
(4,885)
West Virginia
(10,800)
10Missouri(3,700)
2West Virginia(10,800) West Virginia
(10,800)
15Morgan State(5,686)

South bracket

1Duke(363,000) Duke
(363,000)
Duke
(363,000)
Duke
(heads)
Duke
16Arkansas-Pine Bluff(5,056)
8California(2,487) Louisville
(17,444)
9Louisville(17,444)
5Texas A&M(156,000) Texas A&M
(156,000)
Texas A&M
(156,000)
12Utah State(0,883)
4Purdue(12,000) Purdue
(12,000)
13Siena(0,900)
6Notre Dame(14,450) Old Dominion
(14,454)
Old Dominion
(14,454)
Villanova
(tails)
11Old Dominion(14,454)
3Baylor(11,100) Baylor
(11,100)
14Sam Houston State(4,671)
7Richmond(5,714) Saint Mary's
(9,900)
Villanova
(18,000)
10Saint Mary's(9,900)
2Villanova(18,000) Villanova
(18,000)
15Robert Morris(13,000)

Finals

Kansas(tails) Pittsburgh
(heads)
Pittsburgh
Pittsburgh(heads)
New Mexico(heads) New Mexico
(tails)
Duke(tails)
Posted by oldnewthing | 14 Comments
Filed under:

Why does my control send its notifications to the wrong window after I reparent it?

MontagFTB noticed that some controls have the problem that if you reparent the control, it still sends notifications to its old parent. We looked at the faulty diagnosis last time. What's the real reason?

The control cached its original parent window.

Most complex controls communicate with the parent window frequently, and in order to avoid calling GetParent, the control gets its parent once and caches the value for future use. Under normal conditions, this cache works very well since reparenting a window is extremely rare and is generally considered an unusual condition. Like the adoption of a child, it's the sort of thing you should only be doing with the coordination of all three parties (the old parent, the new parent, and the child).

When you reparent the control, the cached value in the child window is no longer correct. But since you didn't coordinate this with the child window, the control doesn't know this, and it keeps talking to the old parent. Unlike the Post Office, you can't submit a change of address form to the window manager and tell it, "Hey, if somebody tries to send a message to windows X, deliver it to window Y if the return address is window Z." (Actually, the Post Office stops forwarding mail after one year.)

Since window reparenting is considered to be an unusual condition, most controls don't have provisions for telling them, "Hey, I reparented you. Please send future notifications to that window over there." The window manager is fine with all your reparenting games, but the other participants may have made assumptions based on the stability of the window hierarchy.

Where does that leave MontagFTB? (It is at this point where a general topic gradually turns into addressing questions that are applicable only to MontagFTB's situation and aren't all that useful to others. This is something I try to avoid, because this is a blog, not a consulting service.)

First, you can avoid the staging window and just create the controls with the correct parent. I don't know why the staging window was necessary, so this may not be a viable solution. If it was merely to avoid flicker, then you can create the controls as hidden windows, and then do a massive ShowWindow when they are ready. Or you can create the controls at negative coordinates (so they don't appear inside the parent's client rectangle), and then when you're ready, perform a big EndDeferWindowPos to move them all into position at once.

If you really need to have the staging window, you can have the staging window do the message forwarding. If it receives a WM_COMMAND or WM_NOTIFY notification message from one of these given-away child windows, it just forwards the message to the new owner. However, this violates the guideline that "When reparenting a window, the old parent, the new parent, and the child all need to be involved in the process if you want the adoption to go smoothly," so I would not recommend it.

If you don't want to make the staging window have to deal with message forwarding (for example, if you intend on destroying the staging window once you have removed all the child windows), then you can insert a level of redirection: Create a container window as a child of the staging window, and create the child windows as children of the container. Then when it's time to reparent the controls, move the container window to the new parent. This adheres to the guideline because the windows involved in the reparenting (the final destination, the staging window, and the container window) are all under your control, and therefore you can make sure all internal state is correct when you change the bookkeeping relationship among them. And since the controls are destined for a dialog box, you should give the container the WS_EX_CONTROLPARENT style so that they can participate in dialog box navigation.

Posted by oldnewthing | 2 Comments
Filed under:

Robots and humans coexisting, can it be done peacefully?

Everybody who follows science fiction knows that if you have robots and humans living in the same world, eventually something bad happens to the humans

But we're going to chance it one more time.

Every so often, I stumble across a Web site that translates my articles into another language. I occasionally see a Japanese translation, and I think there's a Russian translation out there somewhere. In addition to those human translations, there are also robot translations available through your favorite online translation service.

Now the two can coexist.

There's a new widget on this page which generates a robot translation, but there's more to it than that. Microsoft Translator's new Collaborative Translations Framework lets you suggest improvements to the robot translation, thereby helping the robot do a better job in the future. Visitors can view the translation alternatives and vote for the best one, and all this information gets funneled back to the Web site administrator, who can select one of the translations to be the one that appears by default the next time somebody asks to translate the page.

And just because I'm so cool, the translation folks have provided a team of native speakers to act as the translation administrators for my Web site. That's good for me, because I am not really qualified to rate the quality of translations in French, or Spanish, or, um, pretty much anything other than English. (My understanding of German and Swedish gets me as far as identifying what is intelligible, but I don't claim to have a grasp of the subtleties.)

Let's hope this doesn't destroy humanity.² (But if it does, at least your participation puts you on the winning side, right?)

Footnotes

¹Probably because stories that go "Humans and robots work together, but then the humans turn off the robots" aren't as compelling.

²I'm pretty sure the Microsoft Translator team is not too happy that I'm suggesting that their invention may destroy humanity. Hey, I'm not saying it's going to happen. But it's possible.

Bonus chatter: If you have questions about the Collaborative Translations Framework, you can ask them on the Microsoft Translator forum.

Posted by oldnewthing | 37 Comments
Filed under:

A window can have a parent or an owner but not both

MontagFTB had a problem which, upon investigation, allegedly was caused by a subtle "fact": "The parent specified in CreateWindowEx is both the parent of the window and the owner of the window, but when you call SetParent it only sets the parent of the window, not the owner." MontagFTB then concluded that some messages were sent to the parent and others were sent to the owner.

This is a faulty diagnosis. We'll look at the correct diagnosis next time, but today's topic is parents and owners. Actually, parent and owner windows were already covered by my 2005 PDC talk, Five Things Every Win32 Programmer Should Know, so for most of you, today's topic is a review. (And I included the topic in the talk specifically so I wouldn't have to blog about it, but obviously that plan didn't work out.)

A window can be created as a child window (WS_CHILD set) or a top-level window (WS_CHILD not set). A child window has a parent, which you specify when you call CreateWindowEx, and which you can change by calling SetParent. A top-level window, on the other hand, has no parent. Its parent is NULL.

Ownership is a concept that relates top-level windows. A top-level window can optionally have an owner, which is also specified when you call CreateWindowEx, and which you can change by a complicated mechanism described in my talk.

Note that changing a window's parent or owner is not a normal operation. You usually create a window with a specific parent or owner and leave it that way for the lifetime of the window.

Now, a window can have a parent, or it can have an owner, or it can have neither, but it can never have both.

What would it mean for a window to have both a parent and an owner? Well, in order to have a parent, the window must itself be a child. But in order to have an owner, the window must be top-level. And top-level windows and child windows are mutually exclusive (and collectively exhaustive), because you either have the WS_CHILD style (which makes you a child) or you don't (which makes you top-level). Since people like tables so much, here's a table:

Child windowTop-level window
The Parent window is...non-NULL NULL
The Owner window is...N/A NULL or non-NULL

The box for "The Owner window of a Child window..." is marked N/A because the question is meaningless. Ownership is a relationship among top-level windows.

By analogy, consider the people at a school for children. They can be separated into two groups, students and teachers. (We'll treat non-teaching staff as teachers with no students.)

Each student is assigned to a teacher. Each teacher might or might not have another teacher as a mentor. Several students can be assigned the same teacher, but every student must be assigned to some teacher. Similarly, several teachers might have the same mentor, but some teachers won't have a mentor at all, and some mentors might themselves have mentors.

It's impossible for a person to have both a teacher and a mentor, because having a teacher applies only to students, and having a mentor applies only to teachers. Teachers don't attend classes (they lead the classes) so they don't have a teacher. But they might have mentors. Asking for a student's mentor is a meaningless question because students don't have mentors; teachers do.

Since a window cannot have both a parent and an owner, the CreateWindowEx function takes a single HWND parameter which is either the parent or owner of the window being created, depending on what type of window you're creating. If you're creating a child window, then the parameter represents the parent window; if you're creating a top-level window, then the parameter represents the owner window.

A similar overloading of parameters happens with the HMENU: If you're creating a child window, then the parameter represents the child window identifier; if you're creating a top-level window, then the parameter represents the window menu. Because only top-level windows can have menus, and only child windows can have child window identifiers.

If this parameter overloading bothers you, you can write your own helper functions:

HWND CreateChildWindowEx(
    DWORD dwExStyle,
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    UINT_PTR id,
    HINSTANCE hInstance,
    LPVOID lpParam
)
{
 // A child window must have the WS_CHILD style
 if (!(dwStyle & WS_CHILD)) {
  SetLastError(ERROR_INVALID_PARAMETER);
  return NULL;
 }
 return CreateWindowEx(
    dwExStyle,
    lpClassName,
    lpWindowName,
    dwStyle,
    x,
    y,
    nWidth,
    nHeight,
    hWndParent,
    reinterpret_cast<HMENU>(id),
    hInstance,
    lpParam);
}

HWND CreateTopLevelWindowEx(
    DWORD dwExStyle,
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndOwner,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
)
{
 // A top-level window must not have the WS_CHILD style
 if (dwStyle & WS_CHILD) {
  SetLastError(ERROR_INVALID_PARAMETER);
  return NULL;
 }
 return CreateWindowEx(
    dwExStyle,
    lpClassName,
    lpWindowName,
    dwStyle,
    x,
    y,
    nWidth,
    nHeight,
    hWndOwner,
    hMenu,
    hInstance,
    lpParam);
}

There's more to parent windows and owner windows than what I've written here; I refer you to my talk (or other documentation) for more details.

Next time, we'll look at what MontagFTB is really seeing.

Posted by oldnewthing | 19 Comments
Filed under:

Simplifying context menu extensions with IExecuteCommand

The IExecuteCommand interface is a simpler form of context menu extension which takes care of the annoying parts of IContextMenu so you can focus on your area of expertise, namely, doing the actual thing the user selected, and leave the shell to doing the grunt work of managing the UI part.

I've never needed a scratch shell extension before, so I guess it's time to create one. This part is completely boring, and those of you who have written COM inproc servers can skip over it.

#include <windows.h>
#include <new>

LONG g_cObjs;

void DllAddRef() { InterlockedIncrement(&g_cObjs); }
void DllRelease() { InterlockedDecrement(&g_cObjs); }

// guts of shell extension go in here eventually

class CFactory : public IClassFactory
{
public:
 // *** IUnknown ***
 STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
 STDMETHODIMP_(ULONG) AddRef() { return 2; }
 STDMETHODIMP_(ULONG) Release() { return 1; }

 // *** IClassFactory ***
 STDMETHODIMP CreateInstance(IUnknown *punkOuter,
                             REFIID riid, void **ppv);
 STDMETHODIMP LockServer(BOOL fLock);
};

CFactory c_Factory;

STDMETHODIMP CFactory::QueryInterface(REFIID riid, void **ppv)
{
 IUnknown *punk = NULL;
 if (riid == IID_IUnknown || riid == IID_IClassFactory) {
  punk = static_cast<IClassFactory*>(this);
 }
 *ppv = punk;
 if (punk) {
  punk->AddRef();
  return S_OK;
 } else {
  return E_NOINTERFACE;
 }
}

STDMETHODIMP CFactory::CreateInstance(
 IUnknown *punkOuter, REFIID riid, void **ppv)
{
 *ppv = NULL;
 if (punkOuter) return CLASS_E_NOAGGREGATION;
 CShellExtension *pse = new(std::nothrow) CShellExtension();
 if (!pse) return E_OUTOFMEMORY;
 HRESULT hr = pse->QueryInterface(riid, ppv);
 pse->Release();
 return hr;
}

STDMETHODIMP CFactory::LockServer(BOOL fLock)
{
 if (fLock) DllAddRef();
 else       DllRelease();
 return S_OK;
}

STDAPI DllGetClassObject(REFCLSID rclsid,
                         REFIID riid, void **ppv)
{
 if (rclsid == CLSID_ShellExtension) {
  return c_Factory.QueryInterface(riid, ppv);
 }
 *ppv = NULL;
 return CLASS_E_CLASSNOTAVAILABLE;
}

STDAPI DllCanUnloadNow()
{
 return g_cObjs ? S_OK : S_FALSE;
}

I'm assuming that the above code is all old hat. Consider it a prerequisite.

Okay, now the good stuff.

The IExecuteCommand interface is used when you create a static registration for a shell verb but specify DelegateExecute in the command. Our sample shell extension will be active on text files, and all it'll do is print the file names to the debugger.

Since we're a COM server, we need to register our CLSID. This should also be very familiar to you.

[HKEY_CLASSES_ROOT\CLSID\{guid}\InProcServer32]
@="C:\path\to\scratch.dll"
"ThreadingModel"="Apartment"

Here's where we register our object as a verb for text files, specifying that it should be invoked via DelegateExecute:

[HKEY_CLASSES_ROOT\txtfile\shell\printnamestodebugger]
@="Print names to debugger"

[HKEY_CLASSES_ROOT\txtfile\shell\printnamestodebugger\command]
"DelegateExecute"="{guid}"

That was the easy part. Now to roll up our sleeves and write the shell extension.

#include <shobjidl.h>

CLSID CLSID_ShellExtension = { ...guid... };

class CShellExtension
 : public IExecuteCommand
 , public IInitializeCommand
 , public IObjectWithSelection
{
public:
 CShellExtension();

 // *** IUnknown ***
 STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
 STDMETHODIMP_(ULONG) AddRef();
 STDMETHODIMP_(ULONG) Release();

 // *** IInitializeCommand ***
 STDMETHODIMP Initialize(PCWSTR pszCommandName, IPropertyBag *ppb);

 // *** IObjectWithSelection ***
 STDMETHODIMP SetSelection(IShellItemArray *psia);
 STDMETHODIMP GetSelection(REFIID riid, void **ppv);

 // *** IExecuteCommand ***
 STDMETHODIMP SetKeyState(DWORD grfKeyState) { return S_OK; }
 STDMETHODIMP SetParameters(LPCWSTR pszParameters) { return S_OK; }
 STDMETHODIMP SetPosition(POINT pt) { return S_OK; }
 STDMETHODIMP SetShowWindow(int nShow) { return S_OK; }
 STDMETHODIMP SetNoShowUI(BOOL fNoShowUI) { return S_OK; }
 STDMETHODIMP SetDirectory(LPCWSTR pszDirectory) { return S_OK; }
 STDMETHODIMP Execute();

private:
 ~CShellExtension();
private:
 LONG m_cRef;
 IShellItemArray *m_psia;
};

CShellExtension::CShellExtension()
 : m_cRef(1), m_psia(NULL)
{
 DllAddRef();
}

CShellExtension::~CShellExtension()
{
 if (m_psia) m_psia->Release();
 DllRelease();
}

I've written this all out longhand; I'm trusting that you're using some sort of framework (like, say, ATL) which avoids all this tedium, but since different people may choose different frameworks, I won't choose a framework here. Instead, we just have the boring IUnknown methods.

STDMETHODIMP CShellExtension::QueryInterface(
 REFIID riid, void **ppv)
{
 IUnknown *punk = NULL;
 if (riid == IID_IUnknown || riid == IID_IExecuteCommand) {
  punk = static_cast<IExecuteCommand*>(this);
 } else if (riid == IID_IInitializeCommand) {
  punk = static_cast<IInitializeCommand*>(this);
 } else if (riid == IID_IObjectWithSelection) {
  punk = static_cast<IObjectWithSelection*>(this);
 }
 *ppv = punk;
 if (punk) {
  punk->AddRef();
  return S_OK;
 } else {
  return E_NOINTERFACE;
 }
}

STDMETHODIMP_(ULONG) CShellExtension::AddRef()
{
 return ++m_cRef;
}

STDMETHODIMP_(ULONG) CShellExtension::Release()
{
 ULONG cRef = --m_cRef;
 if (cRef == 0) delete this;
 return cRef;
}

Whew. Up until now, it's just been boring typing that you have to do for any shell extension. Finally we can start doing something interesting. Windows 7 will initialize your shell extension with information about the command being executed. For this particular shell extension, we'll just print the command name to the debugger to prove that something happened. (In real life, you might use the same CShellExtension to handle multiple commands, and this lets you determine which command you're being asked to execute.)

STDMETHODIMP CShellExtension::Initialize(
 PCWSTR pszCommandName,
 IPropertyBag *ppb)
{
 OutputDebugStringW(L"Command: ");
 OutputDebugStringW(pszCommandName);
 OutputDebugStringW(L"\r\n");
 return S_OK;
}

The shell will give you the items on which to execute in the form of an IShellItemArray:

STDMETHODIMP CShellExtension::SetSelection(IShellItemArray *psia)
{
 if (psia) psia->AddRef();
 if (m_psia) m_psia->Release();
 m_psia = psia;
 return S_OK;
}

STDMETHODIMP CShellExtension::GetSelection(
 REFIID riid, void **ppv)
{
 if (m_psia) return m_psia->QueryInterface(riid, ppv);
 *ppv = NULL;
 return E_NOINTERFACE;
}

The shell will then call a bunch of IExecuteCommand::SetThis and IExecuteCommand::SetThat methods to inform you of the environment in which you have been asked to execute. We just ignored them all for simplicity, but in practice, you may want to pay attention to some of them, particularly IExecuteCommand::SetPosition, IExecuteCommand::SetShowWindow, and IExecuteCommand::SetNoShowUI.

After all the IExecuteCommand::SetXxx methods have been called, it's show time:

STDMETHODIMP CShellExtension::Execute()
{
 HRESULT hr;
 if (m_psia) {
  IEnumShellItems *pesi;
  if (SUCCEEDED(hr = m_psia->EnumItems(&pesi))) {
   IShellItem *psi;
   while (pesi->Next(1, &psi, NULL) == S_OK) {
    LPWSTR pszName;
    if (SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH,
                                      &pszName))) {
     OutputDebugStringW(L"File: ");
     OutputDebugStringW(pszName);
     OutputDebugStringW(L"\r\n");
     CoTaskMemFree(pszName);
    }
    psi->Release();
   }
   pesi->Release();
   hr = S_OK;
  }
 } else {
  hr = E_UNEXPECTED;
 }
 return hr;
}

All we do is enumerate the contents of the IShellItemArray and print their file names (if they have one). Instead of IEnumShellItems, you can use IShellItemArray::GetCount and IShellItemArray::GetItemAt. Or, if you are porting an existing context menu that uses IDataObject, you can call IShellItemArray::BindToHandler(BHID_DataObject) to turn your IShellItemArray into an IDataObject.

Install this shell extension, right-click on a text file (or a bunch of text files), and select Print names to debugger. If all goes well, the debugger will report Command: printnamestodebugger followed by paths of the files you selected.

But wait, there's more. The IPropertyBag passed to IInitializeCommand::Initialize contains additional configuration options taken from the registry. You can use this to customize the behavior of the shell extension further. Put the bonus information under the command key like this:

[HKEY_CLASSES_ROOT\txtfile\shell\printnamestodebugger]
"extra"="Special"
STDMETHODIMP CShellExtension::Initialize(
 PCWSTR pszCommandName,
 IPropertyBag *ppb)
{
 OutputDebugStringW(L"Command: ");
 OutputDebugStringW(pszCommandName);
 OutputDebugStringW(L"\r\n");
 if (ppb) {
  VARIANT vt;
  VariantInit(&vt);
  if (SUCCEEDED(ppb->Read(L"extra", &vt, NULL))) {
   if (SUCCEEDED(VariantChangeType(&vt, &vt, 0, VT_BSTR))) {
    OutputDebugStringW(L"extra: ");
    OutputDebugStringW(vt.bstrVal);
    OutputDebugStringW(L"\r\n");
   }
   VariantClear(&vt);
  }
 }
 return S_OK;
}

This updated version of CShellExtension looks for that registry value extra we set above and if found prints its value to the debugger.

Okay, so it looks like a lot of typing, but most of that was typing you have to do for any shell extension. The part that is specific to IExecuteCommand is not that bad, and it certainly avoids having to mess with IContextMenu::QueryContextMenu and the fifty bajillion variations on IContextMenu::InvokeCommand. Furthermore, the shell doesn't even load your IExecuteCommand handler until the user selects your command, so switching to a static registration also gives the system a bit of a performance boost.

Bonus tip: You can combine the IExecuteCommand technique with Getting Dynamic Behavior for Static Verbs by Using Advanced Query Syntax and Using Item Attributes to specify the conditions under which you want your verb to appear without having to write a single line of C++ code. Choosing a Static or Dynamic Shortcut Menu Method provides additional guidance on choosing among the various methods for registering verbs.

One nice thing about IExecuteCommand is that it supports out-of-proc activation (i.e., local server rather than in-proc server). This means that it supports cross-bitness shell extensions: If you don't have the time to port your 32-bit shell extension to 64-bit, you can register it as an out-of-proc IExecuteCommand. When running on 64-bit Windows, the 64-bit Explorer will launch your 32-bit server to handle the command. Conversely, if your IExecuteCommand is a 64-bit local server, a 32-bit application can still invoke it.

(We'll see more about local server shell extensions in a few months. This was just foreshadowing.)

Posted by oldnewthing | 16 Comments
Filed under:

Why does the OLE variant date format use 30 December 1899 as its zero point?

In 2006, via the suggestion box, Chris J asks why the OLE variant date format has such a strange zero point. Its zero point is 30 December 1899, as opposed to 1 January 1900 (SQL Server's zero point) or 1 January 1970 (the unix zero point).

It turns out I don't have to answer this because Eric Lippert explained it three years before the question was posted, and then some time later posted a puzzle regarding date calculations.

Public Service Announcement: This weekend marks the start of Daylight Saving Time in most of the United States.

Posted by oldnewthing | 14 Comments
Filed under: ,

Application compatibility layers are there for the customer, not for the program

Some time ago, a customer asked this curious question (paraphrased, as always):

Hi, we have a program that was originally designed for Windows XP and Windows Server 2003, but we found that it runs into difficulties on Windows Vista. We've found that if we set the program into Windows XP compatibility mode, then the program runs fine on Windows Vista. What changes do we need to make to our installer so that when the user runs it on Windows Vista, it automatically runs in Windows XP compatibility mode?

Don't touch that knob; the knob is there for the customer, not for the program. And it's there to clean up after your mistakes, not to let you hide behind them.

It's like saying, "I normally toss my garbage on the sidewalk in front of the pet store, and every morning, when they open up, somebody sweeps up the garbage and tosses it into the trash. But the pet store isn't open on Sundays, so on Sundays, the garbage just sits there. How can I get the pet store to open on Sundays, too?"

The correct thing to do is to figure out what your program is doing wrong and fix it. You can use the Application Compatibility Toolkit to see all of the fixes that go into the Windows XP compatibility layer, then apply them one at a time until you find the one that gets your program running again. For example, if you find that your program runs fine once you apply the VersionLie shim, then go and fix your program's operating system version checks.

But don't keep throwing garbage on the street.

Posted by oldnewthing | 40 Comments
Filed under:

One of the consequences of accepting a job offer is that you might end up working with an interviewer who didn't like you

At an informal gathering, my colleagues and I started talking about our experiences being interviewed at Microsoft. One of the people there remembered how one of the pieces of feedback on the interview lo these many years ago was that although my colleague was certainly smart enough and hardworking enough, there seemed to be insufficient enthusiasm for the subject matter. I mean, my colleague cared about the subject matter but apparently didn't care enough to satisfy the interviewer. The offer was extended despite this reservation, and my colleague joined the team. Years passed, and the details of the encounter were largely forgotten. Microsoft is a large company, and the group you end up assigned to may not have any members from your original interview loop.

The assessment of my colleague as insufficiently enthusiastic was ironic since this particular informal gathering took place at the PDC. If you're speaking at the PDC, then presumably you're not totally disinterested in the material.

Upon completion of this story, another colleague spoke up. "Hey wait, I think I was that interviewer!"

Posted by oldnewthing | 18 Comments
Filed under:

Why is the fine for a basic traffic infraction in the state of Washington such a random-looking number?

Willy-Peter Schaub was puzzled by a sign reminding drivers that the fine for obstructing an intersection is $101 and wonders what the extra $1 is for.

The laws of the State of Washington defer the monetary value of traffic fines to the Infraction Rules for Courts of Limited Jurisdiction (more commonly known as the IRLJ), specifically section 6.2: Monetary Penalty Schedule for Traffic Infractions [pdf].

But wait, the fine listed in the IRLJ is only $42. Where did $101 come from?

In addition to the base fine in the IRLJ, RCW 3.62.090 specifies additional assessments: Section (1) specifies a 70% assessment for public safety and education, and section (2) specifies an additional public safety and education assessment equal to 50% of the earlier assessment. On top of that, RCW 46.63.110 specifies various fees and penalties: Section 7(a) specifies a $5 fee for emergency services, section 7(b) specifies a $10 fee for auto theft prevention, section 7(c) specifies a $2 fee for the traumatic brain injury account, and section 8(a) specifies a $20 penalty to be shared between the state and the local jurisdiction.

There are probably other clauses which add to the fines and penalties. I remember investigating this a few years ago and convincing myself that after taking all the fines and penalties and assessments and whatever-else-they-call-its into account, the total did come to $101. (Actually, they bring it to something close to $101, and then another rule about rounding kicks in.)

And you won't get the numbers to add up to $101 any more because there were changes to the fee schedule in July 2007. The fine for basic traffic infractions is now $124. The new calculation appears to be 42 × 2.05 + 5 + 10 + 2 + 20 = $123.10, which rounds up to $124.

Posted by oldnewthing | 39 Comments
Filed under:

PSM_ISDIALOGMESSAGE is to modeless property sheets as IsDialogMessage is to modeless dialog boxes

Dialog boxes and property sheets are similar in that most of the time, you use them modally. You call DialogBox or PropertySheet, and the function doesn't return until the user closes the dialog box or property sheet. But you can also use dialog boxes and property sheets modelessly, using CreateDialog or by including the PSH_MODELESS flag when you call PropertySheet.

One of the more common problems people have when managing a modeless property sheet is finding that keyboard navigation doesn't work. The reason is the same as with modeless dialog boxes: You forgot to process dialog messages.

But if you use the wrong function to process the dialog messages, then you don't get the right behavior.

If you get confused and use IsDialogMessage to process dialog messages for a property sheet, things will seem to work mostly okay, but most notably, the Ctrl+Tab and Ctrl+Shift+Tab keyboard shortcuts won't work. Those hotkeys are new to property sheets; those keys mean nothing special to dialog boxes, so the IsDialogMessage function won't do anything special if the user types them. There are other property sheet behaviors that go beyond plain dialog boxes, but the keyboard navigation among tabs is what users will probably notice first.

The other mix-up I've seen is sending the PSM_ISDIALOGMESSAGE message to a modeless dialog box, even though the dialog box isn't a property sheet. The PSM_ISDIALOGMESSAGE message is handled only by property sheets; if you send it to something else, who know what'll happen? Remember that PSM_ISDIALOGMESSAGE is in the WM_USER range, and messages in that range belong to the window class.

This is simply another case of keeping track of what you're doing and using the mechanism appropriate for what you have. You're already used to doing this in real life: If you travel to Canada and want to buy something from a vending machine, you have to put in Canadian money, not Turkish lira.

Posted by oldnewthing | 7 Comments
Filed under:

The best actors in the business still lean into the microphone when they talk

Now let me get this straight.

The Oscars honor, among other things, the best actors in Hollywood. These are people who have devoted their professional careers to reciting dialog in front of a camera and making it look spontaneous and natural.

But for some reason, put them on stage at the Oscars, and instead of reciting dialog spontaneously and naturally, they read it stiltedly and lean into the microphone while doing it.

And these are the best actors in the business?¹

It's like hosting a music award show and finding that all the performers suck at singing.

¹Oh wait, sorry, it's not a business. It's a craft. I don't know whose idea it was to start referring to movie-making as a craft, but I'm sick of it already.

Posted by oldnewthing | 31 Comments
Filed under:
More Posts Next page »
 
Page view tracker