The beauty of generic header-names
IMO one of the standout pieces of innovation in mulle-objc, is the pervasive use of and automatic generation of headers with generic names. I think it’s easiest to show by example, what I mean with “generic header-names” and how to achieve the ideal header flow with them.
The generic header-names concept is applicable to C and C++ as well, and is independent from mulle-objc.
Lets write an Objective-C library “my-symbol-lib”, that is supposed
to lookup C symbols at runtime. It should provide a function
NSStringFromSymbol
that returns nil, if the symbol isn’t
defined, and the symbol, if it is.
A first attempt looks like this:
NSStringFromSymbol.m
:
#import <Foundation/Foundation.h> // header for NSString
#include <dlfcn.h> // header for dlsym
NSString *NSStringFromSymbol( NSString *s)
{
if( ! s)
return( nil);
if( ! dlsym( RTLD_DEFAULT, [s UTF8String]))
return( nil);
return( s);
}
This isn’t really good enough yet for a library, because we also need a header file. So split it up into:
NSStringFromSymbol.h
:
#import <Foundation/Foundation.h> // header for NSString
NSString *NSStringFromSymbol( NSString *s);
NSStringFromSymbol.m
:
#import "NSStringFromSymbol.h"
#include <dlfcn.h> // header for dlsym
NSString *NSStringFromSymbol( NSString *s)
{
if( ! s)
return( nil);
if( ! dlsym( RTLD_DEFAULT, [s UTF8String]))
return( nil);
return( s);
}
One important design decision at this point is, if <dlcfn.h>
should
be exposed in the header.
The downside of having <dlfcn.h>
in the header
are:
- slower compilation times for your header consumers
- symbol pollution for auto-complete
- possibility of avoidable global symbol conflicts
- header consumers may start to assume
<dlfcn.h>
’s presence although this is more or less an implementation detail
The upsides are:
- it saves effort by header consumers that may need symbols from
<dlfcn.h>
Sometimes it is unavoidable, if you need to expose types, symbols or constants that are defined by a foreign header and that are exposed by your own method and variable declarations. But this really ties your class tightly to the dependent library.
Beautification
Now here comes the beauty part. Instead of Foundation/Foundation.h
and
dlfcn.h
lets use two generic header-names import.h
and
import-private.h
. import-private.h
is never to be used in any
header but only in implementations.
NSStringFromSymbol.h
:
#import "import.h"
NSString *NSStringFromSymbol( NSString *s);
NSStringFromSymbol.m
:
#import "NSStringFromSymbol.h"
#import "import-private.h"
NSString *NSStringFromSymbol( NSString *s)
{
if( ! s)
return( nil);
if( ! dlsym( RTLD_DEFAULT, [s UTF8String]))
return( nil);
return( s);
}
and
import.h
:
#import <Foundation/Foundation.h>
import-private.h
:
#import "import.h"
#include <dlfcn.h>
Lets also create a my-symbol-lib.h
as the main include header to expose the API of this library.
my-symbol-lib.h
:
#import "NSStringFromSymbol.h"
The required cross-platform headers
Foundation/Foundation.h
and NSStringFromSymbol.h
are “streamed” to
the API consumer, whereas the platform specific header dlfcn.h
stays hidden:
Advantages
Source motility
This is probably the major advantage for me. As the names
of the headers are fixed, header and implementations files are
now uniform in that regard and can be easily moved between
libraries and executables.
The “host” must provide the “glue” in import.h
and import-private.h
so that the sources have the required environment available.
One place for OS specifica
Assume our library has more than one implementation file, it is now
easier to edit the one file import-private.h
than many implementation files.
e.g.
#ifdef _WIN32
# include <whatever.h>
#else
# include <dlfcn.h>
#endif
Another effect of this is that the source files themselves are more
readable and not littered with #ifdefs
. I believe most of the
arguments for single-file libraries become moot, when using generic header-names.
Code generation opportunity
As dependencies to headers outside of the library are contained in two files, it is much easier to use code-generation tools to manage the dependencies of a library. mulle-sde does just that.
Isn’t this the same as a pre-compiled header / <stdafx.h>
?
Its similar, but its not the same. First the clear separation between
import.h
and import-private.h
is not reflected in a pre-compiled
header. Second, you usually do not export a pre-compiled header to your
API customers but you do so with import.h
. Third pre-compiled
headers that are unconditionally read on each compile are a
non-portable hack.
But why are there so many more headers in mulle-objc ?
For libraries I generally like to be able to publish a C-interface
to be used by C-only consumers. So there also exist include.h
and include-private.h
.
This will change in the next release, where you have to opt-in for C-header files for ObjC projects. That reduces the header count by 50% for most projects
For the auto-generation of header code it is generally easier, to not mix user edits and generated code in one file. Additional underscored headers are generated by the mulle-sde tools and included by the “human” editable files.
None of these files are really necessary. You can disable all or some of them with environment variables. See mulle-sde for more information on that.
Post a comment
All comments are held for moderation; basic HTML formatting accepted.