Holy cow, I wrote a book!
(Today is the birthday of the famed civil rights leader, the first time since 2001 that the federal holiday coincides with the correct historical date. It seemed an auspicious date to return to the time machine essay.)
The eighth grade students were given the same in-class writing assignment as the seventh graders, with the same ground rules, I took two days off from work to help read those essays like I did with the seventh grade essays. (What can I say. I volunteer for strange things.)
When I started reading the essays, I immediately noticed the difference one year makes in the quality of writing. The eighth grade essays are better put-together, the ideas more fully fleshed-out. But that doesn't mean that every single essay is a work of art. Here are selections from some essays that one might call the whey of the crop.
Introductions
It's clear that these students have been taught the importance of having an introduction that establishes the topic of the essay. But some students have taken the formula a bit too literally.
It doesn't hurt to flatter the teacher.
Other students took the opportunity to reflect on the nature of the time machine itself.
A sense of scale
People from the past were so stupid
Travelling to the past
Life in the future
Religious-themed
Spelling lightning round
Personality
Finally, we have sentences that give us a peek into the student's psyche.
Craig Ward figures that if he asks enough questions I might answer one of them. "Does Microsoft internally use MFC for writing Windows apps? How about VB?"
People use whatever they decide best meets the requirements for the task at hand. That could be a batch file, a C++ program, a perl script, a web page with a bunch of JScript, use your imagination. Is the number of solutions that use MFC and VB nonzero? I don't know for sure (I'm not in the habit of taking all the programs I see and trying to figure out what language they're written in), but I'd be very surprised if somebody somewhere hasn't thrown together an MFC or VB program to do something, be it a test suite, an install script, a graphical interface over a complicated command-line tool, or something else. It's a big company. I understand that in some parts of the company they even use Macintoshes.
I'm not going to go into details of specific projects because (3) I am not authorized to talk about it. (I gave reasons one and two two years ago.)
For some reason, people are really puzzled by rich edit printing. I'm no expert on printing, but even I was able to figure it out. The kernel is the EM_FORMATRANGE message. Each time you call it, a little bit more of the rich text control is printed, and the message returns the index of the first unprinted character, which you can pass back in to print the next chunk.
EM_FORMATRANGE
The rest is just setting up and tearing down.
BOOL PrintRTF(HWND hwnd, HDC hdc) { int cxPhysOffset = GetDeviceCaps(hdc, PHYSICALOFFSETX); int cyPhysOffset = GetDeviceCaps(hdc, PHYSICALOFFSETY); int cxPhys = GetDeviceCaps(hdc, PHYSICALWIDTH); int cyPhys = GetDeviceCaps(hdc, PHYSICALHEIGHT); SendMessage(hwnd, EM_SETTARGETDEVICE, (WPARAM)hdc, cxPhys); FORMATRANGE fr; fr.hdc = hdc; fr.hdcTarget = hdc; fr.rc.left = cxPhysOffset; fr.rc.right = cxPhysOffset + cxPhys; fr.rc.top = cyPhysOffset; fr.rc.bottom = cyPhysOffset + cyPhys; SendMessage(hwnd, EM_SETSEL, 0, (LPARAM)-1); SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&fr.chrg); BOOL fSuccess = TRUE; while (fr.chrg.cpMin < fr.chrg.cpMax && fSuccess) { fSuccess = StartPage(hdc) > 0; if (!fSuccess) break; int cpMin = SendMessage(hwnd, EM_FORMATRANGE, TRUE, (LPARAM)&fr); if (cpMin <= fr.chrg.cpMin) { fSuccess = FALSE; break; } fr.chrg.cpMin = cpMin; fSuccess = EndPage(hdc) > 0; } SendMessage(hwnd, EM_FORMATRANGE, FALSE, 0); return fSuccess; }
We start by getting the dimensions of the page and telling the rich edit control what we intend to render to by using the EM_SETTARGETDEVICE message. Next, we need to fill out our FORMATRANGE, which we do by specifying the HDC we are rendering to, as well as the paper dimensions. But what about the character range? We are lazy and let the rich edit control take care of it for us: We select all the text and then ask the rich edit control to tell us what we just selected, which comes back in the form of a CHARRANGE, which is exactly what we needed.
EM_SETTARGETDEVICE
FORMATRANGE
HDC
CHARRANGE
Next comes the printing loop. While there is still text to print (and we haven't encountered an error), we start a new page, ask the rich edit control to render that page, remember where the next page should begin, and end the current page. There's a little sanity check in there to make sure that the rich edit control made forward progress; if not, then we'll end up in an infinite loop spewing out blank pages! (I have no idea whether this is theoretically possible, but I'm going to protect against it just the same.)
Once the printing loop is complete, we clean up by sending one last EM_FORMATRANGE message to tell the rich edit control that we're all done and it can discard the information it cached.
We can take all the information we've learned over the past few days to make a simple "print RTF" program.
int CALLBACK _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd) { LoadLibrary(TEXT("riched20.dll")); HWND hwndRTF = CreateWindow(RICHEDIT_CLASS, NULL, ES_MULTILINE | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, 0, 0, 0); if (hwndRTF) { SendMessage(hwndRTF, EM_EXLIMITTEXT, 0, -1); if (FillRichEditFromFile(hwndRTF, lpCmdLine)) { PRINTDLG pd = { sizeof(pd) }; pd.Flags = PD_RETURNDC | PD_RETURNDEFAULT; if (PrintDlg(&pd)) { DOCINFO di = { sizeof(di) }; di.lpszDocName = TEXT("Sample Printout"); if (StartDoc(pd.hDC, &di) > 0) { if (PrintRTF(hwndRTF, pd.hDC)) { EndDoc(pd.hDC); } else { AbortDoc(pd.hDC); } } GlobalFree(pd.hDevMode); GlobalFree(pd.hDevNames); DeleteDC(pd.hDC); } } DestroyWindow(hwndRTF); } return 0; }
There's not really much going on here; it's all just glue and necessary typing.
We create a rich edit control and fill it with the file passed on the command line. We then ask the PrintDlg function to give us a DC to the user's default printer. We give the document a title, start the document, print the rich text into the document, and then end the document (or abort it if something went wrong during printing). A little cleaning up, and we're all done. A tiny program to print an arbitrary RTF document with no fanfare whatsoever.
PrintDlg
See? It's not so hard. Once you find EM_FORMATRANGE the rest is just doing the obvious.
Murray Sargent is a programmer over in the Office division whose blog deals mostly with math in Office, but he also slips into RichEdit history. If you can't get enough of a history fix from this web site, you can surf on over to Murray's for another hit.
Last time we looked at loading an entire file into a rich text control. The code runs great, until you try to use it to display a license agreement provided by your legal department, and then some paranoid user reports that they can't read past page seven. (What, somebody reads those things?) What's going on?
If you don't specify otherwise, the maximum number of characters in a rich edit control is 32,767 charaters. (This limit exists for compatibility with the original rich edit control.) You can raise the limit with the EM_EXLIMITTEXT message. Therefore, we need to slip the line
EM_EXLIMITTEXT
SendMessage(hwnd, EM_EXLIMITTEXT, 0, -1);
into the program before it calls FillRichEditFromStream.
FillRichEditFromStream
Next time, the mystery of rich edit printing.
Ken Levine has written for some of the most well-known television programs of the past few decades. M*A*S*H, Cheers (for which he won an Emmy), Frasier. And that's not counting his second (third? fourth?) career as a baseball announcer. He writes about whatever is on his mind, be it Barry Bonds' 715th home run, what not to put into your television script, more Cheers trivia than you know what to do with, or Thanksgiving travel tips, and every single one is filled with his personality and attitude. It's a joy to read.
To load an entire file into a rich text control, you can use the EM_STREAMIN message, which accepts an IStream of data all at once. Once you find the message, it's pretty straightforward how to use it, but I'll write out the code anyway;
EM_STREAMIN
IStream
DWORD CALLBACK EditStreamCallback(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, PLONG pcb) { HANDLE hFile = (HANDLE)dwCookie; return !ReadFile(hFile, lpBuff, cb, (DWORD *)pcb, NULL); } BOOL FillRichEditFromFile(HWND hwnd, LPCTSTR pszFile) { BOOL fSuccess = FALSE; HANDLE hFile = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hFile != INVALID_HANDLE_VALUE) { EDITSTREAM es = { (DWORD_PTR)hFile, 0, EditStreamCallback }; if (SendMessage(hwnd, EM_STREAMIN, SF_RTF, (LPARAM)&es) && es.dwError == 0) { fSuccess = TRUE; } CloseHandle(hFile); } return fSuccess; }
You pretty much follow your nose. The EM_STREAMIN message wants you to tell it the format of the stream (SF_RTF) and provide a pointer to an EDITSTREAM structure that controls the input. Since we want to read from a file, we open a file for reading and use it as the dwCookie for our EditStreamCallback. The only tricky part is getting the return value correct for the callback. For some reason, the rich edit control wants zero on success and nonzero on failure, so we need to flip the sense of the ReadFile return value accordingly. Aside from that, there's nothing particularly interesting going on.
SF_RTF
EDITSTREAM
dwCookie
EditStreamCallback
ReadFile
"But I tried this, and only the first line of the file gets read in. What am I doing wrong?"
ES_MULTILINE
Don't worry, I made this mistake, too.
"What if my data is in some other format than a file?"
As long as you can write a function that produces the next few bytes of data, you can stream it into a rich edit control. For example, here's a version that loads an arbitrary IStream into a rich edit control:
DWORD CALLBACK EditStreamCallback(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, PLONG pcb) { IStream *pstm = (IStream *)dwCookie; return FAILED(pstm->Read(lpBuff, cb, (ULONG*)pcb)); } BOOL FillRichEditFromStream(HWND hwnd, IStream *pstm) { BOOL fSuccess = FALSE; EDITSTREAM es = { (DWORD_PTR)pstm, 0, EditStreamCallback }; if (SendMessage(hwnd, EM_STREAMIN, SF_RTF, (LPARAM)&es) && es.dwError == 0) { fSuccess = TRUE; } return fSuccess; }
There's still a bug in this code, however, and it's not where you expect it. We'll take another look next time.
Balaclava or baklava?
I always get the two confused, and I have to stop and think. "Why were the bank robbers covered in a tasty dessert?" "Who wants to eat a hooded knit cap?"
Hierarchical storage management is one of the taxes software developers have to pay. What can you safely do to an offline file? What will trigger its recall?
(First, a note on terminology: Recalling a file means to restore it from remote storage to local storage. A file that has been recalled is online; a file that has been placed on remote storage is offline.)
Merely opening the file will not recall it. Therefore, you can still open the file and use the handle in functions like GetFileInformationByHandle, GetFileTime, and GetFileSecurity without triggering a recall. But if you read from or write to the file (or map the file, which is the moral equivalent of reading and writing), then the file will be recalled from storage.
GetFileInformationByHandle
GetFileTime
GetFileSecurity
What about the FILE_FLAG_OPEN_NO_RECALL flag? This flag doesn't affect when the remote storage is accessed. (It's still read/write/map.) What it does is tell the hierarchical storage manager to leave the file offline. In other words, if you open an offline file with the FILE_FLAG_OPEN_NO_RECALL flag, then when you read from the file, the file contents will be read from tape directly and the file will remain in its offline state.
FILE_FLAG_OPEN_NO_RECALL
I'm told that the FILE_FLAG_OPEN_NO_RECALL flag is intended for backup programs so that they can back up all your files (even the offline ones) while still keeping them offline.
(Note: Do not confuse these types of offline files with another feature also confusingly called offline files. This is what happens when you let the Marketing department choose the names of your features.)
Reader cmonachan asked why CPropertySheet::DoModal can cause a first-chance exception. This is mentioned in Knowledge Base article 158552. But why take the exception in the first place?
CPropertySheet::DoModal
First off, let's take MFC out of the picture. The first-chance exception is coming from the property sheet manager. MFC is just the middle-man.
Okay, so why the first-chance exception? It's not obviously backwards compatibility, since property sheets were new for Windows 95; there was no old property sheet manager to be compatible with.
But yes, it was for compatibility. Not with an imaginary "earlier version" of the property sheet manager, but with real honest-to-goodness dialog box editing tools.
Property sheets are nested dialogs. The styles for nested dialogs must include DS_CONTROL and WS_CHILD. What's more, it can't contain styles like DS_ABSALIGN and WS_CAPTION which make no sense for child windows. Most of those styles you can convince your dialog box editing tool to add or remove, but DS_CONTROL is a different story. Since this flag was new for Windows 95, no existing dialog box editing tool supported it.
DS_CONTROL
WS_CHILD
DS_ABSALIGN
WS_CAPTION
Okay, so now you're designing a property sheet manager. What do you do with dialog box templates that have the wrong style?
Proposal 1: Don't write any special code. Just create the dialog box on the assumption that the caller set the styles correctly.
The upside of this proposal is that it's the least amount of work. The downside is that if the caller messes up, they just get bizarre behavior (property sheet pages that pop out of the frame, for example) and have no real clue what they did wrong, much less how they can go about fixing it.
Proposal 2: Write special code to detect incorrect dialog boxes and reject them. Okay, well instead of bizarre behavior, the caller just gets nothing. Still not much help.
There's a serious problem with the first two proposals: How do you get people to set the styles correctly if their tools aren't capable of doing it? Fire up the dialog box editing tool you've been using and it won't have a check-box for DS_CONTROL because your dialog box editing tool was written for Windows 3.1, and DS_CONTROL didn't exist then.
You might say, "Well, those people will have to upgrade their dialog box editing tools in order to write programs for Windows 95." The problem with that position is that it creates a chicken-and-egg problem. It's 1994. Windows 95 Beta 1 has just come out. A company wants to "catch the wave" and have a Windows 95 version of their program ready on the day that Windows 95 hits the shelves, so they anxiously install the beta, install the beta SDK, they see this cool new property sheet thing so they sit down to give it a shot and... they're stuck. They can't use property sheets because they need the Windows 95 version of Microsoft Visual C++ or Delphi or Turbo Pascal. But those products don't exist yet because Windows 95 isn't out yet. And it's not just the development tools that would need to be upgraded. It's also the translation tools and all the other tools that manipulate dialog box templates.
This isn't an imaginary scenario. The Windows 95 team faced this very problem! In order to use these cool property sheet things, they needed to add the DS_CONTROL style to their dialog boxes, but Visual C++ didn't support this style since that style existed only in an operating system that hadn't been released yet.
Proposal 3: Write special code to detect incorrect dialog boxes and fix them so that people can start writing Windows 95 programs now.
To fix the dialog box requires modifying the styles, which means modifying the dialog template, and that's where the first-chance exception comes from. When the property sheet manager writes to the dialog template to fix the style, a first-chance exception is raised because resources start out as read-only pages. The kernel catches this exception and write-enables the page, then returns EXCEPTION_CONTINUE_EXECUTION to say, "I fixed the problem, try it again."
EXCEPTION_CONTINUE_EXECUTION
That's where the first-chance exception comes from.
How do you avoid this first-chance exception? Easy. Get your dialog box styles right in the first place. If you get the styles right, then the property sheet manager won't have to fix them. For the record, here are the style requirements for property sheets:
DS_3DLOOK DS_CONTROL WS_CHILD WS_TABSTOP
DS_SHELLFONT DS_LOCALEDIT WS_CLIPCHILDREN
Any other WS_* and DS_* styles not listed above are forbidden. (Note that I'm talking about styles and not extended styles.)
WS_*
DS_*