Skip to content

Instantly share code, notes, and snippets.

@flisboac
Last active August 9, 2016 01:43
Show Gist options
  • Save flisboac/b5cc3ea957231bf01700c49b87002367 to your computer and use it in GitHub Desktop.
Save flisboac/b5cc3ea957231bf01700c49b87002367 to your computer and use it in GitHub Desktop.
C API Visibility
An example and framework on C/C++ API design and symbol visibility.

(Dynamic) Shared Library example

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.

Usage

Markers are provided for both functions and variables, in three different visibility scopes:

  • Public: Symbols marked with app_API or app_VAR will 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_IAPI or app_IVAR will 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_SAPI or app_SVAR will have static scope. Just as in the case of a symbol marked with the static storage qualifier, if a symbol is marked as having static scope (with app_SAPI or app_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.

Functions

Declaration:

app_API void public_function();
app_IAPI void internal_function();
app_SAPI void static_function();

Definition

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() {
    // ...
}

Declaration and definition

app_API void public_function() {
    // ...
}

app_IAPI void internal_function() {
    // ...
}

app_SAPI void static_function() {
    // ...
}

Variables

Declaration:

app_VAR int public_variable;
app_IVAR int internal_variable;
app_SVAR int static_variable;

Definition

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;

Declaration and definition

app_VAR int public_variable = 0;
app_IVAR int internal_variable = 0;
app_SVAR int static_variable = 0;

Classes

(Only for C++, yet to be done)

Declaration:

class app_CLASS PublicClass;
class app_ICLASS InternalClass;

Definition

class app_CLASS_IMPL PublicClass {
    // ...
}

class app_ICLASS_IMPL InternalClass {
    // ...
}

Optionally (but not truly "portable"),

class PublicClass {
    // ...
}

class InternalClass {
    // ...
}

Declaration and definition

class app_CLASS PublicClass {
    // ...
}

class app_ICLASS InternalClass {
    // ...
}

Notes

Overall Design

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.

About Visibility in general

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:

  • extern defines 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.
  • static defines 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.

About Public Visibility

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).

About the Internal Visibility

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.

