182 lines
6.5 KiB
HTML
182 lines
6.5 KiB
HTML
|
<html>
|
||
|
<header>
|
||
|
<title>Maintaining Binary Compatibility</title>
|
||
|
</header>
|
||
|
<body>
|
||
|
<h1>Binary Compatibility in 3 Easy Steps!<hr></h1>
|
||
|
|
||
|
<h2>An Introduction</h2>
|
||
|
In the early days of the OpenBeOS project, a debate raged concerning one of the projects
|
||
|
primary goals: maintaining binary compatibility with BeOS R5. The idea was that the
|
||
|
only way an effort to rewrite BeOS would be successful was if folks could continue
|
||
|
running the apps they already had. Certainly, a lot of software available for BeOS is
|
||
|
open source or actively maintained -- these apps could just be recompiled if necessary.
|
||
|
Others -- PostMaster, gobe's Productive suite and a few other crucial apps -- weren't
|
||
|
likely to get rebuilt, either because the original author had stopped maintainance
|
||
|
without being kind enough to release the source, or because it just wouldn't be
|
||
|
commercially feasible.<p>
|
||
|
Some said that we were crazy; that it couldn't be done. Thankfully, cooler heads
|
||
|
prevailed and we're well on our way to a binary compatible clone of R5.<p>
|
||
|
|
||
|
"But wait!" you cry. "How did the cooler heads which prevailed know that the holy grail
|
||
|
of binary compatibility was achievable?" I'm so glad you asked! Keeping reading and be
|
||
|
enlightened, Grasshopper.<p>
|
||
|
|
||
|
<h2>The Issues</h2>
|
||
|
There are three basic issues that have to be addressed to ensure binary compatibility:
|
||
|
<ul>
|
||
|
<li>
|
||
|
<b>Names must be identical</b><br>
|
||
|
This includes class and structure names as well as public, protected and global
|
||
|
function and variable names.
|
||
|
</li>
|
||
|
<li>
|
||
|
<b>Object sizes must be identical</b><br>
|
||
|
Classes must contain the same number of bytes of data; global variables must
|
||
|
be the same size. Maybe BGlobalVar should've been an <code>int32</code>
|
||
|
instead of an <code>int16</code>, but we're stuck with it now.
|
||
|
</li>
|
||
|
<li>
|
||
|
<b>Virtual function table layout must be identical</b><br>
|
||
|
The most cryptic and confusing aspect of maintaining binary compatibility.
|
||
|
The issue essentially boils down to this: for any given class, there must
|
||
|
be the same number of virtual functions, declared in the same order as the
|
||
|
original.
|
||
|
</li>
|
||
|
</ul>
|
||
|
|
||
|
<h2>The Nitty Gritty</h2>
|
||
|
"Good grief!" you say. "How on earth do I keep all this stuff straight?" Ah,
|
||
|
Grasshopper, it is easier that you might imagine. Just follow these steps, and you
|
||
|
should be binary compatible in no time!
|
||
|
<ol>
|
||
|
<li>
|
||
|
<b>Make a copy of the appropriate Be header file</b><br>
|
||
|
This is now your header file. You may need to change a thing or two, but what
|
||
|
you can (or will need to) change is quite limited, and discussed below.
|
||
|
</li>
|
||
|
<li>
|
||
|
<b>Implement public, protected and virtual functions</b><br>
|
||
|
In the course of doing this, you may discover that there are some private
|
||
|
non-virtual function declarations that you just don't use. Feel free to axe
|
||
|
them! Since they're private, nobody using the class will miss them, and
|
||
|
because they're not virtual, they don't effect the vtable layout. Conversely,
|
||
|
if you find a need to add some private, non-virtual functions, go right ahead
|
||
|
(for the very same reasons).
|
||
|
</li>
|
||
|
<li>
|
||
|
<b>Make sure you don't change the number of bytes used for data</b><br>
|
||
|
There are two situations that can make this seem difficult. First, there
|
||
|
may be data members that you don't use in your reimplementation. You can
|
||
|
just leave them (safe, but a little messy) or you can add the extra members'
|
||
|
bytes to the class's "unused" data array. An example will make this clear.<br>
|
||
|
Let's say we have a class BFoo:<br>
|
||
|
<code>
|
||
|
<pre>
|
||
|
class BFoo {
|
||
|
public:
|
||
|
BFoo();
|
||
|
void SomeFunc();
|
||
|
private:
|
||
|
int32 fBar;
|
||
|
char fZig;
|
||
|
int32 fQux;
|
||
|
int32 fUnused[2];
|
||
|
};
|
||
|
</pre>
|
||
|
</code>
|
||
|
The Be engineers that originally wrote this BFoo purposely added some data
|
||
|
padding in the form of an array of 2 <code>int32</code>s (they did this with
|
||
|
most classes in the API). Now let's suppose in your implementation, you
|
||
|
really didn't need <code>fQux</code>. You can add <code>fQux</code>'s bytes
|
||
|
into <code>fUnused</code>:<br>
|
||
|
<code>
|
||
|
<pre>
|
||
|
class BFoo
|
||
|
{
|
||
|
...
|
||
|
private:
|
||
|
int32 fBar;
|
||
|
char fZig;
|
||
|
int32 fUnused[3];
|
||
|
};
|
||
|
</pre>
|
||
|
</code>
|
||
|
Nothing could be easier! An additional twist that should be noted is that
|
||
|
data member order must also be preserved. In much the same way as existing
|
||
|
apps will "look" for virtual functions in a specific place in the vtable,
|
||
|
so will they "look" for data members in a specific place in an object.<br>
|
||
|
"But what if I don't need <code>fZig</code>, either?" you wonder. "It's only
|
||
|
one byte, not four like an <code>int32</code>!" Have no fear! Just rename it
|
||
|
"<code>fUnusedChar</code>" and be done with it.<p>
|
||
|
|
||
|
The second situation that can make preserving object size tricky is if there
|
||
|
aren't <i>enough</i> bytes of data available. Building on our cheesy BFoo
|
||
|
example, let's suppose that rather than getting rid of <code>fQux</code> and
|
||
|
<code>fZig</code>, you actually needed to <i>add</i> another 4
|
||
|
<code>int32</code>s worth of data: <code>fNewData1</code> through
|
||
|
<code>fNewData4</code>. The original implementation of BFoo has two extra
|
||
|
<code>int32</code>s which we can use, but that leaves us two <code>int32</code>s
|
||
|
short. What to do? The easiest thing to do is create a data structure to hold
|
||
|
your new data and convert one of the <code>fUnused</code> items into a pointer
|
||
|
to that structure:
|
||
|
<code>
|
||
|
<pre>
|
||
|
// Foo.h
|
||
|
struct _BFooData_;
|
||
|
class BFoo
|
||
|
{
|
||
|
public:
|
||
|
BFoo();
|
||
|
~BFoo();
|
||
|
void SomeFunc();
|
||
|
private:
|
||
|
int32 fBar;
|
||
|
char fZig;
|
||
|
_BFooData_* fNewData;
|
||
|
int32 fUnused[1];
|
||
|
};
|
||
|
|
||
|
// Foo.cpp
|
||
|
struct _BFooData_
|
||
|
{
|
||
|
int32 fNewData1;
|
||
|
int32 fNewData2;
|
||
|
int32 fNewData3;
|
||
|
int32 fNewData4;
|
||
|
};
|
||
|
BFoo::BFoo()
|
||
|
{
|
||
|
fNewData = new _BFooData_;
|
||
|
}
|
||
|
BFoo::~BFoo()
|
||
|
{
|
||
|
delete fNewData;
|
||
|
}
|
||
|
</pre>
|
||
|
</code>
|
||
|
Voila! More data without making the class bigger. Notice the added
|
||
|
destructor; make sure you're cleaning up your new (dynamically allocated) data.
|
||
|
</li>
|
||
|
</ol>
|
||
|
|
||
|
And there you have it: Binary Compatibility in 3 Easy Steps!<br>
|
||
|
Questions, comments, or corrections? Please let
|
||
|
<a href="mailto:erik@cgsoftware.com">me</a> know!<br>
|
||
|
<hr>
|
||
|
|
||
|
<!-- The obligatory SourceForge plug -->
|
||
|
<center>
|
||
|
<small>The OpenBeOS project is hosted by:</small><br><br>
|
||
|
<a href="http://sourceforge.net">
|
||
|
<img src="http://sourceforge.net/sflogo.php?group_id=33869&type=1" width="88" height="31" border="0" alt="SourceForge Logo">
|
||
|
</a>
|
||
|
<p>
|
||
|
|
||
|
<small>Copyright © 2001-2002
|
||
|
<a href="http://www.openbeos.org">OpenBeOS</a> Project</small>
|
||
|
</center>
|
||
|
|
||
|
</body>
|
||
|
</html>
|