• The Old New Thing

    Why does Explorer say "File too large" for my custom file system, when the problem has nothing to do with the file being too large (heck it's not even a file)

    • 34 Comments

    When Explorer copies files around, it doesn't really know what the maximum file size supported by any file system happens to be. (That information is not reported by Get­Volume­Information.) So it guesses.

    If the file system name is "FAT" or "FAT32", then Explorer assumes that the maximum file size is 4GB − 1.

    Also, if a file operation fails with the error ERROR_INVALID_PARAMETER, and Explorer can't figure out why the parameter is invalid, it assumes that the reason is that the file has exceeded the maximum allowed file size.

    Why does Explorer map "invalid parameter" to "file size too large"? Because some file systems use ERROR_INVALID_PARAMETER to report that a file is too large instead of the somewhat more obvious ERROR_FILE_TOO_LARGE.

    Therefore, if you're implementing a file system, and you're getting these spurious "File too large" errors, one thing to check is whether you are reporting "invalid parameter" for a case where all the parameters are actually valid, but something else prevents you from doing what you want. (Maybe "access denied" would be a better error code.)

  • The Old New Thing

    Microspeak: 1 – 1 is not zero

    • 28 Comments

    In his reddit AMA, Joe Belfiore wrote

    i have regular 1-1 meetings with my counterparts in Office, Skype, Xbox.

    The little bit of jargon there is 1-1 meeting. This is an abbreviation for one-on-one meeting, a common business practice wherein two people, typically a manager and a direct report, have a face-to-face meeting with no one else present. In the case Joe used, the meeting is not between a manager and a direct report but between two peers.

    The term is also abbreviated 1:1, which like 1 − 1 also looks like a bit of mathematical nonsense. But it's not zero or one. It's just an abbreviation for business jargon.

    Of course, Microspeak isn't happy with the abbreviation as-is. Microspeak takes it further and nounifies the abbreviation. "I'll bring that up in my 1–1 with Bob tomorrow" means "I'll bring that up in my one-on-one meeting with Bob tomorrow."

    Note that the abbreviation OOO is not used to mean one-on-one. That abbreviation is used to mean Out of the office, although it is more commonly abbreviated OOF at Microsoft.

  • The Old New Thing

    Enumerating the ways of choosing teams in a group of players

    • 16 Comments

    Suppose you have a bunch of people, and you want to break them up into m teams of size n. (Therefore you have a total of nm people.) Today's Little Program will enumerate the ways this can be done.

    Formally, let's say that you have a collection of size nm, and you want to enumerate the ways of partitioning the collection into m subsets, each subset of size n. The order of elements within each subset does not matter, and the order of the subsets doesn't matter. That's saying that a team of Alice and Bob is the same as a team of Bob and Alice, and Alice-Bob versus Charlie-David is the same as Charlie-David versus Alice-Bob.

    The number of ways of doing this is (nm)!/n!mm!. You can see this by first taking all permutations of the players, then dividing out by the things that cause us to overcount: The number of ways of ordering players within each team is n!, and there are m teams, and there are m! ways of ordering the teams themselves. (Note that this is a cute way of expressing the result, but you shouldn't use it for computation. A slightly better way for computation would be (Π1 ≤ knC(mk, m))/m!.

    Okay, but how do you generate the teams themeselves?

    Let's first see how to generate the first team. Well, that's easy. You just select n players and call them Team 1.

    This leaves you n(m − 1) players with which to form m − 1 teams, which you can do recursively.

    function Teams(n, m, f) {
     var a = [];
     for (var i = 1; i <= n * m; i++) {
      a.push(i);
     }
    
     if (m == 1) { f([a]); return; }
    
     Subsets(n * m, n, function(s) {
      var rest = a.filter(function(i) { return s.indexOf(i) < 0; });
      Teams(n, m - 1, function(t) {
        f([s].concat(t.map(function(team) {
            return team.map(function(i) { return rest[i-1]; });
           })));
      });
     });
    }
    
    Teams(2, 3, logToConsole);
    

    The first part of this function builds an array of the form [1, 2, 3, ..., n * m]. If we are asking for only one team, then everybody is on the same team. Otherwise, for all possible choices of n-member teams, first see which people haven't yet been picked for a team. Then generate all remaining possible team arrangements for those leftovers, and combine them to form the final team rosters.

    The combination step is tricky because the recursive call generates subsets in the range [1, 2, 3, ..., n * (m-1)], and we need to convert those values into indices into the array of people waiting to be picked.

    Note that this algorithm over-counts the possibilities since it generates both [[1,2],[3,4]] and [[3,4],[1,2]]. In other words, it assumes that team order is important (say, because the first team will wear red jerseys and the second team will wear blue jerseys). In the original problem statement, the order of the teams is not significant. (Maybe we'll let them pick their own jersey colors.)

    To solve that, we impose a way of choosing one such arrangement as the one we enumerate, and ignore the rest. The natural way to do this is to select a representative player from each team in a predictable manner (say, the one whose name comes first alphabetically), and then arranging the representatives in a predictable manner (say, by sorting them alphabetically).

    The revised version of our algorithm goes like this:

    function Teams(n, m, f) {
     var a = [];
     for (var i = 1; i <= n * m; i++) {
      a.push(i);
     }
    
     if (m == 1) { f([a]); return; }
    
     a.shift();
     Subsets(n * m - 1, n - 1, function(s) {
      var firstTeam = [1].concat(s.map(function(i) { return i+1; }))
      var rest = a.filter(function(i) { return s.indexOf(i) < 0; });
      Teams(n, m - 1, function(t) {
        f([firstTeam].concat(t.map(function(team) {
          return team.map(function(i) { return rest[i-1]; });
         })));
      });
     });
    }
    
    Teams(2, 3, logToConsole);
    

    The first part of the function is the same as before, but the recursive step changes.

    We remove the first element from the array. That guy needs to belong to some team, and since he's the smallest-numbered guy, he will be nominated as the team representative of whatever team he ends up with, and since he's the smallest-numbered guy of all, he will also be the first team representative when they are placed in sorted order. So we pick him right up front.

    We then ask for his n - 1 teammates, and together they make up the first team. The combination is a little tricky because the Subsets function assumes that the underlying set is [1, 2, ..., n-1] but we actually want the subset to be of the form [2, 3, ..., n]; we fix that by adding 1 to each element of the subset.

    We then find all the people who have yet to be assigned to a team and recursively ask for m - 1 more teams to be generated from them. We then combine the first team with the recursively-generated teams. Again, since the recursively-generated teams are numbered starting from 1, we need to convert the returned subsets into the original values we saved away in the rest variable.

    Renumbering elements is turning into a bit of a bother, so let's tweak our original Subsets function. For example, we would prefer to pass the set explicitly rather than letting Subsets assume that the set is [1, 2, 3, ..., n], forcing us to convert the indices back to the original set members. It's also convenient if the callback also included the elements that are not in the subset.

    function NamedSubsets(a, k, f) {
     if (k == 0) { f([], a); return; }
     if (a.length == 0) { return; }
     var n = a[a.length - 1];
     var rest = a.slice(0, -1);
     NamedSubsets(rest, k, function(chosen, rejected) {
      f(chosen, rejected.concat(n));
     });
     NamedSubsets(rest, k-1, function(chosen, rejected) {
      f(chosen.concat(n), rejected);
     });
    }
    
    function takeAndLeave(chosen,rejected) {
     console.log("take " + chosen + ", leave " + rejected);
    }
    
    NamedSubsets(["alice", "bob", "charlie"], 2, takeAndLeave);
    

    The Named­Subsets function takes the last element from the source set and either rejects it (adds it to the "rejected" parameter) or accepts it (adds it to the "chosen" parameter).

    With the Named­Subsets variant, we can write the Teams function much more easily.

    function Teams(a, m, f) {
     var n = a.length / m;
     if (m == 1) { f([a]); return; }
    
     var p = a[0];
     NamedSubsets(a.slice(1), n - 1, function(teammates, rest) {
      var team = [p].concat(teammates);
      Teams(rest, m - 1, function(teams) {
        f([team].concat(teams));
      });
     });
    }
    
    Teams([1,2,3,4,5,6], 3, logToConsole);
    

    Assuming we're not in one of the base cases, we grab the first person p so he can be captain of the first team. We then ask Named­Subsets to generate his teammates and add them to p's team. We then recursively generate all the other teams from the people who haven't yet been picked, and our result is our first team plus the recursively-generated teams.

    There is a lot of potential for style points with the Named­Subsets function. For example, we can avoid generating temporary copies of the a array just to remove an element by instead passing slices (an array and indices marking the start and end of the elements we care about).

    function NamedSubsetsSlice(a, begin, end, k, f) {
     if (k == 0) { f([], a.slice(begin, end)); return; }
     if (begin == end) { return; }
     var n = a[end - 1];
     NamedSubsetsSlice(a, begin, end - 1, k, function(chosen, rejected) {
      f(chosen, rejected.concat(n));
     });
     NamedSubsetsSlice(a, begin, end - 1, k-1, function(chosen, rejected) {
      f(chosen.concat(n), rejected);
     });
    }
    
    function NamedSubsets(a, k, f) {
     NamedSubsetsSlice(a, 0, a.length, k, f);
    }
    

    We could use an accumulator to avoid having to generate closures.

    function AccumulateNamedSubsets(a, begin, end, k, f, chosen, rejected) {
     if (k == 0) { f(chosen, rejected.concat(a.slice(begin, end))); return; }
     if (begin == end) { return; }
     var n = a[begin];
     AccumulateNamedSubsets(a, begin + 1, end, k-1, f, chosen.concat(n), rejected);
     AccumulateNamedSubsets(a, begin + 1, end, k, f, chosen, rejected.concat(n));
    }
    
    function NamedSubsetsSlice(a, begin, end, k, f) {
     AccumulateNamedSubsets(a, begin, end, k, f, [], []);
    }
    
    function NamedSubsets(a, k, f) {
     NamedSubsetsSlice(a, 0, a.length, k, f);
    }
    

    For bonus style points, I recurse on the start of the range rather than the beginning so that the results are in a prettier order.

    We can also get rid of the temporary accumulator objects by manipulating the accumulators destructively.

    function AccumulateNamedSubsets(a, begin, end, k, f, chosen, rejected) {
     if (k == 0) { f(chosen, rejected.concat(a.slice(begin, end))); return; }
     if (begin == end) { return; }
     var n = a[begin];
     chosen.push(n);
     AccumulateNamedSubsets(a, begin + 1, end, k-1, f, chosen, rejected);
     chosen.pop();
     rejected.push(n);
     AccumulateNamedSubsets(a, begin + 1, end, k, f, chosen, rejected);
     rejected.pop();
    }
    

    And then we can take advantage of the accumlator version to pre-select the first player when building teams.

    function Teams(a, m, f) {
     var n = a.length / m;
     if (m == 1) { f([a]); return; }
    
     AccumulateNamedSubsetsSlice(a, 1, a.length, n - 1, function(team, rest) {
      Teams(rest, m - 1, function(teams) {
        f([team].concat(teams));
      });
     }, [a[0]], []);
    }
    

    There is still a lot of potential for improvement here. For example, you can switch to the iterative version of Subsets to avoid the recursion on subset generation. You can use an accumulator in Teams to avoid generating closures. And if you are really clever, you can eliminate many more temporary arrays by reusing the elements in the various recursively-generated arrays by shuffling them around. But I've sort of lost interest in the puzzle by now, so I won't bother.

  • The Old New Thing

    Before claiming that a function doesn't work, you should check what you're passing to it and what it returns

    • 53 Comments

    Before claiming that a function doesn't work, you should check what you're passing to it and what it returns, because it may be that the function is behaving just fine and the problem is elsewhere.

    The Get­Current­DirectoryW function does not appear to support directories with Unicode characters in their names.

    wchar_t currentDirectory[MAX_PATH];
    GetCurrentDirectoryW(MAX_PATH, currentDirectory);
    wcout << currentDirectory << endl;
    

    The correct directory name is obtained if it contains only ASCII characters in its name, but it truncates the string at the first non-ASCII character.

    If you step through the code in the debugger, you'll see that the Get­Current­DirectoryW function is working just fine. The buffer is filled with the current directory, including the non-ASCII characters. The problem is that the wcout stream stops printing the directory name at the first non-ASCII characters. And that's because the default locale for wcout is the "C" locale, and the "C" locale is "the minimal environment for C translation." The "C" locale is useless for actual work involving, you know, locales. You will have to do some language-specific munging to get the characters to reach the screen in the format you want, the details of which are not the point of today's topic.

    In other words, the bug was not in the Get­Current­DirectoryW function. It was in what you did with the result of the Get­Current­DirectoryW function.

    Here's another example of thinking the problem is in a function when it isn't:

    The Set­Window­TextW function does not appear to support Unicode, despite its name.

    wstring line;
    wifstream file("test"); // this file is in Unicode
    getline(file, line);
    SetWindowTextW(hwnd, line.c_str());
    

    If you look at the line variable before you even get around to calling Set­Window­TextW, you'll see that it does not contain the text from your Unicode file. The problem is that the default wifstream reads the text as an 8-bit file, and then internally converts it (according to the lame "C" locale) to Unicode. If the original file is already Unicode, you're doing a double conversion and things don't go well. You then pass this incorrectly-converted string to Set­Window­TextW, which naturally displays something different from what you intended.

    Again, the point is not to delve into the intricacies of wifstream. The point is that the problem occurred even before you called Set­Window­TextW. The observed behavior, then, is simple a case of Garbage In, Garbage Out.

    Here's another example from a few years ago.

  • The Old New Thing

    What is the strange garbage-looking string in the "command" value of a static verb?

    • 14 Comments

    A customer from a major software vendor asked, "What is the significance of the command value that can be found under HKCR\⟨progid⟩\shell\open\command. It appears to be a copy of the default value, but with the program name replaced with apparent garbage. We've seen this both with Microsoft products as well as products by other companies. There is no mention of this value in the documentation on static verbs."

    Name Type Data
    (Default) REG_SZ "C:\Program Files\Contoso\CONTOSO.exe" /NOLOGO "%1"
    command REG_MULTI_SZ 34GY`{XL?{Y)2S($,PP>c=@0l{Ja0N8KUwy@4JdO /NOLOGO "%1"

    The customer didn't explain why they were interested in this particular registry value. Maybe they thought it was enabling some super magical powers, and they wanted to get in on that action. (If that was the case, then they failed to notice that the same command value also existed in the verb registration for their own program!)

    That strange garbage-looking string was placed there by Windows Installer (also known as MSI). It is the so-called Darwin descriptor that Windows Installer uses to figure out what program to run when the verb is invoked by the shell. For compatibility with programs that read the registry directly (because everybody knows that reading the registry is much cooler than using the API), the default value is set to something approximating the local executable's path. That default value might be incorrect if the application has moved in the meantime, and it might be missing entirely if the application is marked as install-on-demand and has never been used, but at least it keeps those rogue programs working 99% of the time.

  • The Old New Thing

    If you want to be notified when your app is uninstalled, you can do that from your uninstaller

    • 28 Comments

    A customer had a rather strange request. "Is there a way to be notified when the user uninstalls any program from Programs and Features (formerly known as Add and Remove Programs)?"

    They didn't explain what they wanted to do this for, and we immediately got suspicious. It sounds like the customer is trying to do something user-hostile, like seeing that a user uninstalled a program and immediately reinstalling it. (Sort of the reverse of force-uninstalling all your competitors.)

    The customer failed to take into account that there are many ways of uninstalling an application that do not involve navigating to the Programs and Features control panel. Therefore, any solution that monitors the activities of Programs and Features may not actually solve the customer's problem.

    The customer liaison went back to the customer to get more information about their problem scenario, and the response was, that the customer is developing something like an App Lending Library. The user goes to the Lending Library and installs an application. They want a way to figure out when the user uninstalls the application so that the software can be "checked back in" to the library (available for somebody else to use).

    The customer was asking for a question far harder than what they needed. They didn't need to be notified if the user uninstalled any application from the Programs and Features control panel. They merely needed to be notified if the user uninstalled one of their own applications from the Programs and Features control panel.

    And that is much easier to solve.

    After all, when an application is installed, it registers a command line to execute when the user clicks the Uninstall button. You can set that command line to do anything you want. For example, you can set it to

    Uninstall­String = "C:\Program Files\Contoso Lending Library\CheckIn.exe" ⟨identification⟩

    where ⟨identification⟩ is something that the Check­In program can use to know what program is being uninstalled, so that it can launch the real uninstaller and update the central database.

  • The Old New Thing

    Did the Windows 95 interface have a code name?

    • 17 Comments

    Commenter kinokijuf wonders whether the Windows 95 interface had a code name.

    Nope.

    We called it "the new shell" while it was under preliminary development, and when it got enabled in the builds, we just called it "the shell."

    (Explorer originally was named Cabinet, unrelated to the container file format of the same name. This original name lingers in the window class: CabinetWClass.)

  • The Old New Thing

    Finding the shortest path to the ground while avoiding obstacles

    • 9 Comments

    Today's Little Program solves the following problem:

    Consider a two-dimensional board, tall and narrow. Into the board are nailed a number of horizontal obstacles. Place a water faucet at the top of the board and turn it on. The water will dribble down, and when it hits an obstacle, some of the water will go left and some will go right. The goal is to find the shortest path to the ground from a given starting position, counting both horizontal and vertical distance traveled.

    In the above diagram, the water falls three units of distance until it encounters Obstacle 1, at which some goes to the left and some goes to the right. The water that goes to the left travels three units of distance before it reaches the end of the obstacle, then falls three units and encounters Obstacle 2. Upon reaching Obstable 2, the water can again choose to flow either left or right. The water that flows to the left falls to the ground; the water that flows to the right falls and encounters a third obstacle. From the third obstacle, the water can flow left or right, and either way it goes, it falls to the ground. On the other hand, the water that chose to flow to the right when it encountered Obstable 1 iwould fall past Obstacle 2 (which is not in a position to intercept the water) and land directly on Obstacle 3.

    In the above scenario, there are five paths to the ground.

    • From Obstacle 1, flow left, then from Obstacle 2, flow left again. Total distance traveled: 17 units.
    • From Obstacle 1, flow left, then from Obstacle 2, flow right, then from Obstacle 3, flow left. Total distance traveled: 18 units.
    • From Obstacle 1, flow left, then from Obstacle 2, flow right, then from Obstacle 3, flow right. Total distance traveled: 20 units.
    • From Obstacle 1, flow right, then from Obstacle 3, flow left. Total distance traveled: 16 units.
    • From Obstacle 1, flow right, then from Obstacle 3, flow right. Total distance traveled: 14 units.

    In this case, the shortest path to the ground is the last path.

    There are many ways to attack this problem. The brute force solution would be to enumerate all the possible paths to the ground, then pick the shortest one.

    A more clever solution would use a path-finding algorithm like A*, where the altitude above the ground is the heuristic.

    In both cases, you can add an optimization where once you discover two paths to the same point, you throw out the longer one. This may short-circuit future computations.

    But I'm going to use an incremental solution, since it has the advantage of incorporating the optimization as a convenient side-effect. Instead of studying individual drops of water, I'm going to study all of them at once. At each step in the algorithm, the data structures represent a horizontal cross-section of the above diagram, representing all possible droplet positions at a fixed altitude.

    In addition to collapsing redundant paths automatically, this algorithm has the nice property that it can be done as an on-line algorithm: You don't need to provide all the obstacles in advance, as long as the obstacles are provided in order of decreasing altitude.

    Instead of presenting the raw code and discussing it later (as is my wont), I'll explain the code as we go via code comments. We'll see how well that works.

    I originally wrote the program in C# because I thought I would need one of the fancy collection classes provided by the BCL, but it turns out that I didn't need anything fancier than a hash table. After I wrote the original C# version, I translated it to JavaScript, which is what I present here.

    The inputs which correspond to the diagram above are

    • Initial X position = 6, Initial Y position = 12
    • Obstacle: Left = 3, Right = 7, height = 9
    • Obstacle: Left = 1, Right = 5, height = 6
    • Obstacle: Left = 4, Right = 8, height = 3

    And here's the program.

    function Obstacle(left, right, y) {
     this.left = left;
     this.right = right;
     this.y = y;
    }
    
    // A single step in a path, representing the cost to reach that point.
    function Step(x, y, cost) {
     this.x = x;
     this.y = y;
     this.cost = cost;
    }
    
     // Add a step to an existing step
    Step.prototype.to = function to(x, y) {
     var dx = Math.abs(this.x - x);
     var dy = Math.abs(this.y - y);
     return new Step(x, y, this.cost + dx + dy);
    }
    
    // Record a droplet position
    function addDroplet(l, step) {
     // If no previous droplet at this position or the new droplet
     // has a cheaper path, then remember this droplet.
     var existingStep = l[step.x];
     if (!existingStep || step.cost < existingStep.cost) {
      l[step.x] = step;
     }
    }
    
    // Take an existing collection of locations and updates them to account
    // for a new obstacle. Obstacles must be added in decreasing altitude.
    // (Consecutive duplicate altitudes allowed.)
    function fallTo(oldLocations, obstacle) {
     var newLocations = {};
     for (var x in oldLocations) {
      var step = oldLocations[x];
    
      // fall to the obstacle's altitude
      step = step.to(step.x, obstacle.y);
        
      // If the falling object does not hit the obstacle,
      // then there is no horizontal displacement.
      if (step.x <= obstacle.left || step.x >= obstacle.right) {
       addDroplet(newLocations, step);
      } else {
       // The falling object hit the obstacle.
       // Split into two droplets, one that goes left
       // and one that goes right.
       addDroplet(newLocations, step.to(obstacle.left, obstacle.y));
       addDroplet(newLocations, step.to(obstacle.right, obstacle.y));
      }
     }
     return newLocations;
    }
    
    function printStep(step) {
     console.log("Cost = " + step.cost + ": " + step.x + "," + step.y);
    }
    
    // Debugging function
    function printLocations(l) {
     for (var x in l) printStep(l[x]);
    }
    
    function shortestPath(x, y, obstacles) {
     var l = {};
     l[x] = new Step(x, y, 0);
     printLocations(l);
    
     obstacles.forEach(function (obstacle) {
      l = fallTo(l, obstacle);
      console.log(["after", obstacle.left, obstacle.right, obstacle.y].join(" "));
      printLocations(l);
      console.log("===");
     });
    
     // Find the cheapest step.
     var best;
     for (x in l) {
      if (!best || l[x].cost < best.cost) best = l[x];
     }
    
     // Fall to the floor and print the result.
     printStep(best.to(best.x, 0));
    }
    
    shortestPath(6,12,[new Obstacle(3,7,9),
                       new Obstacle(1,5,6),
                       new Obstacle(4,8,3)]);
    

    This program finds the cost of the cheapest path to the floor, but it merely tells you the cost and not how the cost was determined. To include the winning path, we need to record the history of how the cost was determined. This is a standard technique in dynamic programming: In addition to remembering the best solution so far, you also remember how that solution was arrived at by remembering the previous step in the solution. You can then walk backward through all the previous steps to recover the full path.

    // A single step in a path, representing the cost to reach that point
    // and the previous step in the path.
    function Step(x, y, cost, previous) {
     this.x = x;
     this.y = y;
     this.cost = cost;
     this.previous = previous;
    }
    
     // Add a step to an existing step
    Step.prototype.to = function to(x, y) {
     var dx = Math.abs(this.x - x);
     var dy = Math.abs(this.y - y);
     // These next two test are not strictly necessary. They are for style points.
     if (dx == 0 && dy == 0) {
      // no movement
      return this;
     } else if (dx == 0 && this.previous && this.previous.x == x) {
      // collapse consecutive vertical movements into one
      return new Step(x, y, this.cost + dx + dy, this.previous);
     } else {
      return new Step(x, y, this.cost + dx + dy, this);
     }
    }
    
    function printStep(firstStep) {
     // Walk the path backwards, then reverse it so we can print
     // the results forward.
     var path = [];
     for (var step = firstStep; step; step = step.previous) {
      path.push("(" + step.x + "," + step.y + ")");
     }
     path.reverse();
     console.log("Cost = " + firstStep.cost + ": " + path.join(" "));
    }
    

    Notice that we didn't change any of the program logic. All we did was improve our record-keeping so that the final result prints the full path from the starting point to the ending point.

  • The Old New Thing

    How do I obtain the computer manufacturer's name from C++?

    • 14 Comments

    Some time ago, I gave a scripting solution to the problem of obtaining the computer manufacturer and model. But what if you want to do this from C++?

    I could translate the script into C++, or I could just point you to Creating a WMI Application Using C++ in MSDN. In particular, one of the WMI C++ Sample Applications does exactly what you want: Example: Creating a WMI Application. The only things you need to do are

    • change SELECT * FROM Win32_Process to SELECT * FROM Win32_ComputerSystem, and
    • change Name to Manufacturer, and then again to Model.
  • The Old New Thing

    When I send a WM_GETFONT message to a window, why don't I get a font?

    • 12 Comments

    A customer reported that the WM_GET­FONT message was not working. Specifically, they sent the message to a window, and they can plainly see that the window is rendering with a particular font, yet the WM_GET­FONT message returns 0. Why isn't the window returning the correct font handle?

    The WM_SET­FONT and WM_GET­FONT messages are not mandatory. A window may choose to support them, or it may choose not to, or it may even choose to support one but not the other. (Though if it supports WM_SET­FONT, it probably ought to support WM_GET­FONT.)

    For example, our scroll bar program creates a custom font for the items in the list, but it does not implement the WM_SET­FONT or WM_GET­FONT messages. If you try to change the font via WM_SET­FONT, nothing happens. If you ask for the font via WM_GET­FONT, you get nothing back.

    A control might ignore your attempt to change the font if it already has its own notion of what font it should be using. Or maybe the control shows content in multiple fonts, so the concept of "the" font does not map well to the render model. (What would WM_GET­FONT on an HTML control return?) Or maybe the control doesn't use GDI fonts at all. (Maybe it uses Direct­Write.)

    That's one of the reasons why the rules for the WM_SET­FONT are set up the way they are. Since there is no way to tell whether a window did anything in response to the WM_SET­FONT message, there would be no way to know whether responsibility for destroying the font should be transferred to the control or retained by the caller.

    Controls that are designed to be used in dialog boxes are the ones most likely to support the WM_SET­FONT message, since that's the message the dialog manager uses to tell each control the font specified in the dialog box template. The hope is that all of the controls will respect that font, so that the controls on the dialog box have a consistent appearance. But there's nothing preventing a control from saying, "Screw you. I'm drawing with OCR-A and there's nothing you can do to stop me."

Page 3 of 430 (4,291 items) 12345»