Holy cow, I wrote a book!
Early beta versions of the taskbar clock did display seconds, and it even blinked the colon like some clocks do. But we had to remove it.
Why?
Because that blinking colon and the constantly-updating time were killing our benchmark numbers.
On machines with only 4MB of memory (which was the minimum memory requirement for Windows 95), saving even 4K of memory had a perceptible impact on benchmarks. By blinking the clock every second, this prevented not only the codepaths related to text rendering from ever being paged out, it also prevented the taskbar's window procedure from being paged out, plus the memory for stacks and data, plus all the context structures related to the Explorer process. Add up all the memory that was being forced continuously present, and you had significantly more than 4K.
Bitmap brushes used to be these little 8x8 monochrome patterns that you could use for hatching and maybe little houndstooth patterns if you were really crazy. But you can do better.
CreatePatternBrush lets you pass in any old bitmap - even a huge one, and it will create a brush from it. The bitmap will automatically be tiled, so this is a quick way to get bitmap tiling. Let GDI do all the math for you!
This is particularly handy when you're stuck with a mechanism where you are forced to pass an HBRUSH but you really want to pass an HBITMAP. Convert the bitmap to a brush and return that brush instead.
HBRUSH
HBITMAP
For example, let's take our scratch program and give it a custom tiled background by using a pattern brush.
HBRUSH CreatePatternBrushFromFile(LPCTSTR pszFile) { HBRUSH hbr = NULL; HBITMAP hbm = (HBITMAP)LoadImage(g_hinst, pszFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); if (hbm) { hbr = CreatePatternBrush(hbm); DeleteObject(hbm); } return hbr; } BOOL InitApp(LPSTR lpCmdLine) { BOOL fSuccess = FALSE; HBRUSH hbr = CreatePatternBrushFromFile(lpCmdLine); if (hbr) { WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = g_hinst; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = hbr; wc.lpszMenuName = NULL; wc.lpszClassName = CLASSNAME; fSuccess = RegisterClass(&wc); // Do not delete the brush - the class owns it now } return fSuccess; }
With a corresponding adjustment to WinMain:
WinMain
if (!InitApp(lpCmdLine)) return 0;
Pass the path to a *.bmp file on the command line, and bingo, the window will tile its background with that bitmap. Notice that we did not have to change anything other than the class registration. No muss, no fuss, no bother.
Here's another way you can use bitmap brushes to save yourself a lot of work. Start with a new scratch program and change it as follows:
HBRUSH g_hbr; // the pattern brush we created HBRUSH CreatePatternBrushFromFile(LPCTSTR pszFile) { ... same as above ... } void PaintContent(HWND hwnd, PAINTSTRUCT *pps) { BeginPath(pps->hdc); Ellipse(pps->hdc, 0, 0, 200, 100); EndPath(pps->hdc); HBRUSH hbrOld = SelectBrush(pps->hdc, g_hbr); FillPath(pps->hdc); SelectBrush(pps->hdc, hbrOld); }
And add the following code to WinMain before the call to CreateWindowEx:
CreateWindowEx
g_hbr = CreatePatternBrushFromFile(lpCmdLine); if (!g_hbr) return 0;
This time, since we are managing the brush ourselves we need to remember to
DeleteObject(g_hbr);
at the end of WinMain before it returns.
FillPath
You may have wondered why VirtualAlloc allocates memory at 64K boundaries even though page granularity is 4K.
You have the Alpha AXP processor to thank for that.
On the Alpha AXP, there is no "load 32-bit integer" instruction. To load a 32-bit integer, you actually load two 16-bit integers and combine them.
So if allocation granularity were finer than 64K, a DLL that got relocated in memory would require two fixups per relocatable address: one to the upper 16 bits and one to the lower 16 bits. And things get worse if this changes a carry or borrow between the two halves. (For example, moving an address 4K from 0x1234F000 to 0x12350000, this forces both the low and high parts of the address to change. Even though the amount of motion was far less than 64K, it still had an impact on the high part due to the carry.)
But wait, there's more.
The Alpha AXP actually combines two signed 16-bit integers to form a 32-bit integer. For example, to load the value 0x1234ABCD, you would first use the LDAH instruction to load the value 0x1235 into the high word of the destination register. Then you would use the LDA instruction to add the signed value -0x5433. (Since 0x5433 = 0x10000 - 0xABCD.) The result is then the desired value of 0x1234ABCD.
LDAH t1, 0x1235(zero) // t1 = 0x12350000 LDA t1, -0x5433(t1) // t1 = t1 - 0x5433 = 0x1234ABCD
So if a relocation caused an address to move between the "lower half" of a 64K block and the "upper half", additional fixing-up would have to be done to ensure that the arithmetic for the top half of the address was adjusted properly. Since compilers like to reorder instructions, that LDAH instruction could be far, far away, so the relocation record for the bottom half would have to have some way of finding the matching top half.
What's more, the compiler is clever and if it needs to compute addresses for two variables that are in the same 64K region, it shares the LDAH instruction between them. If it were possible to relocate by a value that wasn't a multiple of 64K, then the compiler would no longer be able to do this optimization since it's possible that after the relocation, the two variables no longer belonged to the same 64K block.
Forcing memory allocations at 64K granularity solves all these problems.
If you have been paying really close attention, you'd have seen that this also explains why there is a 64K "no man's land" near the 2GB boundary. Consider the method for computing the value 0x7FFFABCD: Since the lower 16 bits are in the upper half of the 64K range, the value needs to be computed by subtraction rather than addition. The naïve solution would be to use
LDAH t1, 0x8000(zero) // t1 = 0x80000000, right? LDA t1, -0x5433(t1) // t1 = t1 - 0x5433 = 0x7FFFABCD, right?
Except that this doesn't work. The Alpha AXP is a 64-bit processor, and 0x8000 does not fit in a 16-bit signed integer, so you have to use -0x8000, a negative number. What actually happens is
LDAH t1, -0x8000(zero) // t1 = 0xFFFFFFFF`80000000 LDA t1, -0x5433(t1) // t1 = t1 - 0x5433 = 0xFFFFFFFF`7FFFABCD
You need to add a third instruction to clear the high 32 bits. The clever trick for this is to add zero and tell the processor to treat the result as a 32-bit integer and sign-extend it to 64 bits.
ADDL t1, zero, t1 // t1 = t1 + 0, with L suffix // L suffix means sign extend result from 32 bits to 64 // t1 = 0x00000000`7FFFABCD
If addresses within 64K of the 2GB boundary were permitted, then every memory address computation would have to insert that third ADDL instruction just in case the address got relocated to the "danger zone" near the 2GB boundary.
Some commenters mentioned that I should write a book. It turns out that writing a book is hard.
A few years ago, MS Press actually approached me about writing a book for them. But I declined because the fashion for technical books is to take maybe fifty pages of information and pad it to a 700-page book, and I can't write that way. None of my topics would ever make it to a 100-page chapter. They're just little page-and-a-half vignettes. And it's not like the world needs yet another book on Win32 programming.
So I'll just continue to babble here. It's easier.
Shared memory is not just for sharing memory with other processes. It also lets you share memory with yourself in sneaky ways.
For example, this sample program (all error checking and cleanup deleted for expository purposes) shows how you can map the same shared memory into two locations simultaneously. Since they are the same memory, modifications to one address are reflected at the other.
#include <windows.h> #include <stdio.h> void __cdecl main(int argc, char **argv) { HANDLE hfm = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(DWORD), NULL); LPDWORD pdw1 = (LPDWORD)MapViewOfFile(hfm, FILE_MAP_WRITE, 0, 0, sizeof(DWORD)); LPDWORD pdw2 = (LPDWORD)MapViewOfFile(hfm, FILE_MAP_WRITE, 0, 0, sizeof(DWORD)); printf("Mapped to %x and %x\n", pdw1, pdw2); printf("*pdw1 = %d, *pdw2 = %d\n", *pdw1, *pdw2); /* Now watch this */ *pdw1 = 42; printf("*pdw1 = %d, *pdw2 = %d\n", *pdw1, *pdw2); }
This program prints
Mapped to 280000 and 290000 *pdw1 = 0, *pdw2 = 0 *pdw1 = 42, *pdw2 = 42
(Missing asterisks added, 8am - thanks to commenter Tom for pointing this out.)
The addresses may vary from run to run, but observe that the memory did get mapped to two different addresses, and changing one value to 42 magically changed the other.
This is a nifty consequence of the way shared memory mapping works. I stumbled across it while investigating how I could copy large amounts of memory without actually copying it. The solution: Create a shared memory block, map it at one location, write to it, then unmap it from the old location and map it into the new location. Presto: The memory instantly "moved" to the new location. This a major win if the memory block is large, since you didn't have to allocate a second block, copy it, then free the old block - the memory block doesn't even get paged in.
During Windows 95 beta testing, people ran the System Properties page and complained about "missing memory".
The Windows 95 System Properties page reports the amount of memory available to Windows as system memory, which is not necessarily the same as the amount of memory installed in your computer.
For example, you may have an old DOS device driver that allocates a large amount of memory for itself, which prevents Windows 95 from using it. Or you may have a dreaded UMA machine, where your so-called 8MB of memory is actually being divided between main system memory and video memory. So if you have an 8MB UMA machine and you're running at 800x600 8bpp, you actually have only 7.5MB of memory, since the other half-meg got eaten by the video card.
When we displayed the actual amount of memory available to Windows, we got lots of bug reports from people asking, "I paid for 8MB of memory, where is it?"
The first letter of the program is the accelerator and there's nothing you can do about it. So if you have ten programs by Microsoft, they all use the accelerator "M". (Yes I hate this too. The first thing I do after installing a Microsoft program is go into the Start menu and delete the word "Microsoft" from the front.)
For Win32 menus, the ampersand character acts as the marker for the menu accelerator. For example, you would set your menu text to "Save &As" to set Alt+A as the accelerator for the menu item.