This is an example of how to design a library for both static and shared linkage.
The visibility markings here proposed are mostly useful when compiling executables or dynamically linked libraries. Nonetheless, mechanisms are provided to reuse the framework for static linkage as well.
Markers are provided for both functions and variables, in three different visibility scopes:
- Public: Symbols marked with
app_APIorapp_VARwill be exported to the final binary object, and will be accessed by external libraries and applications. It is advised for the library to put symbols marked as public in the main (public) headers. - Internal: Symbols marked with
app_IAPIorapp_IVARwill have internal scope. In other words, it is expected that the symbol will be only be visible across the translation units that comprise the library (e.g. inter-object file visibility). - Static: Symbols marked with
app_SAPIorapp_SVARwill have static scope. Just as in the case of a symbol marked with thestaticstorage qualifier, if a symbol is marked as having static scope (withapp_SAPIorapp_SVAR) then the library implementation must guarantee that all these symbols are bundled inside the same translation unit (e.g. by creating an amalgamated source code file from other files).
The app_*API markers are to be used in function declarations, whereas the app_*VAR markers are to be used in variable declarations. When defining, the markers ending with _IMPL should be used in case the definition is done separate of its declaration. In case declaration and definition happen at the same time, only the app_*API or app_*VAR is needed.
When compiling the library, the macro app_BUILDING must be defined for the marking mechanism to work properly.
If the library is a static library, the macro app_STATICLIB must be defined. Else, if the library is a shared library, the macro app_SHAREDLIB must be defined. If not provided, the header will default to app_STATICLIB. Note that the presence of one definition excludes the other possibility; therefore, defining both macros is an error, and the library's header will detect this condition and generate a compilation error.
app_API void public_function();
app_IAPI void internal_function();
app_SAPI void static_function();app_API_IMPL void public_function() {
// ...
}
app_IAPI_IMPL void internal_function() {
// ...
}
app_SAPI_IMPL void static_function() {
// ...
}Optionally (but not truly "portable"),
void public_function() {
// ...
}
void internal_function() {
// ...
}
void static_function() {
// ...
}app_API void public_function() {
// ...
}
app_IAPI void internal_function() {
// ...
}
app_SAPI void static_function() {
// ...
}app_VAR int public_variable;
app_IVAR int internal_variable;
app_SVAR int static_variable;app_VAR_IMPL int public_variable = 0;
app_IVAR_IMPL int internal_variable = 0;
app_SVAR_IMPL int static_variable = 0;Optionally (but not truly "portable"),
int public_variable = 0;
int internal_variable = 0;
int static_variable = 0;app_VAR int public_variable = 0;
app_IVAR int internal_variable = 0;
app_SVAR int static_variable = 0;(Only for C++, yet to be done)
class app_CLASS PublicClass;
class app_ICLASS InternalClass;class app_CLASS_IMPL PublicClass {
// ...
}
class app_ICLASS_IMPL InternalClass {
// ...
}Optionally (but not truly "portable"),
class PublicClass {
// ...
}
class InternalClass {
// ...
}class app_CLASS PublicClass {
// ...
}
class app_ICLASS InternalClass {
// ...
}There is a different set of markers for each possibly different symbol type. This is done as such because some markers may not be applied to specific language elements (e.g. applying register to functions, applying __declspec(noreturn) to variables), and it allows for the library designer to customize the markers while retaining their usage cases (avoiding possible restrictions).
The app_*_IMPL markers take care of specific cases where a marker needs to be present in both declaration and definition, in case declaration and definition do not happen in the same place. In the majority of the cases, it is not needed, but it is there just to safeguard the library designer that follows its use case.
Note that, as per the Standards, we have storage qualifiers, that states the scope and storage (and indirectly, visibility) of symbols. Here is a list of the ones relevant to the discussion:
externdefines a symbol that is referenced in a translation unit but is defined (e.g. implemented, assigned) elsewhere. It means that its data or code is stored somewhere else, but it knows that the symbol exists; it is up to the linker to find that symbol.staticdefines a symbol that is stored and visible only to the current translation unit.- The symbol may also not be marked with neither of those storage classes.
Based on that, it is safe to assume that the only standard way to prevent a symbol from being referenced outside the translation unit that defines it is by declaring it with static storage. Static libraries will have all of the symbols defined in the translation units it is comprised of, due to the nature of static linkage itself.
Some targets and binary formats demand some kind of marking to achieve the desired visibility. For instance, when compiling DLLs, the default is for only symbols marked as __declspec(dllexport) to be externally accessible from the final DLL; conversely, __declspec(dllimport) is used to mark symbols that are to be provided from other DLLs (although this may not be needed in some cases). What this example library does is to provide a mechanism to automate the annotation of declarations (and definitions) with the correct qualifiers for each build stage (compilation, linkage, etc) and binary type (static or dynamic library).
The Internal visibility that is showcased in this proposal is based solely on compiler extensions and implementation details (e.g. in GCC, __attribute__((visibility("hidden")) is used). In the eventual case where the compiler does not have the functionality needed to implement the internal scope, all the symbols will appear in the final binary, just as with the public scope.
Unlike static storage, there is no namespace difference between both scopes. Therefore, it is advisable that the library designer exercises prudence and avoid declaring symbols with the same name publicly and internally. One possibility is by using an internal symbol's naming convention different from the one being used for public ones (e.g. appending _ at the end of the name, as in app_some_function_).
External libraries and applications are not expected to reference internal symbols directly, but in the end this possibility depends mostly on the library's developer excluding that possibility, by declaring such symbols in another unaccessible header (e.g. an internal header, referenced only by the library's source files).
Also worth mentioning is the fact that some platforms and binary formats do not support visibility at all, even if the compiler toolkit does offer a way to inform the desired visibility.