parent
38c36fa048
commit
d4f114adcc
1 changed files with 184 additions and 0 deletions
@ -0,0 +1,184 @@ |
|||||||
|
Lessons learned about how to make a header-file library |
||||||
|
V1.0 |
||||||
|
September 2013 Sean Barrett |
||||||
|
|
||||||
|
Things to do in an stb-style header-file library, |
||||||
|
and rationales: |
||||||
|
|
||||||
|
|
||||||
|
1. #define LIBRARYNAME_IMPLEMENTATION |
||||||
|
|
||||||
|
Use a symbol like the above to control creating |
||||||
|
the implementation. (I used a far-less-clear name |
||||||
|
in my first header-file library; it became |
||||||
|
clear that was a mistake once I had multiple |
||||||
|
libraries.) |
||||||
|
|
||||||
|
Include a "header-file" section with header-file |
||||||
|
guards and declarations for all the functions, |
||||||
|
but don't guard the implementation. That way, |
||||||
|
if client's header file X includes your header file for |
||||||
|
declarations, they can still include header file X |
||||||
|
in the source file that creates the implementation; |
||||||
|
if you guard the implementation too, then the first |
||||||
|
include (before the #define) creates the declarations, |
||||||
|
and the second one (after the #define) does nothing. |
||||||
|
|
||||||
|
|
||||||
|
2. AVOID DEPENDENCIES |
||||||
|
|
||||||
|
Don't rely on anything other than the C standard libraries. |
||||||
|
|
||||||
|
(If you're creating a library specifically to leverage/wrap |
||||||
|
some other library, then obviously you can rely on that |
||||||
|
library. But if that library is public domain, you might |
||||||
|
be better off directly embedding the source, to reduce |
||||||
|
dependencies for your clients. But of course now you have |
||||||
|
to update whenever that library updates.) |
||||||
|
|
||||||
|
If you use stdlib, consider wrapping all stdlib calls in |
||||||
|
macros, and then conditionally define those macros to the |
||||||
|
stdlib function, allowing the user to replace them. |
||||||
|
|
||||||
|
For functions with side effects, like memory allocations, |
||||||
|
consider letting the user pass in a context and pass |
||||||
|
that in to the macros. (The stdlib versions will ignore |
||||||
|
the parameter.) Otherwise, users may have to use global |
||||||
|
or thread-local variables to achieve the same effect. |
||||||
|
|
||||||
|
|
||||||
|
3. AVOID MALLOC |
||||||
|
|
||||||
|
You can't always do this, but when you can, embedded developers |
||||||
|
will appreciate it. I almost never bother avoiding, as it's |
||||||
|
too much work (and in some cases is pretty infeasible; |
||||||
|
see http://nothings.org/gamedev/font_rendering_malloc.txt ). |
||||||
|
But it's definitely something one of the things I've gotten |
||||||
|
the most pushback on from potential users. |
||||||
|
|
||||||
|
|
||||||
|
4. ALLOW STATIC IMPLEMENTATION |
||||||
|
|
||||||
|
Have a #define which makes function declarations and |
||||||
|
function definitions static. This makes the implementation |
||||||
|
private to the source file that creates it. This allows |
||||||
|
people to use your library multiple times in their project |
||||||
|
without collision. (This is only necessary if your library |
||||||
|
has configuration macros or global state, or if your |
||||||
|
library has multiple versions that are not backwards |
||||||
|
compatible. I've run into both of those cases.) |
||||||
|
|
||||||
|
|
||||||
|
5. MAKE ACCESSIBLE FROM C |
||||||
|
|
||||||
|
Making your code accessible from C instead of C++ (i.e. |
||||||
|
either coding in C, or using extern "C") makes it more |
||||||
|
straightforward to be used in C and in other languages, |
||||||
|
which often only have support for C bindings, not C++. |
||||||
|
(One of the earliest results I found in googling for |
||||||
|
stb_image was a Haskell wrapper.) Otherwise, people |
||||||
|
have to wrap it in another set of function calls, and |
||||||
|
the whole point here is to make it convenient for people |
||||||
|
to use, isn't it? (See below.) |
||||||
|
|
||||||
|
I prefer to code entirely in C, so the source file that |
||||||
|
instantiates the implementation can be C itself, for |
||||||
|
those crazy people out there who are programming in C. |
||||||
|
But it's probably not a big hardship for a C programmer |
||||||
|
to create a single C++ source file to instantiate your |
||||||
|
library. |
||||||
|
|
||||||
|
|
||||||
|
6. NAMESPACE PRIVATE FUNCTIONS |
||||||
|
|
||||||
|
Try to avoid having names in your source code that |
||||||
|
will cause conflicts with identical names in client |
||||||
|
code. You can do this either by namespacing in C++, |
||||||
|
or prefixing with your library name in C. |
||||||
|
|
||||||
|
In C, generally, I use the same prefix for API |
||||||
|
functions and private symbols, such as "stbtt_" |
||||||
|
for stb_truetype; but private functions (and |
||||||
|
static globals) use a second underscore as |
||||||
|
in "stbtt__" to further minimize the chance of |
||||||
|
additional collisions in the unlikely but not |
||||||
|
impossible event that users write wrapper |
||||||
|
functions that have names of the form "stbtt_". |
||||||
|
(Consider the user that has used "stbtt_foo" |
||||||
|
*successfully*, and then upgrades to a new |
||||||
|
version of your library which has a new private |
||||||
|
function named either "stbtt_foo" or "stbtt__foo".) |
||||||
|
|
||||||
|
Note that the double-underscore is reserved for |
||||||
|
use by the compiler, but (1) there is nothing |
||||||
|
reserved for "middleware", i.e. libraries |
||||||
|
desiring to avoid conflicts with user symbols |
||||||
|
have no other good options, and (2) in practice |
||||||
|
no compilers use double-underscore in the middle |
||||||
|
rather than the beginning/end. (Unfortunately, |
||||||
|
there is at least one videogame-console compiler that |
||||||
|
will warn about double-underscores by default.) |
||||||
|
|
||||||
|
|
||||||
|
7. EASY-TO-COMPLY LICENSE |
||||||
|
|
||||||
|
I make my libraries public domain. You don't have to. |
||||||
|
But my goal in releasing stb-style libraries is to |
||||||
|
reduce friction for potential users as much as |
||||||
|
possible. That means: |
||||||
|
|
||||||
|
a. easy to build (what this file is mostly about) |
||||||
|
b. easy to invoke (which requires good API design) |
||||||
|
c. easy to deploy (which is about licensing) |
||||||
|
|
||||||
|
I choose to place all my libraries in the public |
||||||
|
domain, abjuring copyright, rather than license |
||||||
|
the libraries. This has some benefits and some |
||||||
|
drawbacks. |
||||||
|
|
||||||
|
Any license which is "viral" to modifications |
||||||
|
causes worries for lawyers, even if their programmers |
||||||
|
aren't modifying it. |
||||||
|
|
||||||
|
Any license which requires crediting in documentation |
||||||
|
adds friction which can add up. Valve used to have |
||||||
|
a page with a list of all of these on their web site, |
||||||
|
and it was insane, and obviously nobody ever looked |
||||||
|
at it so why would you care whether your credit appeared |
||||||
|
there? |
||||||
|
|
||||||
|
Permissive licenses like zlib and BSD license are |
||||||
|
perfectly reasonable, but they are very wordy and |
||||||
|
have only two benefits over public domain: legally-mandated |
||||||
|
attribution and liability-control. I do not believe these |
||||||
|
are worth the excessive verbosity and user-unfriendliness |
||||||
|
these licenses induce, especially in the single-file |
||||||
|
case where those licenses tend to be at the top of |
||||||
|
the file, the first thing you see. (To the specific |
||||||
|
points, I have had no trouble receiving attribution |
||||||
|
for my libraries; liability in the face of no explicit |
||||||
|
disclaimer of liability is an open question.) |
||||||
|
|
||||||
|
However, public domain has frictions of its own, because |
||||||
|
public domain declarations aren't necessary recognized |
||||||
|
in the USA and some other locations. For that reason, |
||||||
|
I recommend a declaration along these lines: |
||||||
|
|
||||||
|
// This software is in the public domain. Where that dedication is not |
||||||
|
// recognized, you are granted a perpetual, irrevocable license to copy |
||||||
|
// and modify this file as you see fit. |
||||||
|
|
||||||
|
I typically place this declaration at the end of the initial |
||||||
|
comment block of the file and just say 'public domain' |
||||||
|
at the top. |
||||||
|
|
||||||
|
I have had people say they couldn't use one of my |
||||||
|
libraries because it was only "public domain" and didn't |
||||||
|
have the additional fallback clause, who asked if |
||||||
|
I could dual-license it under a traditional license. |
||||||
|
|
||||||
|
My answer: they can create a derivative work by |
||||||
|
modifying one character, and then license that however |
||||||
|
they like. (Indeed, *adding* the zlib or BSD license |
||||||
|
would be such a modification!) Unfortunately, their |
||||||
|
lawyers reportedly didn't like that answer. :( |
Loading…
Reference in New Issue