#include "app.h"
#include "app_.h"
static int internal_add(int a, int b) {
return a + b;
}
app_API_IMPL int add(int a, int b) {
int result = internal_add(a, b);
debug(a, b, result);
return result;
}
#if 0
Names:
- Exported -> void function();
- Imported -> extern void function();
- Internal -> static function();
[ API] Exported names, when compiling:
/* Header-implementation */
/* lib.h */ void function();
/* lib.c */ void function() {}
[ API] Exported names, when referenced:
/* lib.h */ export void function();
/* lib.c */ /* No definition */
[IAPI] Library-Internal names, when compiling:
/* lib.h */ <attribute_hidden> void function();
/* lib.c */ void function();
[IAPI] Library-Internal names, when referenced:
/* lib.h */ <attribute_hidden> extern void function();
/* lib.c */ /* No definition */
[SAPI] Static-Internal Referenced names, when compiling:
/* lib.h */ static void function();
/* lib.c */ void function();
[SAPI] Static-Internal Referenced names, when referencing:
/* lib.h */ static void function();
/* lib.c */ /* No definition */
#endif
#ifndef APP_H_
#define APP_H_
#define app
#define app_NAME "app"
#define app_MAJORVERSION 0
#define app_MINORVERSION 1
#define app_PATCHVERSION 0
#define app_RELEASETYPE "dev"
#ifdef app_STATICLIB
# ifdef app_SHAREDLIB
# error "Can not specify two linkage modes (detected: STATIC)."
# endif
#elif app_SHAREDLIB
# ifdef app_STATICLIB
# error "Can not specify two linkage modes (detected: SHARED)."
# endif
#else
# define app_STATICLIB /* Change here the default linkage, if needed */
#endif
#define app_STRQT(s) #s
#define app_STRFY(s) app_STRQT(s)
#ifndef app_RELEASENAME
# define app_RELEASENAME app_NAME \
" " app_STRFY(app_MAJORVERSION) \
"." app_STRFY(app_MINORVERSION) \
"." app_STRFY(app_PATCHVERSION) \
"-" app_RELEASETYPE
#endif
#ifndef app_SKIP_CONFIG_OS
# if defined _WIN32 || defined __WIN32__ || defined __WINDOWS__ || defined __TOS_WIN__ || defined _WIN64
# define app_CONFIG_OS_WINDOWS
# endif
#endif
#ifndef app_SKIP_CONFIG_OS
# if defined(__INTEL_COMPILER) || defined(__GNUC__) || defined __MINGW__ || defined __CYGWIN__
# define app_CONFIG_COMPILER_GCC
# endif
#endif
#ifdef app_SKIP_COMPILER_EXPORT_IMPORT
/* Nothing, will be defined later on */
#elif defined app_CONFIG_OS_WINDOWS && defined app_CONFIG_COMPILER_GCC
# ifdef app_STATICLIB
# define app_EXTERNAL_GCC_ATTRIBUTE_
# else
# define app_EXTERNAL_GCC_DECLSPEC_
# endif
# define app_INTERNAL_GCC_ATTRIBUTE_
#elif defined app_CONFIG_OS_WINDOWS
# ifdef app_SHAREDLIB
# define app_EXTERNAL_DECLSPEC_
# endif
#elif defined app_CONFIG_COMPILER_GCC
# define app_EXTERNAL_GCC_ATTRIBUTE_
# define app_INTERNAL_GCC_ATTRIBUTE_
#endif /* app_H_ */
#ifdef app_FORCE_DEFAULT_VISIBILITY
# pragma GCC visibility push(hidden)
#endif
#define app_SAPI_EXPORT static
#define app_SAPI_IMPORT static
#define app_SAPI_IMPL
#define app_SVAR_EXPORT static
#define app_SVAR_IMPORT static
#define app_SVAR_IMPL
#ifdef app_EXTERNAL_DECLSPEC_
# undef app_EXTERNAL_DECLSPEC_
# define app_API_EXPORT __declspec(dllexport)
# define app_API_IMPORT __declspec(dllimport) extern
# define app_API_IMPL
# define app_VAR_EXPORT __declspec(dllexport)
# define app_VAR_IMPORT __declspec(dllimport) extern
# define app_VAR_IMPL
#elif defined(app_EXTERNAL_GCC_DECLSPEC_)
# undef app_EXTERNAL_GCC_DECLSPEC_
# define app_API_EXPORT __attribute__((dllexport))
# define app_API_IMPORT __attribute__((dllimport)) extern
# define app_API_IMPL
# define app_VAR_EXPORT __attribute__((dllexport))
# define app_VAR_IMPORT __attribute__((dllimport)) extern
# define app_VAR_IMPL
#elif defined(app_EXTERNAL_GCC_ATTRIBUTE_)
# undef app_EXTERNAL_GCC_ATTRIBUTE_
# define app_API_EXPORT __attribute__((visibility("default")))
# define app_API_IMPORT extern
# define app_API_IMPL
# define app_VAR_EXPORT __attribute__((visibility("default")))
# define app_VAR_IMPORT extern
# define app_VAR_IMPL
#else
# define app_API_EXPORT
# define app_API_IMPORT extern
# define app_API_IMPL
# define app_VAR_EXPORT
# define app_VAR_IMPORT extern
# define app_VAR_IMPL
#endif
#ifdef app_INTERNAL_GCC_ATTRIBUTE_
# undef app_INTERNAL_GCC_ATTRIBUTE_
# define app_IAPI_EXPORT __attribute__((visibility("hidden")))
# define app_IAPI_IMPORT extern
# define app_IAPI_IMPL
# define app_IVAR_EXPORT __attribute__((visibility("hidden")))
# define app_IVAR_IMPORT extern
# define app_IVAR_IMPL
#else
# define app_IAPI_EXPORT extern
# define app_IAPI_IMPORT
# define app_IAPI_IMPL
# define app_IVAR_EXPORT
# define app_IVAR_IMPORT
# define app_IVAR_IMPL
#endif
#ifdef app_BUILDING
# define app_API app_API_EXPORT
# define app_IAPI app_IAPI_EXPORT
# define app_SAPI app_SAPI_EXPORT
# define app_VAR app_VAR_EXPORT
# define app_IVAR app_IVAR_EXPORT
# define app_SVAR app_SVAR_EXPORT
# ifdef app_STATICLIB
# define app_BUILDING_STATICLIB
# else
# define app_BUILDING_SHAREDLIB
# endif
#else
# define app_API app_API_IMPORT
# define app_IAPI app_IAPI_IMPORT
# define app_SAPI app_SAPI_IMPORT
# define app_VAR app_VAR_IMPORT
# define app_IVAR app_IVAR_IMPORT
# define app_SVAR app_SVAR_IMPORT
# ifdef app_STATICLIB
# define app_USING_STATICLIB
# else
# define app_USING_SHAREDLIB
# endif
#endif
#ifdef app_DEBUG_MACRO_DEFINITIONS
# pragma message app_STRQT(app_API) ": " app_STRFY(app_API)
# pragma message app_STRQT(app_API_IMPL) ": " app_STRFY(app_API_IMPL)
# pragma message app_STRQT(app_IAPI) ": " app_STRFY(app_IAPI)
# pragma message app_STRQT(app_IAPI_IMPL) ": " app_STRFY(app_IAPI_IMPL)
# pragma message app_STRQT(app_SAPI) ": " app_STRFY(app_SAPI)
# pragma message app_STRQT(app_SAPI_IMPL) ": " app_STRFY(app_SAPI_IMPL)
# pragma message app_STRQT(app_VAR) ": " app_STRFY(app_VAR)
# pragma message app_STRQT(app_VAR_IMPL) ": " app_STRFY(app_VAR_IMPL)
# pragma message app_STRQT(app_IVAR) ": " app_STRFY(app_IVAR)
# pragma message app_STRQT(app_IVAR_IMPL) ": " app_STRFY(app_IVAR_IMPL)
# pragma message app_STRQT(app_SVAR) ": " app_STRFY(app_SVAR)
# pragma message app_STRQT(app_SVAR_IMPL) ": " app_STRFY(app_SVAR_IMPL)
#endif
app_API int add(int a, int b);
#endif /* APP_H_ */
#include "app_.h"
#include <stdio.h>
app_IAPI_IMPL void debug(int a, int b, int result) {
printf("Calculating %d + %d = %d\n", a, b, result);
}
#ifndef APP_H__
#define APP_H__
#include "app.h"
app_IAPI void debug(int a, int b, int result);
#endif /* APP_H__ */
Copyright (c) 2016 Flávio Lisbôa <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
#include "app.h"
#include <stdio.h>
int main(void) {
int result = add(1, 2);
printf("Result is %d\n", result);
}
TEST_SYMBOL_NAME=debug
APPNAME=app
SLIB=lib$(APPNAME).a
DLIB=lib$(APPNAME).so
SEXEC=$(APPNAME)_slib
DEXEC=$(APPNAME)_dlib
_CFLAGS=-I. -Wall -fPIC
_LDFLAGS=-L.
all: build
build: $(SLIB) $(DLIB) $(SEXEC) $(DEXEC)
clean:
$(RM) $(SLIB) $(DLIB) $(SEXEC) $(DEXEC) *.o
test:
@./test_gcc.sh $(SEXEC) $(DEXEC) $(SLIB) $(DLIB) $(TEST_SYMBOL_NAME)
$(SLIB): app.staticlib.o app_.staticlib.o
ar rcs $@ $^
$(DLIB): app.sharedlib.o app_.sharedlib.o
gcc -shared -o $@ $^ $(_LDFLAGS) $(LDFLAGS)
$(SEXEC): app.sexec.o
gcc -o $@ $^ $(SLIB) $(_LDFLAGS) $(LDFLAGS)
$(DEXEC): app.dexec.o
gcc -Wl,-rpath,'$$ORIGIN' -o $@ $^ $(DLIB) $(_LDFLAGS) $(LDFLAGS)
app.sexec.o: main.c app.h
gcc -c -o $@ $< $(_CFLAGS) $(CFLAGS) -Dapp_STATICLIB
app.dexec.o: main.c app.h
gcc -c -o $@ $< $(_CFLAGS) $(CFLAGS) -Dapp_SHAREDLIB
app.staticlib.o: app.c app.h app_.h
gcc -c -o $@ $< $(_CFLAGS) $(CFLAGS) -Dapp_BUILDING -Dapp_STATICLIB
app.sharedlib.o: app.c app.h app_.h
gcc -c -o $@ $< $(_CFLAGS) $(CFLAGS) -Dapp_BUILDING -Dapp_SHAREDLIB
app_.staticlib.o: app_.c app.h app_.h
gcc -c -o $@ $< $(_CFLAGS) $(CFLAGS) -Dapp_BUILDING -Dapp_STATICLIB
app_.sharedlib.o: app_.c app.h app_.h
gcc -c -o $@ $< $(_CFLAGS) $(CFLAGS) -Dapp_BUILDING -Dapp_SHAREDCLIB
#!/bin/sh
NM="nm -g --defined-only"
SEXEC="$1"
DEXEC="$2"
SLIB="$3"
DLIB="$4"
SYMBOL_NAME="$5"
[ -z $SYMBOL_NAME ] || SYMBOL_NAME="debug"
test() {
$NM $2 | grep "$SYMBOL_NAME" > "/dev/null"
if [ $? -ne 0 ]; then
echo "$1 is correct (does not export the symbol $SYMBOL_NAME)."
else
echo "$1 is INCORRECT (exports the symbol $SYMBOL_NAME)."
fi
}
test "Executable using Static Library ($SEXEC)" "$SEXEC"
test "Executable using Shared Library ($DEXEC)" "$DEXEC"
test "Static library ($SLIB)" "$SLIB"
test "Dynamic library ($DLIB)" "$DLIB"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment