Welcome to MSDN Blogs Sign in | Join | Help

Why seventh grade students want to go to weddings

My friend the seventh-grade teacher is getting married this summer, and when her students learned about the impending nuptials, they couldn't contain their excitement.

The students asked her if there was going to be a chocolate fountain. Because you can't not have a chocolate fountain. When they learned that, no, there will not be a chocolate fountain, the students lobbied hard for her to reconsider and please, you have to have a chocolate fountain. The concept of a fancy party without a chocolate fountain defied comprehension. I mean, isn't a chocolate fountain the definition of a fancy party?

Naturally, the students also wanted to be invited as guests. They are after all, the people the teacher spends the most time with, right? They shared so many experiences: They went on field trips together, they cheered when the school cross country team defeated its longtime rival, they were even there for the great Fire Drill of February '08. How could they possibly be left out?

The reasons for wanting to attend were different between the boys and the girls. The boys were basically in it so they could camp out by the chocolate fountain and nibble on sweets all day. The girls were drawn in by the opportunity to witness the pageantry of a wedding, the bride in a beautiful gown, the romance of exchanging vows with one's True Love. Oh, and then to camp out by the chocolate fountain and nibble on sweets all day.

Posted by oldnewthing | 12 Comments
Filed under:

Simulating a drop, part two

Last time, we wrote a tiny program to simulate dropping a file on another file, but we discovered that it didn't work for dropping a file onto Mail Recipient.MAPIMail. The reason, as you no doubt instantly recognized, is that the MAPIMail handler creates a worker thread, and we're exiting the process before the worker thread has finished its work.

And as you no doubt know by now, the solution is to use the SHSetInstanceExplorer function. Let's bring back the ProcessReference class and use it to solve our process lifetime problem.

int __cdecl wmain(int argc, WCHAR **argv)
{
 ProcessReference ref;
 ...

Compile this program and run it with the command line

fakedrop c:\autoexec.bat "%USERPROFILE%\SendTo\Mail Recipient.MAPIMail"

to watch our process reference save the day.

Oh wait, it didn't help. What's going on?

Run this under the debugger and you'll see an interesting exception:

(918.110): Unknown exception - code 80010012 (first chance)

A little hunting through winerror.h reveals what this exception means:

//
// MessageId: RPC_E_SERVER_DIED_DNE
//
// MessageText:
//
//  The callee (server [not server application]) is not available and
//  disappeared; all connections are invalid. The call did not execute.
//
#define RPC_E_SERVER_DIED_DNE            _HRESULT_TYPEDEF_(0x80010012L)

Huh? What's this RPC stuff? Oh wait, COM uses RPC. That should be a clue.

Notice that our ProcessReference's destructor runs after we have already uninitialized COM. When we invoked the IDropTarget::Drop method on the MAPIMail drop target, it kicked off a background thread to do the work, and in order to access the parameters from the background thread, it had to do a bit of marshalling, with the help of the functions with ridiculously long names CoMarshalInterThreadInterfaceInStream and the only slightly less ridiculous CoGetInterfaceAndReleaseStream. But since we tear down COM immediately, when the background thread gets around to asking, "Okay, and what was that file name?" the thread that has the answer (the main thread) has already shut down COM. Hence the "server died" error.

To fix this, we need to fix the scope of the process reference object:

 if (argc == 3 && SUCCEEDED(CoInitialize(NULL))) {
  ProcessReference ref;
  ...

Compile this program and run it with the same command line and... it still doesn't work! But this time you definitely know why: The destructor is running at the wrong time.

I leave it as an exercise to fix the destructor timing problem. To see whether you got it right, run the fakedrop command line again. When you successfully get an email message, then you know you've got it.

Posted by oldnewthing | 8 Comments
Filed under:

Reading a contract from the other side: Simulating a drop

Most people, when they think of the IDropTarget interface, think only of implementing a drop target. But you can read the contract from the other side, because the description of how a drag source interacts with a drop target tells you how to be a drag source.

To summarize, the sequence of drop target operations go like this:

  • IDropTarget::DragEnter is called to indicate that an object has been dragged into the drop target. If the drop target returns a failure code, then the drop operation ends immediately.
  • Otherwise, IDropTarget::DragOver calls are made to advise the drop target as to the object's location.
  • If the user completes the drop operation, then call IDropTarget::Drop. Otherwise call IDropTarget::Leave. A drop operation can fail to complete because the user hit the Escape key, for example, or dragged the mouse out of the drop target.

Let's write a simple program that drops one file onto another.

#include <windows.h>
#include <shlobj.h>
#include <shellapi.h>
#include <tchar.h>

... Insert the function GetUIObjectOfFile here ...

int __cdecl wmain(int argc, WCHAR **argv)
{
 if (argc == 3 && SUCCEEDED(CoInitialize(NULL))) {
  IDataObject *pdto;
  if (SUCCEEDED(GetUIObjectOfFile(NULL, argv[1],
                         IID_IDataObject, (void**)&pdto))) {
   IDropTarget *pdt;
   if (SUCCEEDED(GetUIObjectOfFile(NULL, argv[2],
                          IID_IDropTarget, (void**)&pdt))) {
    POINTL pt = { 0, 0 };
    DWORD dwEffect = DROPEFFECT_COPY | DROPEFFECT_LINK;
    if (SUCCEEDED(pdt->DragEnter(pdto, MK_LBUTTON,
                                 pt, &dwEffect))) {
     dwEffect &= DROPEFFECT_COPY | DROPEFFECT_LINK;
     if (dwEffect) {
      pdt->Drop(pdto, MK_LBUTTON, pt, &dwEffect);
     } else {
      pdt->DragLeave();
     }
    }
    pdt->Release();
   }
   pdto->Release();
  }
  CoUninitialize();
 }
 return 0;
}

This is a pretty straightforward implementation of the host side of the drag/drop protocol. Run this program with the full paths to two files, the first being the file to drop, and the second being the file you want to drop it onto. (Modifying this program to accept relative paths is left as an exercise for the reader.) For example, you might try

fakedrop c:\autoexec.bat c:\windows\notepad.exe

Now, sure, dropping a file on a program is nothing exciting. You could've just run the program with the file as the command line argument, after all. But that's looking at it too narrowly; you are simulating a drop operation, after all. For example, you can drop a file onto a shortcut to a printer, and the file will print; or you can drop a file onto a folder and it will be copied there (since we specified DROPEFFECT_COPY | DROPEFFECT_LINK, but folders prefer copy to link if the Ctrl+Shift keys are not held down); or you can drop a file onto the Mail Recipient.MAPIMail shortcut in your "Send To" folder to create a mail message with the file as an attachment.

Oh wait, that last example with Mail Recipient.MAPIMail doesn't work. We'll look at why next time, although I suspect you already know the reason.

Posted by oldnewthing | 6 Comments
Filed under:

Things other people have written that have amused me

I occasionally post things I've written that have amused other people, but today I'm going to share something Betsy Aoki wrote that amused me. On one of our internal mailing lists, somebody wondered why we don't use email spam filters to attack comment spam and trackback spam. My point was that the goal of email spam is very different from the goals of comment spam and trackback spam:

  • Email spam is about tricking the recipient into reading your message.
  • Comment spam is about tricking search engines into giving a higher ranking to the pages you link to.
  • Trackback spam is about stealing somebody else's content and republishing it, with the intent of driving traffic to your fake site and generate advertising revenue.

I concluded with the remark, "I am not aware of any email spam that takes messages that I wrote and forwards them back to me."

Betsy replied, "I encounter this all the time. It's called working at Microsoft."

Theorize if you want, but if the problem is right there in front of you, why not go for the facts?

On an internal discussion list, somebody asked a question similar to this:

My program launches a helper program. This helper program does different things based on the command line parameters, but the way I'm using it, it just prints its results to the console and exits. When I launch this program and wait for it to exit, my wait never completes. Why isn't the helper program exiting? Here's the code that I'm using to launch the helper process...

It wasn't long before people chimed in with their suggestions.

Have your main program call exit() at the end.
If you're redirecting stdout, you may be forgetting to drain the pipe. Otherwise, if the program generates too much output, the pipe fills and the helper program blocks writing to it.
Alas, that didn't help. Whether I redirect the output or not, the helper process still hangs.

Eventually I had to step in.

I can't believe I'm reading this discussion. It's like watching a room full of child psychologists arguing over why Billy is crying. They're all expounding on their pet theories, and none of them bothers to ask Billy, "Why are you crying?"

Connect a debugger to the helper process. See why it's crying.

You can sit around developing theories all you want, but since the problem is right there in front of you, the quickest way to figure out why the helper process isn't exiting is to connect a debugger and look at it to see why it's not exiting.

This is like the software version of the black crayons story.

Posted by oldnewthing | 28 Comments
Filed under:

The Microsoft Company Picnic has as a side effect a demonstration of Microsoft's rich cultural diversity

You hear parents scolding their kids in so many different languages.

Posted by oldnewthing | 9 Comments
Filed under:

Windows could not properly load the XYZ keyboard layout

In my rôle as the family technical support department, I get to poke around without really knowing what I'm doing and hope to stumble across a solution. Sometimes I succeed; sometimes I fail.

Today, I'm documenting one of my successes in the hope that it might come in handy for you, the technical support department for your family. (If not, then I guess today is not your day.)

The boot drive on the laptop belonging to one of my relatives became corrupted, and her brother-in-law had the honor of extracting the drive, sticking it into a working computer, doing the chkdsk magic, reinstalling the software that got corrupted, and otherwise getting the machine back on its feet. (It's a good thing I wasn't the one to do it because all of the programs are in Chinese, and I can't read Chinese beyond a few dozen characters.) Anyway, the machine returned to life, mostly. The bizarro proprietary hardware (that a certain manufacturer insists on using in order to make their machines special) still doesn't have drivers, but it was happy for the most part.

There was just one problem remaining, and it fell upon me to fix it: She couldn't type Chinese characters any more. Normally, this is done by selecting an appropriate IME, but no matter what we picked, it was as if we were always using the US-English keyboard.

One clue was that if you deleted the IME and then re-added it, you got the error message Windows could not properly load the XYZ keyboard layout.

Here is how I fixed it. (This was a Windows XP machine.) Maybe it will help you, maybe not.

First, go to the Regional and Language Options control panel and set everything back to English (US):

  • On the Advanced tab, under "Select a language to match the language version of the non-Unicode programs you want to use", select "English (United States)".
  • On the Languages tab, under "Text services and input languages", click the Details button. Change your default input language to "English (United States) - US" and remove all the non-English keyboard layouts.

Restart to make sure that nobody is using those old services.

After the restart, go back to the Regional and Language Options control panel, go to the Languages tab, and uncheck "Install files for East Asian languages." That is the whole point of this exercise. All the other steps were just removing enough obstacles so we could do that.

Restart to make sure nobody is using any of the East Asian fonts.

After the restart, add the East Asian fonts back, and when you're asked whether you should use the files already on the machine, say "No." That way, they will be re-copied from the CD.

(This step was trickier for me, because one of the hardware devices that didn't work was the DVD drive! I thought I was stuck, but then I realized that the wireless network antenna still was functional, so I went to another computer in the house, put the Windows XP CD in the drive, and shared out the CD-ROM drive. Then I went back to the first computer and told it to install the files from the second computer.)

Once everything gets reinstalled (including the corrupted keyboard layout files), you can go back and add the Chinese IME back, and reset all those other settings back to Chinese.

Neither I nor the owner of the laptop is very good at the other's native language (though she is far better at English than I am at Chinese), so fixing her computer is the best way I have of showing her my appreciation.

Posted by oldnewthing | 13 Comments
Filed under:

A modest proposal: Getting people to stop buying SUVs

I developed this modest proposal many years ago, but it looks like rising gasoline prices have done the job. But in case they don't, here's my plan.

SUVs are classified as "trucks" for the purpose of C.A.F.E. regulations, and those regulations are more lenient on gasoline efficiency for trucks. As a result, the auto industry happily built SUVs safe in the knowledge that high SUV sales wouldn't lower their overall fleet mileage and therefore not risk violating the CAFE regulations.

On the other hand, most other highway regulations do not consider SUVs to be trucks. As a result, SUVs sit in this sweet spot where they get to pretend they're a truck when it's advantageous to be a truck (avoiding CAFE regulations) and pretend that they're a car when it's advantageous to be a car (highway regulations).

My modest proposal was to get rid of this loophole. If you're going to let SUVs be classified as trucks for the purpose of determining whether they are subject to CAFE regulations, then classify them as trucks for the purpose of highway regulations. If you're driving an SUV on a highway that has different speed limits for cars as opposed to trucks, then you have to adhere to the truck speed limit. Because you're a truck. If you're driving an SUV on a highway which bans trucks from the leftmost lane, then you are banned from the leftmost lane. Because you're a truck.

People who are driving a truck because they actually need it for truck-like activities (hauling lumber, pulling a boat, delivering baked goods, whatever) won't be seriously inconvenienced, since they're not going to be doing any of those car-like things anyway.

If you really want to take this principle to its conclusion, you would even have to stop at the weigh stations to ensure that you're not over weight, and to check your log book (you keep a log book, right?) to ensure that you're not violating the laws regarding the maximum permitted number of hours behind the wheel of a truck. Because you're a truck.

Of course, the loophole could be closed the other way, too: Alter CAFE to classify SUVs as cars. Either way works for me. Just make the CAFE regulations and highway regulations agree on who is a car and who is a truck.

As I noted, it looks like economic forces have solved the problem on their own, but in case SUVs suffer a new surge in popularity, you've got my proposal in your back pocket, ready to spring into action.

Yes, this modest proposal is out of order, but it seemed more timely than the planned topic (solving the problem of identity theft), which I will still get to next time, if there is a next time.

MessageBoxIndirect loads the icon itself; you can't hand it a pre-loaded icon

Commenter 8 wants to know how to tell the MessageBoxIndirect function to use an existing HICON instead of pointing it to an icon resource.

You can't.

The MessageBoxIndirect loads the icon itself. You can't hand it a pre-loaded icon.

Of course, it's hardly rocket science to write your own MessageBoxWithMyIcon function that lets you use whatever icon you want. There's no law that says all Yes/No dialogs must use the MessageBox function. Feel free to write your own.

The MessageBox and MessageBoxIndirect functions are just convenience functions. They don't create new functionality; they don't do anything you couldn't already do yourself. You can have a template dialog box that you use for "generic" purposes and set the icon and text yourself. Or, if you're really adventuresome, you can generate a dialog template on the fly.

The MessageBox and MessageBoxIndirect functions never aspired to be "everything anybody could ever do with a dialog box." They just provide some basic functionality that lots of people find useful. If you need more functionality, then you can always write it yourself. (There's already a function for "everything anybody could ever do with a standard Win32 dialog box": It's called, um, DialogBox.)

Windows Vista introduces a considerably more customizable "message box"-type dialog known as a Task Dialog; you may want to give that one a try.

Posted by oldnewthing | 17 Comments
Filed under:

Dr. Horrible's Sing-Along Blog, available online for a short time only

Get it while it's hot. Available through this weekend only, Dr. Horrible's Sing-Along Blog is a three-act Web series featuring Neil Patrick Harris as Dr. Horrible, a wannabe mad scientist evildoer who video-blogs about his plans for world domination, his application to the Evil League of Evil, his arch-enemy Captain Hammer, and his crush on the cute girl at the local laundromat.

Oh, and it's a musical.

Episodes one and two are available now. Episode three goes online on Saturday.

Watch it.

Trust me.

Watch it.

Posted by oldnewthing | 21 Comments
Filed under:

A new record for the shortest amount of time between an email message and its resend

As I've noted before, people didn't answer your first question for a reason, but one thing I forgot to point out is that you also need to give people a chance to answer the first time at all!

Occasionally, I'll see somebody ask a question, and then resend the question a short time later. You also have to take into account the time of day. There was one message I recall that was sent around 4:30pm on Tuesday, and at 10am on Wednesday, it was "Resent due to no response." Now, sure, you waited nearly 18 hours before resending, but most of that time, people were at home not checking their email, and for a big chunk of that time they were even sleeping!

I thought 90 minutes would hold up as the record, but that was mere wishful thinking. Several months ago, a new record was set. Somebody sent a question to a peer support mailing list, and then resent it a mere 27 minutes later.

That record didn't hold up for very long, however, for the same person then sent the question a third time just 17 minutes later.

Now, it'd be one thing if the mailing list promised responses within, say, 10 or 20 minutes of receipt, but this was to a peer-to-peer discussion group, and a peer-to-peer discussion group does not come with a service level agreement.

Bonus chatter: In the time between writing this article and its arrival at the head of the queue, the record has been broken. Somebody sent a question, and then sent a repeat of the message with the additional plea "Please help!" eight minutes later.

Posted by oldnewthing | 15 Comments
Filed under: ,

The best building name on the University of Washington campus

The best building name on the University of Washington campus is Sieg Hall, for many years home to the computer science department, and named after former university president Lee Paul Sieg.

Posted by oldnewthing | 8 Comments
Filed under:

Microspeak: Well, actually management-speak

I hate management-speak.

Here's an example from an internal Web site.

The purpose of this Web site is two-fold.

  1. Create a reference source (best practices) where individuals can learn how to plan/facilitate and leverage their X activities most effectively.
  2. Establish a library of X material teams can utilize.

Wow, let's look at the first stated purpose. It goes on for so long and uses blatant management-speak such as "facilitate" and "leverage" that by the time it's over, I forget how the sentence started. Going back and reading it again, it appears that the first item is identical to the second! It's just that the first item says it in a more confusing way.

The second item shows evidence of management wordsmithing as well. "Utilize" instead of "use". An action verb like "establish" rather than a state verb like "to be". And those changes actually render the sentence incorrect. The purpose of the site isn't to "establish" a library; the purpose of the site is to be that library. Establishing the library is what you did when you created the site in the first place! That purpose has already been completed.

I think the people who built this Web site just copied their annual review goals into the Web site text, forgetting that the review goal describes what you are supposed to do, not what the thing you created is supposed to do.

Here's how I would have written it:

This Web site is a library of X materials.
Posted by oldnewthing | 30 Comments
Filed under: ,

News flash: Online drug sales are shady!

In what I'm sure is a fantastic surprise to everybody who has visited the Internet, according to a report in the New York Times, researchers at Columbia University have discovered that there are prescription drug pushers on the Internet who will sell you prescription drugs without a prescription.

From what I can gather, they didn't actually check whether what they ordered were indeed what the sites purported the substances to be (although thanks are extended to MasterCard, Visa, American Express and PayPal for their assistance, which means that they did investigate whether the major online payment systems can be used to purchase the drugs). So who knows, maybe the sites were selling fakes. In other words, maybe the issue isn't so much one of readily-accessible prescription drugs but rather one of fraud.

Posted by oldnewthing | 9 Comments
Filed under: ,

The evolution of menu templates: 32-bit extended menus

At last we reach the 32-bit extended menu template. Introduced in Windows 95, this remains the most advanced menu template format through Windows Vista. As you might expect, the 32-bit extended menu template is just a 32-bit version of the 16-bit extended menu template, so if you've been following along, you should find no real surprises here; all the pieces have been telegraphed far in advance.

The header remains the same:

struct MENUHEADER32 {
 WORD wVersion;
 WORD cbHeaderSize;
 BYTE rgbExtra[cbHeaderSize-4];
};

The differences here from the 32-bit classic menu template header are analogous to the changes between the 16-bit classic menu template and the 16-bit extended menu template. The wVersion is set to one for extended templates, and the cbHeaderSize includes the wVersion and cbHeaderSize fields themselves, so the number of extra bytes is four less than the value specified in cbHeaderSize. There is one additional constraint: The cbHeaderSize must be a multiple of four because extended menu item templates must be aligned on DWORD boundaries. But as with 32-bit classic templates, the cbHeaderSize must be four in order to avoid a bug in the Windows 95 family of operating systems.

After the header comes the menu itself, and like the 16-bit extended menu template, there is a prefix structure that comes before the items and which serves the same purpose as in the 16-bit extended menu template:

struct MENUPREFIX32 {
 DWORD dwContextHelpID;
};

The list of menu items is basically the same as the 16-bit version, just with some expanded fields.

struct MENUITEMEX32 {
 DWORD dwType;
 DWORD dwState;
 DWORD dwID;
 WORD  wFlags;
 WCHAR szText[]; // null terminated UNICODE string
};

As we saw before when we studied the 16-bit extended menu template, the big difference between classic and extended menu items is that classic menu items were designed for the InsertMenu function, whereas extended menu items were designed for the InsertMenuItem function. The dwType, dwState, and dwID members correspond to the fType, fState, and wID members of the MENUITEMINFO structure, and the the szText goes into the dwItemData if the item requires a string.

One additional quirk of 32-bit extended menu item templates which the 16-bit version does not have is that 32-bit extended menu item templates must begin on a 32-bit boundary; therefore, you must insert a WORD of padding after the menu text if the text is an odd number of characters long. (Fourteen bytes of the fixed-length part of the MENUITEMEX32 plus an odd number of WCHARs plus the null terminator WCHAR leaves a value that is 2 mod 4; therefore, you need an additional WORD to return to a DWORD boundary.)

The wFlags field has the same values as in the 16-bit extended menu item templates; the high byte is always zero. And, as before, if the bottom bit is set, then the menu item describes a pop-up submenu, which is inserted directly after the extended menu item template.

That's all there is to it. Let's see how our example menu resource looks when converted to a 32-bit extended menu template:

1 MENUEX 1000
BEGIN
  POPUP "&File", 200,,, 1001
  BEGIN
    MENUITEM "&Open\tCtrl+O", 100
    MENUITEM "", -1, MFT_SEPARATOR
    MENUITEM "&Exit\tAlt+X",  101
  END
  POPUP "&View", 201,,, 1002
  BEGIN
    MENUITEM "&Status Bar", 102,, MFS_CHECKED
  END
END

First comes the header, whose contents are fixed:

0000  01 00          // wVersion = 1
0002  04 00          // cbHeaderSize = 4

Before the list of extended menu item templates, we have the context help ID:

0004  E8 03 00 00    // dwContextHelpID = 1000

Since our first menu item is a pop-up submenu, the wFlags will have the bottom bit set:

0008  00 00 00 00    // dwType = MFT_STRING
000C  00 00 00 00    // dwState = 0
0010  C8 00 00 00    // wID = 200
0014  01 00          // wFlags = "pop-up submenu"
0016  26 00 46 00 69 00 6C 00 65 00 00 00
                     // "&File" + null terminator
0022  00 00          // Padding to restore alignment

Notice the two bytes of padding so that we return to DWORD alignment.

The wFlags promised a pop-up submenu, so here it is.

0024  E9 03 00 00    // dwContextHelpID = 1001

// First item
0028  00 00 00 00    // dwType = MFT_STRING
002C  00 00 00 00    // dwState = 0
0030  64 00 00 00    // dwID = 100
0034  00 00          // wFlags = 0
0036  26 00 4F 00 70 00 65 00 6E 00 09 00
      43 00 74 00 72 00 6C 00 2B 00 4F 00 00 00
                     // "&Open\tCtrl+O" + null terminator

// Second item
0050  00 08 00 00     // dwType = MFT_SEPARATOR
0054  00 00 00 00     // dwState = 0
0058  FF FF FF FF     // dwID = -1
005C  00 00           // wFlags = 0
005E  00 00           // ""

// Third (final) item
0060  00 00 00 00     // dwType = MFT_STRING
0064  00 00 00 00     // dwState = 0
0068  65 00 00 00     // dwID = 101
006C  80 00           // wFlags = "this is the last menu item"
0070  26 00 45 00 78 00 69 00 74 00 09 00
      41 00 6C 00 74 00 2B 00 58 00 00 00
                      // "&Exit\tAlt+X" + null terminator
0086  00 00          // Padding to restore alignment

When we see the "end" marker, we pop one level back to the main menu.

0088  00 00 00 00     // dwType = MFT_STRING
008C  00 00 00 00     // dwState = 0
0090  C9 00 00 00     // dwID = 201
0094  81 00           // wFlags = "pop-up submenu" |
                      //          "this is the last menu item"
0096  26 00 56 00 69 00 65 00 77 00 00 00
                      // "&View" + null terminator
00A2  00 00          // Padding to restore alignment

The set bottom bit in the wFlags indicates that another pop-up submenu is coming, and the "end" marker means that once the submenu is finished, we are done.

00A4  EA 03 00 00    // dwContextHelpID = 1002

00A8  00 00 00 00    // dwType = MFT_STRING
00AC  08 00 00 00    // dwState = MFS_CHECKED
00B0  66 00 00 00    // dwID = 102
00B4  80 00          // wFlags = "this is the last menu item"
00B6  26 00 53 00 74 00 61 00 74 00 75 00
      73 00 20 00 42 00 61 00 72 00 00 00
                     // "&Status Bar" + null terminator
00CE  00 00          // Padding to restore alignment

Since the pop-up submenu has only one item, the first item is also the last.

That's it for the evolution of menu templates, starting from a series of calls to the ANSI version of InsertMenu to a series of calls to the Unicode version of InsertMenuItem. Menu templates get much less attention than dialog templates, but if you wanted to know how they work, well, there you have it.

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