Are You and Your Date Binary Compatible?
Many exciting things have been happening since we shipped Office 2007, but most have been "hush hush". As in "Chris, don't tell your friends you're working on TOTAL AWESOME FEATURE X as you will make them jealous."
But not all my time is spent on cool new features. Some of it is spent on servicing Office 2007 and previous releases. This can be in the form of service pack work, security fixes or "hot fix" requests. These types of fixes have their own set of neat technical challenges because they have to be fixed after we’ve shipped the product.
The biggest technical hurdle I’ve found is “binary compatibility”. When you make a fix after shipping, sometimes that fix will occur in more than one EXE or DLL (referred to as a “binary”). And the kicker is: patching does not update all binaries in the product at once.
Really, who wants to download all of Office for every single patch? The answer is no one, so patches usually contain a minimum amount of changes.
Also, during the course of the Office life cycle, there are a number of patches that go out and all of these patches are built from the same source tree (otherwise you could install a patch, but the next patch would uninstall it). So, since we don’t ship updates to every file in Office for every patch and it’s entirely likely that some customers will apply some patches and not others we have to be careful how we change the code to not break when only part of a patch is installed.
Take this for example:
|
|
Application.EXE |
Library.DLL |
|
Update #1 |
Patched! |
Patched! |
|
Update #2 |
Patched! |
(No Change) |
Installing Update #2 will also include the code for Update #1, but only in Application.EXE. Library.DLL remains with the same code as it did before.
The jist of the constraint is: don’t make a code change such that another binary would not function if it does not have the change.
Things that break binary compatibility and how to address them:
Adding a new function to a DLL and calling it without checking for its existence in another DLL or EXE.
If you hardcode a call to an exported function from a DLL and the function isn’t there, it's dump town for you (crash dump that is).
The fix!
Export your new function by name and use GetProcAddress to check for its existence first. Also make sure your code is fail safe in all binaries. Meaning, if the function doesn’t exist, the product behaves reasonably. Usually this means falling back to the behavior that was present before. If instead you fail with “Function BLAHBLAH123@S@Z cannot be found!” after installing a patch, you haven’t fully addressed the problem.
Adding new values in the middle of a shared enumeration.
enum Versions
{
OfficeXP,
Office2003,
Office2003_SP2, // < Just added!
Office2007
}
This causes drift in their values between patched and unpatched binaries. Passing Office2003_SP2 to an unpatched binary will be interpreted as Office2007
The fix!
enum Versions
{
OfficeXP,
Office2003,
Office2007,
Office2003_SP2 // < Just added!
}
Add stuff at the end. But note that the unpatched binary must be able to handle an enumeration value greater than what it was originally compiled with.
Adding or subtracting member variables from a shared class.
class Handler
{
...
public:
int GetNewValue() const { return newValue; } //< Just added
private:
int value1;
int value2;
int newValue; //< Just added
};
This changes the size of the class between the binaries. Note that here the accessor GetNewValue is defined with the class, meaning it’s likely to get inlined. Creating the class in an unpatched binary then and accessing the new value in a patched binary will at best cause Access Violations and at worst cause undefined app behavior as you read or write to memory you don’t own.
You can also leak memory if you add a new complex member variable (something that holds memory and has a destructor), create in the patched binary and destroy in the unpatched binary, the destructor for the new member may not be called.
The Fix
This one is tough, there is no straight forward way to address this. You can try and have a global map where you map pointers to your class with new data you want to add, but this isn’t ideal and is not always thread safe. You can try and store the data on whatever object owns this one, assuming that object relies privately in one binary.
The best approach is to plan for this ahead of time. You could choose to not define functions in the header and instead export them from the DLL. Or you if you’re willing to sacrifice a little memory, add a dummy void* to the end, then use this to store the extra data if you need it post shipping.
In The Real World
These may seem like edge cases and in a way they are. Most fixes are changes to logic in a function, not sweeping changes across binaries. But some things I fix do have this scope. One thing that I wrote is the new ability to ungroup SmartArt in PowerPoint coming your way in Office 2007 SP2 (announced here). But much of the SmartArt logic is shared by several Office apps and is not present in the PowerPoint executable. So I had to deal with the same issues mentioned above and not make blind calls to new DLL functions. This way in case you get the SmartArt bits and not the PowerPoint bits, PowerPoint and SmartArt will still behave as they did before.