The Old New Thing's Journal
[Most Recent Entries]
[Calendar View]
[Friends]
Below are the 14 most recent journal entries recorded in
The Old New Thing's LiveJournal:
Friday, January 3rd, 2014 | 3:00 pm |
What happened in real-mode Windows when somebody did a longjmp into a discardable segment? http://blogs.msdn.com/b/oldnewthing/archive/2014/01/03/10486694.aspx
During the discussion of
how real-mode Windows handled return addresses into discarded segments,
Gabe wondered,
"What happens when
somebody does a longjmp into a discardable segment?"
I'm going to assume that everybody knows how longjmp
traditionally works so I can go straight to the analysis.
The reason longjmp is tricky is that it has to
jump to a return address that isn't on the stack.
(The return address was captured in the jmp_buf .)
If that segment got relocated or discarded, then the jump target
is no longer valid.
It would have gotten patched to a return thunk if it were on the
stack, but since it's in a jmp_buf ,
the stack walker didn't see it, and the result is a return address
that is no longer valid.
(There is a similar problem if the data segment or stack segment
got relocated.
Exercise: Why don't you have to worry about the data segment
or stack segment being discarded?)
Recall that when a segment got discarded, all return addresses
which pointed into that segment were replaced with
return thunks.
I didn't mention it explicitly in the original discussion,
but there are three properties of return thunks
which will help us here:
-
It is safe to invoke a return thunk even if
the associated code segment is in memory.
All that happens is that the
"ensure the segment is present" step is a nop,
and the return thunk simply continues with its work of
recovering the original state.
-
It is safe to abandon a return thunk
without needing to do any special cleanup.
All the state used by the return thunk is stored
in the patched stack itself, so if you want to
abandon a return thunk, all you need to do is
free the stack space.
-
It is safe to reuse a return thunk.
Since they are statically allocated,
you can use them over and over as long as the
associated code segment has not been freed.
The first property
(idempotence of the return thunk) is no accident.
It's required behavior in order for return thunks to work
at all!
After all, if the segment was loaded (say by a
direct call or some other return thunk),
then the return thunk needs to say,
"Well, I guess that was easy," and simply skip
the "load the target segment" step.
(It still needs to do the rest of the work,
of course.)
The second property (abandonment) is also no
accident.
An application might decide to exit without returning
all the way to WinMain
(the equivalent of calling ExitProcess
instead of returning from WinMain ).
This would abandon all the stack frames between
the exit point and the WinMain .
The third property (reuse) is a happy accident.
(Well, it was probably designed in for the purpose
we're about to put it to right here.)
Okay, now let's look at the jump buffer again.
If you've been following along so far,
you may have guessed the solution:
Pre-patch the return address as if it had already been
discarded.
If it turns out that the segment was discarded,
then the return thunk will restore it.
If the segment is present (either because it was
never discarded, or because it was discarded and
reloaded, possibly at a new address),
the return thunk will figure out where the code
is and jump to it.
Actually, since the state is being recorded in a
jmp_buf , the tight space constraints
of stack patching do not apply here.
If it turns out you need 20 bytes of memory to
record this information, then go ahead and
make your jmp_buf 20 bytes.
You don't have to try to make it all fit
inside an existing stack frame.
The jmp_buf therefore
doesn't have to try to play the crazy
air-squeezing games that stack patching did.
It can record the return thunk,
the handles to the data and stack segments,
and the return IP without any encoding at all.
And in fact, the longjmp
function doesn't need to invoke the return
thunk directly.
It can just extract the segment number
after the initial INT 3Fh and
pass that directly to the segment loader.
(There is a little hitch if the address
being returned to is fixed; in that case,
there is no return thunk.
But that just makes things easier:
The lack of a return thunk means that the
return address cannot be relocated,
so there is no patching needed at all!)
This magic with return thunks and segment
reloading is internal to the operating system,
so the core setjmp and
longjmp functionality
was provided by the kernel rather than the
C runtime library in a pair of functions
called Catch and Throw .
The C runtime's setjmp
and longjmp functions
merely forwarded to the kernel versions.
| Thursday, January 2nd, 2014 | 3:00 pm |
If the cursor clip rectangle is a global resource, how come I can't change it with ClipCursor? http://blogs.msdn.com/b/oldnewthing/archive/2014/01/02/10486431.aspx
A customer reported that the ClipCursor
function was not working.
"The cursor clip rectangle is a shared global resource,
so I should be able to change it any time I like.
My app installs a global mouse hook and sets the clip cursor
inside the hook function,
but the change doesn't have any effect.
Why can't I change the clip cursor inside a mouse hook?"
Sure, you can change the clip cursor inside a mouse hook.
But remember, a shared global resource cuts both ways.
Since anybody can change it, your app can change it
any time it likes.
But since anybody can change it,
another app can also change it any time they like.
In this case, what's happening is that your hook comes
in and sets the clip rectangle.
And the application gets the mouse message and passes
it to DefWindowProc ,
and the default behavior for focus changes
is to clear the clip rectangle
so that any clip rectangle set by the previous window
doesn't spill over into the new focus window.
The convention for the clip rectangle is that
the focus window (perhaps after some
negotiation with one of its ancestors)
controls the clip rectangle.
This convention is not enforced for a few reasons.
First of all,
you can't programmatically determine whether
code is executing on behalf of any particular window.
Even if you say "can be called only during the
handling of a message", that doesn't prove
that the code is associated with the window.
The message handler might call into some other
component, and that other component might decide
to clip the cursor just because.
Another reason the rule isn't enforced is that the
clip cursor was invented back in the day when
programmers were trusted to do the right thing.
The theory was that preventing people from doing
sneaky things would also prevent them from doing
clever things.
(Nowadays, the API design philosophy prefers to
prevent people from doing sneaky things, even
though it also prevents them from doing clever
things, because the bad guys are sneakier than
the good guys are clever.)
| Wednesday, January 1st, 2014 | 3:00 pm |
We know your job is hard, you don't have to show us http://blogs.msdn.com/b/oldnewthing/archive/2014/01/01/10486304.aspx
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.
| Tuesday, December 31st, 2013 | 3:00 pm |
| 3:00 pm |
New Year's Eve is sometimes a stressful occasion http://blogs.msdn.com/b/oldnewthing/archive/2013/12/31/10486117.aspx
Today is New Year's Eve,
another opportunity for to mark that an approximately-integral number
of revolutions of the earth have occurred since some point in time
that wasn't even calculated correctly in the first place.
(We retain it for backward compatibility.)
December 31, 1999 was a particularly anxious day in the technology sector.
Microsoft's Director of Y2K Readiness
and vice president of Product Support Services
described some of the steps that were being taken
to prepare for any timekeeping-related issues that would arise
as the calendar ticked over to 2000.
We've analyzed phone capacity,
IT systems backup for both data and power,
and additional lab environments for enhanced product support capabilities.
We have redundant power systems, including back-up generators,
at key locations in the unlikely event we lose power.
I got to see one of those key locations,
or at least I saw it from a distance.
Parked outside Building 26,
the home of the Core Operating Systems Division,
were semi-trailers
filled with computers and back-up generators,
ready to spring into action in case of
a disaster of biblical proportions
and somebody needed to develop a hotfix to Windows on the spot
as civilized society collapsed into anarchy all around.
(Personally, I would think that if civilization were collapsing,
you would have more important things to worry about than patching
an operating system.)
At least to make things more pleasant for the people who had
to remain at work that night,
Microsoft threw
"a New Year's Eve party with a disc jockey,
dinner, and a champagne toast at midnight.
Our employees can bring their spouses and children,
and we'll even host a separate children's party."
Actually, it sounds like they had more fun than I did that night.
I think I stayed at home and watched television.
It turns out that civilization did not collapse into anarchy.
There were minor glitches, but
nothing serious.
"No problems were seen in Angola, Uganda and Kenya,
where the telephone system was said to be functioning
as erratically as usual.
Italy, one of the worst-prepared countries in the West,
also appeared to cross into the new century without any major trouble."
Nice to know you can be snarky in a newspaper article.
(Nitpicker's corner: January 1, 2000 was not the first day of a new
century.)
Depending on how you look at it,
Y2K was either an overhyped event created to generate revenue
for the technology industry,
or was a demonstration of how people can solve a major problem if they
just worked hard and coöperated.
Reality is probably a mix of the two.
Happy new year, everybody.
See you on the other side.
| Monday, December 30th, 2013 | 3:00 pm |
There's no seating up there, so you just have to hang on for dear life http://blogs.msdn.com/b/oldnewthing/archive/2013/12/30/10485906.aspx
I dreamed that through a friend,
I got to join a handful of other people
atop Prince Charles's carriage
as it wound its way through London.
There was no seating up there,
so you just have to hang on for dear life.
When we reached Buckingham Palace,
the assembled crowd and reporters swarmed the carriage
for an opportunity to meet the Prince.
This provided a sufficient diversion to allow us to
climb down from the roof and sneak into the palace
undetected.
We've come to the end of the year,
so that's all for Monday dream blogging.
For those of you who hated it:
You can uncover your eyes now.
| 3:00 pm |
How can I get the list of programs the same way that Programs and Features gets it? http://blogs.msdn.com/b/oldnewthing/archive/2013/12/30/10485905.aspx
A customer wanted to get the list of programs the same way that
the
Programs and Features folder gets it.
Here, here's an idea:
Instead of trying to mimic the Programs and Features folder,
just ask the Programs and Features folder for its contents!
That way, no matter what changes are made to how the
Programs and Features folder obtains its contents
(and those changes occur pretty often),
your program will always match it,
because you're just showing the same thing.
Here's the basic idea, in scripting language since it's quicker:
var shell = new ActiveXObject("Shell.Application");
var programsFolder = shell.Namespace(
"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\" +
"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}");
var items = programsFolder.Items();
for (var i = 0; i < items.Count; i++) {
var item = items.Item(i);
WScript.StdOut.WriteLine(item);
WScript.StdOut.WriteLine("Size = " + item.ExtendedProperty("System.Size"));
WScript.StdOut.WriteLine("------------");
}
Okay, first of all, how did I get that magic string
for the Programs and Features folder?
I opened the Control Panel
and dragged the Uninstall a program
link onto
the program from a few weeks ago.
The program itself is pretty straightforward.
It's the standard
enumerate everything in a folder and print it
program we've seen before.
The only trick was finding the folder.
As for the C++ version, it should also look familiar, because
we've done it before
more than once.
The only difference is the way we create the folder
and the details we choose to display.
(For extra credit:
Change this program to
bind to the persisted pidl instead of the parsing name.)
int __cdecl wmain(int argc, wchar_t **argv)
{
CCoInitialize init;
CComPtr<IShellItem> spPrinters;
SHCreateItemFromParsingName(
L"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\"
L"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}", nullptr,
IID_PPV_ARGS(&spPrograms));
CComPtr<IEnumShellItems> spEnum;
spPrograms->BindToHandler(nullptr, BHID_EnumItems,
IID_PPV_ARGS(&spEnum));
for (CComPtr<IShellItem> spProgram;
spEnum->Next(1, &spProgram, nullptr) == S_OK;
spProgram.Release()) {
CComHeapPtr<wchar_t> spszName;
spProgram->GetDisplayName(SIGDN_NORMALDISPLAY, &spszName);
wprintf(L"%ls\n", spszName);
PrintDetail(CComQIPtr<IShellItem2>(spProgram), &PKEY_Size, L"Size");</font>
}
return 0;
}
Bonus script:
You can even see what verbs are available.
var shell = new ActiveXObject("Shell.Application");
var programsFolder = shell.Namespace(
"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\" +
"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}");
var items = programsFolder.Items();
for (var i = 0; i < items.Count; i++) {
var item = items.Item(i);
WScript.StdOut.WriteLine(item);
WScript.StdOut.WriteLine("Size = " + item.ExtendedProperty("System.Size"));
var verbs = item.Verbs();
for (var j = 0; j < verbs.Count; j++) {
var verb = verbs.Item(j);
WScript.StdOut.WriteLine("Action: " + verb.Name);
}
WScript.StdOut.WriteLine("------------");
}
And if you're really ambitious,
you can even call
verb.DoIt to carry out the action.
Don't use this power for evil.
Note:
Since we are working with the
Programs and Features folder,
we are necessarily targeting Windows Vista and later,
since that was the version of Windows in which the
Programs and Features folder was introduced.
Therefore, I am free to use
functionality introduced in Windows Vista.
I've been doing Little Programs for a year now.
I kind of like it,
so I'm going to continue for another year,
but I'm going to relax the rules a bit:
The Little Programs are now just programs that I think
are interesting for whatever reason.
They don't need to actually solve a problem.
| Friday, December 27th, 2013 | 3:00 pm |
Brief Q&A on the HeapEnableTerminationOnCorruption heap information flag http://blogs.msdn.com/b/oldnewthing/archive/2013/12/27/10484882.aspx
Question:
What type of heaps are controlled by the
HeapEnableTerminationOnCorruption
flag?
Answer:
Any user-mode heap created by the
HeapCreate function.
This includes the process heap (GetProcessHeap )
but
not the managed heap.
Some components use HeapCreate
under the hood.
If so, then those heaps would also be affected.
Question:
What versions of Windows support
HeapEnableTerminationOnCorruption ?
Answer:
The flag was introduced in Windows Vista and Windows Server 2008.
It is also available on Windows XP Service Pack 3.
In table form:
Support |
Client |
Server |
☹
Not Available
|
...
Windows 2000
Windows XP RTM, SP1, SP2
|
...
Windows Server 2000
Windows Server 2003
|
Available
☺
|
Windows XP SP3
Windows Vista
Windows 7
...
|
Windows Server 2008
Windows Server 2008 R2
...
|
Question:
For operating systems that support it,
under what conditions will
termination on corruption
be enabled?
Answer:
- For all 64-bit processes.
- For all 32-bit processes whose executable sets the
subsystem major version
to 6 or higher in the image header.
- If you call
HeapSetInformation with the
HeapEnableTerminationOnCorruption
parameter.
Question:
What is the effect of setting the subsystem major version in a DLL?
Will that control whether termination on corruption is enabled
for any heaps created by my DLL?
Answer:
No.
For the purpose of rule 2 above,
it is the major subsystem of the executable that decides whether
termination on corruption.
The major subsystem of any DLLs loaded by the process have no effect.
This is consistent with
other process decisions.
Question:
Can I enable termination on corruption for some heaps but not others?
Answer:
No.
Turning on termination on corruption turns it on for all heaps
in the process.
Question:
Can I disable termination on corruption after it has been enabled?
Answer:
No.
It is a one-way door.
| Thursday, December 26th, 2013 | 3:00 pm |
I think we're going to be getting frozen leftovers for lunch today http://blogs.msdn.com/b/oldnewthing/archive/2013/12/26/10484684.aspx
There are a few times a year when
a large fraction of employees are out on vacation at the same time,
such as a single work day
wedged between a holiday and a weekend (as happened this year
on July 5).
The most extreme case of this
is the week between the Christmas holiday and New Year's Day,
where the offices are practically empty.
On these days of low demand,
many services are scaled back and some choose to close entirely
so that they can do inventory, perform routine maintenance,
or upgrade equipment.
One of the most visible service reductions is in food service.
Smaller locations (such as snack bars) are closed,
and the kitchens which remain open offer a reduced menu.
But just because most people are on vacation doesn't mean
that nobody is watching.
Here's a menu from one kitchen
that was posted almost exactly one year ago:
Breakfast |
Roberts Waffles |
|
Breakfast Burrito
Warm Tortilla filled with Scrambled
Egg, Golden Hash Browns, Onion Green Chiles, Monterey Jack Cheese
and Salsa. |
Today's Soups |
Clam Chowder |
|
Tomato Basil Bisque |
Exhibition |
Station Closed for Holiday |
|
Shanghai Shanghai |
|
Greek Potato Salad
A delicious blend of potatoes,
tomatoes, red onions, flavored with mustard, parsley, dill seed,
mint and lemon juice. |
Wild Greens |
Grill and Greens |
Chef's Table |
Station Closed for Holiday |
Pizza |
Specialty Pizza by the Slice |
Deli |
Mozzarella, Tomato and Basil Panini
Mozzarella, Tomato and Basil Panini |
|
Spcied [sic] Cranberry Turkey Salad Served on a Flaky Crossaint [sic] |
Entrée |
Check Freezer
⇐ |
Grill |
Tuna Melt |
|
Steak Frites |
| 3:00 pm |
Why is GetWindowLongPtr returning a garbage value on 64-bit Windows? http://blogs.msdn.com/b/oldnewthing/archive/2013/12/26/10484683.aspx
A customer was running into problems with their application
on 64-bit Windows 8.
They claimed that on Windows 8, the
GetWindowLongPtr
is returning a garbage pointer,
which causes their program to crash.
The same program works fine on 64-bit Windows 7.
They asked the Windows team why they broke
GetWindowLongPtr .
An investigation of the customer's code quickly turned up the issue:
INT_PTR CALLBACK AwesomeDialogProc(
HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Awesome *pThis = (Awesome*)GetWindowLongPtr(hdlg, DWLP_USER);
switch (uMsg) {
case WM_INITDIALOG:
pThis = (Awesome*)lParam;
SetWindowLongPtr(hdlg, DWLP_USER, (LONG)pThis);
...
return TRUE;
case WM_COMMAND:
if (pThis != nullptr) {
// This line crashes with pThis = garbage nonzero value
return pThis->OnCommand(wParam, lParam);
}
return FALSE;
...
}
return FALSE;
}
See if you can spot the problem.
The error is in the line that calls
SetWindowLongPtr .
It takes the 64-bit pointer value pThis
and casts it to a LONG ,
which is a 32-bit integer type.
This truncates the pointer and throws away the upper 32 bits of data.
Therefore, when read back, the pointer looks like garbage
because the top 32 bits were set to zero (or to 0xFFFFFFFF ,
depending on the value of bit 31).
Windows 8 made some improvements to the memory manager,
and a side effect was a seemingly harmless change
to the way memory is allocated in 64-bit processes.
As a result of the change, pointer values greater than
4GB
are much more common, which means that the pointer truncation
will actually destroy data.
(In Windows 7, the default heap tended to hang out below the 2GB
boundary, so the code merely truncated zeros, which is
mostly harmless.)
What I found particularly interesting about this error is that
the DWL_USER window long was specifically
renamed to DWLP_USER in 64-bit Windows in order to force
a build break.
Therefore, developers had to go in and convert each separate use of
[GS]etWindowLong with
DWL_USER
to a version that used
[GS]etWindowLongPtr with
DWLP_USER ,
being careful not to truncate the pointer.
This customer missed that last little bit about not truncating the pointer,
and all they did was a global search/replace:
s/\bGetWindowLong\b/GetWindowLongPtr/g;
s/\bSetWindowLong\b/SetWindowLongPtr/g;
s/\bDWL_USER\b/DWLP_USER/g;
"There,
I fixed it."
| Wednesday, December 25th, 2013 | 3:00 pm |
The chain of stories triggered by seeing a package of Ahoj-Brause http://blogs.msdn.com/b/oldnewthing/archive/2013/12/25/10484570.aspx
While surfing the Web aimlessly
doing valuable background research,
I happened across a page that had a picture of a package of
Ahoj-Brause
(pronounced ahoy browse-uh).
Seeing that package triggered a bunch of memories.
My
emergency vacation
from several years ago included a visit to a friend spending the year
at Uppsala University in Sweden.
The following year, he invited one of his classmates (a student
from Germany) to the United States to join his family for the
Christmas holiday season.
She brought with her some small gifts, among them a package of
Ahoj-Brause.
On its own, Ahoj-Brause
is just a drink mix powder,
but those in the know consume it by
dumping the contents of a packet into your mouth,
then adding a shot of vodka.
(When written by hand, it looks like
Ahoj-Braŭse
with a breve over the u.
That's a trait of German handwriting:
A breve is written over the u so that it isn't confused with a
handwritten n.
Compare putting a slash through a 0 or a crossbar through a 7
to avoid confusion with O and 1, respectively.)
During her visit,
I got to practice some German,
telling her the story of
Bill Gates and the hotel next door.
Her conclusion was that my German was fairly good,
and with one month's immersion,
I could become fluent.
Unfortunately,
it's not practical for me to spend a month in Germany
just to bring
my German skills from "fairly good" to "fluent".
For one thing,
my wife would be pretty annoyed.
(And this is completely setting aside the question of
"Why would you devote an entire month of your life to becoming
fluent in German?
If you're going to devote an entire month of your life to
becoming fluent in another language,
shouldn't it be Chinese?")
Man, that's a lot of digressions before getting the story I actually
wanted to tell.
My friend's classmate wanted to head into downtown Seattle
to do touristy things,
so she was taken to the
neighborhood bus stop and given
instructions on which bus number to take and where to get off
in downtown.
That part of the plan worked great.
The part that didn't work so great was returning home.
When you're unfamiliar with an area, traveling a road in the opposite
direction doesn't quite trigger the memory cells.
My friend's classmate got on the return bus,
but couldn't quite remember where to get off to get back home.
She got off somewhere close, but the houses didn't look familiar.
"Okay, now I'm lost. What do I do?"
She had my friend's address and phone number, but she didn't have
a mobile phone or a map of the residential neighborhood.
She walked down the street and saw a house with the sign
Mi casa es tu casa hanging by the front door.
She considered this an indication that the people in the house
were friendly and welcoming, so she knocked on the door.
She was okay with the possibility that the
people in the house spoke only Spanish, because she had been
learning Spanish in anticipation of studying there the following semester.
(For those who are keeping score, this means that my friend's
classmate speaks at a minimum
German, Swedish, English, and Spanish.)
The assumption that they spoke Spanish was correct.
(They also spoke English.)
The assumption that the family was
friendly and helpful also held up.
What she didn't expect was that they spoke German, too!
Apparently, the family spent a few years in Germany
because the father was assigned there by his work.
It so happens that she was only two blocks or so from my friend's home;
the hard part of course is knowing which two blocks to go.
The family was so enamored of their unexpected
German-and-English-and-Spanish-speaking visitor
that they invited her to stay for dinner,
but she had to decline due to other plans for the evening.
Bonus chatter:
When my friend sent back some photos from Uppsala,
he didn't include any description with the photos,
so I made up my own narrative.
I had to make up names for all the people in the photos,
and Astrid was the name I chose for the subject of today's story.
She liked the name so much that she adopted it as a secret nickname.
| Tuesday, December 24th, 2013 | 3:00 pm |
Essays from the funniest man in Microsoft Research http://blogs.msdn.com/b/oldnewthing/archive/2013/12/24/10484402.aspx
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?
| Monday, December 23rd, 2013 | 3:00 pm |
| 3:00 pm |
Creating custom tasks on a jump list http://blogs.msdn.com/b/oldnewthing/archive/2013/12/23/10484187.aspx
Today's Little Program adds a custom task to the application's
jump list.
Take the
scratch program
and make the following changes.
(Remember, Little Programs do very little error checking
because that's how they roll.)
#include <shlobj.h>
#include <propkey.h>
#include <wrl/client.h>
using namespace Microsoft::WRL;
ComPtr<IShellLink>
CreateShellLinkForTask(
PCWSTR pszTitle,
PCTSTR pszArgs,
int idIcon)
{
ComPtr<IShellLink> spsl;
CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spsl));
wchar_t szBuf[MAX_PATH];
GetModuleFileName(g_hinst, szBuf, ARRAYSIZE(szBuf));
spsl->SetPath(szBuf);
spsl->SetArguments(pszArgs);
spsl->SetIconLocation(szBuf, idIcon);
PROPVARIANT pvar;
pvar.vt = VT_LPWSTR;
pvar.pwszVal = const_cast<PWSTR>(pszTitle);
ComPtr<IPropertyStore> spps;
spsl.As(&spps);
spps->SetValue(PKEY_Title, pvar);
spps->Commit();
return spsl;
}
This helper function creates an
in-memory shell link object
with the specified title, command line arguments, and icon.
The underlying executable is assumed to be the running executable.
BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
ComPtr<ICustomDestinationList> spcdl;
CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&spcdl));
ComPtr<IObjectCollection> spoc;
UINT cMinSlots;
spcdl->BeginList(&cMinSlots, IID_PPV_ARGS(&spoc));
spoc->Clear();
spoc->AddObject(CreateShellLinkForTask(L"New frob",
TEXT("/frob"), -2).Get());
spcdl->AddUserTasks(spoc.Get());
spcdl->CommitList();
return TRUE;
}
When our window is created,
we get the destination list for our application
and ask it for an object collection so we can fill it with tasks.
We empty the existing collection and add a single shortcut
called "New frob" and which passes the /frob command line
argument.
The icon here is given as a negative number to indicate
that it is an icon ID rather than an icon index.
We then tell the destination list that this is our new task collection.
Before we forget, let's add the icon to our resource file.
// scratch.rc
1 ICON icon1.ico
2 ICON icon2.ico
I'll leave you to find some icons to use.
Icon number 2 is the one that will be used for the jump list.
(Icon number 1 I left to represent the application itself.)
Finally, we respond to the command line switch.
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
LPSTR lpCmdLine, int nShowCmd)
{
if (strcmp(lpCmdLine, "/frob") == 0) {
MessageBox(nullptr, L"Frob!", L"Title", MB_OK);
return 0;
}
...
}
If the command line switch /frob is passed,
then we say something silly.
In real life, we would create a new frob,
possibly by looking for an existing running copy of the program
and asking it to do the creation.
Okay, run this program and then right-click on the taskbar icon.
Observe that there is now a New frob task,
and if you select it,
you get the silly message.
|
|